В битриксе можно создавать свои модули. Начальник хочет, чтобы этот модуль можно было запустить из конкретной задачи. К примеру, вошел в задачу, нажал меню и там был вызов нужного модуля. По кнопке "ЕЩЁ" И чтобы в модуль передавался номер задачи. Не могу найти образец как это сделать. Сейчас меню модуля появляется в "Администрирование" -> "Контент" в боковом меню.
Уже как только ни пытался, гуглил, лазил по этому форуму. Никак не получается прицепить купон к заказу. Везде примерно один и тот же код предлагают, но оно не работает - купон не появляется на странице заказа в админке.
Пользователи начали жаловаться что зависает приложение битрикс, помогает только завершение процесса и перезапуск. Диагностика проблемы показала что зависание происходит при обращении к поиску, а конкретно при выполнении вот этого запроса: /bitrix/components/bitrix/im.messenger/im.ajax.php?CONTACT_LIST&V=130
Проверил через последний хром, открыв desktop_app, он тоже зависает, конечно не весь как приложение битрикс, а только вкладка, которую он заботливо предлагает закрыть. Значит проблема не в приложении.
А оказалась проблема в функции быстрого поиска чатов. Эта функция загружает с сервера ВСЕ чаты, контакты, сотрудников и телефоны, доступные пользователю. А после она видимо формирует из них html контейнеры чтобы оперативно выводить при поиске.
Но дело в том, что при разработке не учитывалось масштабирование системы, и то что пользователей и чатов может быть очень много. Так например произошло в моем случае, и формирование ответа сервера мало того что занимало >9 секунд, так ещё и ответ весил 2,6 Мб, это всё был json с сотрудниками и чатами открытых линий и CRM:
Решение Сначала попытался отключить параметр "Включить предварительную загрузку всех пользователей портала для быстрого поиска" в настройках модуля Веб-мессенджер. Но это не помогло, так как отключился только запрос сотрудников, а чаты так и продолжили загружаться, плюс проявился ещё один баг, после отключения этой функции, но о нем ниже.
Пришлось опять лезть в ядро что бы поправить ошибку, оказалось что данные для ответа собирает метод \CIMContactList::GetList() Находится он в этом файле /home/bitrix/www/bitrix/modules/im/classes/general/im_contact_list.php:22 И оказывается он принимает параметр LOAD_CHATS который отвечает за загрузку всех доступных пользователю чатов, но видимо забыли вывести этот параметр в настройки модуля.
Предлагаю костылить следующе, в файле обработчике ajax запросов из веб-мессенджера /home/bitrix/www/bitrix/components/bitrix/im.messenger/im.ajax.php в строке 1165 добавляем передачу параметра LOAD_CHATS в метод \CIMContactList::GetList():
Добавляем так же что бы параметр брался из настроек, как и с сотрудниками, что бы можно было отключить. Соблюдаем странный перегон логической переменной в Y/N, что метод нас понял.
На этом всё, ждем исправлений от разработчиков, а пока они их не сделают придётся костылить это после каждого обновления модуля.
Дополнительный баг
А теперь баг о котором написал выше. Если отключить загрузку сотрудников в настройках модуля, то тогда перестаёт работать поиск при делегировании чатов открытых линий, так как он производит поиск только по локально сохранённым сотрудникам.
При этом при добавлении сотрудника в чат, поиск производится так же через запрос на сервер, но при делегировании только локально, забыли наверное. Я лезть в js не стал, включил загрузку сотрудников, без загрузки всех чатов зависания нет, так как чатов грузилось аж 45998. При этом, без чатов, запрос выполняется всё равно долго >7 секунд, но там есть кэширование, которое более менее выручает. В общем ждём исправлений.
Добавление меток (точек) на изображениях элементов инфоблока
Метки на изображениях элементов инфоблока - это бесплатный модуль, который расширяет базовый инструмент административной панели и позволяет управлять метками на изображениях непосредственно на страницах редактирования элементов. Модуль позволяет получить в результирующий массив arResult все координаты точек для дальнейшего позиционирования в шаблоне.
Возможности:
Создание меток для детального изображения
Создание меток пользовательского свойства типа файл
Создание меток для пользовательского свойства типа файл (множественное)
Получение меток в шаблоне компонента.
Метки не привязаны к шаблону, что позволит Вам выполнить любой дизайн отображения меток.
Примечание: модуль не выведет точки автоматически в шаблон, в связи с тем, что это просто невозможно (у всех шаблон разный). Он лишь позволяет получить их в результирующем массиве arResult Достаточно небольших познаний в области верстки и Вы сможете реализовать любой подходящий для Вашего проекта дизайн отображения меток на изображениях.
autoloader собственных классов и подключение событий работая одновременно с несколькими крупными проектами, в которых используется кастомизация бизнес-процессов, может возникнуть вопрос реализации удобной структуры собственных классов и событий в php_interface. У меня такой вопрос возник, и вот что получилось:
init.php создал в папке /bitrix/php_interface. Это сделано, чтобы учесть проекты с многосайтовостью и сохранить новую структуру как для папки /local, так и для /bitrix.
в данном решение обрабатываются 4 основных, но не обязательных файла: functions.php - записываем функции, которые будут везде использоваться, например для debug adminHandlers.php - добавляются события, которые будут отработаны в административной части сайта publicHandlers.php - добавляются события, которые будут отработаны только на публичной части сайта handlers.php - добавляются события, которые будут отрабатываться всегда
в данном примере подключается метод addJsLibrary из класса Custom\PublicSection\Jsinit. Класс подгружается по принципу autoloader'а. Т.е. в папке php_interface/classes/public создан файл jsinit.php:
<?php
namespace Custom\PublicSection;
class Jsinit
{
public function addJsLibrary()
{
$arJsConfig = array(
'owl_carousel' => array(
'js' => '/local/js/owl_carousel/owl.carousel.js',
'css' => '/local/js/owl_carousel/assets/owl.carousel.css',
'rel' => array('jquery'),
),
);
foreach ($arJsConfig as $ext => $arExt) {
\CJSCore::RegisterExt($ext, $arExt);
}
}
}
По такой же схеме можно создать любой файл с собственным классом, например: /local/php_interface/classes/handlers/test.php с namespace Custom\Handlers, где класс будет Test. Далее методы класса можно использовать как в событиях, добавляя их в handlers.php, так и в любом необходимом месте, например, собственном компоненте.
В итогемы получаем отдельные файлы классов, которые подключаются автоматически, и файлы со списками методов подключаемых в события. При этом используем встроенный функционал Bitrix'а.
Добрый день,такой вопрос,у меня есть универсальный список и он заполняется параметрами,по средствам бизнес-процесса, соответственно в нём не срабатывает бизнес-процесс на создание элементов,необходимо через код запускать бизнес-процесса на все новые элементы в этом списке,как это реализуемо,возможно уже есть готовые статьи? Я поискал в интернете примеры,но ничего достойного не нашёл,коробка если что.
Установил 28.07.2021 обновление, в том числе обновился и модуль календаря, сотрудники начали ругаться на неудобство, почему при планировании встречи убрали отображение отсутствий у участников встречи.
Сначала подумал что это новая фича, а потом решил проверить ))) Оказалось баг. В файле: /home/bitrix/www/bitrix/modules/calendar/classes/general/calendar_planner.php в строке 161, 162, 167 и 172 перепутаны имена ключей массива, и вместо DT_TO и DT_FROM, которые приходят из метода CCalendar::GetAccessibilityForUsers вписаны DATE_TO и DATE_FROM.
Решение
Нужно исправить имена ключей, или просто вставить в 160 строку перенос значений в нужные ключи:
Зачастую нужно реализовать следующий сценарий: - Контент-менеджер загружает файлы (картинки товара или раздела каталога) как есть. С кириллическим названием или пробелами. Как результат при валидации кода (если придет дотошный сеошник) могут вылезти куча ошибок связанных с разрывами в имени файла. Ниже публикую 2 скрипта, которые позволяют поправить эту ошибку перезаписав имена на корректные с точки зрения seo. !!! ВАЖНО !!! Перед выполнением скрипта в настройках главного модуля должны быть следующие настройки (см. скриншот). И не забывайте указать свой ID инфоблока.
1. Для разделов
$start = microtime(true);
// включение модуля
\Bitrix\Main\Loader::includeModule('iblock');
$arOrder = array('ID' => 'ASC');
$arFilter = array('IBLOCK_ID' => 60); // указать id инфоблока
$arSelect = array('ID', 'NAME', 'PICTURE');
$countSection = 0;
$countPicture = 0;
// достаем список разделов
$res = CIBlockSection::GetList($arOrder, $arFilter, false, $arSelect, false);
// цикл перебора разделов
while($arSection = $res->Fetch()){
$countSection += 1;
// получаем id файла в системе
$fileID = $arSection['PICTURE'];
// если id файла не получен, значит раздел без картинки
if(isset($fileID)){
$countPicture += 1;
// получим картинку как объект по id
$fileInfo = CFile::GetByID($fileID);
if($fileArr = $fileInfo->Fetch()){
// указываем расположение для временного хранения в склейке с именем файла
// !!! путь к папке задается от корня /upload/
$newFilePath = 'user_files/'.$fileArr['FILE_NAME'];
// копируем файл во временную папку
$fileCopy = CFile::CopyFile($fileID, true, $newFilePath);
}
// получаем информацию в массиве
$arrTmp = CFile::MakeFileArray($fileCopy);
// начинаем обновлять картинку
$bs = new CIBlockSection;
// определим данные для корректной записи массива 'PICTURE'
$picture = array(
'name' => $arrTmp['name'],
'type' => $arrTmp['type'],
'tmp_name' => $arrTmp['tmp_name'],
'error' => 0,
'size' => $arrTmp['size'],
'MODULE_ID' => 'iblock',
);
$arFields = array(
'PICTURE' => $picture,
);
// обновляем раздел
$bs->Update($arSection['ID'], $arFields, false, false, true);
unset($fileID);
}
}
$time = microtime(true) - $start;
echo 'Всего найдено ' . $countSection . ' разделов <br>';
echo 'Обновлено ' . $countPicture . ' картинок <br>';
echo 'Время выполнения ' . $time . ' секунд';
2. Для элементов
$start = microtime(true);
// включение модуля
\Bitrix\Main\Loader::includeModule('iblock');
$arOrder = array('ID' => 'ASC');
$arFilter = array('IBLOCK_ID' => 55); // указать id инфоблока
$arSelect = array('ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PICTURE');
$countElement = 0;
$countPicture = 0;
// достаем список элементов
$ar_res = CIBlockElement::GetList($arOrder, $arFilter, false, false, $arSelect);
// цикл перебора элементов
while($arElement = $ar_res->Fetch()){
$countElement += 1;
$res = CIBlockElement::GetByID($arElement['ID']);
if($arElement = $res->GetNext()){
/*КАРТИНКА АНОНСА*/
if(isset($arElement['PREVIEW_PICTURE'])){
$countPicture += 1;
// получим картинку как объект по id
$fileInfo = CFile::GetByID($arElement['PREVIEW_PICTURE']);
if($fileArr = $fileInfo->Fetch()){
// указываем расположение для временного хранения в склейке с именем файла
// !!! путь к папке задается от корня /upload/
$newFilePath = 'user_files/'.$fileArr['FILE_NAME'];
// копируем файл во временную папку
$fileCopy = CFile::CopyFile($arElement['PREVIEW_PICTURE'], true, $newFilePath);
}
// получаем информацию в массиве
$arrTmp = CFile::MakeFileArray($fileCopy);
// обновляем элемент
$el = new CIBlockElement;
// определим данные для корректной записи массива 'PICTURE'
$picture = array(
'name' => $arrTmp['name'],
'type' => $arrTmp['type'],
'tmp_name' => $arrTmp['tmp_name'],
'error' => 0,
'size' => $arrTmp['size'],
'MODULE_ID' => 'iblock',
);
$arFields = array(
'PREVIEW_PICTURE' => $picture,
);
$res = $el->Update($arElement['ID'], $arFields);
}
/*КАРТИНКА ДЕТАЛЬНАЯ*/
if(isset($arElement['DETAIL_PICTURE'])){
$countPicture += 1;
// получим картинку как объект по id
$fileInfo = CFile::GetByID($arElement['DETAIL_PICTURE']);
if($fileArr = $fileInfo->Fetch()){
// указываем расположение для временного хранения в склейке с именем файла
// !!! путь к папке задается от корня /upload/
$newFilePath = 'user_files/'.$fileArr['FILE_NAME'];
// копируем файл во временную папку
$fileCopy = CFile::CopyFile($arElement['DETAIL_PICTURE'], true, $newFilePath);
}
// получаем информацию в массиве
$arrTmp = CFile::MakeFileArray($fileCopy);
// обновляем элемент
$el = new CIBlockElement;
// определим данные для корректной записи массива 'PICTURE'
$picture = array(
'name' => $arrTmp['name'],
'type' => $arrTmp['type'],
'tmp_name' => $arrTmp['tmp_name'],
'error' => 0,
'size' => $arrTmp['size'],
'MODULE_ID' => 'iblock',
);
$arFields = array(
'DETAIL_PICTURE' => $picture,
);
$res = $el->Update($arElement['ID'], $arFields);
}
}
}
$time = microtime(true) - $start;
echo 'Всего найдено ' . $countElement . ' элементов <br>';
echo 'Обновлено ' . $countPicture . ' картинок <br>';
echo 'Время выполнения ' . $time . ' секунд';
Писалось на скорую руку, так что как есть)) Оставлю это тут на всякий случай.
Когда-то давно я наткнулся на статью "Класс для файла настроек модуля options.php" который помогал формировать страницы опций. С тех пор он прочно осел в проектах, значительно упрощая работу. В том числе файл встречался различных коммерческих модулях что показало актуальность его существования.
К сожалению, оригинальная статья датирована 2012 годом и код несколько устарел. Стали видны слабые момент в виду сложности добавления новых типов полей в классе, да и в плане внешнего вида UI Bitrix продвинулся вперёд.
С учетом этих проблем сделана более улучшенная версия, позволяющая легко добавлять новые типы полей, и испольщую библиотеку интерфейсов битрикс. Создан пакет для composer и, если кому-то зачем-то нужно - был опубликован модуль для маркетплейса. Проблема добавления новых типов полей закрыта, внешний вид значительно улчшился (по моему мнению).
Как это выглядит в админке:
Уже реализованы поля типов: строка, число, текст, чекбокс, выпадающий и множественный списки. Можно вешать различные модификаторы на поля, добавлять теги. В будущем планирую добавить добавление файлов, размещение кнопок на которые можно вешать свои скрипты.
Не буду описывать особенности установки и настройки - они детально описаны как на гитхабе, так и в маркетплейсе. Буду рад, если решение будет полезно, проект открыт для доработок как по самому коду, так и по добавлению новых типов полей.
Отдельное спасибо Андрею Новикову, за класс, служивший до этого долгие годы.
В коде происходит выборка 30 элементов, первыми из которых пойдут элементы у которых есть совпадения тегов "Personal" и "2020", далее хотя бы одного из этих двух Тегов, и в конце статьи, не имеющие совпадений.
По желанию можно добавить условие фильтра для выборки только тех элементов, которые содержат хоть один Тег. Например, условие по одному Тегу будет выглядеть так:
array(
"LOGIC" => "OR",
array(
'%TAGS' => " ".$sTagName."," // строка целиком в середине
),
array(
'TAGS' => $sTagName.",%" // строка целиком в начале
),
array(
'TAGS' => "% ".$sTagName // строка целиком в конце
),
array(
'TAGS' => $sTagName // единственная строка в тегах
)
)
Пример запроса SQL:
select ((case when LOCATE('Personal', TAGS)>0 then 1 else 0 end) + (case when LOCATE('2020', TAGS)>0 then 1 else 0 end)) as SORT,
TAGS, ID fr om b_iblock_element WHERE IBLOCK_ID=18 ORDER BY SORT DESC LIMIT 0, 30;
Представим, что нам надо отправлять сообщения в Битрикс24 на события из bitbucket, например Тимлиду ссылку на пулреквест для кодревью.
Bitbucket webhook Вебхуки bitbucket имеют события на пулреквесты, пуши и ишью. Полный перечень тут. Добавляется вебхука в настройках репозитория:
Укажите путь, куда будет отправлен запрос и выберите какие события вы хотите обрабатывать:
Обработка событий и отправка сообщения в чат Следующий скрипт, принимает запрос от bitbucket и отправляет ссылку на пулреквест Тимлиду на портал. Отправленное сообщение состоит из названия и ссылки на пулреквест.
Вот так просто. Логику поиска получателей или отправителей вы уже должны реализовать самостоятельно, в зависимости от ваших процессов и замысла. В нашем примере, для упрощения, ID пользователей прописаны явно.
Для обработки вебхуков bitbucket используется библиотека. Библиотека понимает большинство событий и комплектует объекты данными ответа в виде методов для получения ника автора, ссылки на пулреквест, комментарии коммитов и так далее.
Стандартное автоопределение клавиатуры почти всегда справляется, но некоторые русские слова всё-таки перекидывает на английскую раскладку (например: "шкаф" превращается в "irfa", а "кувшин" в "rediby", очень неприятно, если это ищут). Ну и специфические потребности никто не отменял.
Как ни странно, но отвечающий за эту радость класс CSearchLanguage поддерживает какую-никакую кастомизацию. Копируем всё из папки /bitrix/modules/search/tools/ru/ в папку /bitrix/php_interface/ru/search/ (и аналогично с en версией). Папка local не поддерживается.
По этому пути ищется кастомный класс для этой петрушки и теперь он есть, но требует небольшой доработки. Добавляем в ru версию в файл language.php функцию
function PreGuessLanguage($text, $lang=false)
{
$stop_list = array(
//сюда вписываем любые слова, которым не требуется автоопреление. Ключи важны, значения - любые, но мне так удобнее
"шкаф" => "irfa",
"шкафы" => "irfas",
);
//Indicates that there is no own guess
if(isset($stop_list[$text]))
return true;
else
return false;
//In subclasses you should return array("from" => lang, "to" => lang) to translate
//or return true when no translation nedded
//or parent::GuessLanguage for futher processing
}
Теперь слово "шкаф" не заменяется на "irfa". Класс.
А в en версию аналогичный, но чуть другой:
function PreGuessLanguage($text, $lang=false)
{
$stop_list = array(
"irfa" => "шкаф",
"irfas" => "шкафы",
);
//Indicates that there is no own guess
if(isset($stop_list[$text]))
return array("from" => "en", "to" => "ru");
else
return false;
//In subclasses you should return array("from" => lang, "to" => lang) to translate
//or return true when no translation nedded
//or parent::GuessLanguage for futher processing
}
Теперь и "irfa" вполне себе меняется на "шкаф".
Собственно вот и всё. Можно вписывать любые другие слова и наслаждаться жизнью.
(А ещё можно вынести словарь в отдельный файл и подтягивать значения из него, но ради пары слов я бы таким не занимался.)
Необходимо выполнить доработки по сайту на Битриксе. Было куплено шаблонное решение, сейчас наполняем сайт для сео, нужны доработки. Задачи по сайту будут постоянные в дальнейшем.
На данный момент есть задачи по ТЗ, посмотрите их, оцените объем, напишите ваши условия.
Хочу поделиться способом автоматической минификации стилей и скриптов. Решение было сделано на БУС, но подойдет для других CMS. Описанное решение может оказаться полезным тем, у кого стили распределены по компонентам и не настроены автоматические сборщики типа gulp или webpack и д.р.
Думаю все знают про настройки Оптимизации CSS и JS в главном модуле, а именно "Подключать минифицированные версии CSS и JS файлов". К сожалению в Битриксе не заложена функция создания минифицированных файлов (*.min.css и *.min.js), но зато заложена опция подключения таких файлов.
CSS
Давайте начнем с CSS. Решение базируется на модуле uglifycss Node.js (https://www.npmjs.com/package/uglifycss) . Устанавливаете uglifycss для использования в командной строке (For a command line usage), это очень важно! Заранее позаботьтесь о необходимых доступах к серверу. Выполните к консоле команду:
$ npm install uglifycss -g
После успешной установки узнайте путь нахождения установленного модуля, выполнив команду. К примеру у меня, на боевом сервере был свой путь, а на серверах разработки другой.
whereis uglifycss
В ответе будет строка с путём до uglifycss. В моём случае - /usr/local/bin/uglifycss. Далее мы этот путь будем использовать в классе.
Сам класс:
<?
/*
* Класс по минификации CSS-файлов. Использует NodeJS плагин uglifycss
*/
class CCssMinify
{
const DIRS_FOR_CSS_MINIFY = [
'/local/admin/css/',
'/local/components/',
'/local/templates/main/components/',
'/local/templates/main/theme/build/styles/'
];
const FILES_FOR_CSS_MINIFY = [
'/local/templates/main/styles.css',
'/local/templates/main/template_styles.css'
];
private static $bNeedCacheRefresh = false;
/**
* Агент по минификации стилей
*
* @return string
*/
public static function minifyAgent(): string
{
try {
self::minify();
} catch (Exception $obException) {
CEventLog::Add(array(
"SEVERITY" => "ERROR",
"AUDIT_TYPE_ID" => "MINIFY_CSS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => $obException->getMessage()
));
}
return __METHOD__.'();';
}
/**
* Минификация стилей, находящихся внутри директорий, обозначенных в константе \dh\CCssMinify::DIRS_FOR_CSS_MINIFY,
* и в файлах в константе \dh\Seo\CCssMinify::FILES_FOR_CSS_MINIFY
*/
public static function minify()
{
foreach (self::DIRS_FOR_CSS_MINIFY as $sDir) {
$arFilePaths = self::findCssFilesInDir($sDir);
if (!empty($arFilePaths)) {
foreach ($arFilePaths as $sFilePath) {
self::execUglifycss($sFilePath);
}
}
}
foreach (self::FILES_FOR_CSS_MINIFY as $sFilePath) {
self::execUglifycss($sFilePath, true);
}
if (self::$bNeedCacheRefresh && BXClearCache(true, "/css/")) {
CEventLog::Add(array(
"SEVERITY" => "INFO",
"AUDIT_TYPE_ID" => "MINIFY_CSS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => "Clear cache"
));
}
}
/**
* Получим путь до UglifyCss
* @return string
*/
private static function getUglifyCssPath(): string
{
return '/usr/local/bin/uglifycss';
}
/**
* Поиск Не минифицированных css файлов в Директории (и её поддиректориях)
*
* @param $sDirPath
* @return array
*/
public static function findCssFilesInDir($sDirPath): array
{
$arFilePaths = [];
$obDirectory = new RecursiveDirectoryIterator($_SERVER["DOCUMENT_ROOT"].$sDirPath);
$obIterator = new RecursiveIteratorIterator($obDirectory);
foreach ($obIterator as $obInfo) {
$file_formal = substr($obInfo->getfileName(), strrpos($obInfo->getfileName(), ".") + 1);
$name_search = array("css"); // Список форматов
foreach ($name_search as $key_name) {
if (
$file_formal == $key_name &&
!stristr($obInfo->getfileName(), '.min.css')
) {
$arFilePaths[] = $obInfo->getPathname();
}
}
}
return $arFilePaths;
}
/**
* Выполнить минификацию
*
* @param $sFilePath
* @param bool $bAddDocumentRoot
*/
private static function execUglifycss($sFilePath, bool $bAddDocumentRoot = false)
{
if ($bAddDocumentRoot) {
$sFilePath = $_SERVER["DOCUMENT_ROOT"].$sFilePath;
}
$sMinFilePath = str_replace('.css', '.min.css', $sFilePath);
if (
!file_exists($sMinFilePath) ||
(
file_exists($sMinFilePath) &&
filectime($sFilePath) > filectime($sMinFilePath)
)
) {
/*
* https://www.npmjs.com/package/uglifycss
--output f puts the result in f file
*/
shell_exec(self::getUglifyCssPath().' '.$sFilePath.' --output '.$sMinFilePath);
self::$bNeedCacheRefresh = true;
}
}
}
В приведенном выше классе есть метод \CCssMinify::getUglifyCssPath, в котором как раз мы возвращаем путь до uglifycss, полученный через whereis uglifycss. Мы не используем специфических настроек запуска uglifycss, только путь до минифицированной версии, которая будет находится рядом с основной.
Сам Агент реализован методом \CCssMinify::minifyAgent(); Как видно из кода, метод автоматически очищает CSS КЕШ сайта. Пересозданию минификаций подвержены только те файлы css, время изменения которых позже созданных для них минификаций. В Административном разделе агент имеет следующие настройки:
Среднее время выполнения агента на моём проекте: 7-8 секунд, для ПОЛНОГО пересоздания. В режиме обычно работы, при пересоздании только изменённых файлов, не превышает 0.1 секунды. Не берусь говорить точно об объёмах, но проект довольно крупный, так что не сильно беспокойтесь, что всё повиснет. Хотя перестраховаться не мешает.
Пример результата работы агента:
C CSS мы закончили. давай перейдем к JS.
JS
Решение базируется на модуле uglifyjs Node.js (https://www.npmjs.com/package/uglify-js). Устанавливаете uglifyjs для использования в командной строке (From NPM for use as a command line app), это очень важно! Заранее позаботьтесь о необходимых доступах к серверу. Выполните к консоле команду:
npm install uglify-js -g
После успешной установки узнайте путь нахождения установленного модуля, выполнив команду.
whereis uglifyjs
В ответе будет строка с путём до uglifyjs. В моём случае - /usr/local/bin/uglifyjs. Далее мы этот путь будем использовать в классе.
Сам класс:
<?
/*
* Класс по минификации JS-файлов. Использует NodeJS плагин uglifyjs
*/
class CJsMinify
{
const DIRS_FOR_JS_MINIFY = [
'/local/admin/js/',
'/local/templates/main/js/',
'/local/components/',
'/local/templates/main/components/'
];
/**
* Агент по минификации скриптов
*
* @return string
*/
public static function minifyAgent()
{
try {
self::minify();
} catch (Exception $obException) {
CEventLog::Add(array(
"SEVERITY" => "ERROR",
"AUDIT_TYPE_ID" => "MINIFY_JS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => $obException->getMessage()
));
}
return __METHOD__.'();';
}
/**
* Минификация скриптов, находящихся внутри директорий, обозначенных в константе \dh\CJsMinify::DIRS_FOR_JS_MINIFY
*/
public static function minify()
{
$bNeedCacheRefresh = false;
foreach (self::DIRS_FOR_JS_MINIFY as $sDir) {
$arFilePaths = self::findJsFilesInDir($sDir);
if (!empty($arFilePaths)) {
foreach ($arFilePaths as $sFilePath) {
$sMinFilePath = str_replace('.js', '.min.js', $sFilePath);
if (
!file_exists($sMinFilePath) ||
(
file_exists($sMinFilePath) &&
filectime($sFilePath) > filectime($sMinFilePath)
)
) {
/*
* https://www.npmjs.com/package/uglify-js
-c, --compress [options] Enable compressor/specify compressor options
-m, --mangle [options] Mangle names/specify mangler options
-o, --output <file> Output file path (default STDOUT)
*/
shell_exec(self::getUglifyJsPath().' '.$sFilePath.' -c -m -o '.$sMinFilePath);
$bNeedCacheRefresh = true;
}
}
}
}
if ($bNeedCacheRefresh && BXClearCache(true, "/js/")) {
CEventLog::Add(array(
"SEVERITY" => "INFO",
"AUDIT_TYPE_ID" => "MINIFY_JS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => "Clear cache"
));
}
}
/**
* Получим путь до UglifyJS
* @return string
*/
private static function getUglifyJsPath() {
return '/usr/local/bin/uglifyjs';
}
/**
* Поиск Не минифицированных js файлов в Директории (и её поддиректориях)
*
* @param $sDirPath
* @return array
*/
public static function findJsFilesInDir($sDirPath)
{
$arFilePaths = [];
$obDirectory = new RecursiveDirectoryIterator($_SERVER["DOCUMENT_ROOT"].$sDirPath);
$obIterator = new RecursiveIteratorIterator($obDirectory);
foreach ($obIterator as $obInfo) {
$file_formal = substr($obInfo->getfileName(), strrpos($obInfo->getfileName(), ".") + 1);
$name_search = array("js"); // Список форматов
foreach ($name_search as $key_name) {
if (
$file_formal == $key_name &&
!stristr($obInfo->getfileName(), '.min.js') &&
!stristr($obInfo->getfileName(), '.map.js')
) {
$arFilePaths[] = $obInfo->getPathname();
}
}
}
return $arFilePaths;
}
}
В приведенном выше классе есть метод CJsMinify::getUglifyJsPath, в котором как раз мы возвращаем путь до uglifyjs, полученный через whereis uglifyjs. В коде имеются пояснения выставленных опций, с которыми подробнее можете ознакомится на странице https://www.npmjs.com/package/uglify-js
Сам Агент реализован методом CJsMinify::minifyAgent(). Как видно из кода, метод автоматически очищает JS КЕШ сайта. Пересозданию минификаций подвержены только те файлы js, время изменения которых позже созданных для них минификаций. В Административном разделе агент имеет следующие настройки:
Среднее время выполнения агента на моём проекте: 30-32 секундs, для ПОЛНОГО пересоздания. В режиме обычной работы, при пересоздании только изменённых файлов, не превышает 0.1 секунды.
Пример результата работы:
Бонус:
В качестве бонуса поделюсь примером .gitignore, чтобы не засорять репозиторий минификациями:
Проблема: у нас, например, есть 10 отдельных кешируемых страниц, нам надо сбрасывать разом весь их кеш. Без тегированного кеша надо знать все ключи и перебирая их сбрасывать. Добавляя 11 страницу, нам надо не забыть добавить его ключ в сбрасыватель кеша, что легко забыть, или сайт начнет выдавать баги.
Тут нам помогает тегированный кеш. При генерации страниц, мы помечаем данные одним общим тегом и по нему сбрасываем кеш. Добавляя 11 страницу, мы добавляем на странице общий тег и эта страница начинает сбрасываться автоматически.
use \Bitrix\Main\Data\Cache;
use \Bitrix\Main\Application;
$cache = Cache::createInstance(); // Служба кеширования
$taggedCache = Application::getInstance()->getTaggedCache(); // Служба пометки кеша тегами
/*
* Чтобы тегированный кеш нашел что ему сбрасывать, необходим
* одинаковый путь в $cache->initCache() и $taggedCache->startTagCache()
* У нас путь указан в $cachePath
*/
$cachePath = 'mycachepath';
$cacheTtl = 3600;
$cacheKey = 'mycachekey';
if ($cache->initCache($cacheTtl, $cacheKey, $cachePath))
{
$vars = $cache->getVars();
/*
* Еще тут можно вывести данные в браузер, через $cache->output();
* Тогда получится замена классу CPageCache
*/
}
elseif ($cache->startDataCache())
{
// Начинаем записывать теги
$taggedCache->startTagCache($cachePath);
$vars = [
'date' => date('r'),
'rand' => rand(0, 9999), // Если данные закешированы - число не будет меняться
];
// Добавляем теги
// Кеш сбрасывать при изменении данных в инфоблоке с ID 1
$taggedCache->registerTag('iblock_id_1');
// Кеш сбрасывать при изменении данных в инфоблоке с ID 2
$taggedCache->registerTag('iblock_id_2');
// Если что-то пошло не так и решили кеш не записывать
$cacheInvalid = false;
if ($cacheInvalid)
{
$taggedCache->abortTagCache();
$cache->abortDataCache();
}
// Всё хорошо, записываем кеш
$taggedCache->endTagCache();
$cache->endDataCache($vars);
}
// Данные будут обновляться раз в час или при обновлении данных в инфоблоках 1 и 2
print_r($vars);
Когда необходимо сбросить кеш по тегу:
use \Bitrix\Main\Application;
$taggedCache = Application::getInstance()->getTaggedCache(); // Служба пометки кеша тегами
/*
* Где-то на отдельной странице чистим кеш по тегу
*/
$taggedCache->clearByTag('iblock_id_28');
Как сделать чтобы мой кеш сбрасывался автоматически, при изменении данных в инфоблоке? Использовать готовые теги: iblock_id_{ID} - это зарезервированные теги битрикса для инфоблоков, сброс кеша по этим тегам уже есть в классах работы с инфоблоками. iblock_id_new - по этому тегу кеш сбрасывается при создании инфоблоков. Списка используемых тегов в битрикс я не нашел.
Для себя вы можете придумать свои теги.
Везде пишут что, чтобы работало всё это чудо, надо добавить в dbconn.php строку: define("BX_COMP_MANAGED_CACHE", true); Но у меня и без нее всё работает
Сталкивались ли вы с тем, что клиенты используют обновления битрикс как манипулятивный рычаг давления, чтобы спихнуть ошибки предыдущих разработчиков на тех, кто произвел обновление ядра (ядро не модифицировалось предыдущим разработчиком)?
(доводы клиента: до обновления все работало и прочее, верните все как было)
или это классическая ментальная ошибка, когда произошедшие одновременно вещи считают связанными?
или все-таки есть риски и большие при обновлении ядра?
хотя обновления ядра объективно - это ресурс, причем платный.
Всё более актуальной становится проблема недоставки писем, отправляемых через phpmailer. Не доверяют почтовые сервисы таким сообщениям... Особенно их не любит iCloud. Тут даже SPF не помогает. Помогает только DKIM. А ещё круче, когда письма отсылает платный сервис. Например, SendPulse. Ну, надо же всем зарабатывать, ведь правда?
Короче, нам было необходимо обеспечить 100% доставку писем о результатах тестов на ковид. В итоге, вот что получилось.
1. Регистрируемся на Sendpulse 2. Скачиваем в php_interface Битрикса библиотеку (враппер) их API 3. Прописываем в init.php следующее:
Таким образом, мы сохранили отправку всех сообщений, на базе почтовых шаблонов Битрикса, с поддержкой прикрепленных файлов (в CEvent::Send теперь есть массивчик под них). Фишка в том, что мы аннулируем отправку стандартных писем Битрикса, добавив функцию custom_mail. При этом перехватываем её через обработчик события, и шлём нужные нам письма через Сендпульс.
Единственный момент - штука не поддерживает спецслова в шаблонах типа DEFAULT_EMAIL_FROM, SITE_NAME и т.д. Только поля почтового события. Ну, это ерунда. Главное, что теперь почта ходит лучше, чем DHL.
После отключения mbstring.func_overload для установки обновлений Битрикса, перестали работать строковые функции типа strtoupper(). Техподдержка поделилась секретной ссылкой, как заставить работать: https://helpdesk.bitrix24.ru/open/11473408
Добрый день. В файле init.php объявлен обработчик события OnProlog, в нем реализована логика добавления js-скрипта в хэдер сайта, но скрипт не добавляется. Функция addJs возвращает true. Скажите, пожалуйста, в чем может быть причина?
Добрый день! Подскажите, пожалуйста! Недавно начал работать с битрикс, достался готовый магазин. При уходе с сайта выскакивает всплывающие окно. Шаблон аспро стоит. перелопатил всю админку, готовых решений не установлено по всплывающим окнам. Можете подсказать в каком направлении искать? Нужно убрать это окно.