İlk temizlik tamamlandı bir önceki projeden

This commit is contained in:
Ümit Tunç
2026-04-28 21:14:25 +03:00
commit f80443aec0
10000 changed files with 959965 additions and 0 deletions
@@ -0,0 +1,664 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Models\DocumentManagerPermission;
class CustomElfinderController extends Controller
{
/**
* Cache for access results to avoid repeated calculations
*/
private static $accessCache = [];
/**
* Cached user instance
*/
private static $currentUser = null;
/**
* Custom access control for Elfinder
*
* @param string $attr - Operation type (read, write, rm, etc.)
* @param string $path - File/folder path
* @param array $data - Additional data
* @param object $volume - Volume object
* @return bool - Whether access is allowed
*/
public static function access($attr, $path, $data, $volume)
{
// 1. Check access cache (Critical Optimization)
// Using path + attr as key
$cacheKey = $path . '#' . $attr;
if (isset(self::$accessCache[$cacheKey])) {
return self::$accessCache[$cacheKey];
}
// 2. Get User (Cached)
if (self::$currentUser === null) {
self::$currentUser = Auth::user();
}
$user = self::$currentUser;
// If user is not authenticated, deny access
if (!$user) {
return self::$accessCache[$cacheKey] = false;
}
// ---------------------------------------------------------
// GLOBAL EXCLUSION LIST (Hidden for EVERYONE - INCLUDING ADMINS)
// ---------------------------------------------------------
// Moved BEFORE admin check to ensure these are hidden for admins too
// Get folder name from path early (needed for checks)
$folderName = self::getFolderNameFromPath($path);
// Default exclusions if setting is empty
$defaultExclusions = [
'.tmb',
'cache',
'register_creator_errors',
'test',
'NewFolder 1',
'files',
'document-templates',
'Excel Form Templates',
'Archive'
];
// Get from settings - Cache this if possible or trust Laravel's config cache
static $globalExcludedFolders = null;
if ($globalExcludedFolders === null) {
$settingsJson = setting('global_excluded_folders');
$settingsExclusions = [];
if (!empty($settingsJson)) {
$settingsExclusions = json_decode($settingsJson, true);
}
$globalExcludedFolders = !empty($settingsExclusions) ? $settingsExclusions : $defaultExclusions;
}
// Check if it is a root folder (no slashes in normalized path)
$isRootFolder = strpos($folderName, '/') === false;
if ($isRootFolder && in_array($folderName, $globalExcludedFolders)) {
// Hide from list
if ($attr === 'hidden') {
return self::$accessCache[$cacheKey] = true;
}
// Block read access to effectively hide contents too
if ($attr === 'read') {
return self::$accessCache[$cacheKey] = false;
}
}
// ---------------------------------------------------------
// 3. Admin Optimization (Fast Track)
if ($user->level == "Admin") {
// Validate path for security (fast check)
if (strpos($path, '..') !== false) {
return self::$accessCache[$cacheKey] = false;
}
// Special handling for operations that need false (NOT locked/hidden)
if ($attr === 'hidden' || $attr === 'locked') {
return self::$accessCache[$cacheKey] = false; // false means NOT hidden/locked (writable/deletable)
}
return self::$accessCache[$cacheKey] = true; // true means ALLOW operation (read/write/rm/etc)
}
// Validate path for security (Normal users)
if (!self::validatePath($path)) {
Log::warning("Path validation failed", ['path' => $path, 'user' => $user->level]);
return self::$accessCache[$cacheKey] = false;
}
// folderName already retrieved above for global exclusion check
// $folderName = self::getFolderNameFromPath($path);
// ---------------------------------------------------------
// GLOBAL EXCLUSION LIST (Moved to top)
// ---------------------------------------------------------
// For non-admin users, handle system folders based on operation
if (self::isSystemFolder($folderName)) {
// For these operations, explicitly hide/lock system folders
if (in_array($attr, ['read', 'write', 'upload', 'mkdir', 'mkfile', 'rename', 'copy', 'move', 'rm', 'edit'])) {
return self::$accessCache[$cacheKey] = false; // Block these operations
}
// For hidden check, return true to hide
if ($attr === 'hidden') {
return self::$accessCache[$cacheKey] = true; // Hide system folders from non-admin
}
// For locked check, return true to lock
if ($attr === 'locked') {
return self::$accessCache[$cacheKey] = true; // Lock system folders for non-admin
}
// For other operations, return null (use default behavior)
return self::$accessCache[$cacheKey] = null;
}
// For other users, implement basic permission checks
$module = "document-manager";
// Check basic module permission first
// Cache basic permissions
static $modulePermissions = [];
if (!isset($modulePermissions[$user->level])) {
$modulePermissions[$user->level] = [
'read' => isAuth($module, "read"),
'write' => isAuth($module, "write"),
'modify' => isAuth($module, "modify")
];
}
$hasModuleRead = $modulePermissions[$user->level]['read'];
$hasModuleWrite = $modulePermissions[$user->level]['write'];
$hasModuleModify = $modulePermissions[$user->level]['modify'];
/*
// DEBUG: Check if PTO user has basic permissions
if ($user->level === 'Manager (PTO)') {
error_log("PTO User Basic Permissions - Read: " . ($hasModuleRead ? 'YES' : 'NO') . ", Write: " . ($hasModuleWrite ? 'YES' : 'NO') . ", Modify: " . ($hasModuleModify ? 'YES' : 'NO'));
}
*/
// Debug logging - enabled for testing
/*
Log::info("Elfinder Access Check", [
'user_level' => $user->level,
'user_id' => $user->id ?? 'unknown',
'attr' => $attr,
'path' => $path,
'folder_name' => $folderName,
'module' => $module,
'permission_separation' => 'write_edit_separated',
'is_dir' => is_dir($path),
'is_file' => is_file($path),
'path_exists' => file_exists($path),
'has_module_read' => $hasModuleRead,
'has_module_write' => $hasModuleWrite,
'has_module_modify' => $hasModuleModify
]);
*/
switch($attr) {
case 'read':
// For folders, check if user should see this folder at all
if (is_dir($path)) {
// First check if this is a system folder for non-admin users
if ($user->level !== "Admin" && self::isSystemFolder($folderName)) {
return self::$accessCache[$cacheKey] = false; // Block system folders from non-admin
}
return self::$accessCache[$cacheKey] = self::checkFolderVisibility($user, $folderName, $module);
} else {
// For files, check if parent folder is system folder for non-admin
if ($user->level !== "Admin" && self::isSystemFolder($folderName)) {
return self::$accessCache[$cacheKey] = false; // Block files in system folders from non-admin
}
// For files, check read permission
return self::checkFolderReadPermission($user, $folderName, $module);
}
case 'write':
case 'upload':
case 'mkdir':
case 'mkfile':
case 'rename':
case 'copy':
case 'move':
// Check if user has write permission for this specific folder
$writeAuth = self::checkFolderWritePermission($user, $folderName, $module);
// Log::info("Write permission check", ['result' => $writeAuth, 'folder' => $folderName, 'operation' => $attr]);
return self::$accessCache[$cacheKey] = $writeAuth;
case 'edit':
// Special handling for edit operations - check file type and edit permission
$editAuth = self::checkFileEditPermission($user, $path, $module);
// Log::info("Edit permission check", ['result' => $editAuth, 'path' => $path, 'operation' => $attr]);
return self::$accessCache[$cacheKey] = $editAuth;
case 'rm':
// Check if user has delete permission for this specific folder
$rmAuth = self::checkFolderDeletePermission($user, $folderName, $module);
// Log::info("Remove permission check", ['result' => $rmAuth, 'folder' => $folderName]);
// Additional protection for critical files
if (self::isCriticalFile($path)) {
// Log::info("Critical file protection - delete blocked", ['path' => $path]);
return self::$accessCache[$cacheKey] = false;
}
return self::$accessCache[$cacheKey] = $rmAuth;
case 'hidden':
// Check if folder should be hidden from user
// For non-admin users, hide system folders
if ($user->level !== "Admin") {
if (self::isSystemFolder($folderName)) {
return self::$accessCache[$cacheKey] = true; // Hide system folders from non-admin
}
}
// Check general visibility
return self::$accessCache[$cacheKey] = !self::checkFolderVisibility($user, $folderName, $module);
default:
// For other operations, return null for default behavior
/*
Log::info("Default permission check", [
'attr' => $attr,
'folder' => $folderName,
'user_level' => $user->level
]);
*/
return self::$accessCache[$cacheKey] = null; // Use Elfinder's default behavior
}
}
/**
* Cache for folder names to improve performance
*/
private static $folderNameCache = [];
/**
* Get folder name from path with improved path normalization (supports subfolders)
*/
private static function getFolderNameFromPath($path)
{
// Check cache first
if (isset(self::$folderNameCache[$path])) {
return self::$folderNameCache[$path];
}
$originalPath = $path;
// Normalize path separators
$path = str_replace('\\', '/', $path);
// Remove common path prefixes (case-insensitive)
$path = preg_replace('#^.*?/storage/documents/?#i', '', $path);
$path = preg_replace('#^.*?/storage/office-server/?#i', '', $path);
$path = preg_replace('#^.*?/public/storage/documents/?#i', '', $path);
$path = preg_replace('#^.*?/public/storage/office-server/?#i', '', $path);
$path = preg_replace('#^.*?/Applications/MAMP/htdocs/stellar/public/storage/documents/?#i', '', $path);
$path = preg_replace('#^.*?/Applications/MAMP/htdocs/stellar/public/storage/office-server/?#i', '', $path);
// Additional path patterns for Elfinder
$path = preg_replace('#^.*?/storage/documents$#i', '', $path);
$path = preg_replace('#^.*?/storage/documents/$#i', '', $path);
// Clean up any remaining leading/trailing slashes
$path = trim($path, '/');
// For subfolder support, return the full relative path instead of just first folder
// This allows us to check permissions for specific subfolders
$folderName = $path;
// Log path processing for debugging
/*
Log::info("Path processing", [
'original_path' => $originalPath,
'normalized_path' => $path,
'folder_name' => $folderName,
'has_slashes' => strpos($folderName, '/') !== false,
'is_empty' => empty($folderName)
]);
*/
self::$folderNameCache[$originalPath] = $folderName;
return $folderName;
}
/**
* Check if folder should be visible to user (for folder listing)
*/
private static function checkFolderVisibility($user, $folderName, $module)
{
// Root path - always visible if user has basic read permission
if (empty($folderName)) {
return isAuth($module, "read");
}
// Office Server files - only Admin can see
if (self::isOfficeServerPath($folderName)) {
return $user->level == "Admin";
}
// Check if this is a system folder - only Admin can see
if (self::isSystemFolder($folderName)) {
return $user->level == "Admin";
}
// Check if user has any permission for this folder (read, write, or edit)
// This includes both explicit permissions and inherited permissions
$hasReadPermission = self::checkFolderReadPermission($user, $folderName, $module);
$hasWritePermission = self::checkFolderWritePermission($user, $folderName, $module);
$hasEditPermission = self::checkFolderEditPermission($user, $folderName, $module);
return $hasReadPermission || $hasWritePermission || $hasEditPermission;
}
/**
* Check if user has read permission for specific folder
*/
private static function checkFolderReadPermission($user, $folderName, $module)
{
// Root path - allow access if user has basic read permission
if (empty($folderName)) {
return isAuth($module, "read");
}
// Office Server files - only Admin can read
if (self::isOfficeServerPath($folderName)) {
return $user->level == "Admin";
}
// Check dynamic permission using cached/recursive check
return self::checkDynamicPermission($user->level, $folderName, 'read');
}
/**
* Check if user has write permission for specific folder
* Write permission covers: upload, create, rename, copy, move, delete
*/
private static function checkFolderWritePermission($user, $folderName, $module)
{
// Office Server files - only Admin can write
if (self::isOfficeServerPath($folderName)) {
return $user->level == "Admin";
}
// Check dynamic permission using cached/recursive check
return self::checkDynamicPermission($user->level, $folderName, 'write');
}
/**
* Check if user has edit permission for specific folder
* Edit permission covers: edit file content only (not file operations)
*/
private static function checkFolderEditPermission($user, $folderName, $module)
{
// Office Server files - only Admin can edit
if (self::isOfficeServerPath($folderName)) {
return $user->level == "Admin";
}
// Check dynamic permission using cached/recursive check
return self::checkDynamicPermission($user->level, $folderName, 'edit');
}
/**
* Check if user has delete permission for specific folder
*/
private static function checkFolderDeletePermission($user, $folderName, $module)
{
// Office Server files - only Admin can delete
if (self::isOfficeServerPath($folderName)) {
return $user->level == "Admin";
}
// Delete permission uses write permission (write includes delete operations)
return self::checkFolderWritePermission($user, $folderName, $module);
}
/**
* Check if user can edit specific file types based on edit permission
* Edit permission covers: edit file content only (not file operations)
*/
private static function checkFileEditPermission($user, $path, $module)
{
// Get file extension
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
// Define editable file types
$editableFileTypes = ['pdf', 'xlsx', 'xlsb', 'csv', 'doc', 'docx', 'txt'];
// Check if file type is editable
if (!in_array($extension, $editableFileTypes)) {
Log::info("File type not editable", ['extension' => $extension, 'path' => $path]);
return false;
}
// Check folder-based permissions for edit
$folderName = self::getFolderNameFromPath($path);
// Check dynamic permission using cached/recursive check
return self::checkDynamicPermission($user->level, $folderName, 'edit');
}
/**
* Static cache for permissions during a single request
* Structure: ['UserLevel' => ['folder/path' => ['read' => bool, 'write' => bool]]]
*/
private static $permissionsCache = [];
/**
* Load all permissions for a user level into static cache
*/
private static function loadPermissionsIntoCache($userLevel)
{
if (isset(self::$permissionsCache[$userLevel])) {
return;
}
// Get all permissions from Model (which uses Redis/File Cache)
// This returns a collection grouped by folder_path
$rawPermissions = \App\Models\DocumentManagerPermission::getUserLevelPermissions($userLevel);
$formatted = [];
foreach ($rawPermissions as $path => $permissions) {
$formatted[$path] = [];
foreach ($permissions as $perm) {
// Store the boolean value directly
$formatted[$path][$perm->permission_type] = $perm->is_allowed;
}
}
self::$permissionsCache[$userLevel] = $formatted;
Log::info("Permissions loaded into static cache", [
'user_level' => $userLevel,
'folder_count' => count($formatted)
]);
}
/**
* Check dynamic permission using cached data with inheritance
*/
private static function checkDynamicPermission($userLevel, $folderName, $permissionType)
{
// Ensure cache is loaded
self::loadPermissionsIntoCache($userLevel);
// 1. Check for explicit permission on this folder
if (isset(self::$permissionsCache[$userLevel][$folderName])) {
$folderPerms = self::$permissionsCache[$userLevel][$folderName];
if (isset($folderPerms[$permissionType])) {
// Explicit permission found (either true or false)
// Return exactly what is set, do not inherit if explicitly set
return $folderPerms[$permissionType];
}
}
// 2. If no explicit permission, check parent folder inheritance
// BUT ONLY for subfolders, not for main folders
$parentFolder = self::getParentFolder($folderName);
if ($parentFolder && $parentFolder !== $folderName && strpos($folderName, '/') !== false) {
$inherited = self::checkDynamicPermission($userLevel, $parentFolder, $permissionType);
if ($inherited) {
// Log only if allowed via inheritance (to reduce noise)
// Log::info("Permission granted via inheritance", ['folder' => $folderName, 'parent' => $parentFolder, 'type' => $permissionType]);
}
return $inherited;
}
// 3. No explicit permission and no parent - default deny
return false;
}
/**
* Get parent folder from a folder path
*/
private static function getParentFolder($folderPath)
{
// Remove trailing slash if exists
$folderPath = rtrim($folderPath, '/');
// Split by slash
$parts = explode('/', $folderPath);
// If only one part, no parent
if (count($parts) <= 1) {
return null;
}
// Remove last part to get parent
array_pop($parts);
return implode('/', $parts);
}
/**
* Check if file is a critical system file that should be protected
*/
private static function isCriticalFile($path)
{
$fileName = basename($path);
$criticalFiles = [
'index.php',
'config.php',
'.htaccess',
'web.config',
'robots.txt',
'sitemap.xml',
'.env',
'composer.json',
'composer.lock',
'package.json',
'yarn.lock'
];
return in_array(strtolower($fileName), array_map('strtolower', $criticalFiles));
}
/**
* Validate file path for security
*/
private static function validatePath($path)
{
// Check for path traversal attempts
if (strpos($path, '..') !== false || strpos($path, '//') !== false) {
Log::warning("Path traversal attempt detected", ['path' => $path]);
return false;
}
// Check for suspicious file extensions
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$dangerousExtensions = ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'bat', 'cmd', 'sh', 'js'];
if (in_array($extension, $dangerousExtensions)) {
Log::warning("Dangerous file extension detected", ['path' => $path, 'extension' => $extension]);
return false;
}
return true;
}
/**
* Get file size in human readable format
*/
private static function formatFileSize($bytes)
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, 2) . ' ' . $units[$i];
}
/**
* Check if path is in Office Server directory
*/
private static function isOfficeServerPath($folderName)
{
// Check if the path contains office-server
return strpos($folderName, 'office-server') !== false ||
strpos($folderName, 'Office_Server') !== false ||
strpos($folderName, 'office_server') !== false;
}
/**
* Check if path is a network drive path
*/
private static function isNetworkDrivePath($path)
{
// Check for network drive patterns
$networkPatterns = [
'/^\\\\/', // Windows UNC path
'/^\/\/[^\/]+/', // Unix network path
'/^smb:\/\//', // SMB protocol
'/^cifs:\/\//', // CIFS protocol
];
foreach ($networkPatterns as $pattern) {
if (preg_match($pattern, $path)) {
return true;
}
}
return false;
}
/**
* Check if folder is a system folder (only visible to Admin)
* System folders are defined in config/document-folders.php
* Uses static cache for performance optimization
*/
private static function isSystemFolder($folderName)
{
// Static cache to avoid repeated config reads
static $exactMatches = null;
static $regexPatterns = null;
static $cache = [];
// Return cached result if available
if (isset($cache[$folderName])) {
return $cache[$folderName];
}
// Load patterns once
if ($exactMatches === null) {
$exactMatches = config('document-folders.system_patterns.exact', []);
$regexPatterns = config('document-folders.system_patterns.regex', []);
}
// Check exact matches first (faster)
if (in_array($folderName, $exactMatches)) {
$cache[$folderName] = true;
return true;
}
// Check regex patterns
foreach ($regexPatterns as $pattern) {
if (preg_match($pattern, $folderName)) {
$cache[$folderName] = true;
return true;
}
}
$cache[$folderName] = false;
return false;
}
}