1131 lines
40 KiB
PHP
1131 lines
40 KiB
PHP
<?php
|
||
// src/controllers/FolderController.php
|
||
|
||
require_once __DIR__ . '/../../config/config.php';
|
||
require_once PROJECT_ROOT . '/src/models/FolderModel.php';
|
||
|
||
class FolderController
|
||
{
|
||
/**
|
||
* @OA\Post(
|
||
* path="/api/folder/createFolder.php",
|
||
* summary="Create a new folder",
|
||
* description="Creates a new folder in the upload directory (under an optional parent) and creates an associated empty metadata file.",
|
||
* operationId="createFolder",
|
||
* tags={"Folders"},
|
||
* @OA\RequestBody(
|
||
* required=true,
|
||
* @OA\JsonContent(
|
||
* required={"folderName"},
|
||
* @OA\Property(property="folderName", type="string", example="NewFolder"),
|
||
* @OA\Property(property="parent", type="string", example="Documents")
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Folder created successfully",
|
||
* @OA\JsonContent(
|
||
* @OA\Property(property="success", type="boolean", example=true)
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Bad Request (e.g., invalid folder name)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=401,
|
||
* description="Unauthorized"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Invalid CSRF token or permission denied"
|
||
* )
|
||
* )
|
||
*
|
||
* Creates a new folder in the upload directory, optionally under a parent folder.
|
||
*
|
||
* @return void Outputs a JSON response.
|
||
*/
|
||
public function createFolder(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
|
||
// Ensure user is authenticated.
|
||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||
http_response_code(401);
|
||
echo json_encode(["error" => "Unauthorized"]);
|
||
exit;
|
||
}
|
||
|
||
// Ensure the request method is POST.
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
echo json_encode(['error' => 'Invalid request method.']);
|
||
exit;
|
||
}
|
||
|
||
// CSRF check.
|
||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||
$receivedToken = $headersArr['x-csrf-token'] ?? '';
|
||
if (!isset($_SESSION['csrf_token']) || trim($receivedToken) !== $_SESSION['csrf_token']) {
|
||
http_response_code(403);
|
||
echo json_encode(['error' => 'Invalid CSRF token.']);
|
||
exit;
|
||
}
|
||
|
||
// Check permissions.
|
||
$username = $_SESSION['username'] ?? '';
|
||
$userPermissions = loadUserPermissions($username);
|
||
if ($username && isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
|
||
http_response_code(403);
|
||
echo json_encode([
|
||
"success" => false,
|
||
"error" => "Read-only users are not allowed to create folders."
|
||
]);
|
||
exit;
|
||
}
|
||
|
||
// Get and decode JSON input.
|
||
$input = json_decode(file_get_contents('php://input'), true);
|
||
if (!isset($input['folderName'])) {
|
||
echo json_encode(['error' => 'Folder name not provided.']);
|
||
exit;
|
||
}
|
||
|
||
$folderName = trim($input['folderName']);
|
||
$parent = isset($input['parent']) ? trim($input['parent']) : "";
|
||
|
||
// Basic sanitation for folderName.
|
||
if (!preg_match(REGEX_FOLDER_NAME, $folderName)) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Invalid folder name.']);
|
||
exit;
|
||
}
|
||
|
||
// Optionally sanitize the parent.
|
||
if ($parent && !preg_match(REGEX_FOLDER_NAME, $parent)) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Invalid parent folder name.']);
|
||
exit;
|
||
}
|
||
|
||
// Delegate to FolderModel.
|
||
$result = FolderModel::createFolder($folderName, $parent);
|
||
echo json_encode($result);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Post(
|
||
* path="/api/folder/deleteFolder.php",
|
||
* summary="Delete an empty folder",
|
||
* description="Deletes a specified folder if it is empty and not the root folder, and also removes its metadata file.",
|
||
* operationId="deleteFolder",
|
||
* tags={"Folders"},
|
||
* @OA\RequestBody(
|
||
* required=true,
|
||
* @OA\JsonContent(
|
||
* required={"folder"},
|
||
* @OA\Property(property="folder", type="string", example="Documents/Subfolder")
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Folder deleted successfully",
|
||
* @OA\JsonContent(
|
||
* @OA\Property(property="success", type="boolean", example=true)
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Bad Request (e.g., invalid folder name or folder not empty)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=401,
|
||
* description="Unauthorized"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Invalid CSRF token or permission denied"
|
||
* )
|
||
* )
|
||
*
|
||
* Deletes a folder if it is empty and not the root folder.
|
||
*
|
||
* @return void Outputs a JSON response.
|
||
*/
|
||
public function deleteFolder(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
|
||
// Ensure user is authenticated.
|
||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||
http_response_code(401);
|
||
echo json_encode(["error" => "Unauthorized"]);
|
||
exit;
|
||
}
|
||
|
||
// Ensure the request is a POST.
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
echo json_encode(["error" => "Invalid request method."]);
|
||
exit;
|
||
}
|
||
|
||
// CSRF Protection.
|
||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||
$receivedToken = isset($headersArr['x-csrf-token']) ? trim($headersArr['x-csrf-token']) : '';
|
||
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
||
http_response_code(403);
|
||
echo json_encode(["error" => "Invalid CSRF token."]);
|
||
exit;
|
||
}
|
||
|
||
// Check user permissions.
|
||
$username = $_SESSION['username'] ?? '';
|
||
$userPermissions = loadUserPermissions($username);
|
||
if ($username && isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
|
||
echo json_encode(["error" => "Read-only users are not allowed to delete folders."]);
|
||
exit;
|
||
}
|
||
|
||
// Get and decode JSON input.
|
||
$input = json_decode(file_get_contents('php://input'), true);
|
||
if (!isset($input['folder'])) {
|
||
echo json_encode(["error" => "Folder name not provided."]);
|
||
exit;
|
||
}
|
||
|
||
$folder = trim($input['folder']);
|
||
// Prevent deletion of the root folder.
|
||
if (strtolower($folder) === 'root') {
|
||
echo json_encode(["error" => "Cannot delete root folder."]);
|
||
exit;
|
||
}
|
||
|
||
// Delegate to the model.
|
||
$result = FolderModel::deleteFolder($folder);
|
||
echo json_encode($result);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Post(
|
||
* path="/api/folder/renameFolder.php",
|
||
* summary="Rename a folder",
|
||
* description="Renames an existing folder and updates its associated metadata files.",
|
||
* operationId="renameFolder",
|
||
* tags={"Folders"},
|
||
* @OA\RequestBody(
|
||
* required=true,
|
||
* @OA\JsonContent(
|
||
* required={"oldFolder", "newFolder"},
|
||
* @OA\Property(property="oldFolder", type="string", example="Documents/OldFolder"),
|
||
* @OA\Property(property="newFolder", type="string", example="Documents/NewFolder")
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Folder renamed successfully",
|
||
* @OA\JsonContent(
|
||
* @OA\Property(property="success", type="boolean", example=true)
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Invalid folder names or folder does not exist"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=401,
|
||
* description="Unauthorized"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Invalid CSRF token or permission denied"
|
||
* )
|
||
* )
|
||
*
|
||
* Renames a folder by validating inputs and delegating to the model.
|
||
*
|
||
* @return void Outputs a JSON response.
|
||
*/
|
||
public function renameFolder(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
|
||
// Ensure user is authenticated.
|
||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||
http_response_code(401);
|
||
echo json_encode(["error" => "Unauthorized"]);
|
||
exit;
|
||
}
|
||
|
||
// Ensure the request method is POST.
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
echo json_encode(['error' => 'Invalid request method.']);
|
||
exit;
|
||
}
|
||
|
||
// CSRF Protection.
|
||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||
$receivedToken = isset($headersArr['x-csrf-token']) ? trim($headersArr['x-csrf-token']) : '';
|
||
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
||
http_response_code(403);
|
||
echo json_encode(["error" => "Invalid CSRF token."]);
|
||
exit;
|
||
}
|
||
|
||
// Check that the user is not read-only.
|
||
$username = $_SESSION['username'] ?? '';
|
||
$userPermissions = loadUserPermissions($username);
|
||
if ($username && isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
|
||
echo json_encode(["error" => "Read-only users are not allowed to rename folders."]);
|
||
exit;
|
||
}
|
||
|
||
// Get JSON input.
|
||
$input = json_decode(file_get_contents('php://input'), true);
|
||
if (!isset($input['oldFolder']) || !isset($input['newFolder'])) {
|
||
echo json_encode(['error' => 'Required folder names not provided.']);
|
||
exit;
|
||
}
|
||
|
||
$oldFolder = trim($input['oldFolder']);
|
||
$newFolder = trim($input['newFolder']);
|
||
|
||
// Validate folder names.
|
||
if (!preg_match(REGEX_FOLDER_NAME, $oldFolder) || !preg_match(REGEX_FOLDER_NAME, $newFolder)) {
|
||
echo json_encode(['error' => 'Invalid folder name(s).']);
|
||
exit;
|
||
}
|
||
|
||
// Delegate to the model.
|
||
$result = FolderModel::renameFolder($oldFolder, $newFolder);
|
||
echo json_encode($result);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Get(
|
||
* path="/api/folder/getFolderList.php",
|
||
* summary="Get list of folders",
|
||
* description="Retrieves the list of folders in the upload directory, including file counts and metadata file names for each folder.",
|
||
* operationId="getFolderList",
|
||
* tags={"Folders"},
|
||
* @OA\Parameter(
|
||
* name="folder",
|
||
* in="query",
|
||
* description="Optional folder name to filter the listing",
|
||
* required=false,
|
||
* @OA\Schema(type="string", example="Documents")
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Folder list retrieved successfully",
|
||
* @OA\JsonContent(
|
||
* type="array",
|
||
* @OA\Items(type="object")
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=401,
|
||
* description="Unauthorized"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Bad request"
|
||
* )
|
||
* )
|
||
*
|
||
* Retrieves the folder list and associated metadata.
|
||
*
|
||
* @return void Outputs JSON response.
|
||
*/
|
||
public function getFolderList(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
if (empty($_SESSION['authenticated'])) {
|
||
http_response_code(401);
|
||
echo json_encode(["error" => "Unauthorized"]);
|
||
exit;
|
||
}
|
||
|
||
$parent = $_GET['folder'] ?? null;
|
||
$folderList = FolderModel::getFolderList($parent);
|
||
echo json_encode($folderList);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Get(
|
||
* path="/api/folder/shareFolder.php",
|
||
* summary="Display a shared folder",
|
||
* description="Renders an HTML view of a shared folder's contents. Supports password protection, file listing with pagination, and an upload container if uploads are allowed.",
|
||
* operationId="shareFolder",
|
||
* tags={"Folders"},
|
||
* @OA\Parameter(
|
||
* name="token",
|
||
* in="query",
|
||
* description="The share token for the folder",
|
||
* required=true,
|
||
* @OA\Schema(type="string")
|
||
* ),
|
||
* @OA\Parameter(
|
||
* name="pass",
|
||
* in="query",
|
||
* description="The password if the folder is protected",
|
||
* required=false,
|
||
* @OA\Schema(type="string")
|
||
* ),
|
||
* @OA\Parameter(
|
||
* name="page",
|
||
* in="query",
|
||
* description="Page number for pagination",
|
||
* required=false,
|
||
* @OA\Schema(type="integer", example=1)
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Shared folder displayed",
|
||
* @OA\MediaType(mediaType="text/html")
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Invalid request"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Access forbidden (expired link or invalid password)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=404,
|
||
* description="Share folder not found"
|
||
* )
|
||
* )
|
||
*
|
||
* Displays a shared folder with file listings, pagination, and an upload container if allowed.
|
||
*
|
||
* @return void Outputs HTML content.
|
||
*/
|
||
|
||
function formatBytes($bytes)
|
||
{
|
||
if ($bytes < 1024) {
|
||
return $bytes . " B";
|
||
} elseif ($bytes < 1024 * 1024) {
|
||
return round($bytes / 1024, 2) . " KB";
|
||
} elseif ($bytes < 1024 * 1024 * 1024) {
|
||
return round($bytes / (1024 * 1024), 2) . " MB";
|
||
} else {
|
||
return round($bytes / (1024 * 1024 * 1024), 2) . " GB";
|
||
}
|
||
}
|
||
|
||
public function shareFolder(): void
|
||
{
|
||
// Retrieve GET parameters.
|
||
$token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_STRING);
|
||
$providedPass = filter_input(INPUT_GET, 'pass', FILTER_SANITIZE_STRING);
|
||
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT);
|
||
if ($page === false || $page < 1) {
|
||
$page = 1;
|
||
}
|
||
|
||
if (empty($token)) {
|
||
http_response_code(400);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => "Missing token."]);
|
||
exit;
|
||
}
|
||
|
||
// Delegate to the model.
|
||
$data = FolderModel::getSharedFolderData($token, $providedPass, $page);
|
||
|
||
// If a password is needed, output an HTML form.
|
||
if (isset($data['needs_password']) && $data['needs_password'] === true) {
|
||
header("Content-Type: text/html; charset=utf-8");
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Enter Password</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
padding: 20px;
|
||
background-color: #f7f7f7;
|
||
}
|
||
|
||
.container {
|
||
max-width: 400px;
|
||
margin: 80px auto;
|
||
background: #fff;
|
||
padding: 20px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
input[type="password"],
|
||
button {
|
||
width: 100%;
|
||
padding: 10px;
|
||
margin: 10px 0;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
button {
|
||
background-color: #007BFF;
|
||
border: none;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="container">
|
||
<h2>Folder Protected</h2>
|
||
<p>This folder is protected by a password. Please enter the password to view its contents.</p>
|
||
<form method="get" action="/api/folder/shareFolder.php">
|
||
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<label for="pass">Password:</label>
|
||
<input type="password" name="pass" id="pass" required>
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
</div>
|
||
</body>
|
||
|
||
</html>
|
||
<?php
|
||
exit;
|
||
}
|
||
|
||
// If the model returned an error, output JSON error.
|
||
if (isset($data['error'])) {
|
||
http_response_code(403);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => $data['error']]);
|
||
exit;
|
||
}
|
||
|
||
// Load admin config so we can pull the sharedMaxUploadSize
|
||
require_once PROJECT_ROOT . '/src/models/AdminModel.php';
|
||
$adminConfig = AdminModel::getConfig();
|
||
$sharedMaxUploadSize = isset($adminConfig['sharedMaxUploadSize']) && is_numeric($adminConfig['sharedMaxUploadSize'])
|
||
? (int)$adminConfig['sharedMaxUploadSize']
|
||
: null;
|
||
|
||
// For human‐readable formatting
|
||
function formatBytes($bytes)
|
||
{
|
||
if ($bytes < 1024) {
|
||
return $bytes . " B";
|
||
} elseif ($bytes < 1024 * 1024) {
|
||
return round($bytes / 1024, 2) . " KB";
|
||
} elseif ($bytes < 1024 * 1024 * 1024) {
|
||
return round($bytes / (1024 * 1024), 2) . " MB";
|
||
} else {
|
||
return round($bytes / (1024 * 1024 * 1024), 2) . " GB";
|
||
}
|
||
}
|
||
|
||
// Extract data for the HTML view.
|
||
$folderName = $data['folder'];
|
||
$files = $data['files'];
|
||
$currentPage = $data['currentPage'];
|
||
$totalPages = $data['totalPages'];
|
||
|
||
// Build the HTML view.
|
||
header("Content-Type: text/html; charset=utf-8");
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Shared Folder: <?php echo htmlspecialchars($folderName, ENT_QUOTES, 'UTF-8'); ?></title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
body {
|
||
background: #f2f2f2;
|
||
font-family: Arial, sans-serif;
|
||
padding: 0px 20px 20px 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
margin-top: 0;
|
||
}
|
||
|
||
.header h1 {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #ddd;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background: #007BFF;
|
||
color: #fff;
|
||
}
|
||
|
||
.pagination {
|
||
text-align: center;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.pagination a,
|
||
.pagination span {
|
||
margin: 0 5px;
|
||
padding: 8px 12px;
|
||
background: #007BFF;
|
||
color: #fff;
|
||
border-radius: 4px;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.pagination span.current {
|
||
background: #0056b3;
|
||
}
|
||
|
||
/* Gallery view styles if needed */
|
||
.shared-gallery-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 10px;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.shared-gallery-card {
|
||
border: 1px solid #ccc;
|
||
padding: 5px;
|
||
text-align: center;
|
||
}
|
||
|
||
.shared-gallery-card img {
|
||
max-width: 100%;
|
||
display: block;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* Upload container */
|
||
.upload-container {
|
||
margin-top: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.upload-container h3 {
|
||
font-size: 1.4rem;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.upload-container form {
|
||
display: inline-block;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.upload-container button {
|
||
background-color: #28a745;
|
||
border: none;
|
||
color: #fff;
|
||
padding: 10px 20px;
|
||
font-size: 1rem;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.upload-container button:hover {
|
||
background-color: #218838;
|
||
}
|
||
|
||
.footer {
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
font-size: 0.9rem;
|
||
color: #777;
|
||
}
|
||
|
||
.toggle-btn {
|
||
background-color: #007BFF;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 8px 16px;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.toggle-btn:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.pagination a:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.pagination span {
|
||
cursor: default;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="header">
|
||
<h1>Shared Folder: <?php echo htmlspecialchars($folderName, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||
</div>
|
||
<div class="container">
|
||
<!-- Toggle Button -->
|
||
<button id="toggleBtn" class="toggle-btn">Switch to Gallery View</button>
|
||
|
||
<!-- List View Container -->
|
||
<div id="listViewContainer">
|
||
<?php if (empty($files)): ?>
|
||
<p style="text-align:center;">This folder is empty.</p>
|
||
<?php else: ?>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Filename</th>
|
||
<th>Size</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php
|
||
// For each file, build a download link using your downloadSharedFile endpoint.
|
||
foreach ($files as $file):
|
||
$filePath = $data['realFolderPath'] . DIRECTORY_SEPARATOR . $file;
|
||
$fileSize = file_exists($filePath) ? formatBytes(filesize($filePath)) : "Unknown";
|
||
$downloadLink = "/api/folder/downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file);
|
||
?>
|
||
<tr>
|
||
<td>
|
||
<a href="<?php echo htmlspecialchars($downloadLink, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<?php echo htmlspecialchars($file, ENT_QUOTES, 'UTF-8'); ?>
|
||
<span class="download-icon">⇩</span>
|
||
</a>
|
||
</td>
|
||
<td><?php echo $fileSize; ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- Gallery View Container (hidden by default) -->
|
||
<div id="galleryViewContainer" style="display:none;"></div>
|
||
|
||
<!-- Pagination Controls -->
|
||
<div class="pagination">
|
||
<?php if ($currentPage > 1): ?>
|
||
<a href="/api/folder/shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage - 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Prev</a>
|
||
<?php else: ?>
|
||
<span>Prev</span>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
$startPage = max(1, $currentPage - 2);
|
||
$endPage = min($totalPages, $currentPage + 2);
|
||
for ($i = $startPage; $i <= $endPage; $i++): ?>
|
||
<?php if ($i == $currentPage): ?>
|
||
<span class="current"><?php echo $i; ?></span>
|
||
<?php else: ?>
|
||
<a href="/api/folder/shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $i; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>"><?php echo $i; ?></a>
|
||
<?php endif; ?>
|
||
<?php endfor; ?>
|
||
|
||
<?php if ($currentPage < $totalPages): ?>
|
||
<a href="/api/folder/shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage + 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Next</a>
|
||
<?php else: ?>
|
||
<span>Next</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- Upload Container (if uploads are allowed by the share record) -->
|
||
<?php if (isset($data['record']['allowUpload']) && $data['record']['allowUpload'] == 1): ?>
|
||
<div class="upload-container">
|
||
<h3>Upload File
|
||
<?php if ($sharedMaxUploadSize !== null): ?>
|
||
(<?php echo formatBytes($sharedMaxUploadSize); ?> max size)
|
||
<?php endif; ?>
|
||
</h3>
|
||
<form action="/api/folder/uploadToSharedFolder.php" method="post" enctype="multipart/form-data">
|
||
<!-- Pass the share token so the upload endpoint can verify -->
|
||
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="file" name="fileToUpload" required>
|
||
<br><br>
|
||
<button type="submit">Upload</button>
|
||
</form>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="footer">
|
||
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
||
</div>
|
||
<!-- non-executing JSON payload, never blocked by CSP -->
|
||
<script type="application/json" id="shared-data">
|
||
{
|
||
"token": <?php echo json_encode($token, JSON_HEX_TAG); ?>,
|
||
"files": <?php echo json_encode($files, JSON_HEX_TAG); ?>
|
||
}
|
||
</script>
|
||
<script src="/js/sharedFolderView.js" defer></script>
|
||
</body>
|
||
|
||
</html>
|
||
<?php
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Post(
|
||
* path="/api/folder/createShareFolderLink.php",
|
||
* summary="Create a share link for a folder",
|
||
* description="Generates a secure share link for a folder along with optional password protection and upload settings.",
|
||
* operationId="createShareFolderLink",
|
||
* tags={"Folders"},
|
||
* @OA\RequestBody(
|
||
* required=true,
|
||
* @OA\JsonContent(
|
||
* required={"folder"},
|
||
* @OA\Property(property="folder", type="string", example="Documents"),
|
||
* @OA\Property(property="expirationMinutes", type="integer", example=60),
|
||
* @OA\Property(property="password", type="string", example="secret"),
|
||
* @OA\Property(property="allowUpload", type="integer", example=1)
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="Share link created successfully",
|
||
* @OA\JsonContent(
|
||
* @OA\Property(property="token", type="string", example="a1b2c3d4..."),
|
||
* @OA\Property(property="expires", type="integer", example=1623456789),
|
||
* @OA\Property(property="link", type="string", example="https://yourdomain.com/api/folder/shareFolder.php?token=...")
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Invalid input"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=401,
|
||
* description="Unauthorized"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Read-only users are not allowed to create share links"
|
||
* )
|
||
* )
|
||
*
|
||
* Creates a share link for a folder by validating input and delegating to the FolderModel.
|
||
*
|
||
* @return void Outputs a JSON response.
|
||
*/
|
||
public function createShareFolderLink(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
|
||
// Auth check
|
||
if (empty($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||
http_response_code(401);
|
||
echo json_encode(["error" => "Unauthorized"]);
|
||
exit;
|
||
}
|
||
|
||
// Read-only check
|
||
$username = $_SESSION['username'] ?? '';
|
||
$perms = loadUserPermissions($username);
|
||
if ($username && !empty($perms['readOnly'])) {
|
||
http_response_code(403);
|
||
echo json_encode(["error" => "Read-only users are not allowed to create share folders."]);
|
||
exit;
|
||
}
|
||
|
||
// Input
|
||
$in = json_decode(file_get_contents("php://input"), true);
|
||
if (!$in || !isset($in['folder'])) {
|
||
http_response_code(400);
|
||
echo json_encode(["error" => "Invalid input."]);
|
||
exit;
|
||
}
|
||
|
||
$folder = trim($in['folder']);
|
||
$value = isset($in['expirationValue']) ? intval($in['expirationValue']) : 60;
|
||
$unit = $in['expirationUnit'] ?? 'minutes';
|
||
$password = $in['password'] ?? '';
|
||
$allowUpload = intval($in['allowUpload'] ?? 0);
|
||
|
||
// Folder name validation
|
||
if ($folder !== 'root' && !preg_match(REGEX_FOLDER_NAME, $folder)) {
|
||
http_response_code(400);
|
||
echo json_encode(["error" => "Invalid folder name."]);
|
||
exit;
|
||
}
|
||
|
||
// Convert to seconds
|
||
switch ($unit) {
|
||
case 'seconds':
|
||
$seconds = $value;
|
||
break;
|
||
case 'hours':
|
||
$seconds = $value * 3600;
|
||
break;
|
||
case 'days':
|
||
$seconds = $value * 86400;
|
||
break;
|
||
case 'minutes':
|
||
default:
|
||
$seconds = $value * 60;
|
||
break;
|
||
}
|
||
|
||
// Delegate
|
||
$res = FolderModel::createShareFolderLink($folder, $seconds, $password, $allowUpload);
|
||
echo json_encode($res);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Get(
|
||
* path="/api/folder/downloadSharedFile.php",
|
||
* summary="Download a file from a shared folder",
|
||
* description="Retrieves and serves a file from a shared folder based on a share token.",
|
||
* operationId="downloadSharedFile",
|
||
* tags={"Folders"},
|
||
* @OA\Parameter(
|
||
* name="token",
|
||
* in="query",
|
||
* description="The share folder token",
|
||
* required=true,
|
||
* @OA\Schema(type="string")
|
||
* ),
|
||
* @OA\Parameter(
|
||
* name="file",
|
||
* in="query",
|
||
* description="The filename to download",
|
||
* required=true,
|
||
* @OA\Schema(type="string")
|
||
* ),
|
||
* @OA\Response(
|
||
* response=200,
|
||
* description="File served successfully",
|
||
* @OA\MediaType(mediaType="application/octet-stream")
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Bad Request (missing parameters, invalid file name, etc.)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Access forbidden (e.g., expired share link)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=404,
|
||
* description="File not found"
|
||
* )
|
||
* )
|
||
*
|
||
* Downloads a file from a shared folder based on a token.
|
||
*
|
||
* @return void Outputs the file with proper headers.
|
||
*/
|
||
public function downloadSharedFile(): void
|
||
{
|
||
// Retrieve and sanitize GET parameters.
|
||
$token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_STRING);
|
||
$file = filter_input(INPUT_GET, 'file', FILTER_SANITIZE_STRING);
|
||
|
||
if (empty($token) || empty($file)) {
|
||
http_response_code(400);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => "Missing token or file parameter."]);
|
||
exit;
|
||
}
|
||
|
||
// Delegate to the model.
|
||
$result = FolderModel::getSharedFileInfo($token, $file);
|
||
if (isset($result['error'])) {
|
||
http_response_code(404);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => $result['error']]);
|
||
exit;
|
||
}
|
||
|
||
$realFilePath = $result['realFilePath'];
|
||
$mimeType = $result['mimeType'];
|
||
|
||
// Serve the file.
|
||
header("Content-Type: " . $mimeType);
|
||
$ext = strtolower(pathinfo($realFilePath, PATHINFO_EXTENSION));
|
||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'])) {
|
||
header('Content-Disposition: inline; filename="' . basename($realFilePath) . '"');
|
||
} else {
|
||
header('Content-Disposition: attachment; filename="' . basename($realFilePath) . '"');
|
||
}
|
||
header('Content-Length: ' . filesize($realFilePath));
|
||
readfile($realFilePath);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* @OA\Post(
|
||
* path="/api/folder/uploadToSharedFolder.php",
|
||
* summary="Upload a file to a shared folder",
|
||
* description="Handles file upload to a shared folder using a share token. Validates file size, extension, and uploads the file to the shared folder, updating metadata accordingly.",
|
||
* operationId="uploadToSharedFolder",
|
||
* tags={"Folders"},
|
||
* @OA\RequestBody(
|
||
* required=true,
|
||
* description="Multipart form data containing the share token and file to upload.",
|
||
* @OA\MediaType(
|
||
* mediaType="multipart/form-data",
|
||
* @OA\Schema(
|
||
* required={"token", "fileToUpload"},
|
||
* @OA\Property(property="token", type="string"),
|
||
* @OA\Property(property="fileToUpload", type="string", format="binary")
|
||
* )
|
||
* )
|
||
* ),
|
||
* @OA\Response(
|
||
* response=302,
|
||
* description="Redirects to the shared folder page on success."
|
||
* ),
|
||
* @OA\Response(
|
||
* response=400,
|
||
* description="Bad Request (missing token, file upload error, file type/size not allowed)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=403,
|
||
* description="Forbidden (share link expired or uploads not allowed)"
|
||
* ),
|
||
* @OA\Response(
|
||
* response=500,
|
||
* description="Server error during file move"
|
||
* )
|
||
* )
|
||
*
|
||
* Handles uploading a file to a shared folder.
|
||
*
|
||
* @return void Redirects upon successful upload or outputs JSON errors.
|
||
*/
|
||
public function uploadToSharedFolder(): void
|
||
{
|
||
// Ensure request is POST.
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
http_response_code(405);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => "Method not allowed."]);
|
||
exit;
|
||
}
|
||
|
||
// Ensure the share token is provided.
|
||
if (empty($_POST['token'])) {
|
||
http_response_code(400);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => "Missing share token."]);
|
||
exit;
|
||
}
|
||
$token = trim($_POST['token']);
|
||
|
||
// Delegate the upload to the model.
|
||
if (!isset($_FILES['fileToUpload'])) {
|
||
http_response_code(400);
|
||
header('Content-Type: application/json');
|
||
echo json_encode(["error" => "No file was uploaded."]);
|
||
exit;
|
||
}
|
||
$fileUpload = $_FILES['fileToUpload'];
|
||
|
||
$result = FolderModel::uploadToSharedFolder($token, $fileUpload);
|
||
if (isset($result['error'])) {
|
||
http_response_code(400);
|
||
header('Content-Type: application/json');
|
||
echo json_encode($result);
|
||
exit;
|
||
}
|
||
|
||
// Optionally, set a flash message in session.
|
||
$_SESSION['upload_message'] = "File uploaded successfully.";
|
||
|
||
// Redirect back to the shared folder view.
|
||
$redirectUrl = "/api/folder/shareFolder.php?token=" . urlencode($token);
|
||
header("Location: " . $redirectUrl);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* GET /api/folder/getShareFolderLinks.php
|
||
*/
|
||
public function getAllShareFolderLinks(): void
|
||
{
|
||
header('Content-Type: application/json');
|
||
$shareFile = META_DIR . 'share_folder_links.json';
|
||
$links = file_exists($shareFile)
|
||
? json_decode(file_get_contents($shareFile), true) ?? []
|
||
: [];
|
||
$now = time();
|
||
$cleaned = [];
|
||
|
||
// 1) Remove expired
|
||
foreach ($links as $token => $record) {
|
||
if (!empty($record['expires']) && $record['expires'] < $now) {
|
||
continue;
|
||
}
|
||
$cleaned[$token] = $record;
|
||
}
|
||
|
||
// 2) Persist back if anything was pruned
|
||
if (count($cleaned) !== count($links)) {
|
||
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
|
||
}
|
||
|
||
echo json_encode($cleaned);
|
||
}
|
||
|
||
/**
|
||
* POST /api/folder/deleteShareFolderLink.php
|
||
*/
|
||
public function deleteShareFolderLink()
|
||
{
|
||
header('Content-Type: application/json');
|
||
$token = $_POST['token'] ?? '';
|
||
if (!$token) {
|
||
echo json_encode(['success' => false, 'error' => 'No token provided']);
|
||
return;
|
||
}
|
||
|
||
$deleted = FolderModel::deleteShareFolderLink($token);
|
||
if ($deleted) {
|
||
echo json_encode(['success' => true]);
|
||
} else {
|
||
echo json_encode(['success' => false, 'error' => 'Not found']);
|
||
}
|
||
}
|
||
}
|