import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const webRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); const sourceRoot = join(webRoot, '..', '.webarchive'); const illustrationsDir = join(webRoot, 'public', 'assets', 'illustrations'); const mediaDir = join(webRoot, 'public', 'assets', 'media'); const dataDir = join(webRoot, 'data'); const rawArticles = JSON.parse(await readFile(join(sourceRoot, 'extracted-articles.json'), 'utf8')); const categories = { 'вирус-самопроизвольный-запуск-брауз': ['Windows', 'Вирусы'], 'использование-jquery-в-webpack': ['JavaScript'], 'компиляция-64-x-разрядных-программ-на-dmd-по': ['DLang', 'DMD', 'Windows'], 'новый-движок-ctfe': ['DLang', 'Compile-time'], 'особенности-vibe-d': ['DLang', 'Vibe.d'], 'ошибка-php-ssl-certificate-error-unable-to-get-local-issuer-certificate': ['PHP', 'SSL'], 'о-tilix-и-d-интервью-с-геральдом-нанном': ['DLang', 'Интервью'], 'пишем-аналог-функции-php-preg_match_all-на-языке-прог': ['DLang', 'Регулярные выражения'], 'bitrix-api-создание-добавление-торгового-пре': ['Bitrix', 'PHP'], 'bitrix-api-функция-для-генерации-кода-элемент': ['Bitrix', 'PHP'], 'bitrix-api-add-foto-editor': ['Bitrix', 'PHP'], 'dconf-2017-под-капотом-мусорщика-ди-дмитрий-ол': ['DLang', 'GC'], 'firefox-увеличение-ожидания-загрузки-timeout-стр': ['Firefox', 'Настройки'], }; const covers = { 'вирус-самопроизвольный-запуск-брауз': '/assets/illustrations/cover-virus.svg', 'использование-jquery-в-webpack': '/assets/illustrations/jquery-webpack.svg', 'компиляция-64-x-разрядных-программ-на-dmd-по': '/assets/illustrations/dmd-win64.svg', 'новый-движок-ctfe': '/assets/illustrations/ctfe-engine.svg', 'особенности-vibe-d': '/assets/illustrations/vibe-cover.svg', 'ошибка-php-ssl-certificate-error-unable-to-get-local-issuer-certificate': '/assets/illustrations/php-ssl-cacert.svg', 'о-tilix-и-d-интервью-с-геральдом-нанном': '/assets/illustrations/tilix-cover.svg', 'пишем-аналог-функции-php-preg_match_all-на-языке-прог': '/assets/illustrations/preg-match-all-d.svg', 'bitrix-api-создание-добавление-торгового-пре': '/assets/illustrations/bitrix-offer-api.svg', 'bitrix-api-функция-для-генерации-кода-элемент': '/assets/illustrations/bitrix-translit-api.svg', 'bitrix-api-add-foto-editor': '/assets/illustrations/bitrix-photo-editor.svg', 'dconf-2017-под-капотом-мусорщика-ди-дмитрий-ол': '/assets/illustrations/dconf-gc-cover.svg', 'firefox-увеличение-ожидания-загрузки-timeout-стр': '/assets/illustrations/firefox-timeout.svg', }; const imageMap = [ ['2017-05-13_13-39-44', '/assets/illustrations/taskschd-run.svg', 'Окно запуска taskschd.msc'], ['2017-05-13_13-41-17', '/assets/illustrations/taskschd-action.svg', 'Подозрительное задание в планировщике Windows'], ['/2017/05/1.png', '/assets/illustrations/vibe-async.svg', 'Асинхронная модель vibe.d'], ['/2017/05/2.png', '/assets/illustrations/vibe-api.svg', 'Короткий API vibe.d'], ['/2017/05/3.png', '/assets/illustrations/vibe-web.svg', 'Веб-стек vibe.d'], ['/2017/05/4.png', '/assets/illustrations/vibe-db.svg', 'Драйверы баз данных vibe.d'], ['/2017/05/5.png', '/assets/illustrations/vibe-io.svg', 'Неблокирующий ввод-вывод'], ['/2017/05/6.png', '/assets/illustrations/vibe-dlang.svg', 'Язык D как основа vibe.d'], ['/2017/05/7.png', '/assets/illustrations/vibe-diet.svg', 'Шаблоны Diet'], ['tilix-screenshot-2', '/assets/illustrations/tilix-screenshot.svg', 'Боковая панель Tilix в действии'], ['/2017/06/1-300x225', '/assets/illustrations/gc-small-pools.svg', 'Пулы малых объектов в GC D'], ['/2017/06/1.jpg', '/assets/illustrations/gc-small-pools.svg', 'Пулы малых объектов в GC D'], ['/2017/06/2-200x300', '/assets/illustrations/gc-large-pool.svg', 'Пул больших объектов в GC D'], ['/2017/06/2.jpg', '/assets/illustrations/gc-large-pool.svg', 'Пул больших объектов в GC D'], ['bitrix-foto-editor-2', '/assets/illustrations/bitrix-photo-editor-ui.svg', 'Редактор картинок Bitrix'], ]; const additions = { 'вирус-самопроизвольный-запуск-брауз': `
После удаления задания перезагрузите компьютер и оставьте систему включенной на тот промежуток времени, через который раньше открывался браузер. Если окно больше не появляется, значит источник найден правильно. Если запуск повторяется, проверьте не только библиотеку планировщика, но и вкладку автозагрузки, ключи Run в реестре и расширения браузера.
В итоге рецепт простой: найти периодический запуск, проверить действие задания, удалить его и убедиться, что источник не восстанавливается после перезагрузки.
`, 'использование-jquery-в-webpack': `Для старого проекта jQuery в Webpack лучше подключать явно: установить пакет, импортировать его в точке входа и, если плагинам нужна глобальная переменная, добавить ProvidePlugin. Тогда один и тот же экземпляр библиотеки будет использоваться и вашим кодом, и плагинами.
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
],
};
Если плагин всё равно не видит jQuery, проверьте порядок импортов и отсутствие второй копии библиотеки в bundle. На этом обычно все проблемы заканчиваются.
`, 'компиляция-64-x-разрядных-программ-на-dmd-по': `Чтобы не возвращаться к этой настройке каждый раз, держите отдельный build-скрипт для 64-битной сборки и явно указывайте архитектуру, компилятор и путь к библиотекам. Главная мысль статьи: не смешивать 32-битные OMF-библиотеки и 64-битную сборку.
dmd -m64 main.d -ofapp.exe
dub build --arch=x86_64
Если линковщик ругается на формат библиотек, сначала проверьте разрядность каждой внешней зависимости. В большинстве случаев ошибка не в D-коде, а в том, что рядом оказалась библиотека другого формата.
`, 'новый-движок-ctfe': `CTFE полезен там, где результат можно посчитать один раз во время компиляции: таблицы констант, парсинг небольших DSL, генерация однотипного кода. Но не стоит превращать компиляцию в полноценный рантайм. Чем проще входные данные и чем меньше побочных эффектов, тем легче будет сопровождать проект.
Практическое правило такое: если вычисление зависит только от литералов и конфигурации сборки, переносите его в CTFE. Если нужен ввод-вывод, сеть или состояние окружения, оставьте это обычному коду.
`, 'особенности-vibe-d': `vibe.d хорошо подходит для небольших и средних веб-сервисов на D, где важны неблокирующий ввод-вывод, простая маршрутизация и единая экосистема DUB. Начать можно с минимального HTTP-сервера, затем добавить Diet-шаблоны, MongoDB или Redis и вынести повторяющиеся части в отдельные модули.
shared static this()
{
auto settings = new HTTPServerSettings;
settings.port = 8080;
listenHTTP(settings, &handleRequest);
}
Если проект требует большого количества готовых интеграций, заранее проверьте наличие пакетов в DUB. Если же нужна компактная серверная часть на D, vibe.d остаётся прямым и понятным выбором.
`, 'ошибка-php-ssl-certificate-error-unable-to-get-local-issuer-certificate': `После подключения файла сертификатов перезапустите веб-сервер или PHP-FPM и выполните тестовый HTTPS-запрос из того же окружения, где возникала ошибка. Важно проверять не браузер, а именно PHP: CLI и веб-сервер могут использовать разные php.ini.
php -i | grep cafile
php -r "var_dump(file_get_contents('https://example.com') !== false);"
Не отключайте проверку SSL через CURLOPT_SSL_VERIFYPEER = false как постоянное решение. Это скрывает проблему и делает соединение уязвимым. Правильный путь — актуальный CA bundle и явно указанная директива openssl.cafile.
`, 'о-tilix-и-d-интервью-с-геральдом-нанном': `Главный практический вывод прост: D может быть не только системным языком, но и удобным языком для прикладных desktop-инструментов. Tilix показывает, что связка D, GtkD и аккуратного следования HIG способна дать зрелое приложение, которым пользуются каждый день.
Если хочется повторить такой путь, начинайте не с большого терминала, а с маленькой GTK-утилиты: окно, настройки, несколько действий, сборка через DUB. Так быстрее станет понятно, насколько привычки из Java, Delphi или PHP мешают или помогают писать на D.
`, 'пишем-аналог-функции-php-preg_match_all-на-языке-прог': `После реализации флагов обязательно добавьте тесты на три случая: нет совпадений, одно совпадение с группами и несколько совпадений с позициями. Именно на позициях чаще всего появляются ошибки, потому что PHP возвращает смещения в исходной строке, а не в подстроке после очередного поиска.
assert(matchAll(r"(\w+)=(\d+)", "a=1 b=22").length == 2);
assert(matchAll(r"z+", "abc").empty);
Дальше можно расширять API постепенно: сначала PREG_PATTERN_ORDER, затем PREG_SET_ORDER, и только после этого режим с OFFSET_CAPTURE.
`, 'bitrix-api-создание-добавление-торгового-пре': `После добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU или не обновлены индексы.
Такой порядок закрывает типичную проблему: элемент есть в админке, но покупатель его не видит.
`, 'bitrix-api-функция-для-генерации-кода-элемент': `Транслитерация имени удобна, но код элемента должен оставаться уникальным. Поэтому после генерации проверяйте существующие символьные коды в инфоблоке и при совпадении добавляйте числовой суффикс.
telefon-samsung
telefon-samsung-2
telefon-samsung-3
Такой простой контроль избавляет от случайной перезаписи URL и от ситуаций, когда два товара получают один и тот же человекочитаемый адрес.
`, 'bitrix-api-add-foto-editor': `После вывода компонента важно не забыть обработать результат формы: сохранить ID файла, проверить тип загруженного файла и права пользователя. Сам редактор решает задачу интерфейса, но безопасность и привязка к сущности остаются на стороне вашего кода.
Если редактор не открывается, проверьте подключение модулей main и fileman, права на загрузку и наличие административных JS-расширений на странице. После этого компонент работает стабильно и может использоваться не только в админке, но и в собственных формах.
`, 'dconf-2017-под-капотом-мусорщика-ди-дмитрий-ол': `Статья важна не только для разработчиков компилятора. Она показывает, почему аллокатор и сборщик мусора нельзя оценивать одной фразой «GC медленный». Нужно смотреть на классы размеров, поиск пулов, стоимость маркировки и то, сколько работы выполняется во время остановки мира.
Для прикладного D-кода вывод такой: держите горячие циклы предсказуемыми, не создавайте лишние временные объекты в tight-loop и измеряйте паузы. Если участок действительно критичен, D позволяет локально отключить GC или заменить аллокации заранее подготовленными буферами.
`, 'firefox-увеличение-ожидания-загрузки-timeout-стр': `Увеличение ожидания помогает при разовых административных операциях, но не должно маскировать медленный сайт. Если обычная страница требует минуты, правильнее вынести задачу в фон: очередь, cron, прогресс-бар и отдельная проверка статуса.
Для phpMyAdmin, импорта и парсинга больших файлов увеличение timeout допустимо. Для публичного пользовательского сценария лучше исправлять серверную часть, иначе браузер станет ждать дольше, но пользователь всё равно получит плохой интерфейс.
`, }; const articleAdditions = { ...additions, 'вирус-самопроизвольный-запуск-брауз': `После удаления задания перезагрузите компьютер и оставьте систему включённой на тот промежуток времени, через который раньше открывался браузер. Если окно больше не появляется, значит источник найден правильно. Если запуск повторяется, проверяем не только библиотеку планировщика, но и автозагрузку, ключи Run в реестре и расширения браузера.
В планировщике заданий важно смотреть не только название задания. Название злоумышленник может сделать вполне безобидным: Update Service, Browser Check, Windows Helper. Смысл находится в действии. Если в действии указан путь к браузеру, а после него стоит адрес сайта, то перед нами как раз тот самый случай.
Если задача появляется снова, значит в системе остался установщик или служба, которая её восстанавливает. В таком случае дополнительно смотрим «Приложения и возможности», папки автозагрузки пользователя, ключи HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run и HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run. После очистки нужно обновить антивирусные базы и выполнить полную проверку.
Итог простой: в этом случае лечится не браузер, а причина его запуска. Находим периодическое задание, проверяем действие, отключаем, убеждаемся, что оно не восстановилось, и только после этого удаляем. Так проблема закрывается полностью, а не до следующей перезагрузки.
`, 'использование-jquery-в-webpack': `Для старого проекта jQuery в Webpack лучше подключать явно: установить пакет, импортировать его в точке входа и отдельно решить вопрос с глобальными переменными. ProvidePlugin удобен, когда в модулях встречаются свободные идентификаторы $ или jQuery. Но если старый плагин лезет именно в window.jQuery, одного ProvidePlugin может быть мало — нужно положить jQuery в window самостоятельно.
import $ from 'jquery';
window.$ = $;
window.jQuery = $;
После этого можно добавить ProvidePlugin, чтобы не писать импорт в каждом файле:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
],
};
Порядок подключения имеет значение. Сначала в entry-файле импортируем jQuery и кладём его в window, потом импортируем старые плагины, которым нужен глобальный объект. Если плагин подключается через require, делаем это после установки window.jQuery.
import './jquery-global';
import 'inputmask/dist/jquery.inputmask';
import './app';
Проверка простая: в браузерной консоли должны существовать window.$ и window.jQuery, а в собранном bundle не должно быть двух разных копий jQuery. Если проект новый, лучше не тащить jQuery без необходимости. Если проект старый и плагины уже написаны под jQuery, такая схема делает зависимость явной и предсказуемой.
`, 'компиляция-64-x-разрядных-программ-на-dmd-по': `Главная мысль остаётся той же: нельзя смешивать 32-битные OMF-библиотеки и 64-битную сборку. В старых установках DMD под Windows это часто упиралось в ручную настройку sc.ini, Windows SDK и Visual Studio linker. В более новых установках официальный установщик DMD обычно делает большую часть этой настройки сам, а для нормальной работы достаточно иметь Visual Studio Build Tools или установленную Visual Studio с C++ tooling.
Для простой 64-битной сборки используем явную архитектуру:
dmd -m64 main.d -of=app.exe
dub build --arch=x86_64
Если используется старая 32-битная сборка, смотрим, какой формат объектных файлов нужен проекту. Старый OMF и MS-COFF несовместимы между собой. Для современных библиотек Windows чаще нужен MS-COFF, а для 64-битной сборки это фактически обычный путь.
Типичная ошибка выглядит так: код компилируется, а линковка падает на внешней библиотеке. В большинстве случаев виноват не D-код, а то, что рядом лежит библиотека другого формата или другой разрядности. Сначала проверяем toolchain и зависимости, и только потом ищем ошибку в исходниках.
`, 'новый-движок-ctfe': `CTFE полезен там, где результат можно посчитать один раз во время компиляции: таблицы констант, парсинг небольших DSL, генерация однотипного кода, предварительная подготовка строк и проверка инвариантов. Но не стоит превращать компиляцию в полноценный runtime. Чем проще входные данные и чем меньше побочных эффектов, тем легче будет сопровождать проект.
enum table = buildLookupTable();
static assert(table.length == 256);
Хороший кандидат для CTFE — функция, которая зависит только от литералов, типов и конфигурации сборки. Плохой кандидат — код, которому нужен ввод-вывод, сеть, текущее время, состояние окружения или большая внешняя база данных. Такой код лучше оставить в runtime или вынести в отдельный генератор, который запускается до сборки.
Для контроля качества CTFE-кода полезно держать рядом static assert. Он сразу показывает, что вычисление действительно произошло на этапе компиляции и вернуло ожидаемый результат. Если compile-time функция стала слишком сложной, её стоит разбить на маленькие чистые функции: так компилятору проще, и человеку проще понять, что происходит.
Практический вывод такой: CTFE в D — мощный инструмент, но сила его не в магии, а в переносе предсказуемых вычислений из runtime в compile-time. Используем его для констант, генерации и проверок. Не используем его как замену обычной программе.
`, 'особенности-vibe-d': `vibe.d хорошо подходит для небольших и средних веб-сервисов на D, где важны неблокирующий ввод-вывод, простая маршрутизация и единая экосистема DUB. Начать можно с минимального HTTP-сервера, затем добавить Diet-шаблоны, MongoDB или Redis и вынести повторяющиеся части в отдельные модули.
shared static this()
{
auto settings = new HTTPServerSettings;
settings.port = 8080;
listenHTTP(settings, &handleRequest);
}
При проектировании сервиса главное не забывать, что удобный синтаксис не отменяет асинхронную модель. Долгие CPU-задачи нельзя выполнять прямо в обработчике запроса, иначе они будут мешать обработке ввода-вывода. Для таких задач лучше использовать отдельные worker-потоки, очередь или заранее подготовленные данные.
Если проект требует большого количества готовых интеграций, заранее проверьте наличие пакетов в DUB и состояние нужных драйверов. Если же нужна компактная серверная часть на D, vibe.d остаётся прямым и понятным выбором: один язык, быстрый нативный код, нормальная маршрутизация, шаблоны и асинхронный ввод-вывод.
`, 'ошибка-php-ssl-certificate-error-unable-to-get-local-issuer-certificate': `После подключения файла сертификатов перезапустите веб-сервер или PHP-FPM и выполните тестовый HTTPS-запрос из того же окружения, где возникала ошибка. Важно проверять не браузер, а именно PHP: CLI и веб-сервер могут использовать разные php.ini.
php --ini
php -i | grep -E "curl.cainfo|openssl.cafile"
php -r "var_dump(file_get_contents('https://example.com') !== false);"
Для cURL указывается curl.cainfo, для потоков OpenSSL — openssl.cafile. На Windows путь лучше писать полностью и без относительных директорий. Если используется не глобальный php.ini, а отдельная настройка клиента, можно передать CA bundle прямо в cURL:
curl_setopt($ch, CURLOPT_CAINFO, 'C:\\php\\extras\\ssl\\cacert.pem');
Не отключайте проверку SSL через CURLOPT_SSL_VERIFYPEER = false как постоянное решение. Это скрывает проблему и делает соединение уязвимым. Такая настройка допустима только для короткой диагностики в локальной среде, и после проверки её нужно вернуть обратно.
Если CA bundle указан верно, но ошибка осталась, проверьте цепочку сертификатов на стороне удалённого сервера. Иногда браузер открывает сайт, потому что умеет достраивать цепочку, а PHP/cURL получает неполную цепочку и честно падает. В таком случае исправлять нужно сертификаты сервера, а не PHP-код.
Правильный итог: актуальный cacert.pem, явно указанные curl.cainfo и openssl.cafile, перезапуск PHP и тест именно из того окружения, где работает приложение.
`, 'о-tilix-и-d-интервью-с-геральдом-нанном': `Главный практический вывод прост: D может быть не только системным языком, но и удобным языком для прикладных desktop-инструментов. Tilix показывает, что связка D, GtkD и аккуратного следования GNOME HIG способна дать зрелое приложение, которым пользуются каждый день.
В интервью хорошо видно, что выбор языка был не религиозным, а практическим. Автору не хотелось писать GUI на C или C++, Python оказался неудобен для крупного статически проверяемого приложения, а D дал знакомую после Java модель, нативную скорость и нормальные GTK-привязки. Это важный критерий выбора технологии: не «самый модный язык», а язык, на котором конкретный автор может быстро и спокойно делать продукт.
Если хочется повторить такой путь, начинать стоит не с большого терминала, а с маленькой GTK-утилиты: окно, настройки, несколько действий, сборка через DUB, упаковка и обновления. На таком проекте сразу проявятся реальные вопросы: работа с GtkD, структура проекта, взаимодействие с C-библиотеками, обработка событий и паузы GC.
Именно поэтому интервью полезно не только поклонникам D. Оно показывает нормальный инженерный путь: выбрать задачу, подобрать инструмент, сделать небольшую работающую версию, а затем постепенно доводить приложение до зрелого состояния.
`, 'пишем-аналог-функции-php-preg_match_all-на-языке-прог': `После реализации флагов обязательно добавьте тесты на три случая: нет совпадений, одно совпадение с группами и несколько совпадений с позициями. Именно на позициях чаще всего появляются ошибки, потому что PHP возвращает смещения в исходной строке, а не в подстроке после очередного поиска.
assert(matchAll(r"(\w+)=(\d+)", "a=1 b=22").length == 2);
assert(matchAll(r"z+", "abc").empty);
В D стоит явно решить, какой формат результата нужен. PHP поддерживает несколько режимов: PREG_PATTERN_ORDER, PREG_SET_ORDER и вариант с PREG_OFFSET_CAPTURE. Если пытаться сделать всё сразу, код быстро станет запутанным. Лучше сначала реализовать простой режим, затем режим группировки по совпадениям, и только после этого добавить позиции.
struct MatchPart
{
string text;
ptrdiff_t offset;
}
alias MatchSet = MatchPart[][];
Отдельно проверьте Unicode. Если шаблон и строка содержат кириллицу, важно понимать, что именно возвращается в offset: позиция в байтах или позиция в символах. PHP для PREG_OFFSET_CAPTURE возвращает байтовое смещение. Если в D вы хотите повторить поведение PHP, нужно документировать именно байтовый offset, иначе результаты будут отличаться.
Финальный класс должен иметь маленькую поверхность API: метод без offset, метод с offset и понятный enum для порядка результата. Тогда аналог preg_match_all будет не просто копией PHP-функции, а удобным D-инструментом, который предсказуемо работает в тестах.
`, 'bitrix-api-создание-добавление-торгового-пре': `После добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU, не сохранён товарный каталог или не обновлены кеши.
Минимальная последовательность такая: создать элемент торгового предложения в инфоблоке SKU, записать связь с товаром, сохранить параметры товара, установить цену, установить количество. Если хотя бы один шаг пропущен, предложение может быть видно в админке, но не попадёт в публичный каталог.
CModule::IncludeModule('iblock');
CModule::IncludeModule('catalog');
$offerId = $el->Add($fields);
CCatalogProduct::Add([
'ID' => $offerId,
'QUANTITY' => 10,
]);
CPrice::SetBasePrice($offerId, 990, 'RUB');
На боевом проекте обязательно логируйте результат Add и текст ошибки LAST_ERROR. Bitrix часто молча возвращает false, а настоящая причина находится именно там: обязательное поле, неправильный инфоблок, неверный код свойства или недостаточные права.
`, 'bitrix-api-функция-для-генерации-кода-элемент': `Транслитерация имени удобна, но код элемента должен оставаться уникальным. Поэтому после генерации проверяйте существующие символьные коды в инфоблоке и при совпадении добавляйте числовой суффикс. Иначе два товара с похожим названием могут получить один URL.
telefon-samsung
telefon-samsung-2
telefon-samsung-3
Также стоит сразу нормализовать результат: привести к нижнему регистру, заменить пробелы на дефис, убрать повторяющиеся дефисы и обрезать дефис в начале или конце строки. Это мелочь, но именно такие мелочи потом портят адреса страниц.
$code = CUtil::translit($name, 'ru', [
'replace_space' => '-',
'replace_other' => '-',
'change_case' => 'L',
]);
$code = trim(preg_replace('/-+/', '-', $code), '-');
Проверку уникальности лучше делать в том же инфоблоке, где будет создан элемент. Если сайт многоязычный или есть разделы с одинаковыми товарами, заранее решите правило: уникальность на весь инфоблок или только внутри раздела. Для SEO обычно проще и надёжнее уникальность на весь инфоблок.
В итоге функция должна не просто транслитерировать строку, а возвращать готовый символьный код: чистый, нижнего регистра, без мусора и без совпадений с уже существующими элементами.
`, 'bitrix-api-add-foto-editor': `После вывода компонента важно не забыть обработать результат формы: сохранить ID файла, проверить тип загруженного файла и права пользователя. Сам редактор решает задачу интерфейса, но безопасность и привязка к сущности остаются на стороне вашего кода.
В параметрах компонента отдельно проверьте allowUpload, medialib, fileDialog, cloud, delete, edit и maxCount. Не все параметры будут иметь эффект в любой установке Bitrix: часть зависит от подключённых модулей, прав пользователя и контекста страницы.
$fileId = (int)$_POST['picture'];
if ($fileId > 0) {
$file = CFile::GetFileArray($fileId);
if ($file && str_starts_with($file['CONTENT_TYPE'], 'image/')) {
// сохраняем ID картинки в своей сущности
}
}
Если редактор не открывается, проверьте подключение модулей main и fileman, права на загрузку, административные JS-расширения и наличие сессии пользователя. В публичной части сайта проблема часто оказывается не в FileInput, а в том, что на странице не подключены нужные скрипты Bitrix.
Рабочая схема такая: выводим FileInput, разрешаем только картинки, ограничиваем количество, после отправки формы валидируем ID файла и сохраняем его в свою сущность. Тогда встроенный редактор становится нормальной частью формы, а не просто красивой кнопкой загрузки.
`, 'dconf-2017-под-капотом-мусорщика-ди-дмитрий-ол': `Статья важна не только для разработчиков компилятора. Она показывает, почему аллокатор и сборщик мусора нельзя оценивать одной фразой «GC медленный». Нужно смотреть на классы размеров, поиск пулов, стоимость маркировки и то, сколько работы выполняется во время остановки мира.
Для прикладного D-кода вывод такой: держите горячие циклы предсказуемыми, не создавайте лишние временные объекты в tight-loop и измеряйте паузы. Если участок действительно критичен, D позволяет локально управлять GC и заменить поток мелких аллокаций заранее подготовленными буферами.
import core.memory : GC;
GC.disable();
scope(exit) GC.enable();
// горячий участок без лишних временных объектов
Отключать GC нужно аккуратно. Это не волшебная оптимизация, а договор: пока сборщик выключен, программа не должна бесконтрольно накапливать мусор. Поэтому такой приём подходит для короткого измеримого участка, а не для всего приложения.
Главный смысл доклада в том, что производительность памяти — это архитектура. Если в программе много мелких объектов, непредсказуемых ссылок и больших пауз, проблема редко решается одной настройкой. Нужно менять модель данных, жизненный цикл объектов и место, где выделяется память.
`, 'firefox-увеличение-ожидания-загрузки-timeout-стр': `Увеличение ожидания помогает при разовых административных операциях, но не должно маскировать медленный сайт. Если обычная страница требует минуты, правильнее вынести задачу в фон: очередь, cron, прогресс-бар и отдельная проверка статуса.
Важно не путать разные таймауты. Ожидание HTTP-ответа — это одно, долго выполняющийся JavaScript на странице — другое, а таймауты прокси или веб-сервера — третье. Если Firefox ждёт дольше, но nginx, Apache, PHP-FPM или upstream уже оборвал соединение, настройка браузера не поможет.
Если параметра в about:config нет, его можно создать вручную только тогда, когда вы понимаете, какая версия Firefox и какой именно timeout вам нужен. После завершения работы лучше вернуть настройку к обычному значению, чтобы браузер не висел слишком долго на реально недоступных сайтах.
Итог: увеличивать timeout можно как временный инструмент администратора. Для пользовательского сценария правильнее исправлять серверную часть, потому что долгий белый экран остаётся плохим интерфейсом даже тогда, когда браузер согласен ждать.
`, }; function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"'); } function decodeHtml(value = '') { const named = { amp: '&', apos: "'", gt: '>', hellip: '...', laquo: '«', lt: '<', mdash: '—', nbsp: ' ', quot: '"', raquo: '»' }; return value .replace(/([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16))) .replace(/(\d+);/g, (_, dec) => String.fromCodePoint(Number.parseInt(dec, 10))) .replace(/&([a-z]+);/gi, (_, name) => named[name] ?? `&${name};`); } function stripTags(value = '') { return decodeHtml(value.replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim(); } function normalizeContent(html) { let content = html .replace(/<\/span>/g, '') .replace(/