Mark PHP version compatibility fallback as legitimate in Php_Fixer
Add public directory asset support to bundle system Fix PHP Fixer to replace ALL Rsx\ FQCNs with simple class names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ namespace App\RSpade\Core\Dispatch;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -215,14 +216,88 @@ class AssetHandler
|
||||
// Set additional security headers
|
||||
static::__set_security_headers($response, $mime_type);
|
||||
|
||||
// Enable gzip if supported
|
||||
if (static::__should_compress($mime_type)) {
|
||||
$response->headers->set('Content-Encoding', 'gzip');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a public asset by relative path with Redis caching
|
||||
*
|
||||
* Resolves paths like "sneat/css/demo.css" to full filesystem paths like
|
||||
* "rsx/public/sneat/css/demo.css" by scanning all public/ directories.
|
||||
*
|
||||
* Results are cached in Redis indefinitely. Cached paths are validated
|
||||
* before use - if file no longer exists, cache is invalidated and re-scan occurs.
|
||||
*
|
||||
* @param string $relative_path Relative path like "sneat/css/demo.css"
|
||||
* @return string Full filesystem path
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException If not found or ambiguous
|
||||
*/
|
||||
public static function find_public_asset(string $relative_path): string
|
||||
{
|
||||
// Ensure directories are discovered
|
||||
static::__ensure_directories_discovered();
|
||||
|
||||
// Sanitize the path
|
||||
$relative_path = static::__sanitize_path($relative_path);
|
||||
|
||||
// Check Redis cache first
|
||||
$cache_key = 'rspade:public_asset:' . $relative_path;
|
||||
$cached_path = Redis::get($cache_key);
|
||||
|
||||
if ($cached_path) {
|
||||
// Verify cached file still exists
|
||||
if (File::exists($cached_path) && File::isFile($cached_path)) {
|
||||
return $cached_path;
|
||||
}
|
||||
|
||||
// Stale cache - invalidate and re-scan
|
||||
Redis::del($cache_key);
|
||||
}
|
||||
|
||||
// NEVER serve PHP files under any circumstances
|
||||
$extension = strtolower(pathinfo($relative_path, PATHINFO_EXTENSION));
|
||||
if ($extension === 'php') {
|
||||
throw new HttpException(403, 'PHP files cannot be served as static assets');
|
||||
}
|
||||
|
||||
// Scan all public directories for matches
|
||||
$matches = [];
|
||||
|
||||
foreach (static::$public_directories as $module => $directory) {
|
||||
$full_path = $directory . '/' . $relative_path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules
|
||||
if (static::__is_file_excluded($full_path, $relative_path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
$matches[] = $full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ambiguous matches
|
||||
if (count($matches) > 1) {
|
||||
// Show first two matches in error
|
||||
$first_two = array_slice($matches, 0, 2);
|
||||
throw new HttpException(
|
||||
500,
|
||||
"Ambiguous public asset request: '{$relative_path}' matches multiple files: '" .
|
||||
implode("', '", $first_two) . "'"
|
||||
);
|
||||
}
|
||||
|
||||
// Check for no matches
|
||||
if (count($matches) === 0) {
|
||||
throw new NotFoundHttpException("Public asset not found: {$relative_path}");
|
||||
}
|
||||
|
||||
// Single match - cache and return
|
||||
$resolved_path = $matches[0];
|
||||
Redis::set($cache_key, $resolved_path);
|
||||
|
||||
return $resolved_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure directories are discovered (lazy initialization)
|
||||
*/
|
||||
@@ -294,52 +369,22 @@ class AssetHandler
|
||||
/**
|
||||
* Find asset file in public directories
|
||||
*
|
||||
* Wrapper around find_public_asset() that returns null instead of throwing
|
||||
* NotFoundHttpException for backward compatibility with existing code.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null Full file path or null if not found
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException For PHP files, exclusions, or ambiguous matches
|
||||
*/
|
||||
protected static function __find_asset_file($path)
|
||||
{
|
||||
// NEVER serve PHP files under any circumstances
|
||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
if ($extension === 'php') {
|
||||
throw new HttpException(403, 'PHP files cannot be served as static assets');
|
||||
try {
|
||||
return static::find_public_asset($path);
|
||||
} catch (NotFoundHttpException $e) {
|
||||
// Not found - return null for backward compatibility
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try each public directory
|
||||
foreach (static::$public_directories as $module => $directory) {
|
||||
$full_path = $directory . '/' . $path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules before returning
|
||||
if (static::__is_file_excluded($full_path, $path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
return $full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path includes module prefix (e.g., "admin/css/style.css")
|
||||
$parts = explode('/', $path, 2);
|
||||
|
||||
if (count($parts) === 2) {
|
||||
$module = $parts[0];
|
||||
$asset_path = $parts[1];
|
||||
|
||||
if (isset(static::$public_directories[$module])) {
|
||||
$full_path = static::$public_directories[$module] . '/' . $asset_path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules before returning
|
||||
if (static::__is_file_excluded($full_path, $asset_path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
return $full_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
// Let other exceptions (403, 500) bubble up
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,32 +719,6 @@ class AssetHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content should be compressed
|
||||
*
|
||||
* @param string $mime_type
|
||||
* @return bool
|
||||
*/
|
||||
protected static function __should_compress($mime_type)
|
||||
{
|
||||
// Compress text-based content
|
||||
$compressible = [
|
||||
'text/',
|
||||
'application/javascript',
|
||||
'application/json',
|
||||
'application/xml',
|
||||
'image/svg+xml'
|
||||
];
|
||||
|
||||
foreach ($compressible as $type) {
|
||||
if (str_starts_with($mime_type, $type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get discovered public directories
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user