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

Список пользователей с городами в Bitrix: фильтр, пагинация, Excel | Готовый код

Список пользователей с городами в Bitrix: фильтр, пагинация, Excel | Готовый код

Список пользователей с городами в Bitrix

⚡ Bitrix10 мин на чтение

Введение

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

Это типичная задача для административных отчётов: менеджеру нужно понимать, из каких городов приходят клиенты, сколько их, и выгрузить список для маркетинговой рассылки. Город берётся из свойства LOCATION последнего заказа пользователя — то есть используется реальный адрес доставки, а не просто профиль.

Код размещается в публичной части сайта — например, в файле /users.php в корне. Он работает как отдельная страница с подключением заголовка и футера Битрикса.

Быстрый старт

КомпонентЗначение
Файл/users.php в корне сайта
Модульsale — для работы с заказами и местоположениями
КлассыCSaleOrder, CSaleOrderPropsValue, CSaleLocation, UserTable
Фильтр?CITY=Москва — поиск по названию города
Пагинация50 записей на страницу, PageNavigation для фильтра, NAV_PARAMS для полного списка
Экспорт?EXPORT=Y — выгрузка в XLS без пагинации

Постановка задачи

Необходимо вывести страницу со следующими возможностями:

  1. Показать всех активных пользователей с их именем, email, телефоном и городом.
  2. Город определять из свойства LOCATION последнего заказа пользователя.
  3. Реализовать фильтрацию по названию города (частичное совпадение).
  4. Добавить постраничную навигацию (50 записей на страницу).
  5. Реализовать экспорт отфильтрованных или всех данных в Excel.

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

Структура страницы

Страница состоит из трёх логических блоков: PHP-логика (подготовка данных), HTML-шаблон (форма и таблица) и CSS-стили. Весь код находится в одном файле, что удобно для быстрого развёртывания.

1. Подключение заголовка и инициализация

В начале страницы подключаем пролог Битрикса, задаём заголовок и подключаем модуль sale — он необходим для работы с заказами и местоположениями.

<?php ob_start(); require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php"); $APPLICATION->SetTitle("Пользователи и города"); CModule::IncludeModule('sale');

ob_start() включает буферизацию вывода — это нужно, чтобы иметь возможность чистить буфер при экспорте в Excel (иначе заголовки HTML-страницы попадут в файл).

2. Получение параметров фильтра и пагинации

Извлекаем GET-параметры: фильтр по городу и номер страницы для постраничной навигации.

$cityFilter = trim($_GET["CITY"]); $page = isset($_GET['PAGEN_1']) ? intval($_GET['PAGEN_1']) : 1; if ($page < 1) $page = 1; $perPage = 50; $_REQUEST['PAGEN_1'] = $page;

$_REQUEST['PAGEN_1'] = $page — это важно для компонента постраничной навигации, который использует эту переменную для определения текущей страницы.

Основная логика: получение данных

Дальше код разветвляется в зависимости от того, задан ли фильтр по городу.

Режим A: фильтр по городу задан

Если передан CITY, нужно найти всех пользователей, у которых хотя бы в одном заказе город совпадает с запросом. Алгоритм:

  1. Перебор всех заказов — получаем ID и USER_ID каждого заказа.
  2. Чтение свойства LOCATION — для каждого заказа проверяем свойство с кодом LOCATION и получаем ID местоположения.
  3. Кеширование городов — используем массив $locCityCache, чтобы не делать повторный запрос к CSaleLocation::GetByID для одного и того же ID.
  4. Совпадение по названию — сравниваем название города с $cityFilter через stripos() (регистронезависимое вхождение).
  5. Сбор ID пользователей — сохраняем ID подходящих пользователей в $filterUserIds.
Производительность: Перебор всех заказов — не самый быстрый способ при большом количестве записей. На реальном проекте с тысячами заказов стоит добавить индекс по свойству LOCATION или использовать агент для предрасчёта города пользователя в отдельное поле.

После сбора ID загружаем данных пользователей порциями, с постраничной навигацией:

$userIds = array_values($filterUserIds); $totalCount = count($userIds); $offset = ($page - 1) * $perPage; $pageIds = array_slice($userIds, $offset, $perPage); $userDb = \Bitrix\Main\UserTable::getList(array( "filter" => array("=ID" => $pageIds, "=ACTIVE" => "Y"), "select" => array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE"), "order" => array("ID" => "ASC"), ));

Для пагинации в этом режиме используется Bitrix\Main\UI\PageNavigation — современный класс, который принимает общее количество записей и самостоятельно рассчитывает страницы.

Режим B: без фильтра — полный список

Если фильтр не задан, выгружаем всех активных пользователей с пагинацией через старый добрый CUser::GetList:

$userDb = CUser::GetList( array("ID" => "ASC"), "", array("ACTIVE" => "Y"), array("NAV_PARAMS" => array("nPageSize" => $perPage, "iNumPage" => $page)), array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE") );

Подгрузка городов для пользователей

Когда фильтр не задан, города догружаются отдельно — одним запросом собираем все заказы для найденных пользователей, для каждого заказа проверяем свойство LOCATION и извлекаем название города. При этом берется первый (последний по ID) заказ пользователя, в котором заполнен адрес:

$orderDb = CSaleOrder::GetList( array("ID" => "DESC"), array("USER_ID" => array_keys($users)), false, false, array("ID", "USER_ID") ); $userOrders = array(); while ($order = $orderDb->Fetch()) { $userOrders[$order["USER_ID"]][] = $order["ID"]; } foreach ($userOrders as $userId => $orderIds) { foreach ($orderIds as $oid) { $propsDb = CSaleOrderPropsValue::GetOrderProps($oid); while ($prop = $propsDb->Fetch()) { if ($prop["CODE"] == "LOCATION") { $loc = CSaleLocation::GetByID(intval($prop["VALUE"])); $users[$userId]["CITY"] = $loc["CITY_NAME"] ?: $prop["VALUE"]; break 2; } } } }

Обратите внимание: break 2 прерывает сразу два цикла — по заказам пользователя и по свойствам заказа. Как только нашли город — остальные заказы этого пользователя не проверяем.

Экспорт в Excel

При передаче параметра ?EXPORT=Y страница формирует XLS-файл — по сути, HTML-таблицу с заголовками Content-Type: application/vnd.ms-excel. Excel открывает такие файлы без ошибок.

Ключевые моменты:

  • Очистка буфера:while (ob_get_level()) ob_end_clean() удаляет весь накопленный HTML-вывод, чтобы в файл попали только данные таблицы.
  • Заголовки HTTP:Content-Type: application/vnd.ms-excel; charset=utf-8 и Content-Disposition: attachment; filename=users.xls заставляют браузер скачать файл.
  • Кодировка:<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> — Excel корректно отобразит кириллицу.
  • Полная выгрузка: При экспорте игнорируется пагинация — выгружаются все пользователи, подходящие под фильтр.
Важно: При экспорте с фильтром (?CITY=...&EXPORT=Y) выгружаются только пользователи из найденного города. Без фильтра — все активные пользователи. После отправки файла вызывается die(), чтобы Битрикс не подмешал в вывод футер или другую обёртку.

Шаблон: форма, таблица, пагинация

После PHP-логики идёт HTML-разметка со вставками PHP для вывода данных.

Форма фильтра

Простая форма с текстовым полем для названия города и кнопкой «Найти». Если фильтр активен, показывается ссылка «Сбросить» и ссылка на скачивание Excel.

Таблица пользователей

Выводится пять колонок: ID, Имя, Email, Телефон, Город. Пустые значения заменяются на (тире). Если пользователи не найдены — сообщение об этом. Все значения экранируются через htmlspecialcharsbx() для защиты от XSS.

Постраничная навигация

Используется компонент bitrix:system.pagenavigation:

  • При фильтре — передаётся объект $nav (PageNavigation).
  • Без фильтра — передаётся результат $userDb (CUser::GetList), который сам хранит информацию о пагинации.
<div class="pagination"> <? if (isset($nav)): ?> <? $APPLICATION->IncludeComponent("bitrix:system.pagenavigation", ".default", array( "NAV_OBJECT" => $nav, "SHOW_ALWAYS" => "Y", ), false, array("HIDE_ICONS" => "Y")); ?> <? elseif (isset($userDb)): ?> <? $APPLICATION->IncludeComponent("bitrix:system.pagenavigation", ".default", array( "NAV_RESULT" => $userDb, "SHOW_ALWAYS" => "Y", ), false, array("HIDE_ICONS" => "Y")); ?> <? endif; ?> </div>

Полный код для копирования

Готовый файл users.php, который можно разместить в корне сайта:

<? ob_start(); require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php"); $APPLICATION->SetTitle("Пользователи и города"); CModule::IncludeModule('sale'); $cityFilter = trim($_GET["CITY"]); $page = isset($_GET['PAGEN_1']) ? intval($_GET['PAGEN_1']) : 1; if ($page < 1) $page = 1; $perPage = 50; $_REQUEST['PAGEN_1'] = $page; $filterUserIds = array(); $filterCityNames = array(); if ($cityFilter) { $locCityCache = array(); $orderDb = CSaleOrder::GetList( array("ID" => "DESC"), array(), false, false, array("ID", "USER_ID") ); while ($order = $orderDb->Fetch()) { $propsDb = CSaleOrderPropsValue::GetOrderProps($order["ID"]); while ($prop = $propsDb->Fetch()) { if ($prop["CODE"] == "LOCATION") { $lid = $prop["VALUE"]; if (!isset($locCityCache[$lid])) { $loc = CSaleLocation::GetByID(intval($lid)); $locCityCache[$lid] = $loc ? $loc["CITY_NAME"] : ""; } if ($locCityCache[$lid] !== "" && stripos($locCityCache[$lid], $cityFilter) !== false) { $filterUserIds[$order["USER_ID"]] = $order["USER_ID"]; $filterCityNames[$order["USER_ID"]] = $locCityCache[$lid]; } break; } } } $users = array(); if (!empty($filterUserIds)) { $userIds = array_values($filterUserIds); $totalCount = count($userIds); $offset = ($page - 1) * $perPage; $pageIds = array_slice($userIds, $offset, $perPage); $userDb = \Bitrix\Main\UserTable::getList(array( "filter" => array("=ID" => $pageIds, "=ACTIVE" => "Y"), "select" => array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE"), "order" => array("ID" => "ASC"), )); while ($user = $userDb->fetch()) { $users[$user["ID"]] = array( "ID" => $user["ID"], "EMAIL" => $user["EMAIL"], "NAME" => trim($user["NAME"]." ".$user["LAST_NAME"]." ".$user["SECOND_NAME"]), "PHONE" => $user["PERSONAL_PHONE"], "CITY" => isset($filterCityNames[$user["ID"]]) ? $filterCityNames[$user["ID"]] : "" ); } $nav = new \Bitrix\Main\UI\PageNavigation("PAGEN_1"); $nav->setPageSize($perPage)->setCurrentPage($page)->setRecordCount($totalCount); } } else { $userDb = CUser::GetList( array("ID" => "ASC"), "", array("ACTIVE" => "Y"), array("NAV_PARAMS" => array("nPageSize" => $perPage, "iNumPage" => $page)), array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE") ); $users = array(); while ($user = $userDb->Fetch()) { $users[$user["ID"]] = array( "ID" => $user["ID"], "EMAIL" => $user["EMAIL"], "NAME" => trim($user["NAME"]." ".$user["LAST_NAME"]." ".$user["SECOND_NAME"]), "PHONE" => $user["PERSONAL_PHONE"], "CITY" => "" ); } if (!empty($users)) { $orderDb = CSaleOrder::GetList( array("ID" => "DESC"), array("USER_ID" => array_keys($users)), false, false, array("ID", "USER_ID") ); $userOrders = array(); while ($order = $orderDb->Fetch()) { $userOrders[$order["USER_ID"]][] = $order["ID"]; } foreach ($userOrders as $userId => $orderIds) { foreach ($orderIds as $oid) { $propsDb = CSaleOrderPropsValue::GetOrderProps($oid); while ($prop = $propsDb->Fetch()) { if ($prop["CODE"] == "LOCATION") { $loc = CSaleLocation::GetByID(intval($prop["VALUE"])); $users[$userId]["CITY"] = $loc["CITY_NAME"] ?: $prop["VALUE"]; break 2; } } } } } } // Экспорт в Excel if (isset($_GET["EXPORT"]) && $_GET["EXPORT"] == "Y") { while (ob_get_level()) ob_end_clean(); header("Content-Type: application/vnd.ms-excel; charset=utf-8"); header("Content-Disposition: attachment; filename=users.xls"); echo '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>'; echo '<table><tr><th>ID</th><th>Имя</th><th>Email</th><th>Телефон</th><th>Город</th></tr>'; $exportUsers = array(); if ($cityFilter) { if (!empty($filterUserIds)) { $userIds = array_values($filterUserIds); $userDb = \Bitrix\Main\UserTable::getList(array( "filter" => array("=ID" => $userIds, "=ACTIVE" => "Y"), "select" => array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE"), "order" => array("ID" => "ASC"), )); while ($user = $userDb->fetch()) { $exportUsers[$user["ID"]] = array( "ID" => $user["ID"], "EMAIL" => $user["EMAIL"], "NAME" => trim($user["NAME"]." ".$user["LAST_NAME"]." ".$user["SECOND_NAME"]), "PHONE" => $user["PERSONAL_PHONE"], "CITY" => isset($filterCityNames[$user["ID"]]) ? $filterCityNames[$user["ID"]] : "" ); } } } else { $userDb = CUser::GetList( array("ID" => "ASC"), "", array("ACTIVE" => "Y"), false, array("ID", "EMAIL", "NAME", "LAST_NAME", "SECOND_NAME", "PERSONAL_PHONE") ); while ($user = $userDb->Fetch()) { $exportUsers[$user["ID"]] = array( "ID" => $user["ID"], "EMAIL" => $user["EMAIL"], "NAME" => trim($user["NAME"]." ".$user["LAST_NAME"]." ".$user["SECOND_NAME"]), "PHONE" => $user["PERSONAL_PHONE"], "CITY" => "" ); } if (!empty($exportUsers)) { $orderDb = CSaleOrder::GetList( array("ID" => "DESC"), array("USER_ID" => array_keys($exportUsers)), false, false, array("ID", "USER_ID") ); $userOrders = array(); while ($order = $orderDb->Fetch()) { $userOrders[$order["USER_ID"]][] = $order["ID"]; } foreach ($userOrders as $userId => $orderIds) { foreach ($orderIds as $oid) { $propsDb = CSaleOrderPropsValue::GetOrderProps($oid); while ($prop = $propsDb->Fetch()) { if ($prop["CODE"] == "LOCATION") { $loc = CSaleLocation::GetByID(intval($prop["VALUE"])); $exportUsers[$userId]["CITY"] = $loc["CITY_NAME"] ?: $prop["VALUE"]; break 2; } } } } } } foreach ($exportUsers as $u): ?><tr><td><?= $u["ID"] ?></td><td><?= htmlspecialcharsbx($u["NAME"]) ?></td><td><?= htmlspecialcharsbx($u["EMAIL"]) ?></td><td><?= htmlspecialcharsbx($u["PHONE"]) ?></td><td><?= htmlspecialcharsbx($u["CITY"]) ?></td></tr> <? endforeach; echo '</table></body></html>'; die(); } ?> <style> table.users-table { width: 100%; border-collapse: collapse; } table.users-table th, table.users-table td { padding: 10px; text-align: left; border: 1px solid #ddd; } table.users-table th { background: #f5f5f5; font-weight: bold; } table.users-table tr:nth-child(even) { background: #fafafa; } .filter-form { margin: 20px 0; padding: 15px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px; } .filter-form input[type="text"] { padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; width: 250px; } .filter-form input[type="submit"] { padding: 8px 20px; background: #0073aa; color: #fff; border: none; border-radius: 4px; cursor: pointer; } .filter-form input[type="submit"]:hover { background: #005a87; } </style> <h1>Список пользователей с городами</h1> <form class="filter-form" method="get"> <label>Фильтр по городу: </label> <input type="text" name="CITY" value="<?= htmlspecialcharsbx($cityFilter) ?>" placeholder="Введите название города"> <input type="submit" value="Найти"> <? if ($cityFilter): ?> <a href="?" style="margin-left: 10px;">Сбросить</a> <? endif; ?> <a href="<?= $cityFilter ? "?CITY=".urlencode($cityFilter)."&EXPORT=Y" : "?EXPORT=Y" ?>" style="margin-left: 10px;">Скачать Excel</a> </form> <table class="users-table"> <tr> <th>ID</th> <th>Имя</th> <th>Email</th> <th>Телефон</th> <th>Город</th> </tr> <? foreach ($users as $user): ?> <tr> <td><?= $user["ID"] ?></td> <td><?= htmlspecialcharsbx($user["NAME"]) ?></td> <td><?= htmlspecialcharsbx($user["EMAIL"]) ?></td> <td><?= htmlspecialcharsbx($user["PHONE"]) ?: "—" ?></td> <td><?= htmlspecialcharsbx($user["CITY"]) ?: "—" ?></td> </tr> <? endforeach; ?> <? if (empty($users)): ?> <tr><td colspan="5" style="text-align:center; color:#999;">Пользователи не найдены</td></tr> <? endif; ?> </table> <div class="pagination"> <? if (isset($nav)): ?> <? $APPLICATION->IncludeComponent("bitrix:system.pagenavigation", ".default", array( "NAV_OBJECT" => $nav, "SHOW_ALWAYS" => "Y", ), false, array("HIDE_ICONS" => "Y")); ?> <? elseif (isset($userDb)): ?> <? $APPLICATION->IncludeComponent("bitrix:system.pagenavigation", ".default", array( "NAV_RESULT" => $userDb, "SHOW_ALWAYS" => "Y", ), false, array("HIDE_ICONS" => "Y")); ?> <? endif; ?> </div> <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

Файл готов к размещению. Откройте в браузере http://вашсайт/users.php — должна отобразиться таблица с пользователями.


Заключение

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

Код использует как классы старого ядра (CSaleOrder, CUser), так и современное D7 API (UserTable, PageNavigation) — это наглядный пример того, как в Битриксе можно сочетать оба подхода в одном файле.


Часто задаваемые вопросы

Как вывести всех пользователей с городами в Bitrix?

Создайте файл в корне сайта, подключите заголовок Битрикса, получите список активных пользователей через CUser::GetList или UserTable, затем для каждого пользователя найдите его заказы и из свойства LOCATION извлеките город через CSaleLocation::GetByID.

Как отфильтровать пользователей по городу в Битрикс?

Переберите все заказы, для каждого получите свойство LOCATION и сравните название города с фильтром через stripos(). Соберите ID подходящих пользователей в массив и загрузите их данные. Готовый код — в разделе «Полный код для копирования».

Как сделать экспорт в Excel из Bitrix?

Установите заголовки Content-Type: application/vnd.ms-excel и Content-Disposition: attachment; filename=...xls, очистите буфер вывода и выведите HTML-таблицу. Excel корректно открывает такие файлы. Не забудьте указать charset=utf-8 в meta-теге.

Какой класс отвечает за местоположения в Bitrix?

CSaleLocation::GetByID($id) — получает массив с данными местоположения по его ID, включая CITY_NAME. ID местоположения хранится в свойстве заказа с кодом LOCATION.

Как работает PageNavigation в Bitrix D7?

Создайте объект new \Bitrix\Main\UI\PageNavigation("PAGEN_1"), передайте ему размер страницы, текущую страницу и общее количество записей через методы setPageSize(), setCurrentPage(), setRecordCount(). Затем передайте его в компонент bitrix:system.pagenavigation в параметре NAV_OBJECT.


Ключевые моменты

  • Модуль Перед работой с заказами и местоположениями обязательно подключите модуль sale через CModule::IncludeModule('sale')
  • ГородCSaleLocation::GetByID() принимает ID местоположения и возвращает массив с ключом CITY_NAME
  • Кеш Кешируйте результат GetByID в памяти — это ускоряет перебор тысяч заказов
  • Экспорт Перед выгрузкой Excel очищайте буфер (ob_end_clean) и завершайте скрипт через die()
  • Пагинация Для PageNavigation используйте NAV_OBJECT, для CUser::GetListNAV_RESULT
Назад к списку