Сервисы
Полезное
Портфолио
Блог
С помощью данного кода вы можете с лёгкостью подтягивать данные из MySQL и PostgreSQL базы на сайты созданные в Creatium.
Авторы творения
Создатель Марк Гаджимурадов: https://t.me/m_ivor
Доработал Вечкасов Кирилл: https://t.me/pomogay_marketing
Перетащите блок "Таблица" на страницу из панели компонентов
В настройках блока выберите "Подключение к таблице" и укажите:
Интерфейс фильтров в Creatium:
id=
, name~
, price>
price>
, name~
)price>
)Операторы добавляются к названию поля в параметре. Если оператор не указан, используется =
(равно).
Оператор | Как писать | Описание | Пример параметра | Пример значения |
---|---|---|---|---|
= | id= или просто id | Точное совпадение | status |
активный |
!= | поле!= | Не равно | status!= |
удалено |
> | поле> | Больше | price> |
1000 |
< | поле< | Меньше | age< |
18 |
>= | поле>= | Больше или равно | score>= |
80 |
<= | поле<= | Меньше или равно | discount<= |
50 |
~ | поле~ | Содержит текст | description~ |
качественный |
!~ | поле!~ | Не содержит текст | title!~ |
тест |
^ | поле^ | Начинается с | name^ |
Алекс |
!^ | поле!^ | Не начинается с | email!^ |
admin |
& | поле& | Заканчивается на | file& |
.pdf |
!& | поле!& | Не заканчивается на | file!& |
.tmp |
Как использовать:
status@
активный,в_работе,завершен
Результат: Покажет записи, где статус равен любому из указанных значений
Как использовать:
user_id$
1
(или да
, true
)Результат: Покажет только уникальные записи по полю user_id
По умолчанию все фильтры работают с логикой "И" (AND) - должны выполняться ВСЕ условия.
Для логики "ИЛИ" добавьте специальный фильтр:
FILTER:OR
1
Блок сортировки:
Настройки пагинации:
Пример для второй страницы:
status
→ Значение: активный
price>
→ Значение: 5000
type@
→ Значение: межкомнатные,входные,балконные
status!=
→ Значение: удалено
description~
→ Значение: качественный
description!~
→ Значение: тест
price<
→ Значение: 1000
FILTER:OR
→ Значение: 1
discount>
→ Значение: 0
Результат: Товары с ценой меньше 1000 ИЛИ с любой скидкой
Теперь вы знаете, как эффективно работать с таблицами в Creatium. Используйте операторы в названиях параметров для создания мощных фильтров!
Базовые: id= id!= price> age< score>= discount<=
Текстовые: name~ title!~ code^ email!^ file& url!&
Специальные: status@ (выбор) user_id$ (уникальные) FILTER:OR (логика)
10 июня 2025
Все пустые записи возвращают состояние null, так как из-за пустых ячеек где отсутствовал текст и статус ячейки был не NULL ломались формы загрузки картинок.
10 июля 2025
Для выделения только уникальных строк оператором $ требуется вместо value прописывать true, чтобы активировался тег вывода только уникальных строку.
30 июля 2025
Технические доработки. Повышена стабильность.
20 сентября 2025
Скорректирован код, убраны опечатки, обновлён мануал работы с коннектором.
<?php /* ===================== Конфигурация ===================== */ // Тип подключения к базе данных: 'mysql' или 'pgsql' $dbType = 'pgsql'; // изменить на 'mysql' для подключения к MySQL // Секретный HASH код для доступа к данным $secretHash = 'asdkjaskjda8sd78asd9'; // ===================== Настройки S3 ===================== $s3Config = [ 'endpoint' => 'https://****', 'bucket' => '****', 'access_key' => '****', 'secret_key' => '****', 'region' => 'ru-1' // или другой регион ]; // ===================== Настройки кеширования ===================== $enableCache = false; // включен для демонстрации S3 $cacheStorage = 's3'; // 'local' или 's3' $noCacheTables = ''; $cacheHours = 2; // кешируем на 2 часа $enableAlwaysCache = false; $alwaysCacheTables = '****'; /* ====================================================== */ // Проверка наличия и корректности секретного HASH if (!isset($_GET['hash']) || $_GET['hash'] !== $secretHash) { echo '<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Ошибка доступа</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f9f9f9; } .message { font-size: 2em; text-align: center; color: #333; } </style> </head> <body> <div class="message">Для загрузки данных введите секретный ключ</div> </body> </html>'; exit; } // Параметры подключения к базе данных $host = 'host'; $username = 'username'; $password = 'password'; $dbname = ($dbType === 'pgsql') ? 'public' : 'mysql'; $port = ($dbType === 'pgsql') ? '5432' : '3306'; /** * Класс для работы с S3 кешем */ class S3Cache { private $config; private $cachePrefix = 'cache/'; private $dbname; public function __construct($config) { $this->config = $config; $this->dbname = $config['dbname'] ?? 'default'; } /** * Генерация подписи для AWS S3 */ private function generateSignature($method, $contentMd5, $contentType, $date, $resource) { $stringToSign = "$method\n$contentMd5\n$contentType\n$date\n$resource"; return base64_encode(hash_hmac('sha1', $stringToSign, $this->config['secret_key'], true)); } /** * Выполнение HTTP запроса к S3 */ private function makeRequest($method, $key, $data = null, $contentType = 'application/json') { $url = $this->config['endpoint'] . '/' . $this->config['bucket'] . '/' . $key; $date = gmdate('r'); $contentMd5 = $data ? base64_encode(md5($data, true)) : ''; $resource = '/' . $this->config['bucket'] . '/' . $key; $signature = $this->generateSignature($method, $contentMd5, $contentType, $date, $resource); $authorization = 'AWS ' . $this->config['access_key'] . ':' . $signature; $headers = [ 'Date: ' . $date, 'Authorization: ' . $authorization, ]; if ($data) { $headers[] = 'Content-Type: ' . $contentType; $headers[] = 'Content-MD5: ' . $contentMd5; } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HEADER => true, // для получения заголовков ]); if ($data) { curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new Exception("cURL Error: $error"); } $headers = substr($response, 0, $headerSize); $body = substr($response, $headerSize); return ['code' => $httpCode, 'data' => $body, 'headers' => $headers]; } /** * Сохранение данных в S3 */ public function put($key, $data) { try { $fullKey = $this->cachePrefix . $this->dbname . '/' . $key; $result = $this->makeRequest('PUT', $fullKey, $data); return $result['code'] === 200; } catch (Exception $e) { error_log("S3 PUT Error: " . $e->getMessage()); return false; } } /** * Получение данных из S3 */ public function get($key) { try { $fullKey = $this->cachePrefix . $this->dbname . '/' . $key; $result = $this->makeRequest('GET', $fullKey); if ($result['code'] === 200) { return $result['data']; } return false; } catch (Exception $e) { error_log("S3 GET Error: " . $e->getMessage()); return false; } } /** * Проверка существования файла в S3 */ public function exists($key) { try { $fullKey = $this->cachePrefix . $this->dbname . '/' . $key; $result = $this->makeRequest('HEAD', $fullKey); return $result['code'] === 200; } catch (Exception $e) { return false; } } /** * Получение времени последнего изменения файла */ public function getLastModified($key) { try { $fullKey = $this->cachePrefix . $this->dbname . '/' . $key; $result = $this->makeRequest('HEAD', $fullKey); if ($result['code'] === 200) { // Парсим заголовки для получения Last-Modified $headers = $result['headers']; if (preg_match('/Last-Modified:\s*(.+)/i', $headers, $matches)) { return strtotime(trim($matches[1])); } } return false; } catch (Exception $e) { return false; } } } /** * Локальный кеш для сравнения */ class LocalCache { private $cacheDir; public function __construct($config) { $this->cacheDir = $config['cache_dir'] ?? (__DIR__ . '/cache_treba_online_ru'); if (!file_exists($this->cacheDir)) { mkdir($this->cacheDir, 0777, true); } } public function put($key, $data) { $file = $this->cacheDir . '/' . $key; return file_put_contents($file, $data) !== false; } public function get($key) { $file = $this->cacheDir . '/' . $key; return file_exists($file) ? file_get_contents($file) : false; } public function exists($key) { $file = $this->cacheDir . '/' . $key; return file_exists($file); } public function getLastModified($key) { $file = $this->cacheDir . '/' . $key; return file_exists($file) ? filemtime($file) : false; } } /** * Универсальный класс для работы с кешем */ class CacheManager { private $storage; private $cacheHours; public function __construct($storageType, $config, $cacheHours = 0) { $this->cacheHours = $cacheHours; if ($storageType === 's3') { $this->storage = new S3Cache($config); } else { $this->storage = new LocalCache($config); } } public function get($key) { $data = $this->storage->get($key); if ($data !== false && $this->cacheHours > 0) { $lastModified = $this->storage->getLastModified($key); if ($lastModified && (time() - $lastModified) > ($this->cacheHours * 3600)) { return false; // кеш устарел } } return $data; } public function put($key, $data) { return $this->storage->put($key, $data); } public function exists($key) { return $this->storage->exists($key); } } // Получаем имя таблицы из GET-параметра 'table' $table = $_GET['table'] ?? null; if (!$table) { http_response_code(400); echo json_encode(['error' => 'Table name is required.']); exit; } // Подготовка списков кеширования $noCacheTableList = array_map('trim', explode(',', $noCacheTables)); $alwaysCacheTableList = array_map('trim', explode(',', $alwaysCacheTables)); $shouldUseCache = ($enableCache && !in_array($table, $noCacheTableList)) || ($enableAlwaysCache && in_array($table, $alwaysCacheTableList)); /** * Определение типа столбца */ function getColumnType($value) { if (is_bool($value)) return 'string'; if (is_int($value) || is_float($value)) return 'number'; return 'string'; } /** * Получение комментариев столбцов из базы данных */ function getColumnComments($pdo, $table, $dbType) { $comments = []; try { if ($dbType === 'pgsql') { // Для PostgreSQL $parts = explode('.', $table); if (count($parts) === 2) { $schema = $parts[0]; $tableName = $parts[1]; } else { $schema = 'public'; $tableName = $table; } $sql = " SELECT c.column_name, COALESCE(col_description(pgc.oid, c.ordinal_position), '') as comment FROM information_schema.columns c LEFT JOIN pg_class pgc ON pgc.relname = c.table_name LEFT JOIN pg_namespace pgn ON pgn.oid = pgc.relnamespace WHERE c.table_schema = :schema AND c.table_name = :table_name AND (pgn.nspname = :schema2 OR pgn.nspname IS NULL) "; $stmt = $pdo->prepare($sql); $stmt->execute([ 'schema' => $schema, 'table_name' => $tableName, 'schema2' => $schema ]); } else { // Для MySQL $parts = explode('.', $table); if (count($parts) === 2) { $database = $parts[0]; $tableName = $parts[1]; } else { // Получаем текущую базу данных $dbResult = $pdo->query("SELECT DATABASE()")->fetchColumn(); $database = $dbResult ?: 'mysql'; $tableName = $table; } $sql = " SELECT COLUMN_NAME as column_name, COALESCE(COLUMN_COMMENT, '') as comment FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = :database AND TABLE_NAME = :table_name "; $stmt = $pdo->prepare($sql); $stmt->execute([ 'database' => $database, 'table_name' => $tableName ]); } while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $comments[$row['column_name']] = $row['comment'] ?: ''; } } catch (Exception $e) { // В случае ошибки возвращаем пустой массив и логируем error_log("Error in getColumnComments: " . $e->getMessage()); return []; } return $comments; } // Читаем JSON из тела запроса $input = json_decode(file_get_contents('php://input'), true) ?: []; // Сразу же логируем его в отдельный файл $logFile = __DIR__ . '/input_json.log'; $logEntry = date('c') . " | INPUT JSON: " . json_encode($input, JSON_UNESCAPED_UNICODE) . "\n"; file_put_contents($logFile, $logEntry, FILE_APPEND); // Блок кеширования if ($shouldUseCache) { if ($cacheStorage === 's3') { // Добавляем dbname в конфигурацию S3 $cacheConfig = array_merge($s3Config, ['dbname' => $dbname]); } else { $cacheConfig = ['cache_dir' => __DIR__ . '/cache_treba_online_ru']; } $cache = new CacheManager($cacheStorage, $cacheConfig, $cacheHours); $cacheKey = md5(json_encode($_GET) . json_encode($input)) . '.json'; // Проверяем кеш $cachedData = $cache->get($cacheKey); if ($cachedData !== false) { header('Content-Type: application/json'); echo $cachedData; exit; } } try { // Подключение к БД if ($dbType === 'pgsql') { $dsn = "pgsql:host={$host};port={$port};dbname={$dbname}"; } else { $dsn = "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8"; } $pdo = new PDO($dsn, $username, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); // Получаем комментарии столбцов (с обработкой ошибок) $colComments = []; try { $colComments = getColumnComments($pdo, $table, $dbType); } catch (Exception $e) { // Логируем ошибку, но продолжаем работу без комментариев error_log("Error getting column comments: " . $e->getMessage()); } $params = []; $distinctColumns = []; $groups = []; $currentGroup = ['joiner' => 'AND', 'conditions' => [], 'grouped' => []]; $globalCounter = 0; if (!empty($input['properties']) && is_array($input['properties'])) { foreach ($input['properties'] as $property => $value) { // GROUP directives FILTER:AND/FILTER:OR if (stripos($property, 'FILTER:') === 0) { $directive = strtoupper(substr($property, 7)); if (in_array($directive, ['AND', 'OR'])) { // flush grouped conditions foreach ($currentGroup['grouped'] as $col => $ops) { foreach ($ops as $op => $vals) { if (count($vals) > 1) { $pls = []; foreach ($vals as $v) { $globalCounter++; $ph = "{$col}_" . str_replace(['=', '!'], '', $op) . "_{$globalCounter}"; $pls[] = ":{$ph}"; $params[$ph] = $v; } $inOp = $op === '=' ? 'IN' : 'NOT IN'; $currentGroup['conditions'][] = "{$col} {$inOp} (" . implode(', ', $pls) . ")"; } else { $globalCounter++; $ph = "{$col}_" . str_replace(['=', '!'], '', $op) . "_{$globalCounter}"; $currentGroup['conditions'][] = "{$col} {$op} :{$ph}"; $params[$ph] = $vals[0]; } } } if (!empty($currentGroup['conditions'])) $groups[] = $currentGroup; $currentGroup = ['joiner' => $directive, 'conditions' => [], 'grouped' => []]; continue; } } // parse operator and column $cleanKey = preg_replace('/\d+$/', '', $property); // Список всех операторов, добавили '@' $ops = ['>=','<=','!=','!~','!^','!&','!%','>','<','=','~','^','&','#','%','@',']; $operator = '='; $column = $cleanKey; foreach ($ops as $op) { if (substr($cleanKey, -strlen($op)) === $op) { $operator = $op; $column = substr($cleanKey, 0, -strlen($op)); break; } } // Новый оператор '@': разбивает строку "1,2,3" на три отдельных условия AND if ($operator === '@') { // если пришло не array, а строка — разбиваем по запятым $values = is_array($value) ? $value : explode(',', $value); $placeholders = []; foreach ($values as $v) { $v = trim($v); $globalCounter++; $ph = "{$column}_at_{$globalCounter}"; $placeholders[] = ":{$ph}"; $params[$ph] = $v; } // добавляем одно условие IN (...) $currentGroup['conditions'][] = "{$column} IN (" . implode(', ', $placeholders) . ")"; // и пропускаем дальнейшую логику для этого оператора continue; } // special: != empty => exclude empty & NULL if ($operator === '!=') { $globalCounter++; $ph = "{$column}_ne_{$globalCounter}"; // Специальная логика для сравнения с true/false, если поле текстовое if (is_string($value) && strtolower($value) === 'true') { // Добавляем условие: не равно 'true' ИЛИ значение NULL $currentGroup['conditions'][] = "({$column} IS NULL OR {$column} != 'true')"; } elseif (is_string($value) && strtolower($value) === 'false') { $currentGroup['conditions'][] = "({$column} IS NULL OR {$column} != 'false')"; } else { // Обычное сравнение $currentGroup['conditions'][] = "{$column} != :{$ph}"; $params[$ph] = $value; } continue; } // skip empty only for '=' $isEmptyScalar = !is_array($value) && ($value === '' || $value === null); $isEmptyArray = is_array($value) && empty($value); if ($operator === '=' && ($isEmptyScalar || $isEmptyArray)) { continue; } // DISTINCT - исправлено if ($operator === ') { // Добавляем в DISTINCT если значение "положительное" if ($value == '1' || $value === 1 || $value === true || (is_string($value) && in_array(strtolower($value), ['1', 'true', 'on', 'yes']))) { $distinctColumns[] = $column; } continue; } // '=' and '!=' grouping if (in_array($operator, ['=', '!='])) { $split = is_array($value) ? $value : [$value]; foreach ($split as $v) { $currentGroup['grouped'][$column][$operator][] = $v; } continue; } // other operators $globalCounter++; $ph = "{$column}_" . preg_replace('~[^A-Za-z0-9]~', '', $operator) . "_{$globalCounter}"; switch ($operator) { case '~': $currentGroup['conditions'][] = "{$column} LIKE :{$ph}"; $params[$ph] = "%{$value}%"; break; case '!~': $currentGroup['conditions'][] = "{$column} NOT LIKE :{$ph}"; $params[$ph] = "%{$value}%"; break; case '^': $currentGroup['conditions'][] = "{$column} LIKE :{$ph}"; $params[$ph] = "{$value}%"; break; case '!^': $currentGroup['conditions'][] = "{$column} NOT LIKE :{$ph}"; $params[$ph] = "{$value}%"; break; case '&': $currentGroup['conditions'][] = "{$column} LIKE :{$ph}"; $params[$ph] = "%{$value}"; break; case '!&': $currentGroup['conditions'][] = "{$column} NOT LIKE :{$ph}"; $params[$ph] = "%{$value}"; break; default: $currentGroup['conditions'][] = "{$column} {$operator} :{$ph}"; $params[$ph] = $value; } } // flush last grouped foreach ($currentGroup['grouped'] as $col => $ops) { foreach ($ops as $op => $vals) { if (count($vals) > 1) { $pls = []; foreach($vals as $v) { $globalCounter++; $ph = "{$col}_" . str_replace(['=', '!'], '', $op) . "_{$globalCounter}"; $pls[] = ":{$ph}"; $params[$ph] = $v; } $inOp = $op === '=' ? 'IN' : 'NOT IN'; $currentGroup['conditions'][] = "{$col} {$inOp} (" . implode(', ', $pls) . ")"; } else { $globalCounter++; $ph = "{$col}_" . str_replace(['=', '!'], '', $op) . "_{$globalCounter}"; $currentGroup['conditions'][] = "{$col} {$op} :{$ph}"; $params[$ph] = $vals[0]; } } } if (!empty($currentGroup['conditions'])) $groups[] = $currentGroup; } // build WHERE $where = ''; $clauses = []; foreach ($groups as $g) { if (count($g['conditions']) > 1) { $clauses[] = '(' . implode(" {$g['joiner']} ", $g['conditions']) . ')'; } elseif (count($g['conditions']) === 1) { $clauses[] = $g['conditions'][0]; } } if ($clauses) $where = 'WHERE ' . implode(' AND ', $clauses); // sorting/limit/offset $orderBy = ''; $groupBy = ''; // Обрабатываем сортировку из input if (!empty($input['sort']) && is_array($input['sort'])) { $sortParts = []; foreach ($input['sort'] as $s) { $sortParts[] = "{$s['column']} {$s['direction']}"; } $orderBy = 'ORDER BY ' . implode(', ', $sortParts); } else { // Если сортировка не указана, применяем сортировку по умолчанию $orderBy = 'ORDER BY id ASC'; } $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : ($input['limit'] ?? 0); $limitClause = $limit ? "LIMIT {$limit}" : ''; $offset = isset($input['skip']) ? (int)$input['skip'] : (isset($_GET['skip']) ? (int)$_GET['skip'] : 0); $offsetClause = $offset ? "OFFSET {$offset}" : ''; // SELECT vs DISTINCT if (!empty($distinctColumns)) { // Для получения уникальных полных строк по определенному столбцу if ($dbType === 'pgsql') { $distinctCol = $distinctColumns[0]; // берем первый столбец для DISTINCT $select = "SELECT DISTINCT ON ({$distinctCol}) *"; $countQ = "SELECT COUNT(*) FROM (SELECT DISTINCT ON ({$distinctCol}) * FROM {$table} {$where}) as sub"; } else { // Для MySQL используем GROUP BY $distinctCol = $distinctColumns[0]; // берем первый столбец для DISTINCT $select = 'SELECT *'; $groupBy = "GROUP BY {$distinctCol}"; $countQ = "SELECT COUNT(*) FROM (SELECT * FROM {$table} {$where} GROUP BY {$distinctCol}) as sub"; } } else { $select = 'SELECT *'; $countQ = "SELECT COUNT(*) FROM {$table} {$where}"; } // get totalCount $cstm = $pdo->prepare($countQ); $cstm->execute($params); $totalCount = (int)$cstm->fetchColumn(); // main query if (!empty($distinctColumns)) { if ($dbType === 'pgsql') { $sql = "{$select} FROM {$table} {$where} {$orderBy} {$limitClause} {$offsetClause}"; } else { // Для MySQL добавляем GROUP BY $sql = "{$select} FROM {$table} {$where} {$groupBy} {$orderBy} {$limitClause} {$offsetClause}"; } } else { $sql = "{$select} FROM {$table} {$where} {$orderBy} {$limitClause} {$offsetClause}"; } $stm = $pdo->prepare($sql); $stm->execute($params); // DEBUG: записываем сгенерированный SQL и параметры $debugInfo = [ 'sql' => $sql, 'params' => $params, 'distinctColumns' => $distinctColumns, 'input' => $input ]; file_put_contents(__DIR__.'/debug_sql.log', date('Y-m-d H:i:s') . "\n" . "SQL: " . $sql . "\n" . "PARAMS: " . print_r($params, true) . "DISTINCT: " . print_r($distinctColumns, true) . "INPUT: " . print_r($input, true) . "\n" . str_repeat("-", 80) . "\n\n", FILE_APPEND ); $items = $stm->fetchAll(PDO::FETCH_ASSOC); // Преобразование пустых строк в null и обработка creatium_template $templateRows = []; // массив для строк с creatium_template $regularRows = []; // массив для обычных строк foreach ($items as &$row) { $hasTemplate = false; // флаг для определения строки с шаблоном foreach ($row as $colName => &$value) { // Проверяем, является ли значение пустой строкой if (is_string($value) && trim($value) === '') { $value = null; } // Проверяем на "creatium_template" - заменяем на пустую строку if (is_string($value) && trim($value) === 'creatium_template') { $value = ''; $hasTemplate = true; // отмечаем, что в этой строке был шаблон } } // Разделяем строки: с шаблоном - в начало, обычные - после if ($hasTemplate) { $templateRows[] = $row; } else { $regularRows[] = $row; } } unset($row, $value); // разрываем ссылки после foreach // Объединяем массивы: сначала строки с шаблонами, потом обычные $items = array_merge($templateRows, $regularRows); // Переупорядочиваем ключи в каждой строке: ID первым foreach ($items as &$row) { $reorderedRow = []; // Сначала добавляем ID (ищем поле независимо от регистра) foreach ($row as $colName => $value) { if (strtolower($colName) === 'id') { $reorderedRow[$colName] = $value; break; } } // Затем добавляем остальные поля foreach ($row as $colName => $value) { if (strtolower($colName) !== 'id') { $reorderedRow[$colName] = $value; } } $row = $reorderedRow; } unset($row); // разрываем ссылку после foreach /** * === НОВАЯ ЧАСТЬ: обработка url-ов изображений === * Список форматов, которые мы считаем «изображением»: * jpg, jpeg, png, webp, gif */ $imageExtensions = ['jpg','jpeg','png','webp','gif']; $extPattern = implode('|', $imageExtensions); $regexImageUrl = '/^https?:\/\/.+\.(' . $extPattern . ')$/i'; foreach ($items as &$row) { foreach ($row as $colName => $value) { if (is_string($value) && preg_match($regexImageUrl, $value)) { // Заменяем строку на вложенный массив с ключами "file" и "size" $row[$colName] = [ 'file' => $value, 'size' => [1920, 1080] ]; } } } unset($row); // разрываем ссылку после foreach // columns meta с учетом комментариев $columns = []; if ($items) { // Перебираем все имена столбцов на основании ключей первой строки foreach (array_keys($items[0]) as $colName) { // Комментарий для этого столбца (или пустая строка, если его нет) $commentText = $colComments[$colName] ?? ''; // Для нечувствительного к регистру поиска $commentLower = mb_strtolower($commentText); // Определяем тип в зависимости от слов 'markdown' или 'image' в комментарии // или если в названии столбца есть 'img' или 'image' $colNameLower = mb_strtolower($colName); if (strpos($commentLower, 'markdown') !== false) { $colType = 'markdown'; } elseif (strpos($commentLower, 'image') !== false || strpos($colNameLower, 'img') !== false || strpos($colNameLower, 'photo') !== false || strpos($colNameLower, 'foto') !== false || strpos($colNameLower, 'image') !== false) { $colType = 'image'; } else { // Стандартный способ: string/number/boolean и т.д. $colType = getColumnType($items[0][$colName]); } // Если комментарий непустой — используем его как отображаемое имя, // иначе берём само имя столбца $displayName = $commentText !== '' ? $commentText : $colName; $columns[] = [ 'id' => $colName, 'name' => $displayName, 'type' => $colType, 'comment' => $commentText ]; } } // Формируем финальный ответ $response = [ 'columns' => $columns, 'rows' => $items, 'totalCount' => $totalCount ]; // Сохраняем в кеш если нужно if ($shouldUseCache) { $json = json_encode($response); $cache->put($cacheKey, $json); } header('Content-Type: application/json'); echo json_encode($response); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } ?>
Хватит читать советы «для всех»
Подписывайся — рассказываю, как маркетинг работает на практике, с цифрами и кейсами.
Без воды, прямо и честно.
Сервисы
Полезное
Портфолио
Портфолио
ИП Вечкасов Кирилл Александрович, ИНН: 860326713173, ОГРН: 323784700359197