Files
rspade_system/app/RSpade/CodeQuality/Rules/Common/HardcodedInternalUrl_CodeQualityRule.php
root f6ac36c632 Enhance refactor commands with controller-aware Route() updates and fix code quality violations
Add semantic token highlighting for 'that' variable and comment file references in VS Code extension
Add Phone_Text_Input and Currency_Input components with formatting utilities
Implement client widgets, form standardization, and soft delete functionality
Add modal scroll lock and update documentation
Implement comprehensive modal system with form integration and validation
Fix modal component instantiation using jQuery plugin API
Implement modal system with responsive sizing, queuing, and validation support
Implement form submission with validation, error handling, and loading states
Implement country/state selectors with dynamic data loading and Bootstrap styling
Revert Rsx::Route() highlighting in Blade/PHP files
Target specific PHP scopes for Rsx::Route() highlighting in Blade
Expand injection selector for Rsx::Route() highlighting
Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls
Update jqhtml packages to v2.2.165
Add bundle path validation for common mistakes (development mode only)
Create Ajax_Select_Input widget and Rsx_Reference_Data controller
Create Country_Select_Input widget with default country support
Initialize Tom Select on Select_Input widgets
Add Tom Select bundle for enhanced select dropdowns
Implement ISO 3166 geographic data system for country/region selection
Implement widget-based form system with disabled state support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 06:21:56 +00:00

363 lines
12 KiB
PHP
Executable File

<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*
* @ROUTE-EXISTS-01-EXCEPTION - This file generates code templates with placeholder route names
*/
namespace App\RSpade\CodeQuality\Rules\Common;
use Illuminate\Support\Facades\Route;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
use App\RSpade\Core\Dispatch\Dispatcher;
use App\RSpade\Core\Manifest\Manifest;
/**
* HardcodedInternalUrlRule - Detect hardcoded internal URLs in href attributes
*
* This rule scans .blade.php and .jqhtml files for href attributes containing
* hardcoded internal routes (URLs starting with "/" without file extensions)
* and suggests using the proper route generation methods instead.
*/
class HardcodedInternalUrl_CodeQualityRule extends CodeQualityRule_Abstract
{
/**
* Get the unique identifier for this rule
*
* @return string
*/
public function get_id(): string
{
return 'URL-HARDCODE-01';
}
/**
* Get the default severity level
*
* @return string One of: critical, high, medium, low, convention
*/
public function get_default_severity(): string
{
return 'medium';
}
/**
* Get the file patterns this rule applies to
*
* @return array
*/
public function get_file_patterns(): array
{
return ['*.blade.php', '*.jqhtml'];
}
/**
* Get the display name for this rule
*
* @return string
*/
public function get_name(): string
{
return 'Hardcoded Internal URL Detection';
}
/**
* Get the description of what this rule checks
*
* @return string
*/
public function get_description(): string
{
return 'Detects hardcoded internal URLs in href attributes and suggests using route generation methods';
}
/**
* Check the file contents for violations
*
* @param string $file_path The path to the file being checked
* @param string $contents The contents of the file
* @param array $metadata Additional metadata about the file
* @return void
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Initialize manifest to ensure routes are available
try {
Manifest::init();
} catch (\Exception $e) {
// If manifest fails to initialize, we can't check routes
return;
}
$is_jqhtml = str_ends_with($file_path, '.jqhtml');
$lines = explode("\n", $contents);
foreach ($lines as $line_num => $line) {
// Find all href attributes in the line
// Match href="..." or href='...'
if (preg_match_all('/href\s*=\s*["\']([^"\']+)["\']/', $line, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[1] as $match) {
$url = $match[0];
$position = $match[1];
// Check if this is a likely internal route
if (!$this->_is_likely_internal_route($url)) {
continue;
}
// Extract base URL and query params
$url_parts = parse_url($url);
$base_url = $url_parts['path'] ?? '/';
$query_string = $url_parts['query'] ?? '';
// Try to resolve the URL to a route
$route_info = null;
try {
$route_info = Dispatcher::resolve_url_to_route($base_url, 'GET');
} catch (\Exception $e) {
// URL doesn't resolve to a known route
continue;
}
$suggested_code = '';
if ($route_info) {
// Found RSX route
$controller_class = $route_info['class'] ?? '';
$method_name = $route_info['method'] ?? '';
$route_params = $route_info['params'] ?? [];
// Parse query string params
$query_params = [];
if ($query_string) {
parse_str($query_string, $query_params);
}
// Merge all params (route params take precedence)
$all_params = array_merge($query_params, $route_params);
// Extract just the class name without namespace
$class_parts = explode('\\', $controller_class);
$class_name = end($class_parts);
// Generate the suggested replacement code
$suggested_code = $this->_generate_suggested_code(
$class_name,
$method_name,
$all_params,
$is_jqhtml
);
} else {
// Check if it's a Laravel route
$laravel_route = $this->_find_laravel_route($base_url);
if ($laravel_route) {
$suggested_code = $this->_generate_laravel_suggestion($laravel_route, $query_string, $is_jqhtml);
} else {
// No route found, skip
continue;
}
}
// Add violation
$this->add_violation(
$line_num + 1,
$position,
"Hardcoded internal URL detected: {$url}",
$line,
"Use route generation instead:\n{$suggested_code}"
);
}
}
}
}
/**
* Check if a URL is likely an internal route
*
* @param string $url
* @return bool
*/
protected function _is_likely_internal_route(string $url): bool
{
// Must start with /
if (!str_starts_with($url, '/')) {
return false;
}
// Allow exactly "/" (root/home URL) - common and acceptable
if ($url === '/') {
return false;
}
// Skip absolute URLs (with protocol)
if (preg_match('#^//#', $url)) {
return false;
}
// Extract path before query string
$path = strtok($url, '?');
// Get the last segment of the path
$segments = explode('/', trim($path, '/'));
$last_segment = end($segments);
// If last segment has a dot (file extension), it's likely a file not a route
if ($last_segment && str_contains($last_segment, '.')) {
return false;
}
// Skip common static asset paths
$static_prefixes = ['/assets/', '/css/', '/js/', '/images/', '/img/', '/fonts/', '/storage/'];
foreach ($static_prefixes as $prefix) {
if (str_starts_with($path, $prefix)) {
return false;
}
}
return true;
}
/**
* Generate suggested replacement code
*
* @param string $class_name
* @param string $method_name
* @param array $params
* @param bool $is_jqhtml
* @return string
*/
protected function _generate_suggested_code(string $class_name, string $method_name, array $params, bool $is_jqhtml): string
{
if ($is_jqhtml) {
// JavaScript version for .jqhtml files using <%= %> syntax
if (empty($params)) {
return "<%= Rsx.Route('{$class_name}', '{$method_name}') %>";
} else {
$params_json = json_encode($params, JSON_UNESCAPED_SLASHES);
return "<%= Rsx.Route('{$class_name}', '{$method_name}', {$params_json}) %>";
}
} else {
// PHP version for .blade.php files
if (empty($params)) {
return "{{ Rsx::Route('{$class_name}', '{$method_name}') }}";
} else {
$params_str = $this->_format_php_array($params);
return "{{ Rsx::Route('{$class_name}', '{$method_name}', {$params_str}) }}";
}
}
}
/**
* Format a PHP array for display
*
* @param array $params
* @return string
*/
protected function _format_php_array(array $params): string
{
$items = [];
foreach ($params as $key => $value) {
$key_str = var_export($key, true);
$value_str = var_export($value, true);
$items[] = "{$key_str} => {$value_str}";
}
return '[' . implode(', ', $items) . ']';
}
/**
* Find Laravel route by URL
*
* @param string $url
* @return string|null Route name if found
*/
protected function _find_laravel_route(string $url): ?string
{
// Get all Laravel routes
$routes = Route::getRoutes();
foreach ($routes as $route) {
// Check if URL matches this route's URI
if ($route->uri() === ltrim($url, '/')) {
// Get the route name if it has one
$name = $route->getName();
if ($name) {
return $name;
}
// No name, but route exists - return the URI for direct use
return $url;
}
}
return null;
}
/**
* Generate Laravel route suggestion
*
* @param string $route_name
* @param string $query_string
* @param bool $is_jqhtml
* @return string
*/
protected function _generate_laravel_suggestion(string $route_name, string $query_string, bool $is_jqhtml): string
{
// If route_name starts with /, it means no named route exists
if (str_starts_with($route_name, '/')) {
// Suggest adding a name to the route
$suggested_name = $this->_suggest_route_name($route_name);
if ($is_jqhtml) {
return "<%= '{$route_name}' %> <!-- Add name to route: ->name('{$suggested_name}'), then use route('{$suggested_name}') -->";
} else {
return "{{ route('{$suggested_name}') }}\n// First add ->name('{$suggested_name}') to the route definition in routes/web.php";
}
}
// Route has a name, use it
if ($is_jqhtml) {
// JavaScript version for .jqhtml files
if ($query_string) {
$query_params = [];
parse_str($query_string, $query_params);
$params_json = json_encode($query_params, JSON_UNESCAPED_SLASHES);
// Note: jqhtml would need a custom helper for Laravel routes
return "<%= route('{$route_name}', {$params_json}) %> <!-- Requires custom route() helper -->";
} else {
return "<%= route('{$route_name}') %> <!-- Requires custom route() helper -->";
}
} else {
// PHP version for .blade.php files
if ($query_string) {
$query_params = [];
parse_str($query_string, $query_params);
$params_str = $this->_format_php_array($query_params);
return "{{ route('{$route_name}', {$params_str}) }}";
} else {
return "{{ route('{$route_name}') }}";
}
}
}
/**
* Suggest a route name based on the URL path
*
* @param string $url
* @return string
*/
protected function _suggest_route_name(string $url): string
{
// Remove leading slash and convert to dot notation
$path = ltrim($url, '/');
// Convert path segments to route name
// /test-bundle-facade => test.bundle.facade
// /_idehelper => idehelper
$path = str_replace('_', '', $path); // Remove leading underscores
$path = str_replace('-', '.', $path); // Convert dashes to dots
$path = str_replace('/', '.', $path); // Convert slashes to dots
return $path ?: 'home';
}
}