Для решения проблемы отправляемся в раздел Настройки магазина – Типы плательщиков, путь имя_сайта/bitrix/admin/sale_person_type.php?lang=ru и удалить там типы плательщиков привязанные к тому сайту который мы удаляем. Перед удалением, возможно, потребуется удаление заказов связанных с этим плательщиком, путь имя_сайта/bitrix/admin/sale_order.php?lang=ru
Содержание
3 ноября 2019 г.
2 минуты287 слов
Иногда возникает необходимость удалить сайт на битриксе и забыть все, что с ним связано.
Но это не такая уж и простая задача, т.к. существуют зависимости, ссылающиеся на сайт, которые нужно удалить.
Ниже краткая инструкция для удаления сайта.
В моем случае было установлено решение Аспро Next. Порядок удаления может отличаться.
Ошибки при удалении сайта на CMS 1C-Bitrix
CRubric::OnBeforeLangDelete (subscribe)
CRubric::OnBeforeLangDelete (subscribe)
Это означает, что нужно удалить списки рассылок.
Для решения проблемы отправляемся в Сервисы > Рассылки > Список рассылок
и удаляем лишние рассылки, привязанные к этому сайту.
CForumNew::OnBeforeLangDelete (forum)
CForumNew::OnBeforeLangDelete (forum)
Это означает, что нужно отправиться в Сервисы > Форумы
и удалить ненужный форум. Скорее всего это форум «Отзывы о товарах».
После удаления форума возвращаемся в Настройки > Сайты > Список сайтов
и спокойно удаляем сайт.
CSalePersonType::OnBeforeLangDelete (sale)
CSalePersonType::OnBeforeLangDelete (sale)
Для того чтобы победить ошибку «Ошибка при удалении в CSalePersonType::OnBeforeLangDelete (sale)» нужно проделать следующее:
Открыть раздел Магазин > Настройки > Типы плательщиков
и удалить там типы плательщиков, привязанные к тому сайту который мы удаляем. Перед удалением, возможно, потребуется удаление заказов, связанных с этим плательщиком.
CIBlock::OnBeforeLangDelete (iblock)
CIBlock::OnBeforeLangDelete (iblock)
Надо удалить несколько инфоблоков: Найдены инфоблоки связанные с сайтом. Их идентификаторы: 11, 20, 21, 22, 23, 24. . Идентификаторы в каждом случае свои, но метод решения одинаковый.
Идем в Контент - Инфоблоки - Типы инфоблоков
и удаляем инфоблоки с указанными id. Скорее-всего нужные нам «жертвы» раскиданы по разным типам инфоблоков и проверить придется все.
Если видим ошибку при удалении каталога, то заходим в настройки инфоблока и снимаем галочку «инфоблок имеет торговые предложения»
Спокойно удаляем сайт.
Удаление сайта из панели 1С-Битрикс
Тривиальная задача удаления лишнего сайта из списка сайтов в 1С-Битрикс может оказаться не такой уж и простой. Пройдемся по процедуре на практике.
Шаг 1. Удаление сайта
Идем в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи, получаем . ошибка по типу 1
Шаг 2. Ошибка при удалении в CForumNew::OnBeforeLangDelete
Сие сообщение — Ошибка при удалении в CForumNew::OnBeforeLangDelete (forum), значит, что нужно отправиться в Сервисы — Форумы и удалить не нужный форум (форум принадлежащий удаляемому сайту). Скорее всего это форум «Отзывы о товарах».
После удаления форума возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи, получаем . ошибка по типу 2
Шаг 3. Ошибка при удалении в CIBlock::OnBeforeLangDelete (iblock):
Система сообщает нам об ошибке CIBlock::OnBeforeLangDelete (iblock) и даже подсказывает, что надо удалить несколько инфоблоков:
Идентификаторы в каждом случае свои, но метод решения одинаковый.
Идем в Контент — Инфоблоки — Типы инфоблоков и удаляем инфоблоки с указанными id (у нас это 11, 20, 21, 22, 23, 24).
Скорее-всего нужные нам «жертвы» раскиданы по разным типам инфоблоков и проверить придется все.
При удалении инфоблока — Каталог вероятно появление ошибки по типу 3
Шаг 4. Ошибка удаления. Возможно есть ссылающиеся объекты.
Данная ошибка более таинственна, для ее решения необходимо войти в инфоблок (нажать изменить)
Перейти на вкладку «Торговый каталог» и снять галочку у параметра Инфоблок имеет торговые предложения
Cохранить изменения
Повторить попытку удалить инфоблок
После удаления инфоблоков возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи, получаем . ошибка по типу 4
Шаг 5. Ошибка при удалении в CSalePersonType::OnBeforeLangDelete
Очередная ошибка связана с наличием лишних типов плательщиков от которых тоже нужно избавиться.
Идем в Магазин — Типы плательщиков
Ставим галочки у лишних плательщиков
Жмем удалить записи, получаем . ошибка по типу 5
Шаг 6. В заказах используется тип плательщика с >Как все догадались — нужно стереть заказы. Идем в Магазин — Заказы и удаляем лишние заказы
Возвращаемся в Магазин — Типы плательщиков
Удаляем плательщиков привязанных к удаляемому магазину
Возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи, получаем . ошибка по типу 6
Шаг 7. Ошибка при удалении в CRubric::OnBeforeLangDelete
Ошибка на данном этапе снова снабжена подсказкой:
Для решения проблемы отправляемся в Сервисы — Рассылки — Список рассылок и удаляем лишние рассылки
Возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи, . ВАУУУУ — сайт удален
Итоги
Естественно, мы привели пример одного из случаев (далеко не худшего) и у Вас могут не возникнуть указанные ошибки или возникнуть ошибки не всех типов. Сайт может удалиться намного проще, но помните, алгоритм всегда одинаков.
Как удалить инфоблок битрикс
Современные сайты наполняются содержанием с помощью специального программного обеспечения: CMS.
Соответственно, контент-менеджер (или редактор сайта) — это специалист, работающий с CMS.
Для кого этот курс?
Курс Контент-менеджер адресован тем, кто работает с содержанием сайта: наполнение, изменение текста, загрузка и размещение картинок и так далее.
- работать с элементами управления в интерфейсе системы;
- приемам работы в визуальном редакторе — основном инструменте при работе над содержанием сайта;
- работать с информационными блоками — местом хранения большей части информации на сайте;
- работать с компонентами — инструментом вывода информации на страницах сайта;
- работать с некоторыми модулями системы.
У нас часто спрашивают, сколько нужно заплатить
Курс полностью бесплатен. Изучение курса, прохождение итоговых тестов и получение сертификатов — ничего из этого оплачивать не нужно.
Ещё у нас есть Академия 1С-Битрикс, где можно обучиться на платной основе на курсах нашей компании либо наших партнёров.
Баллы опыта
В конце каждого урока есть кнопка Прочитано! . При клике на неё в Вашу итоговую таблицу опыта добавляется то количество баллов, которое указано в прочитанном После нажатия кнопки Прочитано! появится
окно подтверждения:
уроке.
Периодически мы заново оцениваем сложность уроков, увеличивая/уменьшая число баллов, поэтому итоговое количество набранных Вами баллов может отличаться от максимально возможного. Не переживайте! Отличный результат — это если общее число набранных Вами баллов отличается от максимального на 1-2%.
Практика и тесты
При изучении курса рекомендуется повторять описываемые действия на демонстрационной версии сайта, установленной локально или в Виртуальной лаборатории.
После изучения курса пройдите тесты на сертификацию. При успешной сдаче линейки тестов со страницы Моё обучение скачайте сертификат об успешном прохождении курса в формате PDF.
Также Вы можете поделиться ссылкой на страницу со своими сертификатами. Для этого на странице Моё обучение отметьте опцию Разрешить публичный доступ к резюме студента и скопируйте ссылку на страницу резюме . Страница с Вашим резюме будет доступна всем, кому Вы отправите ссылку на неё.
Комментарии к урокам
На каждой странице курса авторизованный на сайте посетитель может дать комментарий к содержимому страницы. Комментарий — не форум, там не ведётся обсуждений или разъяснений. Это инструмент для сообщений нам об ошибках, неточностях. Для отправки комментария воспользуйтесь расположенной в правом нижнем углу окна браузера кнопкой: |
Для преподавания офлайн
Если данный курс берётся в качестве основы для офлайнового преподавания, то рекомендуемая продолжительность: 2 дня (16 академических часов).
Если нет интернета
Скачать материалы курса в формате EPUB. Файлы формата EPUB Чем открыть файл на
Android:
EPUB Reader
CoolReader
FBReader
Moon+ Reader
eBoox
iPhone:
FBReader
CoolReader
iBook
Bookmate
Windows:
Calibre
FBReader
Icecream Ebook Reader
Плагины для браузеров:
EpuBReader – для Firefox
Readium – для Google Chrome
iOS
Marvin for iOS
ShortBook
Linux:
Calibre
FBReader
Cool Reader
Okular
обновляются периодически, поэтому возможно некоторое отставание их от онлайновой версии курса. Версия файла — от 25.01.2023.
Удаление решения с сайта
Полное удаление решения с сайта. В итоге останется чистый Битрикс.
Важно. В данном решение показано как удалить ВСЕ демо данные. Останется только чистый Битрикс. После удаление необходимо будет установить новое решение через мастер установки решений.
1. Заходим в Битрикс.
2. Удаление файлов решения.
2.1. Переходим в «Файлы и папки»
2.2. Отображаем все файлы
2.2. Выделяем все файлы в папке кроме папок «bitrix», «upload», файлов «.htaccess», «.access» и если есть файл «web.config»
2.3. Нажимаем «Удалить»
2.4. Итог. Файл urlrewrite.php останется в списке.
3. Удаляем инфоблоки
4. Удаляем шаблоны
5. Удаляем сайт где установлено наше решение.
После удаления сайта с при переходе на вкладку «Сайт» сайт перестанет отображаться. Необходимо перейти в мастер установки решений и установить новое решение.
1.
2. Выбрать решение и приступить к установке
3. Указать id сайта. По умолчанию указываем s1
Подробная установка решения описана в Инструкции по установке решения
Массовое удаление seo-свойств инфоблоков в Битрикс
Как известно, в 14 версии Битрикса появились вычисляемые сео-свойства инфоблоков, которые могут быть заданы шаблонами или точными значениями. В них входят, например, метатеги title и description. Они могут быть заданы для инфоблока в целом, для разделов и элементов.
Заполняются сео-свойства очень легко — в интерфейсе инфоблоков. Но сеошники приходят и уходят, требования к шаблонам и значениям метатегов меняются. Но не предусмотрено возможности массовой чистки сео-свойств для всего каталога, поэтому нужно разобраться как хранятся эти данные и как их нужно очищать.
Все шаблоны и значения вычисляемых сео-свойств хранятся в таблице b_iblock_iproperty :
В поле ENTITY_TYPE хранится тип сущности:
- B — инфоблок (то, что задаётся в настройках инфоблока),
- S — раздел (то, что задаётся при редактировании раздела),
- E — элемент (то, что задаётся при редактировании элемента).
Например, для того, чтобы удалить все значения и шаблоны для целого инфоблока, необходимо выполнить запрос:
Но это еще не всё. Уже вычисленные значения хранятся в отдельных таблицах для инфоблоков (на самом деле, там пусто. не знаю, зачем нужна эта таблица, ведь у инфоблока нет своих метатегов, только шаблоны для разделов и элементов), разделов и элементов:
Например, чтобы почистить все вычисленные значения, нужно выполнить запросы:
Дальше, возможно, нужно почистить кеш сайта и приступить к новому наполнению сео-свойств.
Иногда приходится сталкиваться с сайтами, которые размещены на очень слабом хостинге. Если на таком хостинге нужно удалить все элементы инфоблока — тут или удалять напрямую, через базу данных (что не очень хорошо), либо постранично удалять элементы, что тоже муторно:(
Представляю вашему вниманию скрипт, позволяющий настроить автоматическое удаление всех элементов инфоблока с постраничной переборкой элементов в автоматическом режиме:
use BitrixMain; use BitrixMainApplication; use BitrixMainLocalizationLoc as Loc; use BitrixMainDataCache; require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_before.php"); require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/prolog.php"); Loc::loadMessages(__FILE__); if (!MainLoader::includeModule('iblock')) { die('Module `iblock` is not installed'); } CJSCore::Init(array('jquery')); $POST_RIGHT = $APPLICATION->GetGroupRight("catalog"); // если нет прав - отправим к форме авторизации с сообщением об ошибке if ($POST_RIGHT == "D") $APPLICATION->AuthForm(GetMessage("ACCESS_DENIED")); $context = Application::getInstance()->getContext(); $request = $context->getRequest(); if ($request->isPost() && $request->get('action') && $POST_RIGHT == "W" && check_bitrix_sessid()) { $arResult = array(); $arPostData = $request->getPostList()->toArray(); switch ($request->get('action')) { case 'clearElements': $arSelect = Array("ID", "IBLOCK_ID"); $arFilter = Array("IBLOCK_ID" => IntVal($arPostData['IBLOCK_ID'])); $maxForStep = 5; $pagesParams = ($arPostData['num'] == 1) ? array('nPageSize' => $maxForStep) : array('nTopCount' => $maxForStep); $res = CIBlockElement::GetList(Array(), $arFilter, false, $pagesParams, $arSelect); $arResult['CNT'] = $res->NavPageCount; global $DB; while ($arFields = $res->GetNext()) { $DB->StartTransaction(); if (!CIBlockElement::Delete($arFields['ID'])) { $arResult['STATUS']['ERROR'][] = $arFields['ID'] . ' not deleted!'; $DB->Rollback(); } else { $arResult['STATUS']['OK'][] = $arFields['ID']; $DB->Commit(); } // $arResult[] = $arFields; } break; default: print_r($arPostData); } if ($request->isAjaxRequest() && !empty($arResult)) { $APPLICATION->RestartBuffer(); header('Content-type: application/json'); echo BitrixMainWebJson::encode($arResult); exit(); } } $APPLICATION->SetTitle('Обработчик элементов информационного блока'); require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_after.php"); class clIblockEntitiesProcessor { public static function GetIblockTypes($life_time = 3600 * 24 * 30) { $result = false; $cache_params = array('function' => 'CIBlockType::GetList'); $cache_id = md5(serialize($cache_params)); $cache_dir = __CLASS__; $cache = Cache::createInstance(); if ($life_time < 0) { $cache->clean($cache_id, $cache_dir); } if ($cache->initCache($life_time, $cache_id, $cache_dir)) { $result = $cache->getVars(); } elseif ($cache->startDataCache() && BitrixMainLoader::includeModule('iblock')) { $db_iblock_type = CIBlockType::GetList(); while ($ar_iblock_type = $db_iblock_type->Fetch()) { if ($arIBType = CIBlockType::GetByIDLang($ar_iblock_type["ID"], LANG)) { $arIBType['NAME'] = htmlspecialcharsEx($arIBType["NAME"]); $result[$arIBType['IBLOCK_TYPE_ID']] = array( 'IBLOCK_TYPE_ID' => $arIBType['IBLOCK_TYPE_ID'], 'NAME' => $arIBType['NAME'], ); } } $cache->endDataCache($result); } return $result; } public static function GetIblockList($filter = array(), $life_time = 3600 * 24 * 30) { $result = false; $cache_params = array('function' => 'CIBlock::GetList', 'filter_params' => $filter); $cache_id = md5(serialize($cache_params)); $cache_dir = __CLASS__; $cache = BitrixMainDataCache::createInstance(); if ($life_time < 0) { $cache->clean($cache_id, $cache_dir); } if ($cache->initCache($life_time, $cache_id, $cache_dir)) { $result = $cache->getVars(); } elseif ($cache->startDataCache() && BitrixMainLoader::includeModule('iblock')) { $res = CIBlock::GetList( Array(), $filter, false ); while ($ar_res = $res->Fetch()) { $result[$ar_res['ID']] = array( 'ID' => $ar_res['ID'], 'IBLOCK_TYPE_ID' => $ar_res['IBLOCK_TYPE_ID'], 'CODE' => $ar_res['CODE'], 'NAME' => $ar_res['NAME'], 'ACTIVE' => $ar_res['ACTIVE'], 'PICTURE' => $ar_res['PICTURE'], 'DESCRIPTION' => $ar_res['DESCRIPTION'], 'DESCRIPTION_TYPE' => $ar_res['DESCRIPTION_TYPE'], 'CATALOG' => CCatalogSKU::GetInfoByProductIBlock(intval($ar_res['ID'])), ); } $cache->endDataCache($result); } return $result; } public static function GetIblockElementItems( $arParams = array('filter' => array(), 'select' => false, 'sort' => array('name' => 'asc'), 'page_params' => false, 'group' => false), $life_time = 3600) { if (!isset($arParams['filter']) || empty($arParams['filter'])) return false; $arFilter = $arParams['filter']; $arSelect = (isset($arParams['select'])) ? $arParams['select'] : false; $arSort = (isset($arParams['sort'])) ? $arParams['sort'] : array('name' => 'asc'); $pageParams = (isset($arParams['page_params'])) ? $arParams['page_params'] : false; $groupParams = (isset($arParams['group'])) ? $arParams['group'] : false; $result = false; $cache_params = array(); foreach (array_keys($arParams) as $array_key) { if (is_array($arParams[$array_key])) { foreach ($arParams[$array_key] as $key => $value) { $cache_params[$array_key . '-' . $key] = $value; } } elseif (is_bool($arParams[$array_key])) { $cache_params[$array_key] = $arParams[$array_key] ? 1 : 0; } } $cache_id = md5(serialize($cache_params)); $cache_dir = __CLASS__ . '/' . __FUNCTION__; $cache = BitrixMainDataCache::createInstance(); if ($life_time < 0) { $cache->clean($cache_id, $cache_dir); } if ($cache->initCache($life_time, $cache_id, $cache_dir)) { $result = $cache->getVars(); } elseif ($cache->startDataCache() && BitrixMainLoader::includeModule('iblock')) { $rsItems = CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect); if ( is_array($arSelect) && in_array('IBLOCK_ID', $arSelect) && in_array('ID', $arSelect) ) { $filterProperties = array(); foreach ($arSelect as $select) { if (strpos($select, 'PROPERTY_') !== false) { $filterProperties[] = str_replace('PROPERTY_', '', $select); } } while ($arElement = $rsItems->GetNextElement()) { $arFields = $arElement->GetFields(); if (!empty($filterProperties)) { foreach ($filterProperties as $arFilterCode) { if (!isset($arFields['PROPERTIES'])) $arFields['PROPERTIES'] = array(); $arFields['PROPERTIES'] = array_merge($arFields['PROPERTIES'], $arElement->GetProperties(false, array( 'CODE' => $arFilterCode ))); // $arFields['PROPERTIES'][$arFilterCode] = ; } // $arFields['PROPERTIES'] = $arElement->GetProperties(false,array('CODE'=>$filterProperties)); } $result[] = $arFields; } } elseif (empty($arSelect)) { while ($arElement = $rsItems->GetNextElement()) { $arFields = $arElement->GetFields(); $arFields['PROPERTIES'] = $arElement->GetProperties(); $result[] = $arFields; } } else { while ($arElement = $rsItems->GetNext()) { $result[] = $arElement; } } if (!empty($result)) { foreach ($result as $key => $arItem) { if (!empty($arItem['PROPERTIES'])) { foreach ($arItem['PROPERTIES'] as $pCode => $arProperty) { $result[$key]['PROPERTIES'][$pCode] = CIBlockFormatProperties::GetDisplayValue( array('ID' => $arItem['ID'], 'NAME' => $arItem['NAME']), $arProperty, ''); } } } } if (isset($pageParams) && !empty($pageParams) && $pageParams['nPageSize'] == 1 && !empty($result[0])) { $result = $result[0]; } $cache->endDataCache($result); } return $result; } } $tmp = clIblockEntitiesProcessor::GetIblockList(array('ACTIVE' => 'Y')); $arIblockList = array(); foreach ($tmp as $item) { $arIblockList[$item['ID']] = $item; } // echo '<pre>'; print_r($arIblockList);echo '</pre>'; ?> <form method="post" action="<? echo $APPLICATION->GetCurPage() ?>" enctype="multipart/form-data" name="iblocksProcessorForm"> <? echo bitrix_sessid_post(); ?> <div> <select name="IBLOCK_ID" id="IBLOCK_ID"> <? foreach ($arIblockList as $arIblock) { ?> <option value="<?= $arIblock['ID'] ?>">[<?= $arIblock['ID'] ?>] <?= $arIblock['NAME'] ?></option> <? } ?> </select> </div> <div id="loading"><p class="persents" style="display: none;"> Обработано: <span class="pv">0</span>% (Строка: <span class="rv">1</span>/ <span class="maxrows">0</span>) </p></div> <div> <button id="DeleteElements">Удалить все элементы</button> </div> </form> <script type="text/javascript"> var deferreds = []; var i = 10; var maxRows = false; var Form; $(document).ready(function(){ lastSelectedIblock = localStorage.getItem('IBLOCK_ID'); if(lastSelectedIblock!==undefined && parseInt(lastSelectedIblock)>0){ $('form[name="iblocksProcessorForm"]').find('option[value="'+lastSelectedIblock+'"]').attr('selected','selected'); } }); $(document).on('change','form[name="iblocksProcessorForm"] select[name="IBLOCK_ID"]', function () { localStorage.setItem('IBLOCK_ID',$(this).val()) ; }); $(document).on('click', 'form[name="iblocksProcessorForm"] button#DeleteElements', function () { var that = this; Form = $(this).parents('form'); var num = 1; var wait = BX.showWait('loading'); var persentsContainer = Form.find('p.persents'); var PostParams = { num: num, action: 'clearElements', sessid: Form.find('input[name="sessid"]').val(), 'IBLOCK_ID': Form.find('select[name="IBLOCK_ID"]').val() }; function work_with_row(num, d) { var Persents = (parseInt(num)-1) * 100 / parseInt(maxRows); persentsContainer.find('span.pv').html(Math.round(Persents)); persentsContainer.find('span.rv').html(parseInt(num)-1); PostParams.num = num; $.ajax({ type: "POST", url: location.href, data: PostParams, dataType: "json", success: function (data) { d && d.resolve(); }, onfailure: function () { d && d.resolve(); } }); } $.ajax({ type: "POST", url: location.href, data: PostParams, dataType: "json" }).then(function (data) { if (!maxRows && data.CNT !== undefined) { maxRows = data.CNT; var deferreds = []; var i = 10; persentsContainer.find('span.maxrows').html(maxRows); persentsContainer.show(); for (var index = num + 1; index <= maxRows; index++) { (function (index) { var d = new $.Deferred(); window.setTimeout(function () { work_with_row(index, d) }, 3000 * index + (i++)); deferreds.push(d); })(index); } $.when.apply($, deferreds).done(function () { $('p.persents').html('Обработка завершена!'); BX.closeWait('loading', wait); $(that).remove(); }); } }, function (reason) { console.debug(reason); }); return false; }); </script> <? require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_admin.php");
Таким образом, при первом аякс-запросе скрипт получает информацию о том, сколько всего страниц ему нужно обработать
(количество элементов на странице выбирается на основании производительности сервера),а дальше уже проходится по всем страницам и производит удаление.
P.S. Скрипт написан в виде страницы административного раздела сайта
Что-то много развелось в последнее время статей про минусы битрикса, и их опровержений . Раз уж пошла такая пьянка, то и я добавлю свои 5 копеек.
В комментариях к статьям писали, что не хватает конкретики, примеров, более глубокого обзора.
Данная статья — попытка этот обзор написать. Хотя нет, это скорее пост ненависти и боли (может даже немного нытья). Это такой расширенный вариант поста про минусы от pistol . Я постараюсь описать большинство тех вещей, которые раздражают именно меня и моих коллег в Битриксе. Постараюсь собрать в одном посте все те минусы, которые доставляют ежедневно очень много боли. Под конец я постараюсь сделать выводы.
Кто я такой? Да в общем-то, обычный разработчик. Работаю с битриксом с ноября 2010 года (5.5 лет). Работаю только
с битриксом, не сделал ни одного коммерческого проекта на других CMS, не использовал фреймворки в создании сайтов. По роду деятельности я занимаюсь в основном интернет-магазинами, их созданием, поддержкой и развитием.
Битрикс — УГ, не стоит лезть в этот омут без особой надобности.
Для начала я предлагаю вам провести мысленный эксперимент. Давайте попробуем взять двух backend-разработчиков примерно одного возраста и примерно с одинаковым стажем работы (допустим, 1 — 1.5 года), только чтобы один из них работал все это время с 1С-Битрикс, а другой — с Symfony(например). Можно легко сравнить, с каким набором технологий работал все это время один, а с каким — другой, и какой в итоге набор знаний они получили за это время.
В случае с Symfony разработчиком это будет: php5/7 + глубокое понимание ООП, общепринятые паттерны проектирования (MVC, DI, Factory, Repository как минимум), умение разрабатывать Unit тесты, использовать шаблонизаторы (минимум twig), ORM (с Doctrine), composer, git, стандарты PSR, опыт работы с консолью и написания консольных приложений, базовые навыки настройки веб-сервера.
В случае с 1С-Битрикс разработчиком это будет php5, html/css + javascript/jquery (из коробки шаблонизаторов нет, а битрикс сует логику в шаблоны, как ни крути, придется с этим возиться), возможно git (и это сильно зависит от компании, некоторые динозавры до сих пор пилят на продакшене через FTP), если повезет — немножно sql и… все?
Я понимаю, что это все очень индивидуально и очень большую роль может сыграть окружение человека. Но тут я говорю о том, к чему двигают разработчика системы из коробки. В большинстве случаев, Битрикс разработчик очень сильно уступает в навыках по сравнению с разработчиками под другие фреймворки/CMS — и это неоспоримый факт. А все потому, что Битрикс изначально дает слишком много свободы при отсутствии внятной архитектуры, документации, и правильных решений, тогда как Symfony предлагает все необходимое.
Лишь однажды к нам в компанию пришел опытный человек не из мира 1С-Битрикс (в регионе) и он был на голову сильнее своих коллег с тем же стажем просто за счет того, что ранее ему поставили мозги на правильные рельсы.
Я и сам такой. Мне, к сожалению, с самого начала пустили пыль в глаза той же маркетинговой чепухой, да и попал я в не очень хорошее окружение. Я сам понимаю и чувствую, что мои коллеги с аналогичным стажем работы, но в том же Symfony, имеют больший кругозор, и это очень сильный побочный эффект от битрикса.
Это все наводит на мысли о том, что если ты хочешь развиваться в мире веб-разработки, то в качестве основы нужно выбирать уж точно не битрикс.
Сравнивая двух разработчиков, я хочу обратить внимание на те рамки, в которые загоняет система, и на ту свободу, которую она предоставляет. Что Битрикс, что Symfony — они оба дают почти безграничную гибкость, и в принципе на каждой из них можно создать продукт абсолютно любой сложности. Однако система должна помогать разработчику в решении проблем, вместо того, чтобы вставлять палки в колеса. И тут Битрикс очень сильно проигрывает.
Сразу хочу сказать немного слов об этом, т.к. это основная составляющая успеха Битрикса.
Можно сказать, что духом маркетинга пропитан весь Битрикс, даже документация для разработчиков. Даже там они пишут о том, что их продукт «настолько крутой, что его ценят и уважают все наши партнеры»(пруф , блок «Структура»). В битриксе работают хорошие маркетологи, которые грамотно умеют преподнести свой продукт. Раз в полгода они устраивают конференции для партнеров, где рассказывают о том, что было сделано и о том, какие у них планы. Как показывает практика, никогда эти планы не сбываются в срок и очень часто релизы либо неполные, либо с кучей ошибок.
В качестве примера — нашумевший рефакторинг модуля sale, релиз которого откладывали больше года, и даже самую последнюю дату релиза (23 декабря 2015 года) провалили на 3 месяца, и выпустили новый магазин и БУС (Битрикс ред. «Управление Сайтом») 16 версии только в конце марта 2016го. Но в результате после обновления пользователи не только не получили новых фич. Пользователи получили в большинстве случаев неработоспособный магазин, и горку нового недокументированного кода в придачу.
Новым инструментам дают такие громкие названия, которые у всех на слуху: Композитный сайт — ускорение x100; Highload-блоки; Bitrix BigData. На самом деле за этими словами скрываются вполне обыденные вещи, которые не соответствуют своему имени.
И такой подход прослеживается везде, к сожалению. Снаружи продукт выглядит как конфетка, которую купил, поставил и пользуешься. Но если с битриксом сделать шаг в сторону от стандартной поставки — все, поддержание функциональности при обновлениях превращается в ад.
Впрочем, обо всем по порядку, тема маркетинга еще будет всплывать в этом посте, скорее всего, не раз.
На протяжении десятка лет Битрикс отчаянно загонял сам себя в тупик. Каждая новая фича в продукте выходила в соответствии с интересами бизнеса, без должной проработки с технической точки зрения. И, естественно, все это росло как снежный ком.
Если вдуматься, то в Битриксе нет архитектуры, как таковой. Нет даже общепринятых сформулированных правил, которые позволили бы следовать этой архитектуре. В курсе разработчиков, в разделе Архитектура продукта , сказано, что битрикс следует архитектуре MVC и приводит схему:
Сразу хочу сказать, что это MVC очень сильно отличается от классического варианта. Тут очень сильная подмена понятий, никакого MVC тут на самом деле нет, просто есть некое абстрактное разделение на модули, компоненты, и шаблоны компонентов. А уже из этих кирпичиков строится весь сайт. Но каждый из этих кирпичиков может брать на себя разные задачи, и поэтому они тесно взаимосвязаны между собой.
Попробую рассмотреть каждый из этих аспектов архитектуры более подробно.
Мне сложно судить об API системы как о модели. Да, API предоставляет интерфейс доступа к данным и позволяет ими манипулировать. Но API битрикса позволяет работать не только с данными, но и с шаблонами, да и с пользовательскими запросами тоже. Ну да ладно… это лишь мое мнение.
На данный момент в Битриксе есть 2 варианта API. Условно можно разделить их на старое
и новое
. Новое API называется D7 (честно — не помню почему, но Rizhikov рассказывал об этом на одной из партнерских конференций).
Старое API — это собрание антипаттернов, ужасных примеров плохого кода. В Битриксе всегда считалось нормальным вызывать нестатические методы статически, и наоборот, требовать состояния тогда, когда это неуместно. Например, всем известный CIBlockElement::GetList — пожалуй, один из самых часто-используемых методов при разработке. Его реализация содержит более 500 строк кода, использует глобалки, строит ужасающие, колоссальные запросы, и содержит нереальный, просто нечитабельный недокументированный код.
Function GetList($arOrder=array(«SORT»=>»ASC»), $arFilter=array(), $arGroupBy=false, $arNavStartParams=false, $arSelectFields=array())
{
/*
Filter combinations:
CHECK_PERMISSIONS=»N» — check permissions of the current user to the infoblock
MIN_PERMISSION=»R» — when permissions check, then minimal access level
SHOW_HISTORY=»N» — add history items to list
SHOW_NEW=»N» — if not add history items, then add new, but not published elements
*/
global $DB, $USER;
$MAX_LOCK = intval(COption::GetOptionString(«workflow»,»MAX_LOCK_TIME»,»60″));
$uid = is_object($USER)? intval($USER->GetID()): 0;
$formatActiveDates = CPageOption::GetOptionString(«iblock», «FORMAT_ACTIVE_DATES», «-«) != «-«;
$shortFormatActiveDates = CPageOption::GetOptionString(«iblock», «FORMAT_ACTIVE_DATES», «SHORT»);
$arIblockElementFields = array(«ID»=>»BE.ID»,
«TIMESTAMP_X»=>$DB->DateToCharFunction(«BE.TIMESTAMP_X»),
«TIMESTAMP_X_UNIX»=>»UNIX_TIMESTAMP(BE.TIMESTAMP_X)»,
«MODIFIED_BY»=>»BE.MODIFIED_BY»,
«DATE_CREATE»=>$DB->DateToCharFunction(«BE.DATE_CREATE»),
«DATE_CREATE_UNIX»=>»UNIX_TIMESTAMP(BE.DATE_CREATE)»,
«CREATED_BY»=>»BE.CREATED_BY»,
«IBLOCK_ID»=>»BE.IBLOCK_ID»,
«IBLOCK_SECTION_ID»=>»BE.IBLOCK_SECTION_ID»,
«ACTIVE»=>»BE.ACTIVE»,
«ACTIVE_FROM»=>($formatActiveDates
?
$DB->DateToCharFunction(«BE.ACTIVE_FROM», $shortFormatActiveDates)
:
«IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_FROM)>0, «.$DB->DateToCharFunction(«BE.ACTIVE_FROM», «FULL»).», «.$DB->DateToCharFunction(«BE.ACTIVE_FROM», «SHORT»).»)»),
«ACTIVE_TO»=>($formatActiveDates
?
$DB->DateToCharFunction(«BE.ACTIVE_TO», $shortFormatActiveDates)
:
«IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_TO)>0, «.$DB->DateToCharFunction(«BE.ACTIVE_TO», «FULL»).», «.$DB->DateToCharFunction(«BE.ACTIVE_TO», «SHORT»).»)»),
«DATE_ACTIVE_FROM»=>($formatActiveDates
?
$DB->DateToCharFunction(«BE.ACTIVE_FROM», $shortFormatActiveDates)
:
«IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_FROM)>0, «.$DB->DateToCharFunction(«BE.ACTIVE_FROM», «FULL»).», «.$DB->DateToCharFunction(«BE.ACTIVE_FROM», «SHORT»).»)»),
«DATE_ACTIVE_TO»=>($formatActiveDates
?
$DB->DateToCharFunction(«BE.ACTIVE_TO», $shortFormatActiveDates)
:
«IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_TO)>0, «.$DB->DateToCharFunction(«BE.ACTIVE_TO», «FULL»).», «.$DB->DateToCharFunction(«BE.ACTIVE_TO», «SHORT»).»)»),
«SORT»=>»BE.SORT»,
«NAME»=>»BE.NAME»,
«PREVIEW_PICTURE»=>»BE.PREVIEW_PICTURE»,
«PREVIEW_TEXT»=>»BE.PREVIEW_TEXT»,
«PREVIEW_TEXT_TYPE»=>»BE.PREVIEW_TEXT_TYPE»,
«DETAIL_PICTURE»=>»BE.DETAIL_PICTURE»,
«DETAIL_TEXT»=>»BE.DETAIL_TEXT»,
«DETAIL_TEXT_TYPE»=>»BE.DETAIL_TEXT_TYPE»,
«SEARCHABLE_CONTENT»=>»BE.SEARCHABLE_CONTENT»,
«WF_STATUS_ID»=>»BE.WF_STATUS_ID»,
«WF_PARENT_ELEMENT_ID»=>»BE.WF_PARENT_ELEMENT_ID»,
«WF_LAST_HISTORY_ID»=>»BE.WF_LAST_HISTORY_ID»,
«WF_NEW»=>»BE.WF_NEW»,
«LOCK_STATUS»=>»if (BE.WF_DATE_LOCK is null, «green», if(DATE_ADD(BE.WF_DATE_LOCK, interval «.$MAX_LOCK.» MINUTE)»BE.WF_LOCKED_BY»,
«WF_DATE_LOCK»=>$DB->DateToCharFunction(«BE.WF_DATE_LOCK»),
«WF_COMMENTS»=>»BE.WF_COMMENTS»,
«IN_SECTIONS»=>»BE.IN_SECTIONS»,
«SHOW_COUNTER»=>»BE.SHOW_COUNTER»,
«SHOW_COUNTER_START»=>$DB->DateToCharFunction(«BE.SHOW_COUNTER_START»),
«CODE»=>»BE.CODE»,
«TAGS»=>»BE.TAGS»,
«XML_ID»=>»BE.XML_ID»,
«EXTERNAL_ID»=>»BE.XML_ID»,
«TMP_ID»=>»BE.TMP_ID»,
«USER_NAME»=>»concat(«(«,U.LOGIN,») «,ifnull(U.NAME,»»),» «,ifnull(U.LAST_NAME,»»))»,
«LOCKED_USER_NAME»=>»concat(«(«,UL.LOGIN,») «,ifnull(UL.NAME,»»),» «,ifnull(UL.LAST_NAME,»»))»,
«CREATED_USER_NAME»=>»concat(«(«,UC.LOGIN,») «,ifnull(UC.NAME,»»),» «,ifnull(UC.LAST_NAME,»»))»,
«LANG_DIR»=>»L.DIR»,
«LID»=>»B.LID»,
«IBLOCK_TYPE_ID»=>»B.IBLOCK_TYPE_ID»,
«IBLOCK_CODE»=>»B.CODE»,
«IBLOCK_NAME»=>»B.NAME»,
«IBLOCK_EXTERNAL_ID»=>»B.XML_ID»,
«DETAIL_PAGE_URL»=>»B.DETAIL_PAGE_URL»,
«LIST_PAGE_URL»=>»B.LIST_PAGE_URL»,
«CANONICAL_PAGE_URL»=>»B.CANONICAL_PAGE_URL»,
«CREATED_DATE»=>$DB->DateFormatToDB(«YYYY.MM.DD», «BE.DATE_CREATE»),
«BP_PUBLISHED»=>»if(BE.WF_STATUS_ID = 1, «Y», «N»)»,);
unset($shortFormatActiveDates);
unset($formatActiveDates);
$bDistinct = false;
CIBlockElement::PrepareGetList($arIblockElementFields,
$arJoinProps,
$bOnlyCount,
$bDistinct,
$arSelectFields,
$sSelect,
$arAddSelectFields,
$arFilter,
$sWhere,
$sSectionWhere,
$arAddWhereFields,
$arGroupBy,
$sGroupBy,
$arOrder,
$arSqlOrder,
$arAddOrderByFields,
$arIBlockFilter,
$arIBlockMultProps,
$arIBlockConvProps,
$arIBlockAllProps,
$arIBlockNumProps,
$arIBlockLongProps);
$arFilterIBlocks = isset($arFilter[«IBLOCK_ID»])? array($arFilter[«IBLOCK_ID»]): array();
//******************FROM PART********************************************
$sFrom = «»;
foreach($arJoinProps[«FPS»] as $iblock_id => $iPropCnt)
{
$sFrom .= «tttINNER JOIN b_iblock_element_prop_s».$iblock_id.» FPS».$iPropCnt.» ON FPS».$iPropCnt.».IBLOCK_ELEMENT_ID = BE.IDn»;
$arFilterIBlocks[$iblock_id] = $iblock_id;
}
foreach($arJoinProps[«FP»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property FP».$i.» ON FP».$i.».IBLOCK_ID = B.ID AND «.
(IntVal($propID)>0?
» FP».$i.».ID=».IntVal($propID).»n»:
» FP».$i.».CODE=»».$DB->ForSQL($propID, 200).»»n»);
else
$sFrom .= «tttLEFT JOIN b_iblock_property FP».$i.» ON FP».$i.».IBLOCK_ID = B.ID AND «.
(IntVal($propID)>0?
» FP».$i.».ID=».IntVal($propID).»n»:
» FP».$i.».CODE=»».$DB->ForSQL($propID, 200).»»n»);
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«FPV»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
if($db_prop[«MULTIPLE»]==»Y»)
$bDistinct = true;
if($db_prop[«VERSION»]==2)
$strTable = «b_iblock_element_prop_m».$db_prop[«IBLOCK_ID»];
else
$strTable = «b_iblock_element_property»;
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN «.$strTable.» FPV».$i.» ON FPV».$i.».IBLOCK_PROPERTY_ID = FP».$db_prop[«JOIN»].».ID AND FPV».$i.».IBLOCK_ELEMENT_ID = BE.IDn»;
else
$sFrom .= «tttLEFT JOIN «.$strTable.» FPV».$i.» ON FPV».$i.».IBLOCK_PROPERTY_ID = FP».$db_prop[«JOIN»].».ID AND FPV».$i.».IBLOCK_ELEMENT_ID = BE.IDn»;
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«FPEN»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
if($db_prop[«VERSION»] == 2 && $db_prop[«MULTIPLE»] == «N»)
{
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property_enum FPEN».$i.» ON FPEN».$i.».PROPERTY_ID = «.$db_prop[«ORIG_ID»].» AND FPS».$db_prop[«JOIN»].».PROPERTY_».$db_prop[«ORIG_ID»].» = FPEN».$i.».IDn»;
else
$sFrom .= «tttLEFT JOIN b_iblock_property_enum FPEN».$i.» ON FPEN».$i.».PROPERTY_ID = «.$db_prop[«ORIG_ID»].» AND FPS».$db_prop[«JOIN»].».PROPERTY_».$db_prop[«ORIG_ID»].» = FPEN».$i.».IDn»;
}
else
{
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property_enum FPEN».$i.» ON FPEN».$i.».PROPERTY_ID = FPV».$db_prop[«JOIN»].».IBLOCK_PROPERTY_ID AND FPV».$db_prop[«JOIN»].».VALUE_ENUM = FPEN».$i.».IDn»;
else
$sFrom .= «tttLEFT JOIN b_iblock_property_enum FPEN».$i.» ON FPEN».$i.».PROPERTY_ID = FPV».$db_prop[«JOIN»].».IBLOCK_PROPERTY_ID AND FPV».$db_prop[«JOIN»].».VALUE_ENUM = FPEN».$i.».IDn»;
}
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«BE»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
$sFrom .= «tttLEFT JOIN b_iblock_element BE».$i.» ON BE».$i.».ID = «.
($db_prop[«VERSION»]==2 && $db_prop[«MULTIPLE»]==»N»?
«FPS».$db_prop[«JOIN»].».PROPERTY_».$db_prop[«ORIG_ID»]
:»FPV».$db_prop[«JOIN»].».VALUE_NUM»).
($arFilter[«SHOW_HISTORY»] != «Y»?
» AND ((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)».($arFilter[«SHOW_NEW»]==»Y»? » OR BE.WF_NEW=»Y»»: «»).»)»:
«»).»n»;
if($db_prop[«bJoinIBlock»])
$sFrom .= «tttLEFT JOIN b_iblock B».$i.» ON B».$i.».ID = BE».$i.».IBLOCK_IDn»;
if($db_prop[«bJoinSection»])
$sFrom .= «tttLEFT JOIN b_iblock_section BS».$i.» ON BS».$i.».ID = BE».$i.».IBLOCK_SECTION_IDn»;
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«BE_FPS»] as $iblock_id => $db_prop)
{
$sFrom .= «tttLEFT JOIN b_iblock_element_prop_s».$iblock_id.» JFPS».$db_prop[«CNT»].» ON JFPS».$db_prop[«CNT»].».IBLOCK_ELEMENT_ID = BE».$db_prop[«JOIN»].».IDn»;
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«BE_FP»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
list($propID, $link) = explode(«~», $propID, 2);
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property JFP».$i.» ON JFP».$i.».IBLOCK_ID = BE».$db_prop[«JOIN»].».IBLOCK_ID AND «.
(IntVal($propID)>0?
» JFP».$i.».ID=».IntVal($propID).»n»:
» JFP».$i.».CODE=»».$DB->ForSQL($propID, 200).»»n»);
else
$sFrom .= «tttLEFT JOIN b_iblock_property JFP».$i.» ON JFP».$i.».IBLOCK_ID = BE».$db_prop[«JOIN»].».IBLOCK_ID AND «.
(IntVal($propID)>0?
» JFP».$i.».ID=».IntVal($propID).»n»:
» JFP».$i.».CODE=»».$DB->ForSQL($propID, 200).»»n»);
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«BE_FPV»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
list($propID, $link) = explode(«~», $propID, 2);
if($db_prop[«MULTIPLE»]==»Y»)
$bDistinct = true;
if($db_prop[«VERSION»]==2)
$strTable = «b_iblock_element_prop_m».$db_prop[«IBLOCK_ID»];
else
$strTable = «b_iblock_element_property»;
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN «.$strTable.» JFPV».$i.» ON JFPV».$i.».IBLOCK_PROPERTY_ID = JFP».$db_prop[«JOIN»].».ID AND JFPV».$i.».IBLOCK_ELEMENT_ID = BE».$db_prop[«BE_JOIN»].».IDn»;
else
$sFrom .= «tttLEFT JOIN «.$strTable.» JFPV».$i.» ON JFPV».$i.».IBLOCK_PROPERTY_ID = JFP».$db_prop[«JOIN»].».ID AND JFPV».$i.».IBLOCK_ELEMENT_ID = BE».$db_prop[«BE_JOIN»].».IDn»;
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
foreach($arJoinProps[«BE_FPEN»] as $propID => $db_prop)
{
$i = $db_prop[«CNT»];
list($propID, $link) = explode(«~», $propID, 2);
if($db_prop[«VERSION»] == 2 && $db_prop[«MULTIPLE»] == «N»)
{
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property_enum JFPEN».$i.» ON JFPEN».$i.».PROPERTY_ID = «.$db_prop[«ORIG_ID»].» AND JFPS».$db_prop[«JOIN»].».PROPERTY_».$db_prop[«ORIG_ID»].» = JFPEN».$i.».IDn»;
else
$sFrom .= «tttLEFT JOIN b_iblock_property_enum JFPEN».$i.» ON JFPEN».$i.».PROPERTY_ID = «.$db_prop[«ORIG_ID»].» AND JFPS».$db_prop[«JOIN»].».PROPERTY_».$db_prop[«ORIG_ID»].» = JFPEN».$i.».IDn»;
}
else
{
if($db_prop[«bFullJoin»])
$sFrom .= «tttINNER JOIN b_iblock_property_enum JFPEN».$i.» ON JFPEN».$i.».PROPERTY_ID = JFPV».$db_prop[«JOIN»].».IBLOCK_PROPERTY_ID AND JFPV».$db_prop[«JOIN»].».VALUE_ENUM = JFPEN».$i.».IDn»;
else
$sFrom .= «tttLEFT JOIN b_iblock_property_enum JFPEN».$i.» ON JFPEN».$i.».PROPERTY_ID = JFPV».$db_prop[«JOIN»].».IBLOCK_PROPERTY_ID AND JFPV».$db_prop[«JOIN»].».VALUE_ENUM = JFPEN».$i.».IDn»;
}
if($db_prop[«IBLOCK_ID»])
$arFilterIBlocks[$db_prop[«IBLOCK_ID»]] = $db_prop[«IBLOCK_ID»];
}
if(strlen($arJoinProps[«BES»]))
{
$sFrom .= «ttt».$arJoinProps[«BES»].»n»;
}
if(strlen($arJoinProps[«FC»]))
{
$sFrom .= «ttt».$arJoinProps[«FC»].»n»;
$bDistinct = $bDistinct || (isset($arJoinProps[«FC_DISTINCT»]) && $arJoinProps[«FC_DISTINCT»] == «Y»);
}
if($arJoinProps[«RV»])
$sFrom .= «tttLEFT JOIN b_rating_voting RV ON RV.ENTITY_TYPE_ID = «IBLOCK_ELEMENT» AND RV.ENTITY_ID = BE.IDn»;
if($arJoinProps[«RVU»])
$sFrom .= «tttLEFT JOIN b_rating_vote RVU ON RVU.ENTITY_TYPE_ID = «IBLOCK_ELEMENT» AND RVU.ENTITY_ID = BE.ID AND RVU.USER_ID = «.$uid.»n»;
if($arJoinProps[«RVV»])
$sFrom .= «ttt».($arJoinProps[«RVV»][«bFullJoin»]? «INNER»: «LEFT»).» JOIN b_rating_vote RVV ON RVV.ENTITY_TYPE_ID = «IBLOCK_ELEMENT» AND RVV.ENTITY_ID = BE.IDn»;
//******************END OF FROM PART********************************************
$bCatalogSort = false;
if(count($arAddSelectFields)>0 || count($arAddWhereFields)>0 || count($arAddOrderByFields)>0)
{
if(CModule::IncludeModule(«catalog»))
{
$res_catalog = CCatalogProduct::GetQueryBuildArrays($arAddOrderByFields, $arAddWhereFields, $arAddSelectFields);
if($sGroupBy==»»
&& !$bOnlyCount
&& !(is_object($this) && isset($this->strField)))
$sSelect .= $res_catalog[«SELECT»].» «;
$sFrom .= str_replace(«LEFT JOIN», «ntttLEFT JOIN», $res_catalog[«FROM»]).»n»;
//$sWhere .= $res_catalog[«WHERE»].» «; moved to MkFilter
if(is_array($res_catalog[«ORDER»]) && count($res_catalog[«ORDER»]))
{
$bCatalogSort = true;
foreach($res_catalog[«ORDER»] as $i=>$val)
$arSqlOrder[$i] = $val;
}
}
}
$i = array_search(«CREATED_BY_FORMATTED», $arSelectFields);
if ($i !== false)
{
if ($sSelect
&& $sGroupBy==»»
&& !$bOnlyCount
&& !(is_object($this) && isset($this->strField)))
{
$sSelect .= «,UC.NAME UC_NAME, UC.LAST_NAME UC_LAST_NAME, UC.SECOND_NAME UC_SECOND_NAME, UC.EMAIL UC_EMAIL, UC.ID UC_ID, UC.LOGIN UC_LOGIN»;
}
else
{
unset($arSelectFields[$i]);
}
}
$sOrderBy = «»;
foreach($arSqlOrder as $i=>$val)
{
if(strlen($val))
{
if($sOrderBy==»»)
$sOrderBy = » ORDER BY «;
else
$sOrderBy .= «,»;
$sOrderBy .= $val.» «;
}
}
$sSelect = trim($sSelect, «, tnr»);
if(strlen($sSelect) <= 0)
$sSelect = «0 as NOP «;
$bDistinct = $bDistinct || (isset($arFilter[«INCLUDE_SUBSECTIONS»]) && $arFilter[«INCLUDE_SUBSECTIONS»] == «Y»);
if($bDistinct)
$sSelect = str_replace(«%%_DISTINCT_%%», «DISTINCT», $sSelect);
else
$sSelect = str_replace(«%%_DISTINCT_%%», «», $sSelect);
$sFrom = »
b_iblock B
INNER JOIN b_lang L ON B.LID=L.LID
INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
«.ltrim($sFrom, «tn»)
.(in_array(«USER_NAME», $arSelectFields)? «tttLEFT JOIN b_user U ON U.ID=BE.MODIFIED_BYn»: «»)
.(in_array(«LOCKED_USER_NAME», $arSelectFields)? «tttLEFT JOIN b_user UL ON UL.ID=BE.WF_LOCKED_BYn»: «»)
.(in_array(«CREATED_USER_NAME», $arSelectFields) || in_array(«CREATED_BY_FORMATTED», $arSelectFields)? «tttLEFT JOIN b_user UC ON UC.ID=BE.CREATED_BYn»: «»).»
«;
$strSql = »
FROM «.$sFrom.»
WHERE 1=1 »
.$sWhere.»
«.$sGroupBy.»
«;
if(isset($this) && is_object($this) && isset($this->strField))
{
$this->sFrom = $sFrom;
$this->sWhere = $sWhere;
return «SELECT «.$sSelect.$strSql;
}
if($bOnlyCount)
{
$res = $DB->Query(«SELECT «.$sSelect.$strSql, false, «FILE: «.__FILE__.»
LINE: «.__LINE__);
$res = $res->Fetch();
return $res[«CNT»];
}
if(is_array($arNavStartParams))
{
$nTopCount = intval($arNavStartParams[«nTopCount»]);
$nElementID = intval($arNavStartParams[«nElementID»]);
if($nTopCount > 0)
{
$strSql = «SELECT «.$sSelect.$strSql.$sOrderBy.» LIMIT «.$nTopCount;
$res = $DB->Query($strSql);
}
elseif($nElementID > 0
&& $sGroupBy == «»
&& $sOrderBy != «»
&& strpos($sSelect, «BE.ID») !== false
&& !$bCatalogSort)
{
$nPageSize = intval($arNavStartParams[«nPageSize»]);
if($nPageSize > 0)
{
$DB->Query(«SET @rank_e=0»);
$DB->Query(«SET @rank_r=0″);
$DB->Query(»
SELECT
«.$sSelect.»
,@rank_r:[email protected]_r+1 AS rank1
,if (BE.ID = «.$nElementID.», @rank_e:[email protected]_r, null) rank2
«.$strSql.$sOrderBy.»
«);
$DB->Query(«SET @rank_r=0″);
$res = $DB->Query(»
SELECT *
FROM (SELECT
«.$sSelect.»
,@rank_r:[email protected]_r+1 AS RANK
«.$strSql.$sOrderBy.»
LIMIT 18446744073709551615) el0
WHERE el0.RANK between @rank_e-$nPageSize and @rank_e+$nPageSize
«);
}
else
{
$DB->Query(«SET @rank=0″);
$res = $DB->Query(»
SELECT *
FROM (SELECT
«.$sSelect.»
,@rank:[email protected]+1 AS RANK
«.$strSql.$sOrderBy.»
LIMIT 18446744073709551615) el0
WHERE el0.ID = «.$nElementID.»
«);
}
}
else
{
if($sGroupBy == «»)
{
$res_cnt = $DB->Query(«SELECT COUNT(«.($bDistinct? «DISTINCT BE.ID»: «»x»»).») as C «.$strSql);
$res_cnt = $res_cnt->Fetch();
$cnt = $res_cnt[«C»];
}
else
{
$res_cnt = $DB->Query(«SELECT «x» «.$strSql);
$cnt = $res_cnt->SelectedRowsCount();
}
$strSql = «SELECT «.$sSelect.$strSql.$sOrderBy;
$res = new CDBResult();
$res->NavQuery($strSql, $cnt, $arNavStartParams);
}
}
else//if(is_array($arNavStartParams))
{
$strSql = «SELECT «.$sSelect.$strSql.$sOrderBy;
$res = $DB->Query($strSql, false, «FILE: «.__FILE__.»
LINE: «.__LINE__);
}
$res = new CIBlockResult($res);
$res->SetIBlockTag($arFilterIBlocks);
$res->arIBlockMultProps = $arIBlockMultProps;
$res->arIBlockConvProps = $arIBlockConvProps;
$res->arIBlockAllProps = $arIBlockAllProps;
$res->arIBlockNumProps = $arIBlockNumProps;
$res->arIBlockLongProps = $arIBlockLongProps;
return $res;
}
Как можно догадаться, этот метод получает из БД список элементов инфоблока, и для получения списка не требуется создания экземпляра класса CIBlockElement. Однако, чтобы добавить элемент инфоблока, обязательно нужно состояние, и только для того, чтобы записать инфо о последней произошедшей ошибке в публичное свойство класса.
В старом API очень активно используются такие глобальные переменные, как $APPLICATION, $USER, $DB. Они являются экземплярами определенных классов, и в документации раньше гордо звались синглтонами, правда сейчас я не нашел уже этих слов.
Для того, чтобы сгенерировать ошибку, например, в обработчиках событий, нужно воспользоваться методом $APPLICATION->ThrowException(), который на самом деле исключения не бросает.
Public function ThrowException($msg, $id = false)
{
$this->ResetException();
if(is_object($msg) && (is_subclass_of($msg, «CApplicationException») || (strtolower(get_class($msg))==»capplicationexception»)))
$this->LAST_ERROR = $msg;
else
$this->LAST_ERROR = new CApplicationException($msg, $id);
}
И да — вся эта красота до сих пор используется при разработке новых проектов, т.к. D7 пока еще не поддерживает всех возможностей старого API. Тот же модуль инфоблоков до сих пор позволяет выполнять только выборку сущностей, причем не целиком. Создать новый элемент, или обновить существующий с помощью нового API пока нельзя.
Новое API уже несколько отличается от старого. Во-первых, весь код из нового ядра разложен по неймспейсам, где прослеживается четкая зависимость от модуля. Например, аналог CIBlockElement::GetList из нового ядра — BitrixIblockElementTable::getList, где корневое пространство имен — это имя вендора, а следующее — имя модуля. Для того, чтобы это работало, в битриксе написали свой автозагрузчик BitrixMainLoader::autoLoad, который совсем не совместим с PSR-0/4.
Public static function autoLoad($className)
{
$file = ltrim($className, «»); // fix web env
$file = strtr($file, static::ALPHA_UPPER, static::ALPHA_LOWER);
static $documentRoot = null;
if ($documentRoot === null)
$documentRoot = static::getDocumentRoot();
if (isset(self::$arAutoLoadClasses[$file]))
{
$pathInfo = self::$arAutoLoadClasses[$file];
if ($pathInfo[«module»] != «»)
{
$m = $pathInfo[«module»];
$h = isset(self::$arLoadedModulesHolders[$m]) ? self::$arLoadedModulesHolders[$m] : «bitrix»;
include_once($documentRoot.»/».$h.»/modules/».$m.»/» .$pathInfo[«file»]);
}
else
{
require_once($documentRoot.$pathInfo[«file»]);
}
return;
}
if (preg_match(«#[^/a-zA-Z0-9_]#», $file))
return;
if (substr($file, -5) == «table»)
$file = substr($file, 0, -5);
$file = str_replace(«», «/», $file);
$arFile = explode(«/», $file);
if ($arFile === «bitrix»)
{
array_shift($arFile);
if (empty($arFile))
return;
$module = array_shift($arFile);
if ($module == null || empty($arFile))
return;
}
else
{
$module1 = array_shift($arFile);
$module2 = array_shift($arFile);
if ($module1 == null || $module2 == null || empty($arFile))
return;
$module = $module1.».».$module2;
}
if (!isset(self::$arLoadedModulesHolders[$module]))
return;
$filePath = $documentRoot.»/».self::$arLoadedModulesHolders[$module].»/modules/».$module.»/lib/».implode(«/», $arFile).».php»;
if (file_exists($filePath))
require_once($filePath);
}
Возможно, это все в будущем обрастет своим ServiceLayer»ом (есть некий BitrixMainServiceManager в новом ядре, который пока не используется и не документирован). Но надежды пока мало.
ORM — еще одно из нововведений D7, и это уже что-то, что может претендовать на звание настоящей модели! Отличить класс сущности ORM от любого другого класса можно по его имени. Класс сущности всегда должен заканчиваться на Table (ElementTable, SectionTable, OrderTable и т.д.). Причем, парадокс, имя файла с классом ORM сущности не должно заканчиваться на Table. К примеру, для ElementTable мы должны создать файл element.php. Ниже на скрине представлено содержимое директории lib (только в этой директории работает автозагрузка D7) модуля iblock. Попробуйте на глазок определить, что есть сущности ORM, а что — обычные классы с бизнес-логикой.
ORM, по большому счету, пока что не представляет из себя ничего особенного. Оно позволяет описывать таблицы БД в виде классов и позволяет выполнять запросы к этим таблицам, связывать их между собой. Никакого ActiveRecord и Repository нет и не предвидится.
WF_LOCKED_BY_USER reference to {@link BitrixUserUserTable}
*
*
* @package BitrixIblock
**/
class ElementTable extends MainEntityDataManager
{
const TYPE_TEXT = «text»;
const TYPE_HTML = «html»;
/**
* Returns DB table name for entity.
*
* @return string
*/
public static function getTableName()
{
return «b_iblock_element»;
}
/**
* Returns entity map definition.
*
* @return array
*/
public static function getMap()
{
return array(«ID» => new MainEntityIntegerField(«ID», array(«primary» => true,
«autocomplete» => true,
«title» => Loc::getMessage(«ELEMENT_ENTITY_ID_FIELD»),)),
«TIMESTAMP_X» => new MainEntityDatetimeField(«TIMESTAMP_X», array(«default_value» => new MainTypeDateTime(),
«title» => Loc::getMessage(«ELEMENT_ENTITY_TIMESTAMP_X_FIELD»),)),
«MODIFIED_BY» => new MainEntityIntegerField(«MODIFIED_BY», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_MODIFIED_BY_FIELD»),)),
«DATE_CREATE» => new MainEntityDatetimeField(«DATE_CREATE», array(«default_value» => new MainTypeDateTime(),
«title» => Loc::getMessage(«ELEMENT_ENTITY_DATE_CREATE_FIELD»),)),
«CREATED_BY» => new MainEntityIntegerField(«CREATED_BY», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_CREATED_BY_FIELD»),)),
«IBLOCK_ID» => new MainEntityIntegerField(«IBLOCK_ID», array(«required» => true,
«title» => Loc::getMessage(«ELEMENT_ENTITY_IBLOCK_ID_FIELD»),)),
«IBLOCK_SECTION_ID» => new MainEntityIntegerField(«IBLOCK_SECTION_ID», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_IBLOCK_SECTION_ID_FIELD»),)),
«ACTIVE» => new MainEntityBooleanField(«ACTIVE», array(«values» => array(«N», «Y»),
«default_value» => «Y»,
«title» => Loc::getMessage(«ELEMENT_ENTITY_ACTIVE_FIELD»),)),
«ACTIVE_FROM» => new MainEntityDatetimeField(«ACTIVE_FROM», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_ACTIVE_FROM_FIELD»),)),
«ACTIVE_TO» => new MainEntityDatetimeField(«ACTIVE_TO», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_ACTIVE_TO_FIELD»),)),
«SORT» => new MainEntityIntegerField(«SORT», array(«default_value» => 500,
«title» => Loc::getMessage(«ELEMENT_ENTITY_SORT_FIELD»),)),
«NAME» => new MainEntityStringField(«NAME», array(«required» => true,
«validation» => array(__CLASS__, «validateName»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_NAME_FIELD»),)),
«PREVIEW_PICTURE» => new MainEntityIntegerField(«PREVIEW_PICTURE», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_PREVIEW_PICTURE_FIELD»),)),
«PREVIEW_TEXT» => new MainEntityTextField(«PREVIEW_TEXT», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_PREVIEW_TEXT_FIELD»),)),
«PREVIEW_TEXT_TYPE» => new MainEntityEnumField(«PREVIEW_TEXT_TYPE», array(«values» => array(self::TYPE_TEXT, self::TYPE_HTML),
«default_value» => self::TYPE_TEXT,
«title» => Loc::getMessage(«ELEMENT_ENTITY_PREVIEW_TEXT_TYPE_FIELD»),)),
«DETAIL_PICTURE» => new MainEntityIntegerField(«DETAIL_PICTURE», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_DETAIL_PICTURE_FIELD»),)),
«DETAIL_TEXT» => new MainEntityTextField(«DETAIL_TEXT», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_DETAIL_TEXT_FIELD»),)),
«DETAIL_TEXT_TYPE» => new MainEntityEnumField(«DETAIL_TEXT_TYPE», array(«values» => array(self::TYPE_TEXT, self::TYPE_HTML),
«default_value» => self::TYPE_TEXT,
«title» => Loc::getMessage(«ELEMENT_ENTITY_DETAIL_TEXT_TYPE_FIELD»),)),
«SEARCHABLE_CONTENT» => new MainEntityTextField(«SEARCHABLE_CONTENT», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_SEARCHABLE_CONTENT_FIELD»),)),
«WF_STATUS_ID» => new MainEntityIntegerField(«WF_STATUS_ID», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_STATUS_ID_FIELD»),)),
«WF_PARENT_ELEMENT_ID» => new MainEntityIntegerField(«WF_PARENT_ELEMENT_ID», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_PARENT_ELEMENT_ID_FIELD»),)),
«WF_NEW» => new MainEntityEnumField(«WF_NEW», array(«values» => array(«N», «Y»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_NEW_FIELD»),)),
«WF_LOCKED_BY» => new MainEntityIntegerField(«WF_LOCKED_BY», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_LOCKED_BY_FIELD»),)),
«WF_DATE_LOCK» => new MainEntityDatetimeField(«WF_DATE_LOCK», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_DATE_LOCK_FIELD»),)),
«WF_COMMENTS» => new MainEntityTextField(«WF_COMMENTS», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_WF_COMMENTS_FIELD»),)),
«IN_SECTIONS» => new MainEntityBooleanField(«IN_SECTIONS», array(«values» => array(«N», «Y»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_IN_SECTIONS_FIELD»),)),
«XML_ID» => new MainEntityStringField(«XML_ID», array(«validation» => array(__CLASS__, «validateXmlId»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_XML_ID_FIELD»),)),
«CODE» => new MainEntityStringField(«CODE», array(«validation» => array(__CLASS__, «validateCode»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_CODE_FIELD»),)),
«TAGS» => new MainEntityStringField(«TAGS», array(«validation» => array(__CLASS__, «validateTags»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_TAGS_FIELD»),)),
«TMP_ID» => new MainEntityStringField(«TMP_ID», array(«validation» => array(__CLASS__, «validateTmpId»),
«title» => Loc::getMessage(«ELEMENT_ENTITY_TMP_ID_FIELD»),)),
«SHOW_COUNTER» => new MainEntityIntegerField(«SHOW_COUNTER», array(«default_value» => 0,
«title» => Loc::getMessage(«ELEMENT_ENTITY_SHOW_COUNTER_FIELD»),)),
«SHOW_COUNTER_START» => new MainEntityDatetimeField(«SHOW_COUNTER_START», array(«title» => Loc::getMessage(«ELEMENT_ENTITY_SHOW_COUNTER_START_FIELD»),)),
«PREVIEW_PICTURE_FILE» => new MainEntityReferenceField(«PREVIEW_PICTURE_FILE»,
«BitrixFileFile»,
array(«=this.PREVIEW_PICTURE» => «ref.ID»),
array(«join_type» => «LEFT»)),
«DETAIL_PICTURE_FILE» => new MainEntityReferenceField(«DETAIL_PICTURE_FILE»,
«BitrixFileFile»,
array(«=this.DETAIL_PICTURE» => «ref.ID»),
array(«join_type» => «LEFT»)),
«IBLOCK» => new MainEntityReferenceField(«IBLOCK»,
«BitrixIblockIblock»,
array(«=this.IBLOCK_ID» => «ref.ID»),
array(«join_type» => «LEFT»)),
«WF_PARENT_ELEMENT» => new MainEntityReferenceField(«WF_PARENT_ELEMENT»,
«BitrixIblockElement»,
array(«=this.WF_PARENT_ELEMENT_ID» => «ref.ID»),
array(«join_type» => «LEFT»)),
«IBLOCK_SECTION» => new MainEntityReferenceField(«IBLOCK_SECTION»,
«BitrixIblockSection»,
array(«=this.IBLOCK_SECTION_ID» => «ref.ID»),
array(«join_type» => «LEFT»)),
«MODIFIED_BY_USER» => new MainEntityReferenceField(«MODIFIED_BY_USER»,
«BitrixUserUser»,
array(«=this.MODIFIED_BY» => «ref.ID»),
array(«join_type» => «LEFT»)),
«CREATED_BY_USER» => new MainEntityReferenceField(«CREATED_BY_USER»,
«BitrixUserUser»,
array(«=this.CREATED_BY» => «ref.ID»),
array(«join_type» => «LEFT»)),
«WF_LOCKED_BY_USER» => new MainEntityReferenceField(«WF_LOCKED_BY_USER»,
«BitrixUserUser»,
array(«=this.WF_LOCKED_BY» => «ref.ID»),
array(«join_type» => «LEFT»)),);
}
/**
* Returns validators for NAME field.
*
* @return array
*/
public static function validateName()
{
return array(new MainEntityValidatorLength(null, 255),);
}
/**
* Returns validators for XML_ID field.
*
* @return array
*/
public static function validateXmlId()
{
return array(new MainEntityValidatorLength(null, 255),);
}
/**
* Returns validators for CODE field.
*
* @return array
*/
public static function validateCode()
{
return array(new MainEntityValidatorLength(null, 255),);
}
/**
* Returns validators for TAGS field.
*
* @return array
*/
public static function validateTags()
{
return array(new MainEntityValidatorLength(null, 255),);
}
/**
* Returns validators for TMP_ID field.
*
* @return array
*/
public static function validateTmpId()
{
return array(new MainEntityValidatorLength(null, 40),);
}
/**
* Add iblock element.
*
* @param array $data Element data.
* @return MainEntityAddResult
*/
public static function add(array $data)
{
$result = new MainEntityAddResult();
$result->addError(new MainEntityEntityError(Loc::getMessage(«ELEMENT_ENTITY_MESS_ADD_BLOCKED»)));
return $result;
}
/**
* Updates iblock element by primary key.
*
* @param mixed $primary Element primary key.
* @param array $data Element data.
* @return MainEntityUpdateResult
*/
public static function update($primary, array $data)
{
$result = new MainEntityUpdateResult();
$result->addError(new MainEntityEntityError(Loc::getMessage(«ELEMENT_ENTITY_MESS_UPDATE_BLOCKED»)));
return $result;
}
/**
* Deletes iblock element by primary key.
*
* @param mixed $primary Element primary key.
* @return MainEntityDeleteResult
*/
public static function delete($primary)
{
$result = new MainEntityDeleteResult();
$result->addError(new MainEntityEntityError(Loc::getMessage(«ELEMENT_ENTITY_MESS_DELETE_BLOCKED»)));
return $result;
}
}
И пример работы с этой сущностью
//Выборка данных
$dbElements = BitrixIblockElementTable::query()
->setFilter([«IBLOCK_ID» => CATALOG_IBLOCK_ID, «ACTIVE» => «Y»])
->setSelect([«NAME», «ID», «DETAIL_PAGE_URL», «DATE_ACTIVE_FROM»])
->addSelect(«IBLOCK_SECTION_ID», «PARENT_SECTION»)
->setLimit(10)
->addOrder(«id», «DESC»)
->exec();
while ($arElement = $dbElements->fetch()) {
echo «{$arElement[«NAME»]} — » . $arElement[«DATE_ACTIVE_FROM»]->format(«d.m.Y H:i:s»);
}
//Добавление записи
$addResult = BitrixIblockElementTable::add([
«NAME» => «Название нового элемента»,
«IBLOCK_ID» => CATALOG_IBLOCK_ID
]);
if (!$addResult->isSuccess()) {
echo implode(«
» ,$addResult->getErrorMessages());
}
Битрикс очень гордится своим модулем Highload-блоков, который полностью написан с использованием D7.
Раньше у них в качестве хранилища произвольного набора информации были только инфоблоки. Инфоблок, для тех кто не в курсе, это такая сущность, которая в БД хранится как комплекс из нескольких таблиц (1 таблица на «базовые» поля элемента инфоблока и до 2х таблиц на свойства элемента инфоблока). Все базовые поля элементов всех
инфоблоков хранятся в одной таблице. Если у вас будет 15 инфоблоков, в каждом из которых будет по 500к элементов, все эти элементы по факту будут находиться в одной таблице. Дополнительные свойства элементов инфоблоков джойнятся из других таблиц. Если это инфоблоки первой версии, то все свойства всех инфоблоков также лежат в одной таблице, а в случае с инфоблоками 2.0 (привет, маркетинг) — свойства каждого инфоблока уже разделены по разным таблицам.
И все это дело естественно очень сильно тормозило уже на относительно небольших наборах данных. 400к элементов в одном инфоблоке уже довольно сильно затормаживают работу админки. Маркетологи в битриксе подумали, и запилили Highload-блоки! Разница в реализации между обычными инфоблоками — минимальная. Теперь для каждого highload-блока создается своя таблица + дополнительно создается еще одна таблица для хранения множественных значений. Обычный подход в создании обычной таблицы в БД они назвали гордым именем highload
просто потому, что оно тормозит меньше обычных инфоблоков!
Кроме того, внутри модуля, для того, чтобы он работал согласно D7, классы сущностей генерируются динамически и eval»ятся на каждом хите. Вот такой вот highload.
Посмотреть на это
Public static function compileEntity($hlblock)
{
global $USER_FIELD_MANAGER;
// generate entity & data manager
$fieldsMap = array();
// add ID
$fieldsMap[«ID»] = array(«data_type» => «integer»,
«primary» => true,
«autocomplete» => true);
// build datamanager class
$entity_name = $hlblock[«NAME»];
$entity_data_class = $hlblock[«NAME»];
if (!preg_match(«/^+$/i», $entity_data_class))
{
throw new MainSystemException(sprintf(«Invalid entity name `%s`.», $entity_data_class));
}
$entity_data_class .= «Table»;
if (class_exists($entity_data_class))
{
// rebuild if it»s already exists
EntityBase::destroy($entity_data_class);
}
else
{
$entity_table_name = $hlblock[«TABLE_NAME»];
// make with an empty map
$eval = »
class «.$entity_data_class.» extends «.__NAMESPACE__.»DataManager
{
public static function getTableName()
{
return «.var_export($entity_table_name, true).»;
}
public static function getMap()
{
return «.var_export($fieldsMap, true).»;
}
public static function getHighloadBlock()
{
return «.var_export($hlblock, true).»;
}
}
«;
eval($eval);
}
// then configure and attach fields
/** @var BitrixMainEntityDataManager $entity_data_class */
$entity = $entity_data_class::getEntity();
$uFields = $USER_FIELD_MANAGER->getUserFields(«HLBLOCK_».$hlblock[«ID»]);
foreach ($uFields as $uField)
{
if ($uField[«MULTIPLE»] == «N»)
{
// just add single field
$field = $USER_FIELD_MANAGER->getEntityField($uField, $uField[«FIELD_NAME»]);
$entity->addField($field);
foreach ($USER_FIELD_MANAGER->getEntityReferences($uField, $field) as $reference)
{
$entity->addField($reference);
}
}
else
{
// build utm entity
static::compileUtmEntity($entity, $uField);
}
}
return EntityBase::getInstance($entity_name);
}
Черт бы с ним, но эти самые хайлоад блоки ну никак не могут выступать в роли альтернативы обычным инфоблокам. Оказывается их придумали только для того, чтобы хранить справочные не-иерархичные данные. Кроме того, модуль до сих пор не поддерживает таких нужных функций в админке, как фильтрация по полю типа «Дата», нельзя обозвать сущность HLблока каким-то человекопонятным названием, чтобы администратор каждый раз не пугался при входе на страницу редактирования сущности, к примеру, BrandReference. Все это наводит на мысль, что задумывалось это дело ради альтернативы медленным инфоблокам, но допилить не успели (или не осилили, или пошло в разрез с интересами бизнеса), и в итоге зарелизили полу-готовый функционал как новую фишку, а маркетологи причесали и красиво подали эту идею.
C — Controller, или компонент
Обычный компонент в битриксе можно сравнить с виджетами из Yii. Это некий контейнер, обособленный от всех остальных контейнеров, который принимает на вход какие-то параметры, делает какую-то работу, и с результатом работы подключает вьюху. Разработчики битрикс глубоко убеждены, что те компоненты, которые они предоставляют из коробки, решают большинство задач, которые стоят перед их коллегами. Но, как водится, разработчикам же ничего не нравится всегда, и возможностей стандартных компонентов им всегда «немного» не хватает. Поэтому Битриксоиды решили дать разработчикам возможность модифицировать результат работы компонента… с помощью вьюхи. В директории шаблона компонента можно создать файлик result_modifier.php, в котором можно дополнить результат работы компонента своими данными. И если вы вдруг захотите использовать эти данные в другом шаблоне, вам придется скопипастить этот файлик (ну или заинклюдить этот файл из другого шаблона). Меня всегда мучал вопрос — для чего этот пафос? Почему бы не добавить горку запросов прям в php шаблоне? Разница то невеликая получается.
Что это я про шаблоны в разделе о контроллерах…
В битрикс есть 2 вида компонентов 2.0 (опять привет маркетинг) — обычные и комплексные. Обычный компонент — это виджет. Комплексный компонент — это некий контроллер+роутер, который на основании URL понимает, какую именно страницу с набором виджетов нужно отобразить. Порядок работы примерно такой:
- в url написано /catalog/bolshaya-zelenaya-shapka.html
- с помощью mod_rewrite битрикс понимает, что для физического раздела /catalog нужно всегда подключать файл /catalog/index.php
- комплексный компонент парсит url, и понимает, что нужно подключить детальную страницу товара, назовем ее detail
- комплексный компонент собирает параметры, которые необходимы для работы его дочерних компонентов
- комплексный компонент подключает свой шаблон detail.php, внутри которого прописано подключение дочерних обычных компонентов
С виду не очень красиво, но работать можно. Однако не все так просто… Если вы с помощью визуального редактора поменяете параметры комплексного компонента, то файлик с настройками адресации (urlrewrite.php) будет перезаписан системой. Причем, если вы вдруг что-то неправильно там написали для других страниц обязательно что-то сломается без какого-либо предупреждения. На практике это может привести к потере работоспособности целых разделов сайта.
Настройка параметров комплексного компонента может превратиться в муку. У одного такого компонента с легкостью может быть сотня входных параметров, просто потому, что нужно настраивать параметры дочерних компонентов.
Комплексный компонент — он вроде бы и роутер. Однако все те маршруты, которые вы создадите в этом компоненте, не попадут в автоматически генерируемый sitemap.xml. Эти ссылки не попадут в модуль поиска. У вас не будет никакой возможности сгенерировать адрес до маршрута извне (например, вы хотите поставить ссылку на детальную страницу бренда где-то в сайдбаре, и нельзя будет обратиться к роутеру с просьбой сгенерить этот URL).
Вообще говоря, функции роутера в битриксе толком не выполняет никто. В инфоблоках есть возможность настроить шаблон URL для страницы инфоблока, страницы раздела инфоблока и страницы элемента инфоблока. Все, у инфоблоков больше не может быть страниц.
Для форумов есть возможность настроить шаблоны некоторых страниц. Для блогов можно настроить. Возможно, где-то еще можно что-то настроить… все это настолько децентрализовано, что собрать воедино это все становится достаточно тяжело.
Обычные компоненты — это чуть более простые сущности, чем комплексные компоненты. Их задача — принять на вход набор параметров, обработать их, скормить результат работы шаблону и закешировать все и вся.
Вся логика компонента содержится в файле component.php. С 12й версии битрикса (ныне актуальной является версия 16, 4 года прошло) появилась возможность «использовать ООП» в компонентах. Это нововведение заключается в том, что вместо файла component.php можно создать файл class.php, в котором можно вместо обычной лапши написать класс, унаследованный от CBitrixComponent. И это был большой шаг вперед, т.к. появилась возможность наследовать компоненты и не использовать result_modifier.php вообще, и не практиковать копипасту, если нужно вдруг сильно кастомизировать компонент. Но и тут до сих пор все не так ладно. Из всего набора компонентов, лишь процентов 25-30 могут похвастать наличием класса в своем арсенале. Причем добрая половина из них просто не даст вам возможности расширить себя полностью, т.к. написаны они часто нелогично.
К слову, добрые люди пытаются стандартизировать, как-то помочь разработчикам в написании компонентов, и есть соответствующий инструментарий
V — View, или шаблоны
Шаблоны в битриксе можно разделить на несколько типов:
- Шаблоны обычных и комплексных компонентов 2.0
- Шаблоны сайта
- Шаблоны прочих сущностей (почтовых отправлений, выпусков рассылок, веб-форм, генераторов экспортов и еще много чего)
В шаблонах компонентов есть даже возможность использовать шаблонизаторы. В принципе можно подключить любой шаблонизатор, но из коробки никаких вспомогательных инструментов нет. Если кому надо, есть у меня пара ссылок на расширения для twig и blade , которые работают и вполне себе используются на продакшене. Но и тут битриксоиды извратились. Шаблонизатор можно использовать только с компонентами. Подключить шаблонизатор к рендереру шаблона сайта, или других сущностей не получится, т.к. нет там никакого рендерера.
В шаблонах компонентов раздражает еще момент с их размещением. Компонент подключается с помощью нехитрой контструкции
$APPLICATION->IncludeComponent(«bitrix:catalog.section», «template_name», );
В качестве второго параметра идет название шаблона компонента. Так вот в зависимости от различных условий, местоположение этого шаблона может быть в самых неожиданных местах:
- bitrix/components/bitrix/catalog.section/templates/template_name
- local/components/bitrix/catalog.section/templates/template_name
- bitrix/templates/.default/components/bitrix/catalog.section/template_name
- bitrix/templates/site_template/components/bitrix/catalog.section/template_name
- local/templates/.default/components/bitrix/catalog.section/template_name
- local/templates/site_template/components/bitrix/catalog.section/template_name
- bitrix/components/bitrix/catalog/templates/.default/bitrix/catalog.section/template_name
- local/templates/site_template/components/bitrix/catalog/.default/bitrix/catalog.section/template_name
И это еще я не все варианты перечислил…
Шаблон сайта можно рассматривать как набор файлов: header.php, footer.php (да, у сайта обязательно они должны быть), description.php (системное описание шаблона сайта), template_styles.css (стили шаблона сайта), директорию с шаблонами компонентов и еще группка менее значимых файликов. И все. И никак на это не повлиять, ничего с этим не сделать. Невозможно подцепить шаблонизатор.
Про другие шаблоны и говорить нечего. Они либо просто хранятся в БД в виде верстки со включением в нее каких-то «переменных» данных, либо это тупой php файл, который делает всю работу, от выборки параметров из БД до вывода информации. Для примера, можно посмотреть на генератор YML файла для маркета. Нет никакого смысла выкладывать его сюда, просто потому, что он достаточно большой, около 2к строк. Кому нужно, тот нагуглит, лежит он в /bitrix/modules/catalog/load/yandex_run.php
Файловая природа
Как стало ясно выше, в битриксе с архитектурой все не очень хорошо. Но есть у битрикса и еще один важный аспект архитектуры.
Битрикс — это на половину файловая CMS. Очень многие вещи управляются с помощью каких-то файлов:
- Нужна страница — создай файл
- Нужен набор страниц — создай файл и подключи там компонент, работающий с инфоблоками
- Нужно задать title для страницы — отредактируй файл
- Нужно задать title для всех страниц раздела — создай специальный файл.section.php в корне этого раздела
- Нужно отредактировать права — редактируй файл.access.php
- Настройки до инициализации системы — в файле dbconn.php, .settings.php и.settings_extra.php
- result_modifier.php, component_epilog.php, init.php, .parameters.php, .description.php ….
И таких специальных файликов по битриксу разбросано огромное множество. С одной стороны, это дает определенную гибкость при работе с системой. С другой — это может превратиться в муку как для разработчика, так и для менеджера сайта. Файлы страниц иногда превращаются в кашу из php кода, верстки, и подключаемых компонентов. В результате визуальный редактор может некорректно распарсить этот файл, и при редактировании он запросто может экранировать php теги в некоторых местах, что приведет к неработоспособности страницы. Вы скажете — не надо писать php код в таких файлах? Да, я знаю. Но битрикс очень часто и безальтернативно заставляет так поступить.
Да и в голове нужно держать постоянно информацию о том, что это за файлы такие, и какие данные они могут содержать. В разных файлах должны содержаться разные данные с разной структурой, и нужно ее помнить для каждого варианта. В документации искать это каждый раз — тяжелый труд.
В дополнение к вышесказанному
Можно бесконечно жаловаться на то, как все плохо устроено в битриксе. На мой взгляд, все эти жалобы можно охарактеризовать одним словосочетанием — «как-то не до конца». И действительно, если вдруг битриксоиды анонсируют какую-то фишку, то они ее релизят как-то не полностью, не доделывают, не доводят до ума. Примеров — масса:
- внедрили ORM — не доделали, пользоваться в полной мере нельзя
- сделали автозагрузчик, он работает только в модулях, и не по стандартам
- дали возможность подключить шаблонизатор, но использовать его можно не везде, и не полностью
- и т.д. и т.п.
В двух словах попробую охарактеризовать остальные проблемы, с которым приходится сталкиваться ежедневно.
Админка
Если кто-то работал с админкой, создавал свои страницы в административной части так, как это предлагает делать битрикс, тот меня поймет. Это просто ад. Для тех, кто не в курсе — битрикс предлагает для каждой страницы использовать файл с лапшой. Например, страница детального просмотра заказа в админке в исполнении разработчиков битрикса занимает over 4к строк. У меня IDE начинает подтормаживать при просмотре содержимого этого файла. Там тебе и php, и js, и html. Хорошо хоть, от SQL избавились, хотя я уверен, что на других административных страницах он есть.
И что мешало сделать работу административных страниц с помощью тех же компонентов — не понятно. Кастомизировать большинство административных страниц просто нет никакой возможности. В случае с компонентами это можно было бы сделать в два счета.
К слову, добрые люди сделали модуль , который поможет вам в построении административных страниц
js-фреймворк
В битриксе есть js составляющая, которая выполняет роль некоего клиентского фреймворка. Никто из разработчиков не любит его по нескольким причинам:
- он почти не документирован
- он монструозен
- он во многом дублирует привычный многим jquery
Битрикс очень часто использует его в своих компонентах, тем самым вызывая еще больше гнева разработчиков. Ядро этой библиотеки в минифицированном виде составляет 85кб, что очень не мало. Избежать его подключения не получится, если вы хотите использовать все возможности битрикса (композит, asset-management).
Дух копипасты
В последнее время все меньше, но все равно довольно часто, битрикс заставляет что-то копипастить. Хочешь модифицировать работу компонента — скопипасти. Хочешь создать свой шаблон выгрузки — скопипасти системный и допили. Хочешь сделать почти такой же шаблон, который у тебя есть — скопипасти и немного измени его. И об этом даже рассказывают на курсах для начинающих разработчиков. Слов нет.
Asset-management и CDN
Очень «мне нравится» в битриксе способ управления ресурсами. В принципе, можно зарегистрировать набор определенных «библиотек». Каждая библиотека — это набор css/js файлов, который может зависеть от каких-то других библиотек. Если подключить какую-то библиотеку на страницу, то перед ее подключением будут разрешены все зависимости и все зависимые библиотеки будут вставлены на страницу. Все вроде бы хорошо, только каждый ресурс будет вставлен в виде отдельного файла в тег script или link. И благодаря этому существуют сайты, у которых подключено по 30-50 скриптов и столько же файлов стилей.
Говно-вопрос, сказали в битриксе, и сделали волшебную галочку, которая объединяет все эти файлы в один. И появились сайты, где вместо 50 скриптов стало 2, каждый по 300-500кб. Какое-то время назад это объединение работало с ошибками и объединяло одни и те же ресурсы по нескольку раз, но сейчас вроде бы исправили.
И тут битриксоиды выкрутились — прикрутили возможность выгрузить все ресурсы на CDN сервер. Который вечно отваливается…
Потом появился Google Pagespeed Insights, который рекомендовал опустить все ресурсы в нижнюю часть страницы. И в битриксе опять сделали волшебную галочку, которая тупо опускает все ресурсы в body, если они не помечены специальным атрибутом.
А еще они вместе с коробкой распространяют минифицированные версии своих скриптов, которые подключаются при использовании еще одной волшебной галочки в админке.
В общем, никаких вам scss, никаких TypeScript. Хотите грамотно управлять ресурсами — не используйте встроенную систему битрикса, юзайте webpack, который можно спокойно с битриксом подружить.
Многосайтовость / многоязычность
Это, наверно, самая страшная головная боль разработчика, которая продолжается с момента зарождения продукта. Нельзя просто так взять, и создать многоязычный сайт. А если вам нужен многоязычный каталог с разными ценами и валютами — то это превращается в муку, за которую нужно еще и выложить кругленькую сумму (на покупку доп.лицензии для очередной языковой версии сайта придется раскошелиться).
Если вы создаете многоязычный и многовалютный сайт, то будьте готовы к тому, что битрикс будет очень агрессивно сопротивляться этому. Настройки многосайтовости децентрализованы по всей админке. Каждая сущность в админке имеет свою зависимость от языковой версии сайта. Какие-то сущности могут вообще не поддерживать зависимости от сайта/языка, а какие-то имеют только однозначную привязку к языку, так что придется эту сущность продублировать и потом поддерживать.
В базовом варианте, чтобы заставить инфоблок работать в нескольких языках, вам придется создать дубль этого инфоблока. Но на практике никто этого не делает, и пытается придумать свои способы хранения одной сущности централизованно, разнося ее языкозависимые атрибуты по другим хранилищам.
Нельзя задать дефолтный язык при локализации. Если у вас есть языковая переменная, описывающая какую-то фразу на русском, и этой языковой переменной нет в английском исполнении, то на английском сайте будет показана пустая строка, и никак на это нельзя повлиять (во-многих случаях можно было бы оставить русскую фразу, чтобы не было пустот).
Механизм управления правами
Очень замудрили с этой подсистемой. Часто бывает сложно разобраться, почему ты выдал права на просмотр какой-то сущности, а пользователь не может ими воспользоваться. Например, чтобы дать право на редактирование инфоблока, нужно дать доступ к директории /bitrix/admin, выдать права для конкретного инфоблока и выдать права в главном модуле. Чересчур много операций нужно сделать, чтобы выдать права для одной сущности. А если прав не хватает, то без ковыряния в исходниках никак не получится понять — почему.
Конфигурирование
В битриксе нет централизованного хаба, который бы позволил управлять настройками системы. Настройки опять таки децентрализованы по всей системе. Опции есть в настройках модуля, в настройках компонентов, в COption (будучи не вынесенными в админку). В админке опции одного модуля могут быть разнесены по 3-4м разным страницам, которые находятся в совершенно разных местах. urlrewrite можно править через админку! Теперь еще и.settings и.settings_extra. Иногда совершенно не ясно, какие из них более приоритетны, очень часто не хватает пояснений для опций, непонятны взаимосвязи. Нет никакого нативного способа расшаривать конфигурацию между разработчиками.
Настройки бывают очень нелогичными. Иногда доходит до абсурда… посмотрите компонент бигдаты — разве его сможет настроить неподготовленный человек?
Интеграция с 1С
Это тот пункт в списке фич битрикса, на который клюет достаточно большое количество заказчиков. Битрикс обещает в 2 клика настроить двустороннюю интеграцию сайта с 1С, которая будет мгновенно доставлять контент и документы от одной системы к другой.
Да, оно действительно так и есть, но с несколькими оговорками.
Во-первых, чтобы сделать интеграцию «из коробки» без дополнительных усилий, нужно сделать все именно так, как написано в документации битрикса — построить каталог на сайте по тем правилам, которые предлагает битрикс и построить каталог в 1С, которые требует битрикс. В идеале — создать вообще все с нуля, и тогда может быть, у вас все заработает из коробки.
Во-вторых, Битрикс дружит не со всеми конфигурациями 1С из коробки. Стоит предварительно ознакомиться
В-третьих, идеального мира не бывает. Обычно у заказчика, который хочет сайт, уже есть розничный бизнес, а значит уже есть 1С, которая является огромной мусоркой. И эту мусорку приходится прокидывать на сайт. А чтобы на сайте не получилось такой же мусорки, требуется значительно доработать механизм обмена.
Очень часто требования заказчика сильно расходятся с тем видением продукта, которое сформировано у команды Битрикса, и тогда доработка механизма обмена может быть достаточно дорогой, по трудоемкости сопоставимой с разработкой уникального модуля обмена под конкретный случай.
Поэтому не нужно пытать иллюзий по поводу того, что вам удастся легко интегрировать сайт с 1С. Это все происки маркетологов.
Доработка обмена с 1С — это тоже отдельная тема. За организацию обмена каталогом отвечает класс CIBlockCMLImport.- 5.7к строк. Один из главных методов, который чаще всего требует расширения — CIBlockCMLImport::ImportElement, содержит больше 1к строк. Достаточно раз унаследоваться, пару раз обновить продукт на протяжении длительного времени, и можно получить неработающий обмен с 1С. Поэтому часто разработчики не лезут в этот класс и пытаются как-то влезть в процесс импорта с помощью обработчиков событий. Работать с обработчиками событий в битриксе, особенно в модуле инфоблоков — тоже не очень приятное занятие, хотя бы из-за того, что однотипные события устроены не однородно, а некоторых событий просто не хватает.
В общем с этим дела обстоят также печально, как и ранее.
Несогласованность
Мне порой кажется, что разработчики разных модулей не особенно то общаются между собой. Изучая исходники ядра натыкаешься на очень разнородные решения, которые можно было бы выполнить на одном движке, но они реализованы почему-то по разному.
Для примера можно взять свойства элементов инфоблоков и UserFields. И та и другая сущность по факту является дополнительным полем для другой сущности. Она имеет тип, имеет значение и описание. Значение хранится в отдельной(ых) таблице(ах) БД, имеют примерно схожий интерфейс доступа к данным. Так почему бы не сделать для них одинаковый интерфейс?
Вот в конце марта обновился модуль sale до последней версии, и там тоже обещали произвольные свойства для заказов. Неужели там теперь новый, третий интерфейс для работы со расширенными свойствами сущности?
Битрикс24
Это вообще отдельная тема для разговора. На почве этой системы часто возникает путаница. Есть 2 варианта исполнения Б24 — SaaS и Standlone. Есть маркетплейс для Б24, но в нем содержатся приложения только для SaaS версии! Если у вас коробочная версия, купленная за 200 кусков, вы не сможете поставить такое популярнейшие приложения, как конструктор документов , да и вообще вы не сможете на свой Битрикс24 поставить ни одно приложение из маркетплейса для Битрикс24. Вот такой парадокс.
Вместо этого в вашем Битрикс24 будет доступен маркетплейс от обычной версии. Там решений гораздо больше, но они сконцентрированы в основном вокруг Управления Сайтом, а не Б24.
Битрикс24, как мне сказали в отделе технической поддержки, это целостная система. Если вы вмешиваетесь в работу стандартных компонентов системы, то будьте готовы, что эта функциональность сломается при последующих обновлениях. Битрикс не будет рассчитывать на то, что вы дорабатываете компоненты портала, и это несмотря на то, что они официально отправляют своих клиентов к партнерам
К слову, дорабатывать компоненты в коробочной версии Б24 — та еще задачка. Компоненты, которые генерируют js код, который с помощью ajax обращается к php коду, который в ответ генерирует html+js. Это адовая смесь, в которую очень не хочется погружаться.
Документация
Документация по битриксу отстает от развития продукта на 1-1.5 года. Код очень слабо покрыт phpDoc»ами, и часто комментарий перед классом стоит исключительно «для галочки», будучи автоматически сгенерированным в IDE.
Сам стиль изложения документации в официальных источниках часто бывает слишком «вольным», а содержимое некоторых статей в документации может не иметь никакого отношения к самому битриксу.
Курс разработчика имеет очень много информации, однако формат, в котором разработчика знакомят с возможностями системы, не дает того уровня восприятия, который требуется. Если вы зайдете в Cookbook Symfony, то там все разложено по полочкам, расписаны все необходимые аспекты в зависимости от версии. Тогда как в битриксе курс обучения разработчика содержит непонятно по какому принципу структурированную информацию по старому и новому ядру, которая подается сначала отдельно, а потом вперемешку, от чего у начинающих возникает головная боль.
Организация процесса разработки
Из-за специфичности системы не так уж просто организовать удобный процесс разработки. Не самая свежая версия редакции Бизнес (что была под рукой) после установки занимает, вдумайтесь, почти 530 мегабайт
$ du -s *|sort -nr|cut -f 2-|while read a;do du -hs $a;done
523M bitrix
204K upload
64K bitrixsetup.php
56K desktop_app
20K readme.html
20K license.html
4,0K web.config
4,0K urlrewrite.php
4,0K readme.php
4,0K license.php
4,0K install.config
4,0K index.php
Из этого объема добрая половина — это бинарники и установочники, которые в общем-то не нужны для версионного контроля. Вообще говоря, принято не версионировать битриксовое ядро. Разработчики Битрикса как бы сами гарантируют целостность ядра, управляют сами зависимостями версий разных модулей при обновлениях. Но это несет в себе сразу, как минимум, один большой минус — невозможно одной командой из версионного контроля развернуть полностью работающий проект, приходится собирать его по частям: исходники ядра доставать из битриксового бекапа, а исходники разработчиков — из git.
С базой тоже все не ладно. Если сами вы можете использовать миграции при разработке, то битрикс накатывает обновления в базу с помощью обычных скриптов, которые вы не можете контролировать. Поэтому при обновлениях все равно придется перекидывать бекапы баз от центрального хоста разработки к другим разработчикам.
Добрые люди, опять же, пилят инструменты , которые помогают это все организовать, но заставить битрикс следовать этим правилам к сожалению до сих пор не удается.
Официально битрикс разрешает иметь 2 копии одного дистрибутива. Один — для продакшена, второй для разработки. Если у вас несколько разработчиков на одном проекте — то вы, как бы, вне закона) На самом деле, достаточно отрубить машине с битриксом входящие и исходящие подключения с/на www.bitrixsoft.com , и тогда можно наклепать сколь угодно много копий разработки, просто они не смогут самостоятельно обновляться.
Коллеги
И последний вопрос, которого хотелось бы коснуться.
В связи с тем, что битрикс имеет низкий порог вхождения, среди компаний, которые предоставляют услуги на этом рынке очень много неквалифицированных кадров. Мне довелось повидать множество различных проектов за свою карьеру (суммарно более сотни), выполненных на 1С-Битрикс. Могу с уверенностью сказать, что 95% из них были выполнены «тяп-ляп». Очень редко попадались проекты, к разработке которых чувствовался подход, однако это были единицы. Это все очень печально.
Выводы
Конечно же, всех минусов в рамках одной статьи не рассмотреть. Каждый день натыкаешься на какие-то мелочи, которые ежедневно мешают работать. Но рассмотреть все такие мелочи просто невозможно, да и наверно ни к чему.
Какие тут можно сделать выводы. Битрикс — крайне сложная система в связи с тем, что имеет непродуманную архитектуру, множество изъянов, которые так и продолжают жить в продукте на протяжении длительного времени. С другой стороны Битрикс — это достаточно простая система, которая для старта требует гораздо меньший уровень квалификации, в отличие от фреймворков.
Поддержка этого продукта — весьма неблагодарное занятие, по сравнению с такими продуктами, как Symfony, Laravel, Yii. Продукт очень любит вставлять палки в колеса как неопытным, так и опытным разработчикам, что, в свою очередь, может отражаться и на стоимости услуг опытных разработчиков под Битрикс.
Жалею ли я о том, что так много времени потратил на работу с этой системой? Скорее да, чем нет. Разумнее было бы потратить это время на изучение чего-то более правильного и более логичного (чем я стараюсь активно заниматься сейчас). Но так уж получилось, что некому было меня направить в правильное русло в начале моего пути.
Если вы — начинающий php разработчик, то предпочтите Битриксу изучение фреймворков, таких как Symfony, Laravel, Yii, ZendFramework. Поверьте, в будущем это с лихвой окупится. Освоив любой из этих фреймворков вам не составит труда в будущем разрабатывать что-то под Битрикс. Если у вас нет выбора, то изучайте Битрикс, но в свободное время лучше все-таки пытаться погрузиться в мир фреймворков, чтобы поставить мозги на место.
Если вы — разработчик со стажем в Битрикс, но без опыта в других фреймворках, то обязательно окунитесь в другой мир, вам откроется очень много новых и полезных знаний, которые помогут вам в написании гораздо более качественных решений под 1С-Битрикс. Старайтесь использовать решения из других фреймворков в своих проектах, благо сделать это совсем несложно благодаря компонентному подходу последних и composer.
Если вы — заказчик, то не верьте маркетологам Битрикса. Ничего не будет так легко, как рассказывают в презенташках битрикса. И не вините в этом своих разработчиков, они тут не причем. Если вы хотите создать большой и сложный интернет-магазинище уровня эльдорадо/мвидео/спортмастер, то, возможно, Битрикс будет не самым лучшим выбором.
19 июля 2015 в 21:22
Что нужно знать о Битриксе некоторым потенциальным покупателям
- CMS ,
- 1С-Битрикс
Эта статья написана не для холивара. Здесь не будет полного обзора плюсов и минусов. Это просто несколько фактов из моего опыта, которые я сам хотел бы знать, перед тем как выбрать 1С Битрикс в качестве CMS.
Предыстория, которую можно не читать
Давным-давно, когда словосочетание «web 2.0» было модным, а тени с округлостями были верхом дизайнерской мысли, нашей организации понадобилось упорядочить общение с клиентами и завести себе HelpDesk. И как это обычно бывает, работы по выбору, установке, настройке и внедрению были поручены автору затеи, то есть мне – рядовому сотруднику техподдержки.
Навыки программирования у меня на тот момент были исчезающее малы – немного ковыряния в вордпрессе и пара бесполезных «Hello World!» написанных в Notepad++. И вот с этим багажом знаний я свободное от звонков время стал читать мануалы к имеющимся на рынке на тот момент системам HelpDesk и ServiceDesk.
Битрикс казался наиболее понятной, достаточно полно документированной и простой в установке системой, в которой помимо самого HelpDesk были еще другие полезные плюшки, вроде CMS =) Остальные системы были на тот момент либо на басурманских языках, либо непонятно каких денег стоили (цен на сайтах не было), либо требовали сурового бородатого напильника для доведения до ума.
Так вот к чему это я. Мы выбрали редакцию 1С Битрикс – Управление сайтом (БУС на местном сленге) не для интернет магазина. И модуль интернет магазина никогда не использовали (почти). Этот факт сильно сказался на «пользовательском опыте», каким образом – опишу ниже.
Факт 1. Битрикс: Управление сайтом ≈ Интернет магазин
Даже если ничего не знать о внутренностях битрикса и не разу не заглядывать в админку, то просто просмотрев содержание всех презентаций и конференций их маркетологов за последние 5 лет легко понять, что кроме модуля интернет магазина ничего особенно и не развивается.
Все новые громкие плюшки вроде фасетных индексов, конверсий и всяких сомнительных мобильных приложений это всё для интернет магазина. Каждый раз когда я смотрю вебинар от битрикса, я чувствую что меня обманули, т.к. кроме разнообразного жонглирования заказами и размусоливания статистики по ним ничего нового в CMS за несколько лет не произошло.
Где-то внутри появилось «ядро D7», но документация об этом не знает (а в коде там не всё очевидно, иногда до нужного места можно добраться только перелопатив 5-7 файлов).
Из чего-то действительно полезного можно вспомнить парноидальный кэш, названый «Композитный сайт». Но все кто видел как битрикс строит запросы, и без композитного кэша понимали что лишний раз базу лучше не беспокоить.
Те модули, которые не нужны интернет магазину, существуют для галочки в списке фич на промо страницах битрикса. Они более менее работают, но не развиваются. Модуль техподдержки каким был в середине нулевых, таким и остался к 2015 году. Форум, wiki, блоги, обучение – это всё мало изменилось со дня своего появления.
Вывод:
если вам не нужен интернет магазин и вы планируете использовать другие модули, то не рассчитывайте на их развитие.
Факт 2. Долгое исправление ошибок
Сначала я хотел поставить этот факт в конец списка, но он логически вытекает из первого. Из-за того, что в приоритете у битрикса интернет магазин, то исправление некритичных багов в других модулях происходит крайне долго. Полгода — год, это вполне нормальные сроки. Иногда дольше.
Сейчас, например, в админке модуля техподдержки нельзя осуществить поиск обращений по email’у среди обращений поступивших по почте. Некритично, но неприятно. Висит этот баг уже с прошлого года =)
Вывод:
если нашли ошибку в модуле – не рассчитывайте на быстрое её устранение (но сообщить о ней стоит)
Факт 3. Медленные инфоблоки
Большая часть данных в битриксе хранится в инфоблоках. Если вдруг кто не в курсе что это за зверь такой, вот выдержка из документации:
Информационные блоки — модуль, позволяющий каталогизировать и управлять различными типами (блоками) однородной информации. С помощью информационных блоков может быть реализована публикация различных типов динамической информации: каталоги товаров, блоки новостей, справочники и т.д.
Информационные блоки — ключевой момент Bitrix Framework. Практически всё, что делается в системе в той или иной мере завязано на этот модуль, даже если это и не отображается явно.
dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&CHAPTER_ID=04610&LESSON_PATH=3913.4610
Инфоблоки избыточны и обладают всем что может понадобится разработчику сайтов в повседневной жизни. Тут есть инабор типичных полей: название, описание, теги, seo, картинки для превью и т.д.). Также можно создать свои свойства различных типов (типы тоже можно создавать).
Также стоит упомянуть довольно удобную админку с разграничением прав, массовым редактированием, выгрузками туда/сюда и давно уже устоявшийся API для всего этого. В общем, инфоблоки это удобно.
Но тут есть подвох:
Инфоблоки — сущность, которая в физической структуре БД создает 4 таблицы, не меняющиеся при изменении структуры данных: типы объектов, экземпляры объектов, свойства объектов и значения свойств объектов.
Если перевести с языка документации на пользовательский это грозит вот чем: каждое свойство инфоблока будет храниться в отдельной таблице и чтобы его получить понадобится отдельный запрос к базе данных.
Этот подход не доставляет проблем при небольшом количестве записей (зависит от настроек и производительности сервера базы данных) Но когда записей становится несколько миллионов, появляются запросы исполняющиеся за неприличное время. И чем дальше, тем более неприличным это время становится.
Проблему можно решить хорошим производительным сервером БД с правильным конфигом и индексами. Но серебряной пули тут нет, техподдержка не поможет и на форумах информации не так много. Всё придётся делать самостоятельно: анализировать, профилировать и принимать решения.
Есть еще конечно высоконагруженные инфоблоки, но они пока не документированы. Живых примеров по ним мало. И переносить на них несколько десятков инфоблоков с кучей свойств задача не из простых.
Вывод:
если у вас нет штатных специалистов по БД, а данных может появиться много, то лучше подумать над выбором других CMS с более оптимальной структурой хранения данных, ну или заложить в проект услуги по настройке БД.
Факт последний. НЕНАВИСТЬ!
Битрикс обладает очень своеобразной репутацией. Очень многие разработчики относятся к нему скептически. Многие разработчики обладают на него аллергией, а некоторые так и вообще ненавидят.
Очень многие студии гордо заявляют что они не работают с битриксом. В курилке этих студий ходят легенды про то «как я поддерживал один проект на битриксе».
Вероятно, благодаря этой репутации работники веб студий изначально скептически относятся к заказам связанным с битриксом и выполняют их не лучшим образом. Тут может сказаться некоторая сложность для новичка, т.к. не все вещи в битриксе устроены очевидным образом и многие задачи можно выполнить сотней неправильных или неоптимальных решений.
Вывод:
выбирая битрикс в качестве CMS нужно учитывать его репутацию в IT сообществе и тщательно выбирать исполнителей. Ну и морально подготовиться к тому, что в некоторых местах вас будут отговаривать от его покупки, а после покупки могут уговаривать отказаться от него.
Общий вывод
Если вы не планируете пользоваться модулем интернет магазина, но планируете создать популярный динамичный сайт, то битрикс с этим справится. При этом нужно учитывать, что количество накладных расходов может быть больше чем при выборе другой системы. Лучше внимательно взвесить все за и против.
1. Какое информационное содержимое можно позиционировать по сайтам?
Пользователи
+ Курс обучения
+ Информационные блоки
+ Формы
2. При использовании многосайтовой системы
+ используются единые бюджеты пользователей на все сайты
— для каждого сайта заводятся отдельные бюджеты пользователей
Нужная хорошая электроэнергия? Сначала нужно научиться её контролировать, а с этим отлично может справиться прибор измерения ПКЭ . Энерготестер ПКЭ-А-С4 позволяет проводить энергетическое обследование, измерение и и регистрация электроэнергетических величин, потребления электроэнергии, профилей нагрузки и измерение нагрузки вторичных цепей.
3. Если при настройке сайта, в секции «Параметры» не задавать название сайта, то
+ система будет использовать название сайта, заданное в настройках Главного модуля
— будет использовано значение, заданное в параметре «Название» в основной секции настроек
— значение будет не определено
4. Количество сайтов в системе
+ определяется лицензионным соглашением и лицензией на дополнительные сайты
— зависит от количества языков интерфейса в системе
— неограниченное
5. Настройка параметров языка для публичного раздела сайта выполняется:
В форме редактирования параметров корневого каталога сайта
— в форме создания/редактирования языка
+ отдельно для каждого сайта в форме создания и редактирования сайта
6. Можно ли настроить больше двух сайтов в многосайтовой конфигурации?
+ Можно, если докупить лицензии на дополнительные сайты
— Нет, это запрещено лицензионным соглашением
— Да, без ограничений
7. При активации лицензии на 4 дополнительных сайта (дополнительно к двум, доступным для использования по умолчанию), может использоваться следующее количество копий ядра системы:
3
— 6
+ 1
— 2
8. При просмотре файловой структуры разделение по сайтам будет выполнено в случае:
Организации многосайтовости на одном домене
+ логического представления структуры
+ организации многосайтовости на разных доменах
9. Технологии переноса cookies пользователя между сайтами используется
+ если активирована опция «Распространять куки на все домены» в настройках главного модуля
— всегда при наличии модуля статистики в системе
— если активирована опция «Распространять авторизацию на все домены»
Установить соответствующую опцию в настройках сайтов
+ отметить соответствующую опцию в настройках Главного модуля
— установить соответствующую опцию в настройках Группы пользователей
11. Как можно закрыть только один сайт с идентификатором ru для посещения пользователей?
С помощью кнопки: Настройки –> Главный модуль –> Служебные процедуры -> Закрыть доступ для посетителей
+ разместив специальный программный код в файле: /bitrix/php_interface/ru/init.php
12. В настройках каких модулей можно выполнить разделение параметров для различных сайтов
Ни один не позволяет
— модуль Веб-форм, Документооборот, модуль Управления структурой
— Интернет-магазин, модуль Блогов, Главный модуль
— Все позволяют
+ модуль Блогов, модуль Управления структурой, Интернет-магазин
13. Для заведения дополнительных сайтов в системе сверх разрешенных в лицензии требуется:
Создать дополнительный язык интерфейса
— завести учетную запись в системе, создать отдельную поддиректорию
+ получить лицензию на дополнительный сайт, создать учетную запись в системе, определить путь к файлам публичной части сайта
14. Возможно ли разделить права на просмотр статистики разных сайтов в продукте:
Да, если произвести соответствующие настройки доступа в модуле Статистика
+ Нет, пользователь, имеющий право на просмотр статистики, сможет просматривать статистику по всем сайтам
15. Отдельно для каждого сайта можно устанавливать следующие настройки модуля Управление структурой:
+ задавать типы свойств
— организовывать систему доступа к разделам по каждому сайту
— выбирать визуальный HTML редактор и его настройки индивидуально для каждого сайта
+ задавать типы меню
+ определять количество дополнительных параметров меню
Тривиальная задача удаления лишнего сайта из списка сайтов
в 1С-Битрикс может оказаться не такой уж и простой. Пройдемся по процедуре на практике.
Шаг 1. Удаление сайта
Идем в
Http://ВАШ_САЙТ/bitrix/admin/site_admin.php
Ставим галочку возле ненужного сайта
Жмем удалить записи
, получаем…. ошибка по типу 1
Шаг 2. Ошибка при удалении в CForumNew::OnBeforeLangDelete
Сие сообщение — Ошибка при удалении в CForumNew::OnBeforeLangDelete (forum)
, значит, что нужно отправиться в Сервисы — Форумы
и удалить не нужный форум (форум принадлежащий удаляемому сайту). Скорее всего это форум «Отзывы о товарах»
.
После удаления форума возвращаемся в Настройки — Сайты — Список сайтов
Жмем удалить записи
, получаем…. ошибка по типу 2
Шаг 3. Ошибка при удалении в CIBlock::OnBeforeLangDelete (iblock):
Система сообщает нам об ошибке CIBlock::OnBeforeLangDelete (iblock)
и даже подсказывает, что надо удалить несколько инфоблоков:
Найдены инфоблоки связанные с сайтом. Их идентификаторы: 11, 20, 21, 22, 23, 24.
Идентификаторы в каждом случае свои, но метод решения одинаковый.
Идем в Контент — Инфоблоки — Типы инфоблоков
и удаляем инфоблоки с указанными id
(у нас это 11, 20, 21, 22, 23, 24).
Скорее-всего нужные нам «жертвы» раскиданы по разным типам инфоблоков
и проверить придется все.
При удалении инфоблока — Каталог
вероятно появление ошибки по типу 3
Шаг 4. Ошибка удаления. Возможно есть ссылающиеся объекты.
Данная ошибка более таинственна, для ее решения необходимо войти в инфоблок (нажать изменить)
Перейти на вкладку «Торговый каталог» и снять галочку у параметра Инфоблок имеет торговые предложения
Cохранить изменения
Повторить попытку удалить инфоблок
После удаления инфоблоков возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи
, получаем…. ошибка по типу 4
Шаг 5. Ошибка при удалении в CSalePersonType::OnBeforeLangDelete
Очередная ошибка связана с наличием лишних типов плательщиков
от которых тоже нужно избавиться.
Идем в Магазин — Типы плательщиков
Ставим галочки у лишних плательщиков
Жмем удалить записи
, получаем…. ошибка по типу 5
Шаг 6. В заказах используется тип плательщика с ID=Х
Как все догадались — нужно стереть заказы. Идем в Магазин — Заказы
и удаляем лишние заказы
Возвращаемся в Магазин — Типы плательщиков
Удаляем плательщиков привязанных к удаляемому магазину
Возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи
, получаем…. ошибка по типу 6
Шаг 7. Ошибка при удалении в CRubric::OnBeforeLangDelete
Ошибка на данном этапе снова снабжена подсказкой:
Ошибка при удалении в CRubric::OnBeforeLangDelete (subscribe): Существуют рубрики (1) связанные с удаляемым сайтом. Сначала удалите или свяжите их с другим сайтом.
Для решения проблемы отправляемся в Сервисы — Рассылки — Список рассылок
и удаляем лишние рассылки
Возвращаемся в Настройки — Сайты — Список сайтов
Ставим галочку возле ненужного сайта
Жмем удалить записи
, … ВАУУУУ — сайт удален
Итоги
Естественно, мы привели пример одного из случаев (далеко не худшего) и у Вас могут не возникнуть указанные ошибки или возникнуть ошибки не всех типов. Сайт может удалиться намного проще, но помните, алгоритм всегда одинаков.
Краткий алгоритм удаления лишнего сайта в 1С-Битрикс
- «Отвязать» друг от друга и удалить все инфоблоки
связанные с сайтом - Удалить все рассылки
и форумы
- Удалить все заказы
- Удалить лишние типы плательщиков
- Удалить сам сайт
И что они говорят, эти голоса. Одно и. Же: Im the vendor, Im the vendor. What will you do. What can you do. Да, Hydra onion в россии 2016
. А на контакт Bull Gates, значит, не идет. Не идет. А может, он идет. А ты не понимаешь. Может, Минотавр и есть эта дохлая крыса на потолке. . Может, и.
А они тебе ничего не объяснили. И значит, на спецпроцедуру. Так втемную и поехал. Козлы убогие… Ну ничего, разберемся и накажем виновных. Будет им, нигелла сатива свечи инструкция,
тринадцатая зарплата в твердой валюте… Он поднял. Стола блокнот с профилем Данте Алигьери на обложке и некоторое время сосредоточенно водил по бумаге ручкой, причем я сразу догадался, что он рисует внутри такие же профили Данте, только маленькие. Почему-то эти люди думают, что за долгий двадцатый век нигелла сатива свечи инструкция
не изучили их методов работы. Положив блокнот. Он шагнул ко мне, словно собираясь своим запоздавшим объятием исцелить все мои душевные раны, но тут на его столе зазвонил телефон. Шмыга чертыхнулся и поднял трубку. Несколько секунд он слушал, а потом его лицо стало хмурым и внимательным. Так точно, . Сказал он и положил трубку. Подняв на меня глаза, он виновато развел руками. Видишь, что творится.
Коммерческая тайна. У матросов нет вопросов, — отозвался Малюта. Как-то странно поглядел Степе в. Ровно через три часа позвонил капитан Лебедкин. Я тебе зачем жизнь. Спас? — грозно спросил. — Чтоб ты в политику совался. Я… — начал Степа. Да не ссы, — весело сказал капитан. — Шучу.
Желаю. Тогда вперед, . Сказал Чапаев, поднимаясь из-за отель ecstasy hotel.
Выйдя из штабного вагона, мы пошли в хвост поезда. Происходящее казалось мне все более странным. Несколько вагонов, по которым мы прошли, были темными и казались. Пустыми. Свет нигде не горел; из-за дверей не долетало ни единого звука. Мне слабо верилось, что за ореховыми панелями, в полированной поверхности которых отражался огонек сигары Чапаева. Спит красная солдатня, но я старался не рефлексировать по этому поводу.
К 2003 году японским специалистам. Удалось разработать комплект из нескольких микрозондов, которые вейп гидра
непосредственно мозг и позволяли до некоторой степени объективировать картину человеческого восприятия. Японская аппаратура не могла определить, что именно чувствует и думает наблюдаемый. Но она позволяла получить цветное (хотя и размытое) изображение того, что он вейп гидра.
Причем не только наяву, но и в быстрой фазе сна. Это стало возможным потому, что сигнал снимался не с оптического нерва, а с тех зон. Мозга, которые ответственны за непосредственную репрезентацию. Оборудование было немедленно закуплено командой Поташинского. Сигнал с вживленного в мозг комплекта зондов мог передаваться по беспроводной. Связи, что позволяло баблонавту вести обычный образ жизни, никак не стесненный участием в эксперименте. Необходимо было только, чтобы где-нибудь неподалеку располагался приемник сигнала. Который затем в реальном времени передавал информацию на компьютер. Вкратце схема опытов Поташинского выглядела. Сначала в мозг баблонавту-экспериментатору (на эту роль, как обычно, были отобраны добровольцы из числа. Молодых офицеров ФСБ) вживлялся комплект контрольных электродов.
С мертвой точки. Слушай, брат, — сказал он, — а что это за природа. Ты о чем? — спросил Иса. Ну ты в машине говорил, что у шрапнельно-осколочного тела такая же природа, как у радужного. А что это за природа. Тебе про это лучше не спрашивать. Брат, — нахмурился Иса. Почему. Ты к этому еще не готов. Как не готов. А амфетамин марихуана одновременно.
Был бы готов, не спрашивал. Так ты можешь ответить. Или.
Скоро. спросила. Купить гашиш во владивостоке
сейчас, сказал я, вот… Иной разборчивый. Любовник мог бы обидеться, что его не пускают дальше сетевой прихожей. Но Порфирий не таков. Первым делом я подключился к ее огмент-очкам. Хорош, сказала. Какие бакенбарды… Я тем временем картинку с очков на панель. Сморфив ее с видом из потолочной камеры. Айфак поднимал любые морфы не напрягаясь мощность у. Была чудовищная. Теперь Мара видела меня в своих огмент-очках на месте айфака и одновременно могла наблюдать.
Разработанным шифром. Иногда он отмечал, что его снова мучили повторяющиеся сны по схеме 1. Или по схеме 2. И вдруг открытым текстом, как вырвавшийся крик: Снились курительные миксы круглосуточно,
убитые мной в детстве… Голос за ширмой. Замолчал. Чего это она? — спросил Сэм. Уснула, — ответила Наташа. Сэм нежно погладил колючий кончик ее брюшка и откинулся на диван. Наташа тихонько сглотнула. Сэм подтянул к себе стоящий на полу кейс, раскрыл его, вынул маленькую стеклянную. Баночку, сплюнул в нее красным, завинтил и кинул обратно — вся эта операция заняла у него курительные миксы круглосуточно
секунд. Знаешь, Наташа, — сказал.
После этого он гашиш санкт.
Эй, — Татарский. Ответа не. Татарский подождал еще минуту и понял, что остался один. Один со своим умом, готовым пойти вразнос. Надо было срочно чем-то себя занять. Звонить, — прошептал. — Кому. Гирееву. Он знает, что делать. Долгое время. Трубку никто не брал. Наконец, на пятнадцатом или двадцатом гудке, Гиреев хмуро отозвался: Алло. Андрюша.
Нет, — сказал. В запертой комнате сидит человек, не знающий китайского языка. В окошко ему дают записки с вопросами на китайском. Для него это просто бумажки с нарисованными закорючками, смысла которых он не понимает. Но у него в комнате полно разных книг. Правилами, в которых подробно описано, как и в какой последовательности отвечать одними закорючками на. И он, действуя по этим правилам, выдает в другое окошко ответы на китайском. Создают у всех стоящих снаружи полную уверенность в том, что он знает китайский язык. Хотя сам он совершенно не понимает, о чем ему задают вопросы. Address hydra onion browser
в чем смысл его ответов. Представили. Ну, представил. Сура — это такая же китайская комната, только автоматизированная. Вместо человека со справочниками в ней сканер, который считывает иероглифы. Огромная база референций и правил, позволяющих подбирать иероглифы для ответа.
Как ни странно, именно это привело меня к ясности. Во всяком случае, в практическом отношении. Я поняла, что вставшая передо мной проблема. Не просто сложна она неуловима. Трудно было даже правильно сформулировать связанные с ней вопросы. Единственным утешением казалось Как найти сайт гидра в торе язык,
так же скользко дело обстоит. С сознанием человека. Разобраться с этим было мне не под силу. И я решила, что лучшим выходом из ситуации будет вернуться. К бизнесу as usual, оставив экзистенциальные экзерсисы на потом или забыв про них.
Вскоре дорога вывела в богатое село со свежевыкрашенной белой церковью. У церковной ограды сидел печальный одноногий солдат в полинявшем сером мундире. Не знаешь, где тут Оптина Пустынь. спросил Т.нагибаясь к нему с лошади. Это про которую мужики бають. переспросил солдат. Которое недавно устроили заведение. решил, что служивый выжил из ума. Как это недавно устроили заведение. А значить, по-любому все прямо, ваше благородие, сказал солдат и Гидра рулетка
рукой, далеко еще буде. Дорог тут только две, и обе в одну сторону. Хучь по первой поезжайте, хучь по второй. А хочешь покороче, тогда через лес. Там развилка, так можете взять любую сторону.
И прочие гримасы, о которых вы, я думаю, наслышаны… Лена не поняла, что это за папа-мама дурка восемнадцать (молодой человек пробормотал эти слова быстро и тихо), но сразу же забыла про это — ей вдруг до такой степени захотелось отхлебнуть вина за двадцать тысяч евро, что ее рот наполнился слюной. По залу прошел тихий вздох, подтвердивший, что собравшиеся не просто наслышаны о гримасах. А успели в мельчайших деталях изучить всю доступную о них информацию. В последнее время спецслужбы Запада развернули настоящую охоту на наших богачей-недотеп, — продолжал молодой человек. — Вы слышали, конечно, про громкие скандалы адрес hydra onion tk okey сайта гидра в торе
аресты: сначала Куршевель, затем Фиджи, потом бутик Гермес, а теперь вот Сен-Моритц, Мальдивы и Антарктида. Кампания тщательно спланирована и преследует две основные цели — во-первых, дискредитировать. Российскую цивилизацию, — установить контроль над ее ресурсами посредством сбора компромата на владельцев ее основных активов. Наша элита стала мишенью, а объективная реальность текущей точки пространства-времени такова, что. С ней стали мишенью мы. Нахмурившись, он замолчал, словно давая слушателям возможность осознать всю серьезность ситуации. Затем на его лицо вернулась грустная улыбка, и он продолжил: Мы должны удержать ситуацию под контролем.
Она ухмыльнулась. Хотя бы перед своими не надо строить оскорбленную невинность. О чем. Когда я его спровоцировала. Когда выскочила голая из Гидра онион вход
и встала перед ним раком. Ты считаешь это провокацией. Конечно. Зачем ты, спрашивается, развернулась к нему задом. Я пожала плечами. Для надежности. А что в этом особенно надежного. Хвост ближе к цели, — сказала я не совсем уверенно. Ну. А глядеть надо через плечо.
Третье монталь ваниль экстази
таким: Его Превосходительству О. Константину Петровичу Победоносцеву, служебное. Настоящим препровождаю Вашему Превосходительству перевод древнеегипетской надписи. Листа сусального золота, обнаруженного в медальоне монталь ваниль экстази
трупе отца Варсонофия Нетребко в рамках расследования по делу графа Т. По мнению специалистов Египетского музея, начертание иероглифов позволяет датировать текстэпохой XVIII династии или несколько более поздним временем. Надпись гласит: Тайное имя гермафродита с кошачьей головой, дающее над ним власть, суть. АНГЦ. Если сможешь управлять гермафродитом с помощью этого имени. Хорошо. Переводчики что АНГЦ может быть так же переведено как традиционное БХГВ (или иначе, в зависимости от выбора таблиц соответствий при использовании иероглифических реестров). Сам медальон, однако, не может быть передан Вашему Превосходительству несмотря на Ваше ходатайство.
Навигация по записям