session_id = $session_id; $flash_alert->message = $message; $flash_alert->class_attribute = $class_attribute; $flash_alert->created_at = now(); $flash_alert->save(); } /** * Render all flash alerts for the current session * * Returns HTML for all flash messages and deletes them from the database. * Messages are rendered as Bootstrap 5 alerts with dismissible buttons. * * @return string HTML string containing all flash alerts or empty string */ public static function render_flash_alerts() { $session_id = Session::get_session_id(); if ($session_id === null) { return ''; } // Get all flash alerts for this session $alerts = FlashAlert::where('session_id', $session_id) ->orderBy('created_at', 'asc') ->get(); if ($alerts->isEmpty()) { return ''; } // Delete the alerts now that we're rendering them FlashAlert::where('session_id', $session_id) ->delete(); // Build HTML for all alerts $html = ''; foreach ($alerts as $alert) { $message = htmlspecialchars($alert->message); $class = htmlspecialchars($alert->class_attribute); $html .= << {$message} HTML; } return $html; } /** * Helper method to add a success flash alert * * @param string $message * @return void */ public static function flash_success($message) { self::flash_alert($message, 'alert alert-success alert-flash'); } /** * Helper method to add an error flash alert * * @param string $message * @return void */ public static function flash_error($message) { self::flash_alert($message, 'alert alert-danger alert-flash'); } /** * Helper method to add a warning flash alert * * @param string $message * @return void */ public static function flash_warning($message) { self::flash_alert($message, 'alert alert-warning alert-flash'); } /** * Helper method to add an info flash alert * * @param string $message * @return void */ public static function flash_info($message) { self::flash_alert($message, 'alert alert-info alert-flash'); } /** * Create a route proxy for type-safe URL generation * * This method creates a route proxy that can generate URLs for a specific controller action. * The proxy ensures all required route parameters are provided and handles extra parameters * as query string values. * * Placeholder Routes: * When the action starts with '#' (e.g., '#index', '#show'), it indicates a placeholder/unimplemented * route for scaffolding purposes. These skip validation and return href="#" to allow incremental * development without requiring all controllers to exist. * * Usage examples: * ```php * // Simple route without parameters (defaults to 'index' action) * $url = Rsx::Route('Frontend_Index_Controller')->url(); * // Returns: /dashboard * * // Route with explicit action * $url = Rsx::Route('Frontend_Index_Controller')->url(); * // Returns: /dashboard * * // Route with required parameter * $url = Rsx::Route('Frontend_Client_View_Controller')->url(['id' => 'C001']); * // Returns: /clients/view/C001 * * // Route with required and query parameters * $url = Rsx::Route('Frontend_Client_View_Controller')->url([ * 'id' => 'C001', * 'tab' => 'history' * ]); * // Returns: /clients/view/C001?tab=history * * // Generate absolute URL * $absolute = Rsx::Route('Frontend_Index_Controller')->absolute_url(); * // Returns: https://example.com/dashboard * * // Check if route is current * if (Rsx::Route('Frontend_Index_Controller')->is_current()) { * // This is the currently executing route * } * * // Placeholder route for scaffolding (controller doesn't need to exist) * $url = Rsx::Route('Future_Feature_Controller', '#index')->url(); * // Returns: # * ``` * * @param string $class_name The controller class name (e.g., 'User_Controller') * @param string $action_name The action/method name (defaults to 'index'). Use '#action' for placeholders. * @return Rsx_Route_Proxy Route proxy instance for URL generation * @throws RuntimeException If class doesn't exist, isn't a controller, method doesn't exist, or lacks Route attribute */ public static function Route($class_name, $action_name = 'index') { // Placeholder route: action starts with # means unimplemented/scaffolding // Skip all validation and return a placeholder proxy if (str_starts_with($action_name, '#')) { return new Rsx_Route_Proxy($class_name, $action_name, '#'); } // Try to find the class in the manifest try { $metadata = Manifest::php_get_metadata_by_class($class_name); } catch (RuntimeException $e) { // Report error at caller's location (the blade template or PHP code calling Rsx::Route) throw new Rsx_Caller_Exception("Class {$class_name} not found in manifest"); } // Verify it extends Rsx_Controller_Abstract $extends = $metadata['extends'] ?? ''; $is_controller = false; if ($extends === 'Rsx_Controller_Abstract') { $is_controller = true; } else { // Check if it extends a class that extends Rsx_Controller_Abstract $current_class = $extends; $max_depth = 10; while ($current_class && $max_depth-- > 0) { try { $parent_metadata = Manifest::php_get_metadata_by_class($current_class); if (($parent_metadata['extends'] ?? '') === 'Rsx_Controller_Abstract') { $is_controller = true; break; } $current_class = $parent_metadata['extends'] ?? ''; } catch (RuntimeException $e) { // Check if parent is the abstract controller with FQCN if ($current_class === 'Rsx_Controller_Abstract' || $current_class === 'App\\RSpade\\Core\\Controller\\Rsx_Controller_Abstract') { $is_controller = true; } break; } } } if (!$is_controller) { throw new Rsx_Caller_Exception("Class {$class_name} must extend Rsx_Controller_Abstract"); } // Check if method exists and has Route attribute if (!isset($metadata['public_static_methods'][$action_name])) { throw new Rsx_Caller_Exception("Method {$action_name} not found in class {$class_name}"); } $method_info = $metadata['public_static_methods'][$action_name]; // All methods in public_static_methods are guaranteed to be static // No need to check - but we assert for safety if (!isset($method_info['static']) || !$method_info['static']) { shouldnt_happen("Method {$class_name}::{$action_name} in public_static_methods is not static - extraction bug"); } // Check for Route or Ajax_Endpoint attribute $has_route = false; $has_ajax_endpoint = false; $route_pattern = null; if (isset($method_info['attributes'])) { foreach ($method_info['attributes'] as $attr_name => $attr_instances) { if ($attr_name === 'Route' || str_ends_with($attr_name, '\\Route')) { $has_route = true; // Get the route pattern from the first instance if (!empty($attr_instances)) { $route_args = $attr_instances[0]; $route_pattern = $route_args[0] ?? ($route_args['pattern'] ?? null); } break; } if ($attr_name === 'Ajax_Endpoint' || str_ends_with($attr_name, '\\Ajax_Endpoint')) { $has_ajax_endpoint = true; break; } } } // If has Ajax_Endpoint, return AJAX route URL if ($has_ajax_endpoint) { $ajax_url = '/_ajax/' . urlencode($class_name) . '/' . urlencode($action_name); return new Rsx_Route_Proxy($class_name, $action_name, $ajax_url); } if (!$has_route) { throw new Rsx_Caller_Exception("Method {$class_name}::{$action_name} must have Route or Ajax_Endpoint attribute"); } if (!$route_pattern) { throw new Rsx_Caller_Exception("Route attribute on {$class_name}::{$action_name} must have a pattern"); } return new Rsx_Route_Proxy($class_name, $action_name, $route_pattern); } }