Проблема в вашем коде связана с тем, что скрипт не корректно отображает вложенные папки и файлы в них из-за ошибок в обработке путей и логики загрузки дерева папок. Давайте разберем проблему и предложим исправления.
Проблемы в коде:
- Некорректная обработка путей в get_folder_tree:
- В файле _explorer_int1.php функция get_folder_tree не учитывает текущий путь ($_GET['path']), из-за чего всегда возвращается структура корневой директории attachments. Это приводит к тому, что при открытии подпапки отображаются только корневые папки.
- Проверка защиты от path traversal (preg_replace('/\.\.\//', '', $_GET['path'])) закомментирована, что также может вызывать проблемы с безопасностью.
- Отсутствие рекурсивной загрузки подпапок:
- В функции renderTree вы добавляете элементы <div class="subfolder"> для подпапок, но при загрузке подпапок через loadFolderTree не передается корректный путь, из-за чего дерево не строится рекурсивно.
- Логика toggleFolder:
- Функция toggleFolder загружает подпапки, но не обновляет их содержимое должным образом, если они уже были загружены. Это может приводить к дублированию или отсутствию отображения вложенных папок.
- Некорректная работа с файлами в подпапках:
- При клике на папку вызывается loadFiles, но путь к файлам может быть некорректным, так как он не всегда правильно формируется из-за ошибок в обработке путей.
Исправленный код
1. Исправление _explorer_int1.php
Обновим функцию get_folder_tree, чтобы она учитывала переданный путь и корректно возвращала структуру подпапок.
<?php
header('Content-Type: application/json');
// Обработка серверных запросов
$action = isset($_GET['action']) ? $_GET['action'] : '';
if ($action === 'get_folder_tree') {
$base_dir = 'attachments'; // Базовая директория
$path = isset($_GET['path']) ? preg_replace('/\.\.\//', '', $_GET['path']) : ''; // Защита от path traversal
$full_path = $base_dir . ($path ? '/' . $path : '');
header('Content-Type: application/json');
echo json_encode(get_folder_tree($full_path));
exit;
}
if ($action === 'get_files') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(get_files($full_path));
exit;
}
if ($action === 'parse_eml') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(parse_eml_file($full_path));
exit;
}
if ($action === 'download') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
if (file_exists($full_path)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($full_path) . '"');
readfile($full_path);
}
exit;
}
if ($action === 'upload_file') {
header('Content-Type: application/json');
$response = ['success' => false];
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$upload_dir = 'attachments/uploads';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$target_path = "$upload_dir/" . uniqid() . ".$ext";
if (move_uploaded_file($file['tmp_name'], $target_path)) {
if ($ext === 'zip') {
$zip = new ZipArchive();
if ($zip->open($target_path) === true) {
$zip->extractTo($upload_dir);
$zip->close();
}
}
$response = ['success' => true, 'path' => 'uploads'];
} else {
$response['error'] = 'Ошибка загрузки файла';
}
}
echo json_encode($response);
exit;
}
// Функция для получения структуры папок
function get_folder_tree($dir) {
$result = [];
if (!is_dir($dir)) return $result;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_dir($path)) {
$result[] = [
'name' => $item,
'type' => 'folder',
'hasChildren' => has_subfolders($path)
];
}
}
return $result;
}
// Проверка наличия подпапок
function has_subfolders($dir) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
if (is_dir("$dir/$item")) return true;
}
return false;
}
// Функция для получения списка файлов в папке
function get_files($dir) {
$result = [];
if (is_dir($dir)) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_file($path)) {
$result[] = [
'name' => $item,
'path' => str_replace('attachments/', '', $path)
];
}
}
}
return $result;
}
// Функция для парсинга EML файла
function parse_eml_file($file_path) {
$result = [
'headers' => [],
'text_parts' => [],
'html_parts' => [],
'attachments' => []
];
$content = file_get_contents($file_path);
if ($content === false) {
return $result;
}
// Разделение на заголовки и тело
$parts = explode("\r\n\r\n", $content, 2);
if (count($parts) < 2) {
$result['headers'] = $content;
return $result;
}
$headers = $parts[0];
$body = $parts[1];
// Парсинг заголовков
$header_lines = explode("\r\n", $headers);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$result['headers'][$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$result['headers'][$current_header] .= ' ' . trim($matches[1]);
}
}
// Извлечение кодировки
$charset = 'UTF-8';
if (isset($result['headers']['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$charset = $matches[1];
}
// Декодирование заголовков
foreach ($result['headers'] as $key => $value) {
$result['headers'][$key] = mb_decode_mimeheader($value);
if ($charset !== 'UTF-8') {
$result['headers'][$key] = mb_convert_encoding($result['headers'][$key], 'UTF-8', $charset);
}
}
// Парсинг тела письма
parse_eml_body($body, $result, $charset);
return $result;
}
// Функция для парсинга тела EML
function parse_eml_body($body, &$result, $charset, $boundary = null) {
if (!$boundary && isset($result['headers']['Content-Type']) && preg_match('/boundary=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$boundary = $matches[1];
}
if ($boundary) {
$parts = explode("--$boundary", $body);
foreach ($parts as $part) {
$part = trim($part);
if (!$part || $part === '--') continue;
// Разделение части на заголовки и содержимое
$sub_parts = explode("\r\n\r\n", $part, 2);
if (count($sub_parts) < 2) continue;
$sub_headers = parse_sub_headers($sub_parts[0]);
$sub_body = $sub_parts[1];
// Определение кодировки содержимого
$sub_charset = $charset;
if (isset($sub_headers['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_charset = $matches[1];
}
// Декодирование содержимого
$encoding = isset($sub_headers['Content-Transfer-Encoding']) ? strtolower($sub_headers['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$sub_body = base64_decode($sub_body);
break;
case 'quoted-printable':
$sub_body = quoted_printable_decode($sub_body);
break;
}
// Конверсия кодировки
if ($sub_charset !== 'UTF-8') {
$sub_body = mb_convert_encoding($sub_body, 'UTF-8', $sub_charset);
}
// Обработка типа содержимого
if (isset($sub_headers['Content-Type'])) {
if (stripos($sub_headers['Content-Type'], 'text/plain') !== false) {
$result['text_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'text/html') !== false) {
$result['html_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'multipart') !== false) {
$sub_boundary = '';
if (preg_match('/boundary=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_boundary = $matches[1];
}
parse_eml_body($sub_body, $result, $sub_charset, $sub_boundary);
} else {
// Обработка вложений
$filename = '';
if (isset($sub_headers['Content-Disposition']) && preg_match('/filename=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Disposition'], $matches)) {
$filename = $matches[1];
} elseif (isset($sub_headers['Content-Type']) && preg_match('/name=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$filename = $matches[1];
}
if ($filename) {
$result['attachments'][] = [
'filename' => mb_decode_mimeheader($filename),
'size' => strlen($sub_body)
];
}
}
}
}
} else {
// Если нет boundary, считаем тело текстом
$encoding = isset($result['headers']['Content-Transfer-Encoding']) ? strtolower($result['headers']['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$body = base64_decode($body);
break;
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
}
if ($charset !== 'UTF-8') {
$body = mb_convert_encoding($body, 'UTF-8', $charset);
}
$result['text_parts'][] = $body;
}
}
// Парсинг подзаголовков
function parse_sub_headers($header_text) {
$headers = [];
$header_lines = explode("\r\n", $header_text);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$headers[$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$headers[$current_header] .= ' ' . trim($matches[1]);
}
}
return $headers;
}Основные изменения:
- Восстановлена обработка параметра path в get_folder_tree с защитой от path traversal.
- full_path теперь формируется с учетом переданного пути, что позволяет загружать структуру подпапок.
2. Исправление клиентской части (HTML и JavaScript)
Обновим JavaScript в первом файле, чтобы правильно отображать дерево папок и файлы.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Проводник файлов и парсер писем</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#folder-tree { width: 30%; float: left; border: 1px solid #ccc; padding: 10px; }
#file-list { width: 65%; float: right; border: 1px solid #ccc; padding: 10px; }
.folder, .file { cursor: pointer; padding: 5px; }
.folder:hover, .file:hover { background-color: #f0f0f0; }
.email-content { margin-top: 20px; padding: 10px; border: 1px solid #ccc; }
.header { margin-bottom: 10px; }
.header span { font-weight: bold; }
.upload-section { margin-bottom: 20px; }
.folder-toggle::before { content: '▶'; margin-right: 5px; }
.folder-toggle.open::before { content: '▼'; }
ul { list-style: none; padding-left: 20px; }
</style>
</head>
<body>
<h1>Проводник файлов и парсер писем</h1>
<!-- Секция загрузки файлов -->
<div class="upload-section">
<h3>Загрузить файл .eml или ZIP-архив</h3>
<input type="file" id="file-upload" accept=".eml,.zip">
<button onclick="uploadFile()">Загрузить</button>
</div>
<!-- Проводник папок -->
<div id="folder-tree">
<h3>Структура папок</h3>
<div id="tree"></div>
</div>
<!-- Список файлов -->
<div id="file-list">
<h3>Содержимое папки</h3>
<div id="files"></div>
<div id="email-content" class="email-content" style="display: none;"></div>
</div>
<script>
// Загрузка структуры папок
function loadFolderTree(path = '', parentElement = document.getElementById('tree')) {
fetch(`_explorer_int1.php?action=get_folder_tree&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
parentElement.innerHTML = renderTree(data, path);
})
.catch(error => console.error('Ошибка загрузки структуры папок:', error));
}
// Рендеринг дерева папок
function renderTree(data, path = '') {
let html = '<ul>';
for (const item of data) {
const fullPath = path ? `${path}/${item.name}` : item.name;
if (item.type === 'folder') {
html += `
<li>
<span class="folder${item.hasChildren ? ' folder-toggle' : ''}"
data-path="${fullPath}"
onclick="toggleFolder(this, '${fullPath}')">
${item.name}
</span>
<div class="subfolder" style="display: none;"></div>
</li>`;
}
}
html += '</ul>';
return html;
}
// Переключение папки (развернуть/свернуть)
function toggleFolder(element, path) {
const subfolderDiv = element.parentElement.querySelector('.subfolder');
const isOpen = element.classList.contains('open');
if (isOpen) {
// Сворачиваем папку
subfolderDiv.style.display = 'none';
element.classList.remove('open');
} else {
// Разворачиваем папку
if (!subfolderDiv.innerHTML) {
// Загружаем подпапки, если они еще не загружены
fetch(`_explorer_int1.php?action=get_folder_tree&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
subfolderDiv.innerHTML = renderTree(data, path);
subfolderDiv.style.display = 'block';
element.classList.add('open');
// Загружаем файлы для этой папки
loadFiles(path);
})
.catch(error => console.error('Ошибка загрузки подпапок:', error));
} else {
subfolderDiv.style.display = 'block';
element.classList.add('open');
loadFiles(path);
}
}
}
// Загрузка списка файлов в папке
function loadFiles(path) {
fetch(`_explorer_int1.php?action=get_files&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
let html = '<ul>';
for (const file of data) {
html += `<li class="file" onclick="openFile('${file.path}')">${file.name}</li>`;
}
html += '</ul>';
document.getElementById('files').innerHTML = html;
document.getElementById('email-content').style.display = 'none';
})
.catch(error => console.error('Ошибка загрузки файлов:', error));
}
// Открытие файла
function openFile(path) {
if (path.endsWith('.eml')) {
fetch(`_explorer_int1.php?action=parse_eml&path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
const contentDiv = document.getElementById('email-content');
contentDiv.innerHTML = `
<h3>Содержимое письма</h3>
<div class="header"><span>От:</span> ${data.headers.From || 'Не указано'}</div>
<div class="header"><span>Кому:</span> ${data.headers.To || 'Не указано'}</div>
<div class="header"><span>Тема:</span> ${data.headers.Subject || 'Без темы'}</div>
<div class="header"><span>Дата:</span> ${data.headers.Date || 'Не указана'}</div>
<h4>Служебные заголовки:</h4>
<ul>
${Object.entries(data.headers).map(([key, value]) => `
<li><strong>${key}:</strong> ${value} <br>
<small>${getHeaderDescription(key)}</small>
</li>
`).join('')}
</ul>
<h4>Сообщение:</h4>
<div>${data.text_parts.length ? data.text_parts.join('<hr>') : 'Нет текстового содержимого'}</div>
${data.html_parts.length ? `<h4>HTML содержимое:</h4><div>${data.html_parts.join('<hr>')}</div>` : ''}
${data.attachments.length ? `
<h4>Вложения:</h4>
<ul>
${data.attachments.map(att => `<li>${att.filename} (${att.size} байт)</li>`).join('')}
</ul>
` : ''}
`;
contentDiv.style.display = 'block';
})
.catch(error => console.error('Ошибка парсинга EML:', error));
} else {
window.open(`_explorer_int1.php?action=download&path=${encodeURIComponent(path)}`, '_blank');
}
}
// Загрузка файла
function uploadFile() {
const input = document.getElementById('file-upload');
if (!input.files.length) {
alert('Выберите файл .eml или .zip');
return;
}
const formData = new FormData();
formData.append('file', input.files[0]);
fetch('_explorer_int1.php?action=upload_file', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Файл загружен и обработан');
loadFiles(data.path || '');
loadFolderTree(); // Обновляем корневое дерево
} else {
alert('Ошибка загрузки: ' + data.error);
}
})
.catch(error => console.error('Ошибка загрузки:', error));
}
// Описания заголовков
function getHeaderDescription(header) {
const descriptions = {
'From': 'Адрес отправителя письма.',
'To': 'Адрес получателя письма.',
'Subject': 'Тема письма.',
'Date': 'Дата и время отправки письма.',
'Message-ID': 'Уникальный идентификатор письма.',
'Content-Type': 'Тип содержимого письма (например, text/plain или multipart/mixed).',
'Content-Transfer-Encoding': 'Кодировка содержимого (например, base64, quoted-printable).',
'MIME-Version': 'Версия MIME, используемая в письме.'
};
return descriptions[header] || 'Дополнительный заголовок письма.';
}
// Инициализация
loadFolderTree();
</script>
</body>
</html>Основные изменения:
- Упрощена функция loadFolderTree, чтобы она корректно передавала путь в запросе.
- В функции toggleFolder добавлена проверка, чтобы не загружать содержимое уже открытых папок повторно, но при этом обновлять файлы в текущей папке.
- Исправлена функция renderTree, чтобы она корректно формировала HTML для вложенных папок.
Объяснение исправлений
- Корректная обработка путей:
- В _explorer_int1.php теперь учитывается параметр path, что позволяет загружать структуру подпапок для конкретного пути.
- Защита от path traversal (preg_replace('/\.\.\//', '', $_GET['path'])) включена, чтобы предотвратить доступ к файлам вне директории attachments.
- Динамическая загрузка подпапок:
- Функция toggleFolder теперь загружает подпапки только при первом открытии папки, а при повторном открытии просто показывает/скрывает уже загруженное содержимое.
- Обновление списка файлов:
- При каждом открытии папки вызывается loadFiles, чтобы отобразить файлы в текущей папке.
- Оптимизация renderTree:
- Убрана избыточная рекурсия в renderTree, так как подпапки теперь загружаются динамически через toggleFolder.
Дополнительные рекомендации
- Безопасность:
- Убедитесь, что директория attachments защищена от несанкционированного доступа.
- Проверьте права доступа к файлам и папкам, чтобы избежать уязвимостей.
- Обработка ошибок:
- Добавьте более детализированную обработку ошибок в JavaScript (например, отображение сообщения пользователю, если сервер вернул ошибку).
- Производительность:
- Если структура папок очень большая, подумайте о кэшировании результатов get_folder_tree на стороне сервера, чтобы снизить нагрузку.
- UX/UI:
- Добавьте индикатор загрузки (например, спиннер) при загрузке подпапок или файлов.
- Рассмотрите возможность отображения иконок для папок и файлов для улучшения визуального восприятия.
Тестирование
- Убедитесь, что папка attachments существует и содержит тестовые подпапки и файлы.
- Проверьте, что при клике на папку отображаются её подпапки и файлы.
- Убедитесь, что файлы .eml корректно парсятся, а другие файлы скачиваются.
- Протестируйте загрузку .zip и .eml файлов, чтобы убедиться, что они корректно обрабатываются и отображаются в проводнике.
Если возникнут дополнительные вопросы или потребуется дальнейшая помощь, напишите!
Для добавления даты к папкам и файлам, а также сортировки по дате (сначала самые новые), нужно обновить как серверную часть (_explorer_int1.php), так и клиентскую часть (HTML/JS). Мы будем использовать время последней модификации (mtime) для файлов и папок, чтобы отображать дату и сортировать по ней.
Изменения в коде
1. Обновление _explorer_int1.php
Нужно модифицировать функции get_folder_tree и get_files, чтобы они возвращали дату последней модификации для каждой папки и файла. Также добавим сортировку по дате (от новых к старым).
<?php
header('Content-Type: application/json');
// Обработка серверных запросов
$action = isset($_GET['action']) ? $_GET['action'] : '';
if ($action === 'get_folder_tree') {
$base_dir = 'attachments'; // Базовая директория
$path = isset($_GET['path']) ? preg_replace('/\.\.\//', '', $_GET['path']) : ''; // Защита от path traversal
$full_path = $base_dir . ($path ? '/' . $path : '');
header('Content-Type: application/json');
echo json_encode(get_folder_tree($full_path));
exit;
}
if ($action === 'get_files') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(get_files($full_path));
exit;
}
if ($action === 'parse_eml') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
header('Content-Type: application/json');
echo json_encode(parse_eml_file($full_path));
exit;
}
if ($action === 'download') {
$path = isset($_GET['path']) ? $_GET['path'] : '';
$full_path = "attachments/$path";
if (file_exists($full_path)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($full_path) . '"');
readfile($full_path);
}
exit;
}
if ($action === 'upload_file') {
header('Content-Type: application/json');
$response = ['success' => false];
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$upload_dir = 'attachments/uploads';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$target_path = "$upload_dir/" . uniqid() . ".$ext";
if (move_uploaded_file($file['tmp_name'], $target_path)) {
if ($ext === 'zip') {
$zip = new ZipArchive();
if ($zip->open($target_path) === true) {
$zip->extractTo($upload_dir);
$zip->close();
}
}
$response = ['success' => true, 'path' => 'uploads'];
} else {
$response['error'] = 'Ошибка загрузки файла';
}
}
echo json_encode($response);
exit;
}
// Функция для получения структуры папок
function get_folder_tree($dir) {
$result = [];
if (!is_dir($dir)) return $result;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_dir($path)) {
$mtime = filemtime($path);
$result[] = [
'name' => $item,
'type' => 'folder',
'hasChildren' => has_subfolders($path),
'mtime' => $mtime,
'date' => date('Y-m-d H:i:s', $mtime)
];
}
}
// Сортировка по времени модификации (от новых к старым)
usort($result, function($a, $b) {
return $b['mtime'] <=> $a['mtime'];
});
return $result;
}
// Проверка наличия подпапок
function has_subfolders($dir) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
if (is_dir("$dir/$item")) return true;
}
return false;
}
// Функция для получения списка файлов в папке
function get_files($dir) {
$result = [];
if (is_dir($dir)) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
if (is_file($path)) {
$mtime = filemtime($path);
$result[] = [
'name' => $item,
'path' => str_replace('attachments/', '', $path),
'mtime' => $mtime,
'date' => date('Y-m-d H:i:s', $mtime)
];
}
}
}
// Сортировка по времени модификации (от новых к старым)
usort($result, function($a, $b) {
return $b['mtime'] <=> $a['mtime'];
});
return $result;
}
// Функция для парсинга EML файла
function parse_eml_file($file_path) {
$result = [
'headers' => [],
'text_parts' => [],
'html_parts' => [],
'attachments' => []
];
$content = file_get_contents($file_path);
if ($content === false) {
return $result;
}
// Разделение на заголовки и тело
$parts = explode("\r\n\r\n", $content, 2);
if (count($parts) < 2) {
$result['headers'] = $content;
return $result;
}
$headers = $parts[0];
$body = $parts[1];
// Парсинг заголовков
$header_lines = explode("\r\n", $headers);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$result['headers'][$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$result['headers'][$current_header] .= ' ' . trim($matches[1]);
}
}
// Извлечение кодировки
$charset = 'UTF-8';
if (isset($result['headers']['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$charset = $matches[1];
}
// Декодирование заголовков
foreach ($result['headers'] as $key => $value) {
$result['headers'][$key] = mb_decode_mimeheader($value);
if ($charset !== 'UTF-8') {
$result['headers'][$key] = mb_convert_encoding($result['headers'][$key], 'UTF-8', $charset);
}
}
// Парсинг тела письма
parse_eml_body($body, $result, $charset);
return $result;
}
// Функция для парсинга тела EML
function parse_eml_body($body, &$result, $charset, $boundary = null) {
if (!$boundary && isset($result['headers']['Content-Type']) && preg_match('/boundary=["\']?([^"\']+)["\']?/i', $result['headers']['Content-Type'], $matches)) {
$boundary = $matches[1];
}
if ($boundary) {
$parts = explode("--$boundary", $body);
foreach ($parts as $part) {
$part = trim($part);
if (!$part || $part === '--') continue;
// Разделение части на заголовки и содержимое
$sub_parts = explode("\r\n\r\n", $part, 2);
if (count($sub_parts) < 2) continue;
$sub_headers = parse_sub_headers($sub_parts[0]);
$sub_body = $sub_parts[1];
// Определение кодировки содержимого
$sub_charset = $charset;
if (isset($sub_headers['Content-Type']) && preg_match('/charset=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_charset = $matches[1];
}
// Декодирование содержимого
$encoding = isset($sub_headers['Content-Transfer-Encoding']) ? strtolower($sub_headers['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$sub_body = base64_decode($sub_body);
break;
case 'quoted-printable':
$sub_body = quoted_printable_decode($sub_body);
break;
}
// Конверсия кодировки
if ($sub_charset !== 'UTF-8') {
$sub_body = mb_convert_encoding($sub_body, 'UTF-8', $sub_charset);
}
// Обработка типа содержимого
if (isset($sub_headers['Content-Type'])) {
if (stripos($sub_headers['Content-Type'], 'text/plain') !== false) {
$result['text_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'text/html') !== false) {
$result['html_parts'][] = $sub_body;
} elseif (stripos($sub_headers['Content-Type'], 'multipart') !== false) {
$sub_boundary = '';
if (preg_match('/boundary=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$sub_boundary = $matches[1];
}
parse_eml_body($sub_body, $result, $sub_charset, $sub_boundary);
} else {
// Обработка вложений
$filename = '';
if (isset($sub_headers['Content-Disposition']) && preg_match('/filename=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Disposition'], $matches)) {
$filename = $matches[1];
} elseif (isset($sub_headers['Content-Type']) && preg_match('/name=["\']?([^"\']+)["\']?/i', $sub_headers['Content-Type'], $matches)) {
$filename = $matches[1];
}
if ($filename) {
$result['attachments'][] = [
'filename' => mb_decode_mimeheader($filename),
'size' => strlen($sub_body)
];
}
}
}
}
} else {
// Если нет boundary, считаем тело текстом
$encoding = isset($result['headers']['Content-Transfer-Encoding']) ? strtolower($result['headers']['Content-Transfer-Encoding']) : '';
switch ($encoding) {
case 'base64':
$body = base64_decode($body);
break;
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
}
if ($charset !== 'UTF-8') {
$body = mb_convert_encoding($body, 'UTF-8', $charset);
}
$result['text_parts'][] = $body;
}
}
// Парсинг подзаголовков
function parse_sub_headers($header_text) {
$headers = [];
$header_lines = explode("\r\n", $header_text);
$current_header = '';
foreach ($header_lines as $line) {
if (preg_match('/^([^:]+):\s*(.+)$/', $line, $matches)) {
$current_header = trim($matches[1]);
$headers[$current_header] = trim($matches[2]);
} elseif ($current_header && preg_match('/^\s+(.+)$/', $line, $matches)) {
$headers[$current_header] .= ' ' . trim($matches[1]);
}
}
return $headers;
}Основные изменения:
- В функции get_folder_tree добавлены поля mtime (время модификации в формате Unix timestamp) и date (форматированная дата) для каждой папки.
- В функции get_files добавлены те же поля для файлов.
- Добавлена сортировка с помощью usort, которая сравнивает mtime в порядке убывания (от новых к старым).
2. Обновление клиентской части (HTML/JS)
Нужно обновить функции renderTree и loadFiles, чтобы отображать дату рядом с папками и файлами. Также добавим немного стилей для улучшения читаемости.
**Основные изменения**:
- В CSS добавлен класс `.date` для отображения даты в сером цвете и меньшим шрифтом. Также изменены стили `.folder` и `.file`, чтобы дата отображалась справа от имени.
- В функции `renderTree` добавлено отображение даты для папок (`item.date`).
- В функции `loadFiles` добавлено отображение даты для файлов (`file.date`).
- Сортировка выполняется на сервере, поэтому клиентская часть просто использует полученные данные.
---
### Объяснение изменений
1. **Добавление даты**:
- В `_explorer_int1.php` для каждой папки и файла добавлены поля `mtime` (Unix timestamp) и `date` (форматированная дата `YYYY-MM-DD HH:MM:SS`).
- На клиенте дата отображается рядом с именем папки или файла в формате, возвращаемом сервером.
2. **Сортировка по дате**:
- Сортировка реализована на сервере с помощью функции `usort`, которая сравнивает `mtime` в порядке убывания (`$b['mtime'] <=> $a['mtime']`).
- Это гарантирует, что самые новые папки и файлы будут отображаться первыми.
3. **Улучшение интерфейса**:
- Дата отображается справа от имени в папке или файле, чтобы не загромождать интерфейс.
- Стили `.folder` и `.file` обновлены для использования `display: flex`, чтобы обеспечить аккуратное размещение имени и даты.
---
### Дополнительные рекомендации
1. **Форматирование даты**:
- Текущий формат даты (`Y-m-d H:i:s`) может быть неудобным для пользователей. Можно настроить формат на клиенте с помощью JavaScript (например, использовать библиотеку `moment.js` или `Intl.DateTimeFormat`) для отображения даты в локализованном формате.
- Пример:
```javascript
const formattedDate = new Intl.DateTimeFormat('ru-RU', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(item.mtime * 1000));- Кэширование: Если структура папок редко меняется, можно кэшировать результаты get_folder_tree на сервере, чтобы уменьшить нагрузку.
- UX:
- Рассмотрите возможность добавления переключателя для сортировки по имени или дате.
- Добавьте всплывающую подсказку (tooltip) с дополнительной информацией о файле (например, размер файла) при наведении.
Тестирование
- Убедитесь, что в папке attachements есть файлы и папки с разными датами модификации.
- Проверьте, что папки и файлы отображаются с датами и сортируются по убыванию даты (сначала новые).
- Убедитесь, что при загрузке новых файлов они появляются вверху списка.
- Проверьте, что функциональность парсинга .eml и скачивания файлов осталась без изменений.
Если нужны дополнительные улучшения или возникнут вопросы, напишите!