VvvebJs / save.php
CatPtain's picture
Upload 2 files
6f6abdf verified
<?php
/*
Copyright 2017 Ziadin Givan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
https://github.com/givanz/VvvebJs
*/
// Include storage classes
require_once __DIR__ . '/storage.php';
require_once __DIR__ . '/user-manager.php';
// Enhanced error logging for debugging
error_log("VvvebJs Save Request - Method: " . ($_SERVER['REQUEST_METHOD'] ?? 'unknown'));
error_log("VvvebJs Save Request - Action: " . ($_GET['action'] ?? $_POST['action'] ?? 'none'));
error_log("VvvebJs Save Request - Has HTML: " . (isset($_POST['html']) ? 'yes' : 'no'));
error_log("VvvebJs Save Request - Has File: " . (isset($_POST['file']) ? 'yes' : 'no'));
// Authentication check
function checkAuth() {
$userManager = new UserManager();
// Check if user is logged in via session
if ($userManager->isLoggedIn()) {
return true;
}
// Enhanced auth error logging
error_log("VvvebJs Auth Failed - Session logged in: " . ($userManager->isLoggedIn() ? 'yes' : 'no'));
error_log("VvvebJs Auth Failed - Session data: " . json_encode($_SESSION ?? []));
// For AJAX requests, return JSON error instead of redirect
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Authentication required', 'redirect' => 'index.html']);
exit;
}
// No authentication found - redirect to index.html which is the login page
header('Location: index.html');
exit;
}
// Check authentication
$action = $_GET['action'] ?? $_POST['action'] ?? '';
// Enhanced debug logging for action detection
error_log("VvvebJs Action Debug - GET action: " . ($_GET['action'] ?? 'none'));
error_log("VvvebJs Action Debug - POST action: " . ($_POST['action'] ?? 'none'));
error_log("VvvebJs Action Debug - Final action: '$action'");
// Special handling for auth check
if ($action === 'checkAuth') {
$userManager = new UserManager();
if ($userManager->isLoggedIn()) {
http_response_code(200);
echo json_encode(['authenticated' => true, 'user' => $userManager->getCurrentUser()]);
} else {
http_response_code(401);
echo json_encode(['authenticated' => false]);
}
exit;
}
checkAuth();
// Initialize storage manager
$storageManager = new StorageManager();
define('MAX_FILE_LIMIT', 1024 * 1024 * 2);//2 Megabytes max html file size
define('ALLOW_PHP', false);//check if saved html contains php tag and don't save if not allowed
define('ALLOWED_OEMBED_DOMAINS', [
'https://www.youtube.com/',
'https://www.vimeo.com/',
'https://www.x.com/',
'https://x.com/',
'https://publish.twitter.com/',
'https://www.twitter.com/',
'https://www.reddit.com/',
]);//load urls only from allowed websites for oembed
function sanitizeFileName($file, $allowedExtension = 'html') {
$basename = basename($file);
$disallow = ['.htaccess', 'passwd'];
if (in_array($basename, $disallow)) {
showError('Filename not allowed!');
return '';
}
//sanitize, remove double dot .. and remove get parameters if any
$file = preg_replace('@\?.*$@' , '', preg_replace('@\.{2,}@' , '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $file)));
if (!$file) {
return '';
}
// For loadFile action, return the sanitized filename as-is for storage manager
// Only add __DIR__ for local file operations
$action = $_GET['action'] ?? $_POST['action'] ?? '';
if ($action === 'loadFile' || $action === 'listFiles') {
// For external storage operations, just sanitize and add extension
if ($allowedExtension) {
$file = preg_replace('/\.[^.]+$/', '', $file) . ".$allowedExtension";
}
return $file;
}
// For local file operations, add the full path
$file = __DIR__ . DIRECTORY_SEPARATOR . $file;
//allow only .html extension
if ($allowedExtension) {
$file = preg_replace('/\.[^.]+$/', '', $file) . ".$allowedExtension";
}
return $file;
}
function showError($error) {
// Enhanced error response for AJAX requests
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode(['success' => false, 'message' => $error]);
exit;
}
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
die($error);
}
function validOembedUrl($url) {
foreach (ALLOWED_OEMBED_DOMAINS as $domain) {
if (strpos($url, $domain) === 0) {
return true;
}
}
return false;
}
$html = '';
$file = '';
if (isset($_POST['startTemplateUrl']) && !empty($_POST['startTemplateUrl'])) {
$startTemplateUrl = sanitizeFileName($_POST['startTemplateUrl']);
$html = '';
if ($startTemplateUrl) {
$html = file_get_contents($startTemplateUrl);
}
} else if (isset($_POST['html'])){
$html = substr($_POST['html'], 0, MAX_FILE_LIMIT);
if (!ALLOW_PHP) {
//if (strpos($html, '<?php') !== false) {
if (preg_match('@<\?php|<\? |<\?=|<\s*script\s*language\s*=\s*"\s*php\s*"\s*>@', $html)) {
showError('PHP not allowed!');
}
}
}
if (isset($_POST['file'])) {
$file = sanitizeFileName($_POST['file']);
}
if ($action && $action !== '') {
//file manager actions, delete and rename
switch ($action) {
case 'listFiles':
// List files for current user with enhanced error handling
try {
$files = $storageManager->listFiles();
// Enhanced debugging
$github = StorageConfig::getGitHubConfig();
$debug = [
'hasToken' => !empty($github['token']),
'owner' => $github['owner'],
'repo' => $github['repo'],
'branch' => $github['branch'],
'path' => $github['path'],
'user' => $storageManager->getCurrentUser(),
'userPath' => $storageManager->getUserPath()
];
error_log("GitHub List Files Debug: " . json_encode($debug));
error_log("Files found: " . count($files));
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'files' => $files,
'user' => $storageManager->getCurrentUser(),
'userPath' => $storageManager->getUserPath(),
'debug' => $debug
]);
} catch (Exception $e) {
error_log("GitHub List Files Error: " . $e->getMessage());
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Error loading files: ' . $e->getMessage(),
'user' => $storageManager->getCurrentUser(),
'files' => []
]);
}
exit;
case 'loadFile':
// Load a specific file for current user
$filename = sanitizeFileName($_GET['file'] ?? '');
error_log("VvvebJs LoadFile Debug - Original filename: " . ($_GET['file'] ?? ''));
error_log("VvvebJs LoadFile Debug - Sanitized filename: " . $filename);
if ($filename) {
try {
$content = $storageManager->getFile($filename);
error_log("VvvebJs LoadFile Debug - Content length: " . ($content !== false ? strlen($content) : 'false'));
if ($content !== false) {
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'content' => $content,
'filename' => $filename
]);
} else {
// Enhanced error information
$github = StorageConfig::getGitHubConfig();
$debugInfo = [
'filename' => $filename,
'user' => $storageManager->getCurrentUser(),
'userPath' => $storageManager->getUserPath(),
'fullPath' => $github['path'] . $storageManager->getUserPath() . $filename,
'hasToken' => !empty($github['token']),
'repo' => $github['owner'] . '/' . $github['repo']
];
error_log("VvvebJs LoadFile Error - Debug info: " . json_encode($debugInfo));
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'File not found or access denied',
'debug' => $debugInfo
]);
}
} catch (Exception $e) {
error_log("VvvebJs LoadFile Exception: " . $e->getMessage());
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Error loading file: ' . $e->getMessage(),
'filename' => $filename
]);
}
} else {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Invalid filename'
]);
}
exit;
case 'checkAuth':
// Check if user is authenticated
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'user' => $storageManager->getCurrentUser(),
'authenticated' => true
]);
exit;
case 'rename':
$newfile = sanitizeFileName($_POST['newfile']);
if ($file && $newfile) {
// For user isolation, we need to handle this through storage manager
$content = $storageManager->getFile($file);
if ($content !== false) {
if ($storageManager->saveFile($newfile, $content)) {
$storageManager->deleteFile($file);
echo "File '$file' renamed to '$newfile'";
} else {
showError("Error renaming file '$file' to '$newfile'");
}
} else {
showError("File '$file' not found");
}
}
break;
case 'delete':
if ($file) {
if ($storageManager->deleteFile($file)) {
echo "File '$file' deleted";
} else {
showError("Error deleting file '$file'");
}
}
break;
case 'saveReusable':
//block or section
$type = $_POST['type'] ?? false;
$name = $_POST['name'] ?? false;
$html = $_POST['html'] ?? false;
if ($type && $name && $html) {
$file = sanitizeFileName("$type/$name");
if ($file) {
$dir = dirname($file);
if (!is_dir($dir)) {
echo "$dir folder does not exist\n";
if (mkdir($dir, 0777, true)) {
echo "$dir folder was created\n";
} else {
showError("Error creating folder '$dir'\n");
}
}
if (file_put_contents($file, $html)) {
echo "File saved '$file'";
} else {
showError("Error saving file '$file'\nPossible causes are missing write permission or incorrect file path!");
}
} else {
showError('Invalid filename!');
}
} else {
showError("Missing reusable element data!\n");
}
break;
case 'oembedProxy':
$url = $_GET['url'] ?? '';
if (validOembedUrl($url)) {
$options = array(
'http'=>array(
'method'=>"GET",
'header'=> 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'] . "\r\n"
)
);
$context = stream_context_create($options);
header('Content-Type: application/json');
echo file_get_contents($url, false, $context );
} else {
showError('Invalid url!');
}
break;
}
} else {
// No action specified - this should be a save request
error_log("VvvebJs: No action specified, treating as save request");
error_log("VvvebJs Save Request Debug - POST data keys: " . implode(', ', array_keys($_POST)));
//save page
if ($html) {
if ($file) {
// Extract relative filename for external storage
$relativePath = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $file);
$relativePath = str_replace('\\', '/', $relativePath);
// Enhanced debug logging
error_log("VvvebJs Save Debug - File: $relativePath");
error_log("VvvebJs Save Debug - HTML length: " . strlen($html));
error_log("VvvebJs Save Debug - Storage type: " . StorageConfig::getStorageType());
error_log("VvvebJs Save Debug - Current user: " . $storageManager->getCurrentUser());
// Try external storage first
$externalSuccess = false;
$externalError = '';
try {
$externalSuccess = $storageManager->saveFile($relativePath, $html);
if (!$externalSuccess) {
$externalError = 'Storage manager returned false';
}
} catch (Exception $e) {
$externalError = $e->getMessage();
error_log("VvvebJs Save Error: " . $externalError);
}
// Also save locally as backup/cache
$dir = dirname($file);
$localSuccess = false;
if (!is_dir($dir)) {
if (mkdir($dir, 0777, true)) {
error_log("VvvebJs: Created directory $dir");
}
}
if (is_dir($dir)) {
$localSuccess = file_put_contents($file, $html);
}
// Enhanced JSON response for AJAX requests
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
if ($externalSuccess || $localSuccess) {
echo json_encode([
'success' => true,
'message' => 'File saved successfully',
'file' => $relativePath,
'external_storage' => $externalSuccess,
'local_cache' => $localSuccess !== false,
'user' => $storageManager->getCurrentUser()
]);
} else {
$github = StorageConfig::getGitHubConfig();
echo json_encode([
'success' => false,
'message' => 'Failed to save file',
'external_error' => $externalError,
'debug_info' => [
'has_token' => !empty($github['token']),
'owner' => $github['owner'],
'repo' => $github['repo'],
'target_file' => $relativePath,
'user' => $storageManager->getCurrentUser()
]
]);
}
exit;
}
// Traditional text response for non-AJAX requests
if ($externalSuccess || $localSuccess) {
$message = "File saved '$file'";
if ($externalSuccess) {
$message .= " βœ… (external storage SUCCESS)";
} else {
$message .= " ❌ (external storage FAILED: $externalError)";
}
if ($localSuccess) {
$message .= " βœ… (local cache SUCCESS)";
} else {
$message .= " ❌ (local cache FAILED)";
}
// Additional GitHub debug info
if (!$externalSuccess) {
$github = StorageConfig::getGitHubConfig();
$message .= "\n\nπŸ” Debug Info:";
$message .= "\n- GitHub Token: " . (empty($github['token']) ? '❌ Missing' : 'βœ… Present');
$message .= "\n- GitHub Owner: " . ($github['owner'] ?: '❌ Missing');
$message .= "\n- GitHub Repo: " . ($github['repo'] ?: '❌ Missing');
$message .= "\n- GitHub Path: " . ($github['path'] ?: 'root');
$message .= "\n- Target file: " . $relativePath;
}
echo $message;
} else {
showError("Error saving file '$file'\nExternal: $externalError\nLocal: File write failed\nPossible causes are missing write permission or incorrect file path!");
}
} else {
// For AJAX requests, return JSON error
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Filename is empty!']);
exit;
}
showError('Filename is empty!');
}
} else {
// For AJAX requests, return JSON error
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Html content is empty!']);
exit;
}
showError('Html content is empty!');
}
}