<?php
namespace Helper;
use CIBlockFormatProperties;
use CSite;
use Bitrix\Main\Application;
use Bitrix\Main\IO;
class ToolsHelper
{
/**
* Упорядочиваем массив по значению ключа.
* По возрастанию.
*/
public static function orderArrayByKey(array $array, string $key): array
{
usort($array, static function ($a, $b) use ($key) {
return $a[$key] <=> $b[$key];
});
return $array ?? [];
}
/**
* Разбивает массив на более мелкие массивы с количеством элементов, указанных в $pieces, вида [3, 3, 5, 4].
*/
public static function splitArray(array $items, array $pieces): array
{
$result = [];
foreach ($pieces as $length) {
$result[] = array_slice($items, 0, $length);
$items = array_slice($items, $length);
}
if (!empty($items)) {
$result[] = $items;
}
return $result;
}
/**
* Возвращает дату в формате "27 февраля 2007"
*/
public static function getDateCreate(string $dateCreate): string
{
return CIBlockFormatProperties::DateFormat('j F Y', MakeTimeStamp($dateCreate, CSite::GetDateFormat()));
}
/**
* Проверяем длину строки. Получаем значение референс, сравниваем по нему.
*/
public static function stringLengthMoreThan(string $string, int $length = 150): bool
{
return strlen($string) > $length;
}
/**
* Проверяем ведет ли ссылка на внешний ресурс.
*/
public static function isExtLink(string $url): bool
{
$parsedUrl = parse_url($url);
// Если есть схема и это 'http' или 'https'.
return isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['http', 'https']);
}
/**
* Проверяем является ли файл локальным. По наличию upload в пути к файлу.
*/
public static function isLocalFile(string $path): bool
{
return strpos($path, 'upload') && file_exists($_SERVER['DOCUMENT_ROOT'] . $path);
}
/**
* Возвращаем расширение файла в строковом представлении.
*/
public static function getFileExtension(int $fileId = 0, string $path = ''): string
{
$filePath = $fileId !== 0 ? \CFile::GetPath($fileId) : $path;
if ($filePath !== '') {
if (self::isExtLink($filePath)) {
return 'ext';
}
$file = new IO\File(Application::getDocumentRoot() . $filePath);
$extension = $file->getExtension();
}
return $extension ?? '';
}
/**
* Валидация E-mail.
*/
public static function validationEmail($email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* Удаление из строки всех ненужных символов.
*/
public static function cleanString(string $string, string $patternKey = '', array $pattern = []): string
{
if (empty($pattern) && empty($patternKey)) {
return '';
}
$patterns = [
'phone' => ['+7', ' ', '(', ')', '-'],
];
$input = !empty($patternKey) && isset($patterns[$patternKey]) ? $patterns[$patternKey] : $pattern;
return str_replace($input, '', $string);
}
/**
* Удаляем из строки теги, пробелы, переносы.
*/
public static function cleanStringTags($input): string
{
// Удаляем все HTML-теги.
$cleaned = strip_tags($input);
// Удаляем и заменяем его на обычный пробел.
$cleaned = str_replace(' ', ' ', $cleaned);
// Удаляем переносы строк и лишние пробелы.
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
return trim($cleaned);
}
/**
* Форматируем номер телефона с удалением лишних символов для вставки в ссылку.
* Добавляем добавочный номер по разделителю "_".
* Из строки (8-38553) 25-3-01_2 делаем:
* 1) 83855325301,2 - formatPhoneForTel()
* 2) (8-38553) 25-3-01 доб.2 - formatPhoneForDisplay()
*/
public static function formatPhoneForTel($phone): string
{
// Убираем пробелы и заменяем символ "_".
$phone = str_replace('_', ',', $phone);
// Удаляем символы, которые не являются цифрами, кроме запятой.
return preg_replace('/[^\d,]/', '', $phone);
}
public static function formatPhoneForDisplay($phone): string
{
return str_replace('_', ' доб.', $phone);
}
/**
* Считаем кол-во полных лет Пользователя. Принимаем строку/объект с датой.
*/
public static function calculateAge(string | object $birthDate): int
{
// Преобразуем дату в формат DateTime.
$dateOfBirth = \DateTime::createFromFormat('d.m.Y', $birthDate);
// Проверяем, была ли дата корректно создана.
if (!$dateOfBirth) {
return 0;
}
// Получаем текущую дату
$currentDate = new \DateTime();
// Вычисляем разницу
return $currentDate->diff($dateOfBirth)->y;
}
/**
* Окончание и форма слова "лет".
*/
public static function getYearsText(int $age): string
{
if ($age < 0) {
return "Некорректный возраст";
}
if ($age % 10 === 1 && $age % 100 !== 11) {
return "$age год";
}
if ($age % 10 >= 2 && $age % 10 <= 4 && ($age % 100 < 10 || $age % 100 >= 20)) {
return "$age года";
}
return "$age лет";
}
/**
* Окончание и форма слова "лет".
*/
public static function getProductsText(int $countProducts): string
{
if ($countProducts < 0) {
return "Некорректное количество";
}
if ($countProducts % 10 === 1 && $countProducts % 100 !== 11) {
return "$countProducts товар";
}
if (
$countProducts % 10 >= 2 && $countProducts % 10 <= 4
&& ($countProducts % 100 < 10
|| $countProducts % 100
>= 20)
) {
return "$countProducts товара";
}
return "$countProducts товаров";
}
/**
* Приводим число к виду двух знаков после точки.
*/
public static function formatNumber($number): string
{
return number_format($number, 2, '.', '');
}
/**
* Для catalog.section.list
* Собираем древовидную структуру из плоского массива.
*
* Ожидаем наличие 'IBLOCK_SECTION_ID' внутри.
*/
public static function buildCategoriesTree(array $sections): array
{
$categories = [];
$sectionsMap = [];
// Сначала создаём временный массив для быстрого доступа к категориям по ID.
foreach ($sections as $section) {
$sectionsMap[$section['ID']] = [
'ID' => $section['ID'],
'NAME' => $section['NAME'],
'SECTION_PAGE_URL' => $section['SECTION_PAGE_URL'],
'ELEMENT_CNT' => $section['ELEMENT_CNT'],
'RELATIVE_DEPTH_LEVEL' => $section['RELATIVE_DEPTH_LEVEL'],
'CHILDREN' => [],
];
}
// Строим дерево.
foreach ($sections as $section) {
// Если есть родитель.
if (
isset($section['IBLOCK_SECTION_ID'])
&& $section['IBLOCK_SECTION_ID'] > 0
&& isset($sectionsMap[$section['IBLOCK_SECTION_ID']])
) {
// Добавляем текущую категорию к дочерним элементам родителя.
$sectionsMap[$section['IBLOCK_SECTION_ID']]['CHILDREN'][] = &$sectionsMap[$section['ID']];
} else {
// Если родителя нет, это корневая категория.
$categories[] = &$sectionsMap[$section['ID']];
}
}
return $categories;
}
/**
* Универсальная функция для группировки свойств по родительским элементам.
* Например, свойства тип "Список", полученные GetList-ом.
*
* Пример вызова:
*
* $fieldsToGroup = [
* 'STEP_LIST_VALUE' => 'STEP_LIST_VALUES',
* 'STEP_EQUIPMENT_VALUE' => 'STEP_EQUIPMENT_VALUES'
* ];
*
* $arResult['ABOUT_SYSTEM'] = ToolsHelper::groupPropertiesByParent($rawData, $fieldsToGroup);
*
* @param array $data Исходный массив данных.
* @param array $groupKeys Ключи полей для группировки (формат: ['FIELD_NAME' => 'GROUPED_ARRAY_NAME']).
* @param string $idField Поле, которое используется как идентификатор родителя (по умолчанию 'ID').
* @param string $nameField Поле, которое используется как имя родителя (по умолчанию 'NAME').
*
* @return array Сгруппированный массив с родительскими элементами и их свойствами.
*/
public static function groupPropertiesByParent(
array $data,
array $groupKeys = [],
string $idField = 'ID',
string $nameField = 'NAME'
): array {
$result = [];
if (empty($groupKeys)) {
return ['error' => '$groupKeys пустой'];
}
foreach ($data as $item) {
$parentId = $item[$idField];
$parentName = $item[$nameField];
// Если такого родителя еще нет в результирующем массиве - создаем его.
if (!isset($result[$parentId])) {
// Копируем все исходные поля элемента.
$result[$parentId] = $item;
// Инициализируем массивы для группировки.
foreach ($groupKeys as $sourceKey => $groupedArrayName) {
$result[$parentId][$groupedArrayName] = [];
}
}
// Заполняем массивы сгруппированных значений.
foreach ($groupKeys as $sourceKey => $groupedArrayName) {
if (isset($item[$sourceKey]) && !empty($item[$sourceKey]) &&
!in_array($item[$sourceKey], $result[$parentId][$groupedArrayName])) {
$result[$parentId][$groupedArrayName][] = $item[$sourceKey];
}
}
// Обновляем остальные поля, если они не пустые в текущем элементе, но пустые в результирующем.
foreach ($item as $key => $value) {
if (!in_array($key, array_keys($groupKeys)) && $key !== $idField && $key !== $nameField) {
if (!empty($value) && (empty($result[$parentId][$key]) || $result[$parentId][$key] === null)) {
$result[$parentId][$key] = $value;
}
}
}
}
// Преобразуем ассоциативный массив в индексированный.
return array_values($result);
}
/**
* Рассчитывает время чтения текста
*
* @param string $text Исходный текст статьи
* @param int $wordsPerMinute Скорость чтения (слов в минуту)
*
* @return string Отформатированная строка с временем чтения
*
* // Пример использования
* $articleContent = '<h1>Заголовок статьи</h1><p>Это пример содержания вашей статьи...</p>';
* $readingTime = calculateReadingTime($articleContent);
*
* Echo "Время чтения: {$readingTime}";
*/
public static function calculateReadingTime(string $text, int $wordsPerMinute = 200): string {
// Удаляем HTML-теги и лишние пробелы
$cleanText = trim(strip_tags($text));
// Если текст пустой
if (empty($cleanText)) {
return "меньше минуты";
}
// Подсчет слов (учитываем кириллицу, латиницу и цифры)
$words = preg_split('/\s+/u', $cleanText, -1, PREG_SPLIT_NO_EMPTY);
$wordCount = count($words);
// Расчет минут
$minutes = (int) max(1, ceil($wordCount / $wordsPerMinute));
// Склонение слова "минута"
/*$lastDigit = $minutes % 10;
$lastTwoDigits = $minutes % 100;
if ($lastTwoDigits >= 11 && $lastTwoDigits <= 19) {
$form = 'минут';
} elseif ($lastDigit === 1) {
$form = 'минута';
} elseif ($lastDigit >= 2 && $lastDigit <= 4) {
$form = 'минуты';
} else {
$form = 'минут';
}*/
// Или
$form = 'мин';
return "{$minutes} {$form}";
}
/**
* Обрезаем строку по заданному разделителю.
*/
public static function cutStringByDelimiter(string $str, string $delimiter): string
{
$pos = strpos($str, $delimiter);
if ($pos === false) {
// Разделитель не найден - возвращаем исходную строку
return $str;
}
// Обрезаем до разделителя (исключая сам разделитель)
return substr($str, 0, $pos);
}
}