Add client-side Permission class and resolved_permissions to rsxapp
Refactor date/time classes to reduce code redundancy 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,17 +4,19 @@ NAME
|
||||
acls - Role-based access control with supplementary permissions
|
||||
|
||||
SYNOPSIS
|
||||
// Check if current user has a permission
|
||||
Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)
|
||||
PHP (server-side):
|
||||
Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)
|
||||
Permission::has_role(User_Model::ROLE_SITE_ADMIN)
|
||||
$user->has_permission(User_Model::PERM_EDIT_DATA)
|
||||
$user->get_resolved_permissions() // All permissions as array
|
||||
$user->can_admin_role($target_user->role_id)
|
||||
|
||||
// Check if current user has at least a certain role
|
||||
Permission::has_role(User_Model::ROLE_SITE_ADMIN)
|
||||
|
||||
// Check on specific user instance
|
||||
$user->has_permission(User_Model::PERM_EDIT_DATA)
|
||||
|
||||
// Check if user can administer another user's role
|
||||
$user->can_admin_role($target_user->role_id)
|
||||
JavaScript (client-side):
|
||||
Permission.has_permission(User_Model.PERM_EDIT_DATA)
|
||||
Permission.has_any_permission([User_Model.PERM_EDIT_DATA, User_Model.PERM_VIEW_DATA])
|
||||
Permission.has_all_permissions([...])
|
||||
Permission.has_role(User_Model.ROLE_MANAGER)
|
||||
Permission.can_admin_role(User_Model.ROLE_USER)
|
||||
|
||||
DESCRIPTION
|
||||
RSpade provides a role-based access control (RBAC) system where:
|
||||
@@ -72,19 +74,20 @@ ARCHITECTURE
|
||||
|
||||
Role Hierarchy
|
||||
|
||||
ID Constant Label Can Admin Roles
|
||||
-- -------- ----- ---------------
|
||||
1 ROLE_ROOT_ADMIN Root Admin 2,3,4,5,6,7
|
||||
2 ROLE_SITE_OWNER Site Owner 3,4,5,6,7
|
||||
3 ROLE_SITE_ADMIN Site Admin 4,5,6,7
|
||||
4 ROLE_MANAGER Manager 5,6,7
|
||||
5 ROLE_USER User (none)
|
||||
6 ROLE_VIEWER Viewer (none)
|
||||
7 ROLE_DISABLED Disabled (none)
|
||||
ID Constant Label Can Admin Roles
|
||||
--- -------- ----- ---------------
|
||||
100 ROLE_DEVELOPER Developer 200-800 (system only)
|
||||
200 ROLE_ROOT_ADMIN Root Admin 300-800 (system only)
|
||||
300 ROLE_SITE_OWNER Site Owner 400-800
|
||||
400 ROLE_SITE_ADMIN Site Admin 500-800
|
||||
500 ROLE_MANAGER Manager 600-800
|
||||
600 ROLE_USER User (none)
|
||||
700 ROLE_VIEWER Viewer (none)
|
||||
800 ROLE_DISABLED Disabled (none)
|
||||
|
||||
"Can Admin Roles" means a user with that role can create, edit,
|
||||
or change the role of users with the listed role IDs. This
|
||||
prevents privilege escalation (admin can't create root admin).
|
||||
IDs are 100-based for future expansion. Lower ID = higher privilege.
|
||||
"Can Admin Roles" prevents privilege escalation (Site Admin can't
|
||||
create Site Owner). Developer and Root Admin are system-assigned only.
|
||||
|
||||
PERMISSIONS
|
||||
|
||||
@@ -92,7 +95,7 @@ PERMISSIONS
|
||||
|
||||
ID Constant Granted By Default To
|
||||
-- -------- ---------------------
|
||||
1 PERM_MANAGE_SITES_ROOT Root Admin only
|
||||
1 PERM_MANAGE_SITES_ROOT Developer, Root Admin only
|
||||
2 PERM_MANAGE_SITE_BILLING Site Owner+
|
||||
3 PERM_MANAGE_SITE_SETTINGS Site Admin+
|
||||
4 PERM_MANAGE_SITE_USERS Site Admin+
|
||||
@@ -109,17 +112,17 @@ PERMISSIONS
|
||||
|
||||
Role-Permission Matrix
|
||||
|
||||
Permission Root Owner Admin Mgr User View Dis
|
||||
---------- ---- ----- ----- --- ---- ---- ---
|
||||
MANAGE_SITES_ROOT X
|
||||
MANAGE_SITE_BILLING X X
|
||||
MANAGE_SITE_SETTINGS X X X
|
||||
MANAGE_SITE_USERS X X X
|
||||
VIEW_USER_ACTIVITY X X X X
|
||||
EDIT_DATA X X X X X
|
||||
VIEW_DATA X X X X X X
|
||||
API_ACCESS - - - - - -
|
||||
DATA_EXPORT - - - - - -
|
||||
Permission Dev Root Owner Admin Mgr User View Dis
|
||||
---------- --- ---- ----- ----- --- ---- ---- ---
|
||||
MANAGE_SITES_ROOT X X
|
||||
MANAGE_SITE_BILLING X X X
|
||||
MANAGE_SITE_SETTINGS X X X X
|
||||
MANAGE_SITE_USERS X X X X
|
||||
VIEW_USER_ACTIVITY X X X X X
|
||||
EDIT_DATA X X X X X X
|
||||
VIEW_DATA X X X X X X X
|
||||
API_ACCESS - - - - - - -
|
||||
DATA_EXPORT - - - - - - -
|
||||
|
||||
Legend: X = granted by role, - = must be granted individually
|
||||
|
||||
@@ -129,14 +132,15 @@ MODEL IMPLEMENTATION
|
||||
|
||||
class User_Model extends Rsx_Model_Abstract
|
||||
{
|
||||
// Role constants
|
||||
const ROLE_ROOT_ADMIN = 1;
|
||||
const ROLE_SITE_OWNER = 2;
|
||||
const ROLE_SITE_ADMIN = 3;
|
||||
const ROLE_MANAGER = 4;
|
||||
const ROLE_USER = 5;
|
||||
const ROLE_VIEWER = 6;
|
||||
const ROLE_DISABLED = 7;
|
||||
// Role constants (100-based, lower = higher privilege)
|
||||
const ROLE_DEVELOPER = 100;
|
||||
const ROLE_ROOT_ADMIN = 200;
|
||||
const ROLE_SITE_OWNER = 300;
|
||||
const ROLE_SITE_ADMIN = 400;
|
||||
const ROLE_MANAGER = 500;
|
||||
const ROLE_USER = 600;
|
||||
const ROLE_VIEWER = 700;
|
||||
const ROLE_DISABLED = 800;
|
||||
|
||||
// Permission constants
|
||||
const PERM_MANAGE_SITES_ROOT = 1;
|
||||
@@ -151,55 +155,44 @@ MODEL IMPLEMENTATION
|
||||
|
||||
public static $enums = [
|
||||
'role_id' => [
|
||||
self::ROLE_ROOT_ADMIN => [
|
||||
'constant' => 'ROLE_ROOT_ADMIN',
|
||||
'label' => 'Root Admin',
|
||||
'permissions' => [
|
||||
self::PERM_MANAGE_SITES_ROOT,
|
||||
self::PERM_MANAGE_SITE_BILLING,
|
||||
self::PERM_MANAGE_SITE_SETTINGS,
|
||||
self::PERM_MANAGE_SITE_USERS,
|
||||
self::PERM_VIEW_USER_ACTIVITY,
|
||||
self::PERM_EDIT_DATA,
|
||||
self::PERM_VIEW_DATA,
|
||||
],
|
||||
'can_admin_roles' => [2,3,4,5,6,7],
|
||||
300 => [
|
||||
'constant' => 'ROLE_SITE_OWNER',
|
||||
'label' => 'Site Owner',
|
||||
'permissions' => [2, 3, 4, 5, 6, 7],
|
||||
'can_admin_roles' => [400, 500, 600, 700, 800],
|
||||
],
|
||||
// ... additional roles
|
||||
],
|
||||
];
|
||||
|
||||
public function has_permission(int $permission): bool
|
||||
// Get all resolved permissions (role + supplementary applied)
|
||||
public function get_resolved_permissions(): array
|
||||
{
|
||||
if ($this->role_id === self::ROLE_DISABLED) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$permissions = $this->role_id__permissions ?? [];
|
||||
// Add supplementary GRANTs, remove supplementary DENYs
|
||||
// ... (see User_Model for full implementation)
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
// Check supplementary DENY (overrides everything)
|
||||
if ($this->has_supplementary_deny($permission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check supplementary GRANT
|
||||
if ($this->has_supplementary_grant($permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check role default permissions
|
||||
return in_array($permission, $this->role_permissions ?? []);
|
||||
public function has_permission(int $permission): bool
|
||||
{
|
||||
return in_array($permission, $this->get_resolved_permissions(), true);
|
||||
}
|
||||
|
||||
public function can_admin_role(int $role_id): bool
|
||||
{
|
||||
return in_array($role_id, $this->role_can_admin_roles ?? []);
|
||||
return in_array($role_id, $this->role_id__can_admin_roles ?? [], true);
|
||||
}
|
||||
}
|
||||
|
||||
Magic Properties (via enum system)
|
||||
Magic Properties (via enum system, BEM-style double underscore)
|
||||
|
||||
$user->role_label // "Site Admin"
|
||||
$user->role_permissions // [3,4,5,6,7]
|
||||
$user->role_can_admin_roles // [4,5,6,7]
|
||||
$user->role_id__label // "Site Admin"
|
||||
$user->role_id__permissions // [3,4,5,6,7]
|
||||
$user->role_id__can_admin_roles // [400,500,600,700,800]
|
||||
|
||||
PERMISSION CLASS API
|
||||
|
||||
@@ -235,6 +228,10 @@ PERMISSION CLASS API
|
||||
$user->has_permission(int $permission): bool
|
||||
Check if this specific user has permission.
|
||||
|
||||
$user->get_resolved_permissions(): array
|
||||
Get all resolved permission IDs for this user.
|
||||
Applies role defaults + grants - denies.
|
||||
|
||||
$user->can_admin_role(int $role_id): bool
|
||||
Check if user can create/edit users with given role.
|
||||
|
||||
@@ -244,6 +241,78 @@ PERMISSION CLASS API
|
||||
$user->has_supplementary_deny(int $permission): bool
|
||||
Check if user has explicit DENY for permission.
|
||||
|
||||
JAVASCRIPT PERMISSION CLASS
|
||||
|
||||
The Permission class provides client-side permission checking using
|
||||
pre-resolved permissions from window.rsxapp.resolved_permissions.
|
||||
|
||||
This array is computed server-side and includes role defaults with
|
||||
supplementary grants added and denies removed, ensuring JS checks
|
||||
match PHP exactly.
|
||||
|
||||
Static Methods
|
||||
|
||||
Permission.is_logged_in(): boolean
|
||||
Check if user is authenticated.
|
||||
|
||||
if (Permission.is_logged_in()) {
|
||||
// Show authenticated UI
|
||||
}
|
||||
|
||||
Permission.get_user(): Object|null
|
||||
Get current user object from rsxapp.
|
||||
|
||||
Permission.has_permission(permission): boolean
|
||||
Check if user has specific permission.
|
||||
|
||||
if (Permission.has_permission(User_Model.PERM_EDIT_DATA)) {
|
||||
// Show edit button
|
||||
}
|
||||
|
||||
Permission.has_any_permission(permissions): boolean
|
||||
Check if user has ANY of the listed permissions.
|
||||
|
||||
if (Permission.has_any_permission([
|
||||
User_Model.PERM_EDIT_DATA,
|
||||
User_Model.PERM_VIEW_DATA
|
||||
])) {
|
||||
// User can view or edit
|
||||
}
|
||||
|
||||
Permission.has_all_permissions(permissions): boolean
|
||||
Check if user has ALL of the listed permissions.
|
||||
|
||||
if (Permission.has_all_permissions([
|
||||
User_Model.PERM_MANAGE_SITE_USERS,
|
||||
User_Model.PERM_VIEW_USER_ACTIVITY
|
||||
])) {
|
||||
// User can manage users AND view activity
|
||||
}
|
||||
|
||||
Permission.has_role(role_id): boolean
|
||||
Check if user has at least the specified role level.
|
||||
Lower role_id = higher privilege.
|
||||
|
||||
if (Permission.has_role(User_Model.ROLE_MANAGER)) {
|
||||
// User is Manager or higher (Admin, Owner, etc.)
|
||||
}
|
||||
|
||||
Permission.can_admin_role(role_id): boolean
|
||||
Check if user can administer users with given role.
|
||||
|
||||
if (Permission.can_admin_role(User_Model.ROLE_USER)) {
|
||||
// Show role assignment dropdown including User role
|
||||
}
|
||||
|
||||
Permission.get_resolved_permissions(): number[]
|
||||
Get array of all permission IDs the user has.
|
||||
|
||||
Data Source
|
||||
|
||||
The Permission class reads from window.rsxapp.resolved_permissions,
|
||||
which is populated by the bundle renderer from the session user's
|
||||
get_resolved_permissions() result. Empty array if not authenticated.
|
||||
|
||||
ROUTE PROTECTION
|
||||
|
||||
Using #[Auth] Attribute
|
||||
@@ -443,7 +512,7 @@ ADDING NEW PERMISSIONS
|
||||
|
||||
2. Add to role definitions in $enums if role should grant it:
|
||||
|
||||
self::ROLE_SITE_ADMIN => [
|
||||
400 => [ // ROLE_SITE_ADMIN
|
||||
'permissions' => [
|
||||
// ... existing
|
||||
self::PERM_NEW_FEATURE,
|
||||
@@ -460,15 +529,13 @@ ADDING NEW PERMISSIONS
|
||||
|
||||
ADDING NEW ROLES
|
||||
|
||||
1. Add constant (maintain hierarchy order):
|
||||
1. Add constant (maintain hierarchy order, 100-based):
|
||||
|
||||
const ROLE_SUPERVISOR = 4; // Between Admin and Manager
|
||||
const ROLE_MANAGER = 5; // Renumber if needed
|
||||
// ...
|
||||
const ROLE_SUPERVISOR = 450; // Between Admin (400) and Manager (500)
|
||||
|
||||
2. Add to $enums with permissions and can_admin_roles:
|
||||
|
||||
self::ROLE_SUPERVISOR => [
|
||||
450 => [
|
||||
'constant' => 'ROLE_SUPERVISOR',
|
||||
'label' => 'Supervisor',
|
||||
'permissions' => [
|
||||
@@ -476,13 +543,13 @@ ADDING NEW ROLES
|
||||
self::PERM_EDIT_DATA,
|
||||
self::PERM_VIEW_DATA,
|
||||
],
|
||||
'can_admin_roles' => [5,6,7],
|
||||
'can_admin_roles' => [500, 600, 700, 800],
|
||||
],
|
||||
|
||||
3. Update can_admin_roles for roles above:
|
||||
|
||||
self::ROLE_SITE_ADMIN => [
|
||||
'can_admin_roles' => [4,5,6,7], // Add new role ID
|
||||
400 => [ // ROLE_SITE_ADMIN
|
||||
'can_admin_roles' => [450, 500, 600, 700, 800], // Add new role ID
|
||||
],
|
||||
|
||||
4. Run migration if role_id column needs updating
|
||||
@@ -594,5 +661,6 @@ SEE ALSO
|
||||
enums - Enum system for role/permission metadata
|
||||
routing - Route protection with #[Auth] attribute
|
||||
session - Session management and user context
|
||||
rsxapp - Global JS object containing resolved_permissions
|
||||
|
||||
RSpade 1.0 November 2024 ACLS(7)
|
||||
RSpade 1.0 January 2026 ACLS(7)
|
||||
|
||||
108
app/RSpade/man/pagedata.txt
Executable file
108
app/RSpade/man/pagedata.txt
Executable file
@@ -0,0 +1,108 @@
|
||||
PAGEDATA(3) RSX Framework Manual PAGEDATA(3)
|
||||
|
||||
NAME
|
||||
PageData - Pass server-side data to JavaScript via window.rsxapp.page_data
|
||||
|
||||
SYNOPSIS
|
||||
PHP Controller:
|
||||
PageData::add(['key' => $value, 'another' => $data]);
|
||||
|
||||
Blade Directive:
|
||||
@rsx_page_data(['key' => $value])
|
||||
|
||||
JavaScript Access:
|
||||
const value = window.rsxapp.page_data.key;
|
||||
|
||||
DESCRIPTION
|
||||
PageData provides a simple mechanism for passing server-side data to
|
||||
JavaScript. Data added via PageData::add() or @rsx_page_data is
|
||||
accumulated during request processing and automatically included in
|
||||
window.rsxapp.page_data when the bundle renders.
|
||||
|
||||
This is useful for:
|
||||
- Passing IDs needed by JavaScript components
|
||||
- Pre-loading configuration for client-side logic
|
||||
- Sharing computed values without additional Ajax calls
|
||||
|
||||
USAGE IN BLADE ROUTES
|
||||
For traditional Blade views, use the @rsx_page_data directive:
|
||||
|
||||
{{-- In your Blade view --}}
|
||||
@rsx_page_data(['user_id' => $user->id, 'can_edit' => $can_edit])
|
||||
|
||||
<div id="user-profile">
|
||||
...
|
||||
</div>
|
||||
|
||||
Or call PageData::add() in the controller before returning the view:
|
||||
|
||||
use App\RSpade\Core\View\PageData;
|
||||
|
||||
#[Route('/users/:id')]
|
||||
public static function view(Request $request, array $params = [])
|
||||
{
|
||||
$user = User_Model::findOrFail($params['id']);
|
||||
|
||||
PageData::add([
|
||||
'user_id' => $user->id,
|
||||
'permissions' => $user->get_permissions(),
|
||||
]);
|
||||
|
||||
return rsx_view('User_View', ['user' => $user]);
|
||||
}
|
||||
|
||||
USAGE IN SPA CONTROLLERS
|
||||
For SPA entry points, call PageData::add() before returning rsx_view(SPA):
|
||||
|
||||
use App\RSpade\Core\View\PageData;
|
||||
|
||||
#[SPA]
|
||||
public static function index(Request $request, array $params = [])
|
||||
{
|
||||
// Load data needed by SPA actions
|
||||
$internal_contact = Contact_Model::where('type_id', Contact_Model::TYPE_INTERNAL)
|
||||
->first();
|
||||
|
||||
PageData::add([
|
||||
'contact_internal_id' => $internal_contact?->id,
|
||||
]);
|
||||
|
||||
return rsx_view(SPA, ['bundle' => 'Frontend_Bundle']);
|
||||
}
|
||||
|
||||
The data is then available in any SPA action or component:
|
||||
|
||||
class Sidebar_Component {
|
||||
on_ready() {
|
||||
const internal_id = window.rsxapp.page_data.contact_internal_id;
|
||||
if (internal_id) {
|
||||
this.$sid('internal_link').attr('href', Rsx.Route('Contact_View_Action', internal_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MULTIPLE CALLS
|
||||
PageData::add() merges data, so you can call it multiple times:
|
||||
|
||||
PageData::add(['user_id' => $user->id]);
|
||||
PageData::add(['site_config' => $config]); // Merged with previous
|
||||
|
||||
Later calls overwrite earlier keys with the same name.
|
||||
|
||||
API
|
||||
PageData::add(array $data)
|
||||
Add key-value pairs to page_data. Merged with existing data.
|
||||
|
||||
PageData::get()
|
||||
Returns all accumulated page data (used internally by bundle renderer).
|
||||
|
||||
PageData::has_data()
|
||||
Returns true if any page data has been set (used internally).
|
||||
|
||||
SEE ALSO
|
||||
rsxapp(3), spa(3), bundle_api(3)
|
||||
|
||||
AUTHOR
|
||||
RSpade Framework
|
||||
|
||||
RSpade January 2026 PAGEDATA(3)
|
||||
207
app/RSpade/man/rsxapp.txt
Executable file
207
app/RSpade/man/rsxapp.txt
Executable file
@@ -0,0 +1,207 @@
|
||||
RSXAPP(3) RSX Framework Manual RSXAPP(3)
|
||||
|
||||
NAME
|
||||
rsxapp - Global JavaScript object containing runtime configuration and data
|
||||
|
||||
SYNOPSIS
|
||||
JavaScript:
|
||||
window.rsxapp.build_key // Manifest build hash
|
||||
window.rsxapp.user // Current user model data
|
||||
window.rsxapp.site // Current site model data
|
||||
window.rsxapp.resolved_permissions // Pre-computed user permissions
|
||||
window.rsxapp.page_data // Custom page-specific data
|
||||
window.rsxapp.is_spa // Whether current page is SPA
|
||||
window.rsxapp.csrf // CSRF token for forms
|
||||
|
||||
DESCRIPTION
|
||||
window.rsxapp is a global JavaScript object rendered with every page bundle.
|
||||
It contains session data, configuration, and runtime state needed by
|
||||
client-side JavaScript. The object is built during bundle rendering in
|
||||
Rsx_Bundle_Abstract::__generate_html() and output as an inline <script> tag
|
||||
before bundle assets load.
|
||||
|
||||
The rsxapp object provides:
|
||||
- Session context (user, site, authentication state)
|
||||
- Build information for cache management
|
||||
- Server time for client-server synchronization
|
||||
- Custom page data passed via PageData::add()
|
||||
- Debug configuration in development mode
|
||||
|
||||
HOW IT WORKS
|
||||
When a bundle renders (e.g., Frontend_Bundle::render()), the framework:
|
||||
1. Collects runtime data from various sources
|
||||
2. Merges custom page_data from PageData::add() calls
|
||||
3. JSON-encodes the data
|
||||
4. Outputs: <script>window.rsxapp = {...};</script>
|
||||
5. Bundle script tags follow, using rsxapp for initialization
|
||||
|
||||
Framework core classes (Rsx_Time, Rsx_Storage, Ajax) read from rsxapp
|
||||
during their initialization, before application code runs.
|
||||
|
||||
OBJECT STRUCTURE
|
||||
Core Properties (always present):
|
||||
|
||||
build_key String. Manifest hash for cache-busting.
|
||||
Changes when any source file changes.
|
||||
|
||||
session_hash String. Hashed session token for storage scoping.
|
||||
Non-reversible hash of rsx cookie value.
|
||||
|
||||
debug Boolean. True in non-production environments.
|
||||
|
||||
current_controller String. PHP controller handling this request.
|
||||
|
||||
current_action String. Controller method name.
|
||||
|
||||
is_auth Boolean. True if user is logged in.
|
||||
|
||||
is_spa Boolean. True if page is SPA bootstrap.
|
||||
|
||||
params Object. URL parameters from route (e.g., {id: "4"}).
|
||||
|
||||
csrf String. CSRF token for form submissions.
|
||||
|
||||
Session Data (when authenticated):
|
||||
|
||||
user Object. Current user model with all fields.
|
||||
Includes enum properties (role_id__label, etc.)
|
||||
and __MODEL marker.
|
||||
|
||||
site Object. Current site model.
|
||||
|
||||
resolved_permissions Array. Pre-computed permission IDs for current user.
|
||||
Includes role defaults plus supplementary grants,
|
||||
minus supplementary denies. Empty array if not
|
||||
authenticated. Use Permission.has_permission() to check.
|
||||
|
||||
Time Synchronization:
|
||||
|
||||
server_time String. ISO 8601 UTC timestamp from server.
|
||||
Used by Rsx_Time to correct client clock skew.
|
||||
|
||||
user_timezone String. IANA timezone (e.g., "America/Chicago").
|
||||
Resolved: user preference > site > config > default.
|
||||
|
||||
Custom Data:
|
||||
|
||||
page_data Object. Data added via PageData::add().
|
||||
Only present if data was added.
|
||||
|
||||
Debug Mode Only:
|
||||
|
||||
console_debug Object. Console debug configuration.
|
||||
Controls console_debug() output filtering.
|
||||
|
||||
ajax_disable_batching Boolean. When true, Ajax calls bypass batching.
|
||||
|
||||
Optional:
|
||||
|
||||
flash_alerts Array. Pending flash messages to display.
|
||||
Consumed by Server_Side_Flash component.
|
||||
|
||||
log_browser_errors Boolean. When true, JS errors logged to server.
|
||||
|
||||
EXAMPLE OUTPUT
|
||||
Typical rsxapp object for authenticated SPA page:
|
||||
|
||||
window.rsxapp = {
|
||||
"build_key": "72d8554b3a6a4382d9130707caff4009",
|
||||
"session_hash": "9b8cdc5ebf5a3db1e88d400bafe1af06...",
|
||||
"debug": true,
|
||||
"current_controller": "Frontend_Spa_Controller",
|
||||
"current_action": "index",
|
||||
"is_auth": true,
|
||||
"is_spa": true,
|
||||
"params": {"id": "4"},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"email": "test@example.com",
|
||||
"role_id": 300,
|
||||
"role_id__label": "Site Owner",
|
||||
"__MODEL": "User_Model"
|
||||
},
|
||||
"site": {
|
||||
"id": 1,
|
||||
"name": "Test Site",
|
||||
"timezone": "America/Chicago",
|
||||
"__MODEL": "Site_Model"
|
||||
},
|
||||
"resolved_permissions": [2, 3, 4, 5, 6, 7],
|
||||
"csrf": "f290180b609f8f353c3226accdc798961...",
|
||||
"page_data": {
|
||||
"contact_internal_id": 17
|
||||
},
|
||||
"server_time": "2026-01-13T07:48:11.482Z",
|
||||
"user_timezone": "America/Chicago"
|
||||
};
|
||||
|
||||
COMMON USAGE PATTERNS
|
||||
Check authentication:
|
||||
if (window.rsxapp.is_auth) {
|
||||
// User is logged in
|
||||
}
|
||||
|
||||
Access current user:
|
||||
const user_name = window.rsxapp.user.first_name;
|
||||
const is_admin = window.rsxapp.user.role_id === User_Model.ROLE_SITE_OWNER;
|
||||
|
||||
Check permissions (use Permission class):
|
||||
if (Permission.has_permission(User_Model.PERM_EDIT_DATA)) {
|
||||
// User can edit data
|
||||
}
|
||||
if (Permission.has_any_permission([User_Model.PERM_EDIT_DATA, User_Model.PERM_VIEW_DATA])) {
|
||||
// User can edit or view data
|
||||
}
|
||||
|
||||
Read page data:
|
||||
const contact_id = window.rsxapp.page_data?.contact_internal_id;
|
||||
|
||||
Get CSRF token for forms:
|
||||
$('form').append(`<input type="hidden" name="_token" value="${window.rsxapp.csrf}">`);
|
||||
|
||||
Check if SPA mode:
|
||||
if (window.rsxapp.is_spa) {
|
||||
// Use client-side navigation
|
||||
Spa.dispatch('/new-route');
|
||||
}
|
||||
|
||||
ADDING CUSTOM DATA
|
||||
Use PageData::add() in PHP to add custom data to page_data:
|
||||
|
||||
PageData::add([
|
||||
'feature_flags' => ['new_ui', 'beta_feature'],
|
||||
'config' => $site_config,
|
||||
]);
|
||||
|
||||
Access in JavaScript:
|
||||
if (window.rsxapp.page_data.feature_flags.includes('new_ui')) {
|
||||
// Enable new UI
|
||||
}
|
||||
|
||||
See pagedata(3) for detailed usage.
|
||||
|
||||
UNDERSCORE KEY FILTERING
|
||||
Keys starting with single underscore (e.g., _internal) are automatically
|
||||
filtered out before JSON encoding. Keys with double underscore (e.g., __MODEL)
|
||||
are preserved. This allows models to have internal properties that don't
|
||||
leak to JavaScript.
|
||||
|
||||
FRAMEWORK CONSUMERS
|
||||
These framework classes read from rsxapp during initialization:
|
||||
|
||||
Rsx_Time Reads server_time and user_timezone for clock sync
|
||||
Rsx_Storage Reads session_hash, user.id, site.id, build_key for scoping
|
||||
Ajax Reads csrf for request headers
|
||||
Spa Reads is_spa, params for routing
|
||||
Permission Reads resolved_permissions for access control checks
|
||||
Debugger Reads console_debug for output filtering
|
||||
|
||||
SEE ALSO
|
||||
pagedata(3), bundle_api(3), time(3), storage(3), spa(3), acls(3)
|
||||
|
||||
AUTHOR
|
||||
RSpade Framework
|
||||
|
||||
RSpade January 2026 RSXAPP(3)
|
||||
@@ -158,6 +158,32 @@ BOOTSTRAP CONTROLLER
|
||||
- One per feature/bundle
|
||||
- Naming: {Feature}_Spa_Controller::index
|
||||
|
||||
Passing Page Data:
|
||||
Use PageData::add() to pass server-side data to JavaScript actions:
|
||||
|
||||
use App\RSpade\Core\View\PageData;
|
||||
|
||||
#[SPA]
|
||||
public static function index(Request $request, array $params = [])
|
||||
{
|
||||
// Load data needed by SPA actions/components
|
||||
$internal_contact = Contact_Model::where('type_id', Contact_Model::TYPE_INTERNAL)
|
||||
->first();
|
||||
|
||||
PageData::add([
|
||||
'contact_internal_id' => $internal_contact?->id,
|
||||
'feature_flags' => config('rsx.features'),
|
||||
]);
|
||||
|
||||
return rsx_view(SPA, ['bundle' => 'Frontend_Bundle']);
|
||||
}
|
||||
|
||||
Access in JavaScript via window.rsxapp.page_data:
|
||||
|
||||
const internal_id = window.rsxapp.page_data.contact_internal_id;
|
||||
|
||||
See pagedata(3) for detailed usage.
|
||||
|
||||
Multiple SPA Bootstraps:
|
||||
Different features can have separate SPA bootstraps:
|
||||
- /app/frontend/Frontend_Spa_Controller::index (regular users)
|
||||
@@ -899,4 +925,6 @@ SEE ALSO
|
||||
routing(3) - URL generation and route patterns
|
||||
modals(3) - Modal dialogs in SPA context
|
||||
ajax_error_handling(3) - Error handling patterns
|
||||
pagedata(3) - Passing server-side data to JavaScript
|
||||
rsxapp(3) - Global JavaScript runtime object
|
||||
scss(3) - SCSS scoping conventions and component-first philosophy
|
||||
|
||||
Reference in New Issue
Block a user