<?php 
 
declare(strict_types=1); 
 
/************************************************************************************** 
 * 
 * Catalyst PHP Framework 
 * PHP Version 8.3 (Required). 
 * 
 * @package   Catalyst 
 * @subpackage Public 
 * @see       https://github.com/arcanisgk/catalyst 
 * 
 * @author    Walter Nuñez (arcanisgk/original founder) <[email protected]> 
 * @copyright 2023 - 2025 
 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 
 * 
 * @note      This program is distributed in the hope that it will be useful 
 *            WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 *            or FITNESS FOR A PARTICULAR PURPOSE. 
 * 
 * @category  Framework 
 * @filesource 
 * 
 * @link      https://catalyst.dock Local development URL 
 * 
 */ 
 
use Catalyst\Framework\Core\Exceptions\RouteNotFoundException; 
use Catalyst\Framework\Core\Response\JsonResponse; 
use Catalyst\Framework\Core\Response\RedirectResponse; 
use Catalyst\Framework\Core\Response\Response; 
use Catalyst\Framework\Core\Response\ViewResponse; 
use Catalyst\Framework\Core\Route\Router; 
use Random\RandomException; 
 
if (!function_exists('route')) { 
    /** 
     * Generate a URL to a named route 
     * 
     * @param string $name The name of the route 
     * @param array $parameters Parameters for the route 
     * @param bool $absolute Whether to generate an absolute URL 
     * @return string The generated URL 
     * @throws RouteNotFoundException If the route doesn't exist 
     */ 
    function route(string $name, array $parameters = [], bool $absolute = false): string 
    { 
        return Router::getInstance()->url($name, $parameters, $absolute); 
    } 
} 
 
if (!function_exists('redirect')) { 
    /** 
     * Create a redirect response to the given URL 
     * 
     * @param string $url The URL to redirect to 
     * @param int $status The HTTP status code (default: 302) 
     * @param array $headers Additional headers 
     * @return RedirectResponse 
     */ 
    function redirect(string $url, int $status = 302, array $headers = []): RedirectResponse 
    { 
        return Response::redirect($url, $status, $headers); 
    } 
} 
 
if (!function_exists('redirect_to_route')) { 
    /** 
     * Create a redirect response to a named route 
     * 
     * @param string $name The name of the route 
     * @param array $parameters Parameters for the route 
     * @param int $status The HTTP status code 
     * @param array $headers Additional headers 
     * @return RedirectResponse 
     * @throws RouteNotFoundException If the route doesn't exist 
     */ 
    function redirect_to_route( 
        string $name, 
        array  $parameters = [], 
        int    $status = 302, 
        array  $headers = [] 
    ): RedirectResponse 
    { 
        $url = route($name, $parameters); 
        return redirect($url, $status, $headers); 
    } 
} 
 
if (!function_exists('route_is')) { 
    /** 
     * Determine if the current request URL matches a pattern 
     * 
     * @param string $pattern Pattern to check against 
     * @return bool 
     */ 
    function route_is(string $pattern): bool 
    { 
        $request = $_SERVER['REQUEST_URI'] ?? '/'; 
 
        // Remove query string 
        if (($pos = strpos($request, '?')) !== false) { 
            $request = substr($request, 0, $pos); 
        } 
 
        // Simple wildcard matching 
        $pattern = str_replace('*', '.*', preg_quote($pattern, '/')); 
        return (bool)preg_match('/^' . $pattern . '$/', $request); 
    } 
} 
 
if (!function_exists('view')) { 
    /** 
     * Create a view response 
     * 
     * @param string $view View name 
     * @param array $data View data 
     * @param int $status HTTP status code 
     * @param array $headers Response headers 
     * @return ViewResponse 
     */ 
    function view( 
        string $view, 
        array  $data = [], 
        int    $status = 200, 
        array  $headers = [] 
    ): ViewResponse 
    { 
        return new ViewResponse($view, $data, $status, $headers); 
    } 
} 
 
if (!function_exists('view_with_layout')) { 
    /** 
     * Create a view response with a layout 
     * 
     * @param string $view View name 
     * @param array $data View data 
     * @param string $layout Layout name 
     * @param int $status HTTP status code 
     * @param array $headers Response headers 
     * @return ViewResponse 
     */ 
    function view_with_layout( 
        string $view, 
        array  $data = [], 
        string $layout = 'default', 
        int    $status = 200, 
        array  $headers = [] 
    ): ViewResponse 
    { 
        return ViewResponse::withLayout($view, $data, $layout, $status, $headers); 
    } 
} 
 
if (!function_exists('json')) { 
    /** 
     * Create a JSON response 
     * 
     * @param mixed $data The data to encode as JSON 
     * @param int $status The HTTP status code 
     * @param array $headers Array of HTTP headers 
     * @param int $options JSON encoding options 
     * @return JsonResponse 
     */ 
    function json( 
        mixed $data = null, 
        int   $status = 200, 
        array $headers = [], 
        int   $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES 
    ): JsonResponse 
    { 
        return new JsonResponse($data, $status, $headers, $options); 
    } 
} 
 
if (!function_exists('json_success')) { 
    /** 
     * Create a JSON success response 
     * 
     * @param mixed $data The data payload 
     * @param string|null $message Optional success message 
     * @param int $status HTTP status code 
     * @param array $headers HTTP headers 
     * @return JsonResponse 
     */ 
    function json_success( 
        mixed   $data = null, 
        ?string $message = null, 
        int     $status = 200, 
        array   $headers = [] 
    ): JsonResponse 
    { 
        return JsonResponse::api($data, true, $message, $status, $headers); 
    } 
} 
 
if (!function_exists('json_error')) { 
    /** 
     * Create a JSON error response 
     * 
     * @param string $message Error message 
     * @param mixed $errors Detailed error information 
     * @param int $status HTTP status code 
     * @param array $headers HTTP headers 
     * @return JsonResponse 
     */ 
    function json_error( 
        string $message, 
        mixed  $errors = null, 
        int    $status = 400, 
        array  $headers = [] 
    ): JsonResponse 
    { 
        return JsonResponse::error($message, $errors, $status, $headers); 
    } 
} 
 
if (!function_exists('route_url')) { 
    /** 
     * Generate a URL by concatenating segments 
     * 
     * @param string ...$segments URL segments 
     * @return string The generated URL 
     */ 
    function route_url(string ...$segments): string 
    { 
        $url = ''; 
        foreach ($segments as $segment) { 
            $segment = trim($segment, '/'); 
            $url .= ($segment ? "/$segment" : ''); 
        } 
 
        return $url ?: '/'; 
    } 
} 
 
if (!function_exists('current_route_url')) { 
    /** 
     * Get the current URL 
     * 
     * @param bool $withQueryString Include query string 
     * @return string Current URL 
     */ 
    function current_route_url(bool $withQueryString = false): string 
    { 
        $url = $_SERVER['REQUEST_URI'] ?? '/'; 
 
        if (!$withQueryString && ($pos = strpos($url, '?')) !== false) { 
            $url = substr($url, 0, $pos); 
        } 
 
        return $url; 
    } 
} 
 
if (!function_exists('csrf_token')) { 
    /** 
     * Generate or retrieve a CSRF token for the current session 
     * 
     * @return string The CSRF token 
     * @throws RandomException 
     */ 
    function csrf_token(): string 
    { 
        // Start session if not already started 
        if (session_status() === PHP_SESSION_NONE) { 
            session_start(); 
        } 
 
        // Generate a new token if one doesn't exist 
        if (!isset($_SESSION['_csrf_token'])) { 
            $_SESSION['_csrf_token'] = bin2hex(random_bytes(32)); 
        } 
 
        return $_SESSION['_csrf_token']; 
    } 
} 
 
if (!function_exists('csrf_field')) { 
    /** 
     * Generate a hidden input field containing the CSRF token 
     * 
     * @return string HTML input element with CSRF token 
     * @throws RandomException 
     */ 
    function csrf_field(): string 
    { 
        return '<input type="hidden" name="_token" value="' . csrf_token() . '">'; 
    } 
} 
 
if (!function_exists('csrf_verify')) { 
    /** 
     * Verify that the provided token matches the stored CSRF token 
     * 
     * @param string|null $token The token to verify 
     * @return bool Whether the token is valid 
     */ 
    function csrf_verify(?string $token = null): bool 
    { 
        // Get token from request if not provided 
        if ($token === null) { 
            $token = $_POST['_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null; 
        } 
 
        // Start session if not already started 
        if (session_status() === PHP_SESSION_NONE) { 
            session_start(); 
        } 
 
        // Compare tokens 
        return $token !== null && 
            isset($_SESSION['_csrf_token']) && 
            hash_equals($_SESSION['_csrf_token'], $token); 
    } 
} 
 
if (!function_exists('asset')) { 
    /** 
     * Generate URL for an asset file 
     * 
     * @param string $path Path to the asset file 
     * @param bool $absolute Whether to return an absolute URL 
     * @return string The asset URL 
     */ 
    function asset(string $path, bool $absolute = false): string 
    { 
        // Remove leading slash if present 
        $path = ltrim($path, '/'); 
 
        // Base path for assets 
        $basePath = '/assets/'; 
 
        // Build the URL 
        $url = $basePath . $path; 
 
        // Add domain for absolute URLs 
        if ($absolute) { 
            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'; 
            $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; 
            $url = $protocol . '://' . $host . $url; 
        } 
 
        return $url; 
    } 
}
 
 |