View file admin/save_handler.php

File size: 3.42Kb
<?php
/**
 * CMS: LaiCMS (v1.0 Edition 2026)
 * File: admin/save_handler.php
 * Description: Высоконадёжный AJAX-обработчик с защитой ядра.
 */

header('Content-Type: application/json');
require_once '../system/db.php';
require_once '../system/functions.php';

// 1. Усиленная проверка доступа
if (!isAdmin()) {
    http_response_code(403);
    exit(json_encode(['status' => 'error', 'message' => 'Доступ заблокирован']));
}

$input = json_decode(file_get_contents('php://input'), true);

// 2. Валидация CSRF и входных данных
if (empty($input['csrf_token']) || !check_csrf($input['csrf_token'])) {
    exit(json_encode(['status' => 'error', 'message' => 'Ошибка безопасности (CSRF)']));
}

$base_dir = dirname(__DIR__);
$allowed_files = [
    'style.css'  => $base_dir . '/theme/style.css',
    'app.js'     => $base_dir . '/theme/app.js',
    'footer.php' => $base_dir . '/system/footer.php',
    'header.php' => $base_dir . '/system/header.php'
];

$file_key = $input['file_key'] ?? '';
$new_content = $input['content'] ?? '';

if (!array_key_exists($file_key, $allowed_files)) {
    exit(json_encode(['status' => 'error', 'message' => 'Файл не входит в белый список']));
}

$path = $allowed_files[$file_key];

// 3. ПРОВЕРКА СИНТАКСИСА (Для PHP файлов)
if (pathinfo($path, PATHINFO_EXTENSION) === 'php') {
    $tmp_check = tempnam(sys_get_temp_dir(), 'lincms_check');
    file_put_contents($tmp_check, $new_content);
    
    // Выполняем PHP Lint (проверка на ошибки)
    exec("php -l " . escapeshellarg($tmp_check), $output, $return_var);
    unlink($tmp_check);

    if ($return_var !== 0) {
        exit(json_encode([
            'status' => 'error', 
            'message' => 'Ошибка в коде: ' . ($output[0] ?? 'Синтаксическая ошибка PHP. Сохранение отменено во избежание поломки сайта.')
        ]));
    }
}

// 4. АТОМАРНОЕ СОХРАНЕНИЕ С БЭКАПОМ
try {
    // Создаем бэкап с меткой времени
    if (file_exists($path)) {
        copy($path, $path . '.v' . time() . '.bak');
        
        // Очистка старых бэкапов (оставляем только последние 3)
        $backups = glob($path . '.*.bak');
        if (count($backups) > 3) {
            array_map('unlink', array_slice($backups, 0, count($backups) - 3));
        }
    }

    // Запись через временный файл (Atomic Write)
    $tmp_file = $path . '.tmp';
    if (file_put_contents($tmp_file, $new_content) === false) {
        throw new Exception("Ошибка записи во временный буфер.");
    }

    if (!rename($tmp_file, $path)) {
        throw new Exception("Не удалось обновить основной файл. Проверьте права CHMOD.");
    }

    // Логируем успешное действие
    write_log("Файл $file_key успешно обновлен", "success");

    echo json_encode([
        'status' => 'success',
        'time' => date('H:i:s'),
        'size' => strlen($new_content) . ' bytes'
    ]);

} catch (Exception $e) {
    if (isset($tmp_file) && file_exists($tmp_file)) unlink($tmp_file);
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}