Framework updates
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,50 +30,42 @@ use App\RSpade\Core\Files\File_Storage_Model;
|
||||
* provides the basic structure for categorizing uploaded files.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* _AUTO_GENERATED_ Database type hints - do not edit manually
|
||||
* Generated on: 2025-12-26 02:43:29
|
||||
* Table: _file_attachments
|
||||
*
|
||||
* @property int $id
|
||||
* @property mixed $key
|
||||
* @property int $file_storage_id
|
||||
* @property mixed $file_name
|
||||
* @property mixed $file_extension
|
||||
* @property int $file_type_id
|
||||
* @property int $width
|
||||
* @property int $height
|
||||
* @property int $duration
|
||||
* @property bool $is_animated
|
||||
* @property int $frame_count
|
||||
* @property mixed $fileable_type
|
||||
* @property int $fileable_id
|
||||
* @property mixed $fileable_category
|
||||
* @property mixed $fileable_type_meta
|
||||
* @property int $fileable_order
|
||||
* _AUTO_GENERATED_
|
||||
* @property integer $id
|
||||
* @property string $key
|
||||
* @property integer $file_storage_id
|
||||
* @property string $file_name
|
||||
* @property string $file_extension
|
||||
* @property integer $file_type_id
|
||||
* @property integer $width
|
||||
* @property integer $height
|
||||
* @property integer $duration
|
||||
* @property boolean $is_animated
|
||||
* @property integer $frame_count
|
||||
* @property integer $fileable_type
|
||||
* @property integer $fileable_id
|
||||
* @property string $fileable_category
|
||||
* @property string $fileable_type_meta
|
||||
* @property integer $fileable_order
|
||||
* @property string $fileable_meta
|
||||
* @property int $site_id
|
||||
* @property mixed $session_id
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
*
|
||||
* @property-read string $file_type_id__label
|
||||
* @property-read string $file_type_id__constant
|
||||
*
|
||||
* @method static array file_type_id__enum() Get all enum definitions with full metadata
|
||||
* @method static array file_type_id__enum_select() Get selectable items for dropdowns
|
||||
* @method static array file_type_id__enum_labels() Get simple id => label map
|
||||
* @method static array file_type_id__enum_ids() Get array of all valid enum IDs
|
||||
*
|
||||
* @property integer $site_id
|
||||
* @property string $session_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property integer $created_by
|
||||
* @property integer $updated_by
|
||||
* @method static mixed file_type_id_enum()
|
||||
* @method static mixed file_type_id_enum_select()
|
||||
* @method static mixed file_type_id_enum_ids()
|
||||
* @property-read mixed $file_type_id_constant
|
||||
* @property-read mixed $file_type_id_label
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class File_Attachment_Model extends Rsx_Site_Model_Abstract
|
||||
{
|
||||
/**
|
||||
* _AUTO_GENERATED_ Enum constants
|
||||
*/
|
||||
{
|
||||
/** __AUTO_GENERATED: */
|
||||
const FILE_TYPE_IMAGE = 1;
|
||||
const FILE_TYPE_ANIMATED_IMAGE = 2;
|
||||
const FILE_TYPE_VIDEO = 3;
|
||||
@@ -81,9 +73,6 @@ class File_Attachment_Model extends Rsx_Site_Model_Abstract
|
||||
const FILE_TYPE_TEXT = 5;
|
||||
const FILE_TYPE_DOCUMENT = 6;
|
||||
const FILE_TYPE_OTHER = 7;
|
||||
|
||||
/** __AUTO_GENERATED: */
|
||||
|
||||
/** __/AUTO_GENERATED */
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,9 +111,18 @@ class Rsx_Date {
|
||||
// CURRENT DATE
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Alias for today()
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
static now() {
|
||||
return this.today();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date as "YYYY-MM-DD"
|
||||
* Uses the user's timezone to determine what "today" is
|
||||
* Uses user → site → default timezone from rsxapp
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
@@ -236,4 +245,309 @@ class Rsx_Date {
|
||||
|
||||
return Math.round((ms2 - ms1) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format as relative date ("Today", "Yesterday", "3 days ago", "in 5 days")
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string}
|
||||
*/
|
||||
static relative(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const days = this.diff_days(this.today(), parsed);
|
||||
|
||||
if (days === 0) {
|
||||
return 'Today';
|
||||
} else if (days === 1) {
|
||||
return 'Tomorrow';
|
||||
} else if (days === -1) {
|
||||
return 'Yesterday';
|
||||
} else if (days > 1) {
|
||||
return `in ${days} days`;
|
||||
} else {
|
||||
return `${Math.abs(days)} days ago`;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARITHMETIC
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Add days to a date
|
||||
*
|
||||
* @param {*} date
|
||||
* @param {number} days Can be negative to subtract
|
||||
* @returns {string|null} "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
static add_days(date, days) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
d.setDate(d.getDate() + days);
|
||||
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// WEEK/MONTH BOUNDARIES
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get the Monday of the week containing the date
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string|null} "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
static start_of_week(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
const dow = d.getDay();
|
||||
// Convert to Monday=0 based, then subtract to get Monday
|
||||
const daysToSubtract = (dow === 0) ? 6 : dow - 1;
|
||||
d.setDate(d.getDate() - daysToSubtract);
|
||||
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sunday of the week containing the date
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string|null} "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
static end_of_week(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
const dow = d.getDay();
|
||||
// Days to add to get to Sunday
|
||||
const daysToAdd = (dow === 0) ? 0 : 7 - dow;
|
||||
d.setDate(d.getDate() + daysToAdd);
|
||||
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first day of the month containing the date
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string|null} "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
static start_of_month(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month] = parsed.split('-').map(Number);
|
||||
return year + '-' + String(month).padStart(2, '0') + '-01';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last day of the month containing the date
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string|null} "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
static end_of_month(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month] = parsed.split('-').map(Number);
|
||||
// Day 0 of next month = last day of current month
|
||||
const d = new Date(year, month, 0);
|
||||
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekend (Saturday or Sunday)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static is_weekend(date) {
|
||||
const dow = this.dow(date);
|
||||
if (dow === null) {
|
||||
return false;
|
||||
}
|
||||
return dow === 0 || dow === 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekday (Monday-Friday)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static is_weekday(date) {
|
||||
const dow = this.dow(date);
|
||||
if (dow === null) {
|
||||
return false;
|
||||
}
|
||||
return dow >= 1 && dow <= 5;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static day(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
return parseInt(parsed.split('-')[2], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static dow(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
return d.getDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string}
|
||||
*/
|
||||
static dow_human(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string}
|
||||
*/
|
||||
static dow_short(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', { weekday: 'short' }).format(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static month(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
return parseInt(parsed.split('-')[1], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string}
|
||||
*/
|
||||
static month_human(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', { month: 'long' }).format(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {string}
|
||||
*/
|
||||
static month_human_short(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [year, month, day] = parsed.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', { month: 'short' }).format(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
*
|
||||
* @param {*} date
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static year(date) {
|
||||
const parsed = this.parse(date);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
return parseInt(parsed.split('-')[0], 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,4 +609,157 @@ class Rsx_Time {
|
||||
stop: () => clearInterval(interval)
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static day(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
return parseInt(this.format_in_timezone(time, { day: 'numeric' }), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static dow(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
// Get the day name and map to number
|
||||
const dayName = this.format_in_timezone(time, { weekday: 'short' });
|
||||
const dayMap = { 'Sun': 0, 'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5, 'Sat': 6 };
|
||||
return dayMap[dayName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {string}
|
||||
*/
|
||||
static dow_human(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return '';
|
||||
|
||||
return this.format_in_timezone(time, { weekday: 'long' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {string}
|
||||
*/
|
||||
static dow_short(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return '';
|
||||
|
||||
return this.format_in_timezone(time, { weekday: 'short' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static month(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
// Use 2-digit to get padded month, then parse
|
||||
const monthStr = this.format_in_timezone(time, { month: '2-digit' });
|
||||
return parseInt(monthStr, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {string}
|
||||
*/
|
||||
static month_human(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return '';
|
||||
|
||||
return this.format_in_timezone(time, { month: 'long' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {string}
|
||||
*/
|
||||
static month_human_short(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return '';
|
||||
|
||||
return this.format_in_timezone(time, { month: 'short' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static year(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
return parseInt(this.format_in_timezone(time, { year: 'numeric' }), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hour (0-23)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static hour(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
// Use hour12: false and 2-digit for consistent 24-hour format
|
||||
const hourStr = this.format_in_timezone(time, { hour: '2-digit', hour12: false });
|
||||
// "24" is returned for midnight in some locales, treat as 0
|
||||
const hour = parseInt(hourStr, 10);
|
||||
return hour === 24 ? 0 : hour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minute (0-59)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param {*} time
|
||||
* @returns {number|null}
|
||||
*/
|
||||
static minute(time) {
|
||||
const date = this.parse(time);
|
||||
if (!date) return null;
|
||||
|
||||
return parseInt(this.format_in_timezone(time, { minute: '2-digit' }), 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,46 +23,41 @@ use App\RSpade\Core\Models\User_Profile_Model;
|
||||
* See: php artisan rsx:man acls
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* _AUTO_GENERATED_ Database type hints - do not edit manually
|
||||
* Generated on: 2025-12-26 02:43:29
|
||||
* Table: users
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $login_user_id
|
||||
* @property int $site_id
|
||||
* @property mixed $first_name
|
||||
* @property mixed $last_name
|
||||
* @property mixed $phone
|
||||
* @property int $role_id
|
||||
* @property bool $is_enabled
|
||||
* @property int $user_role_id
|
||||
* @property mixed $email
|
||||
* @property string $deleted_at
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property int $deleted_by
|
||||
* @property mixed $invite_code
|
||||
* @property string $invite_accepted_at
|
||||
* @property string $invite_expires_at
|
||||
*
|
||||
* @property-read string $role_id__label
|
||||
* @property-read string $role_id__constant
|
||||
*
|
||||
* @method static array role_id__enum() Get all enum definitions with full metadata
|
||||
* @method static array role_id__enum_select() Get selectable items for dropdowns
|
||||
* @method static array role_id__enum_labels() Get simple id => label map
|
||||
* @method static array role_id__enum_ids() Get array of all valid enum IDs
|
||||
*
|
||||
* _AUTO_GENERATED_
|
||||
* @property integer $id
|
||||
* @property integer $login_user_id
|
||||
* @property integer $site_id
|
||||
* @property string $first_name
|
||||
* @property string $last_name
|
||||
* @property string $phone
|
||||
* @property integer $role_id
|
||||
* @property boolean $is_enabled
|
||||
* @property integer $user_role_id
|
||||
* @property string $email
|
||||
* @property \Carbon\Carbon $deleted_at
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property integer $created_by
|
||||
* @property integer $updated_by
|
||||
* @property integer $deleted_by
|
||||
* @property string $invite_code
|
||||
* @property \Carbon\Carbon $invite_accepted_at
|
||||
* @property \Carbon\Carbon $invite_expires_at
|
||||
* @method static mixed role_id_enum()
|
||||
* @method static mixed role_id_enum_select()
|
||||
* @method static mixed role_id_enum_ids()
|
||||
* @property-read mixed $role_id_constant
|
||||
* @property-read mixed $role_id_label
|
||||
* @property-read mixed $role_id_permissions
|
||||
* @property-read mixed $role_id_can_admin_roles
|
||||
* @property-read mixed $role_id_selectable
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User_Model extends Rsx_Site_Model_Abstract
|
||||
{
|
||||
/**
|
||||
* _AUTO_GENERATED_ Enum constants
|
||||
*/
|
||||
{
|
||||
/** __AUTO_GENERATED: */
|
||||
const ROLE_DEVELOPER = 100;
|
||||
const ROLE_ROOT_ADMIN = 200;
|
||||
const ROLE_SITE_OWNER = 300;
|
||||
@@ -71,9 +66,6 @@ class User_Model extends Rsx_Site_Model_Abstract
|
||||
const ROLE_USER = 600;
|
||||
const ROLE_VIEWER = 700;
|
||||
const ROLE_DISABLED = 800;
|
||||
|
||||
/** __AUTO_GENERATED: */
|
||||
|
||||
/** __/AUTO_GENERATED */
|
||||
|
||||
// =========================================================================
|
||||
|
||||
@@ -11,44 +11,33 @@ use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
|
||||
* and two-factor authentication via email or SMS.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* _AUTO_GENERATED_ Database type hints - do not edit manually
|
||||
* Generated on: 2025-12-26 02:43:29
|
||||
* Table: user_verifications
|
||||
*
|
||||
* @property int $id
|
||||
* @property mixed $email
|
||||
* @property mixed $verification_code
|
||||
* @property int $verification_type_id
|
||||
* @property string $verified_at
|
||||
* @property string $expires_at
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
*
|
||||
* @property-read string $verification_type_id__label
|
||||
* @property-read string $verification_type_id__constant
|
||||
*
|
||||
* @method static array verification_type_id__enum() Get all enum definitions with full metadata
|
||||
* @method static array verification_type_id__enum_select() Get selectable items for dropdowns
|
||||
* @method static array verification_type_id__enum_labels() Get simple id => label map
|
||||
* @method static array verification_type_id__enum_ids() Get array of all valid enum IDs
|
||||
*
|
||||
* _AUTO_GENERATED_
|
||||
* @property integer $id
|
||||
* @property string $email
|
||||
* @property string $verification_code
|
||||
* @property integer $verification_type_id
|
||||
* @property \Carbon\Carbon $verified_at
|
||||
* @property \Carbon\Carbon $expires_at
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property integer $created_by
|
||||
* @property integer $updated_by
|
||||
* @method static mixed verification_type_id_enum()
|
||||
* @method static mixed verification_type_id_enum_select()
|
||||
* @method static mixed verification_type_id_enum_ids()
|
||||
* @property-read mixed $verification_type_id_constant
|
||||
* @property-read mixed $verification_type_id_label
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User_Verification_Model extends Rsx_Model_Abstract
|
||||
{
|
||||
/**
|
||||
* _AUTO_GENERATED_ Enum constants
|
||||
*/
|
||||
{
|
||||
/** __AUTO_GENERATED: */
|
||||
const VERIFICATION_TYPE_EMAIL = 1;
|
||||
const VERIFICATION_TYPE_SMS = 2;
|
||||
const VERIFICATION_TYPE_EMAIL_RECOVERY = 3;
|
||||
const VERIFICATION_TYPE_SMS_RECOVERY = 4;
|
||||
|
||||
/** __AUTO_GENERATED: */
|
||||
|
||||
/** __/AUTO_GENERATED */
|
||||
|
||||
/**
|
||||
|
||||
@@ -113,16 +113,26 @@ class Rsx_Date
|
||||
// CURRENT DATE
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Alias for today()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function now(): string
|
||||
{
|
||||
return static::today();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date as "YYYY-MM-DD"
|
||||
* Uses the user's timezone to determine what "today" is
|
||||
* Uses user → site → default timezone resolution
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function today(): string
|
||||
{
|
||||
$user_tz = Rsx_Time::get_user_timezone();
|
||||
return Carbon::now($user_tz)->format('Y-m-d');
|
||||
$tz = Rsx_Time::get_user_timezone();
|
||||
return Carbon::now($tz)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -230,6 +240,299 @@ class Rsx_Date
|
||||
return $carbon1->diffInDays($carbon2, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format as relative date ("Today", "Yesterday", "3 days ago", "in 5 days")
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function relative($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$days = static::diff_days(static::today(), $parsed);
|
||||
|
||||
if ($days === 0) {
|
||||
return 'Today';
|
||||
} elseif ($days === 1) {
|
||||
return 'Tomorrow';
|
||||
} elseif ($days === -1) {
|
||||
return 'Yesterday';
|
||||
} elseif ($days > 1) {
|
||||
return "in {$days} days";
|
||||
} else {
|
||||
return abs($days) . ' days ago';
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARITHMETIC
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Add days to a date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @param int $days Can be negative to subtract
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function add_days($date, int $days): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->addDays($days);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// WEEK/MONTH BOUNDARIES
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get the Monday of the week containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function start_of_week($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->startOfWeek(Carbon::MONDAY);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sunday of the week containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function end_of_week($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->endOfWeek(Carbon::SUNDAY);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first day of the month containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function start_of_month($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->startOfMonth();
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last day of the month containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function end_of_month($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->endOfMonth();
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekend (Saturday or Sunday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_weekend($date): bool
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->isWeekend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekday (Monday-Friday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_weekday($date): bool
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->isWeekday();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function day($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function dow($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->dayOfWeek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_human($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('l');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_short($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('D');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function month($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('F');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human_short($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('M');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function year($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[0];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DATABASE
|
||||
// =========================================================================
|
||||
|
||||
@@ -31,6 +31,40 @@ use App\RSpade\Core\Session\Session;
|
||||
*/
|
||||
class Rsx_Time
|
||||
{
|
||||
// =========================================================================
|
||||
// TIMEZONE CACHING
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Cached user timezone
|
||||
* @var string|null
|
||||
*/
|
||||
private static ?string $_cached_user_timezone = null;
|
||||
|
||||
/**
|
||||
* User ID when timezone was cached (for invalidation)
|
||||
* @var int|null
|
||||
*/
|
||||
private static ?int $_cached_user_id = null;
|
||||
|
||||
/**
|
||||
* Site ID when timezone was cached (for invalidation)
|
||||
* @var int|null
|
||||
*/
|
||||
private static ?int $_cached_site_id = null;
|
||||
|
||||
/**
|
||||
* Clear cached timezone (called when session user/site changes)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_timezone_cache(): void
|
||||
{
|
||||
static::$_cached_user_timezone = null;
|
||||
static::$_cached_user_id = null;
|
||||
static::$_cached_site_id = null;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CURRENT TIME
|
||||
// =========================================================================
|
||||
@@ -210,24 +244,65 @@ class Rsx_Time
|
||||
/**
|
||||
* Get the current user's timezone
|
||||
* Resolution: user setting → site default → config default → America/Chicago
|
||||
* Result is cached and invalidated when session user/site changes
|
||||
*
|
||||
* @return string IANA timezone identifier
|
||||
*/
|
||||
public static function get_user_timezone(): string
|
||||
{
|
||||
$current_user_id = Session::get_login_user_id();
|
||||
$current_site_id = Session::get_site_id();
|
||||
|
||||
// Check if cache is valid
|
||||
if (static::$_cached_user_timezone !== null
|
||||
&& static::$_cached_user_id === $current_user_id
|
||||
&& static::$_cached_site_id === $current_site_id) {
|
||||
return static::$_cached_user_timezone;
|
||||
}
|
||||
|
||||
// Cache miss - recalculate
|
||||
$timezone = null;
|
||||
|
||||
// Check logged-in user's preference
|
||||
$login_user = Session::get_login_user();
|
||||
if ($login_user && !empty($login_user->timezone)) {
|
||||
return $login_user->timezone;
|
||||
$timezone = $login_user->timezone;
|
||||
}
|
||||
|
||||
// Check site default (future enhancement)
|
||||
// $site = Session::get_site();
|
||||
// if ($site && !empty($site->timezone)) {
|
||||
// return $site->timezone;
|
||||
// }
|
||||
// Check site default
|
||||
if ($timezone === null) {
|
||||
$site = Session::get_site();
|
||||
if ($site && !empty($site->timezone)) {
|
||||
$timezone = $site->timezone;
|
||||
}
|
||||
}
|
||||
|
||||
// Config default
|
||||
if ($timezone === null) {
|
||||
$timezone = config('rsx.datetime.default_timezone', 'America/Chicago');
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
static::$_cached_user_timezone = $timezone;
|
||||
static::$_cached_user_id = $current_user_id;
|
||||
static::$_cached_site_id = $current_site_id;
|
||||
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current site's timezone (ignoring user preference)
|
||||
* Resolution: site default → config default → America/Chicago
|
||||
*
|
||||
* @return string IANA timezone identifier
|
||||
*/
|
||||
public static function get_site_timezone(): string
|
||||
{
|
||||
$site = Session::get_site();
|
||||
if ($site && !empty($site->timezone)) {
|
||||
return $site->timezone;
|
||||
}
|
||||
|
||||
return config('rsx.datetime.default_timezone', 'America/Chicago');
|
||||
}
|
||||
|
||||
@@ -308,6 +383,28 @@ class Rsx_Time
|
||||
return $end_carbon->diffInSeconds($start_carbon, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seconds until a future time (negative if past)
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int
|
||||
*/
|
||||
public static function seconds_until($time): int
|
||||
{
|
||||
return static::diff_seconds(static::now(), $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seconds since a past time (negative if future)
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int
|
||||
*/
|
||||
public static function seconds_since($time): int
|
||||
{
|
||||
return static::diff_seconds($time, static::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration as human-readable string
|
||||
*
|
||||
@@ -517,6 +614,170 @@ class Rsx_Time
|
||||
return static::format($time, 'M j, Y g:i A T', $timezone);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function day($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function dow($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return static::to_user_timezone($carbon)->dayOfWeek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_human($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('l');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_short($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('D');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function month($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('F');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human_short($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('M');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function year($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hour (0-23)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function hour($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('G');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minute (0-59)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function minute($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('i');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DATABASE HELPERS
|
||||
// =========================================================================
|
||||
|
||||
@@ -5,41 +5,29 @@ namespace App\RSpade\Lib\Flash;
|
||||
use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
|
||||
|
||||
/**
|
||||
* _AUTO_GENERATED_ Database type hints - do not edit manually
|
||||
* Generated on: 2025-12-26 02:43:30
|
||||
* Table: _flash_alerts
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $session_id
|
||||
* @property int $type_id
|
||||
* _AUTO_GENERATED_
|
||||
* @property integer $id
|
||||
* @property integer $session_id
|
||||
* @property integer $type_id
|
||||
* @property string $message
|
||||
* @property string $created_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $updated_at
|
||||
*
|
||||
* @property-read string $type_id__label
|
||||
* @property-read string $type_id__constant
|
||||
*
|
||||
* @method static array type_id__enum() Get all enum definitions with full metadata
|
||||
* @method static array type_id__enum_select() Get selectable items for dropdowns
|
||||
* @method static array type_id__enum_labels() Get simple id => label map
|
||||
* @method static array type_id__enum_ids() Get array of all valid enum IDs
|
||||
*
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property integer $created_by
|
||||
* @property integer $updated_by
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @method static mixed type_id_enum()
|
||||
* @method static mixed type_id_enum_select()
|
||||
* @method static mixed type_id_enum_ids()
|
||||
* @property-read mixed $type_id_constant
|
||||
* @property-read mixed $type_id_label
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Flash_Alert_Model extends Rsx_Model_Abstract
|
||||
{
|
||||
/**
|
||||
* _AUTO_GENERATED_ Enum constants
|
||||
*/
|
||||
{
|
||||
/** __AUTO_GENERATED: */
|
||||
const TYPE_SUCCESS = 1;
|
||||
const TYPE_ERROR = 2;
|
||||
const TYPE_INFO = 3;
|
||||
const TYPE_WARNING = 4;
|
||||
|
||||
/** __AUTO_GENERATED: */
|
||||
|
||||
/** __/AUTO_GENERATED */
|
||||
|
||||
// Enum constants (auto-generated by rsx:migrate:document_models)
|
||||
|
||||
@@ -18,7 +18,11 @@ SYNOPSIS
|
||||
|
||||
$today = Rsx_Date::today();
|
||||
$formatted = Rsx_Date::format($date);
|
||||
$relative = Rsx_Date::relative($date);
|
||||
$is_past = Rsx_Date::is_past($date);
|
||||
$next_week = Rsx_Date::add_days($date, 7);
|
||||
$monday = Rsx_Date::start_of_week($date);
|
||||
$month_name = Rsx_Date::month_human($date);
|
||||
|
||||
JavaScript Datetime:
|
||||
const now = Rsx_Time.now();
|
||||
@@ -29,7 +33,10 @@ SYNOPSIS
|
||||
JavaScript Date:
|
||||
const today = Rsx_Date.today();
|
||||
const formatted = Rsx_Date.format(date);
|
||||
const relative = Rsx_Date.relative(date);
|
||||
const is_past = Rsx_Date.is_past(date);
|
||||
const next_week = Rsx_Date.add_days(date, 7);
|
||||
const monday = Rsx_Date.start_of_week(date);
|
||||
|
||||
DESCRIPTION
|
||||
RSpade provides two separate classes for handling temporal values:
|
||||
@@ -111,6 +118,9 @@ RSX_DATE CLASS
|
||||
PHP: $today = Rsx_Date::today();
|
||||
JS: const today = Rsx_Date.today();
|
||||
|
||||
now()
|
||||
Alias for today(). Provided for consistency with Rsx_Time::now().
|
||||
|
||||
Formatting
|
||||
format($date)
|
||||
Display format: "Dec 24, 2025"
|
||||
@@ -118,12 +128,42 @@ RSX_DATE CLASS
|
||||
format_iso($date)
|
||||
Ensures "YYYY-MM-DD" format.
|
||||
|
||||
relative($date)
|
||||
Human-readable relative date.
|
||||
"Today", "Yesterday", "Tomorrow", "3 days ago", "in 5 days"
|
||||
|
||||
Comparison
|
||||
is_today($date) True if date is today
|
||||
is_past($date) True if date is before today
|
||||
is_future($date) True if date is after today
|
||||
diff_days($d1, $d2) Days between dates (positive if d2 > d1)
|
||||
|
||||
Arithmetic
|
||||
add_days($date, $days)
|
||||
Add (or subtract with negative) days from date.
|
||||
Returns "YYYY-MM-DD" or null if invalid input.
|
||||
|
||||
Week/Month Boundaries
|
||||
start_of_week($date) Monday of the week containing date
|
||||
end_of_week($date) Sunday of the week containing date
|
||||
start_of_month($date) First day of month (YYYY-MM-01)
|
||||
end_of_month($date) Last day of month
|
||||
|
||||
is_weekend($date) True if Saturday or Sunday
|
||||
is_weekday($date) True if Monday-Friday
|
||||
|
||||
Component Extractors
|
||||
All extractors return null for invalid input.
|
||||
|
||||
day($date) Day of month (1-31)
|
||||
dow($date) Day of week integer (0=Sunday, 6=Saturday)
|
||||
dow_human($date) Full day name ("Monday", "Tuesday", etc.)
|
||||
dow_short($date) Short day name ("Mon", "Tue", etc.)
|
||||
month($date) Month number (1-12)
|
||||
month_human($date) Full month name ("January", "February")
|
||||
month_human_short($date) Short month name ("Jan", "Feb")
|
||||
year($date) Four-digit year (e.g., 2025)
|
||||
|
||||
Database
|
||||
to_database($date)
|
||||
Returns "YYYY-MM-DD" for database storage (same as ISO format).
|
||||
@@ -181,11 +221,11 @@ RSX_TIME CLASS
|
||||
diff_seconds($start, $end)
|
||||
Seconds between two datetimes.
|
||||
|
||||
seconds_until($time) (JS only)
|
||||
Seconds until future time.
|
||||
seconds_until($time)
|
||||
Seconds until future time (negative if in past).
|
||||
|
||||
seconds_since($time) (JS only)
|
||||
Seconds since past time.
|
||||
seconds_since($time)
|
||||
Seconds since past time (negative if in future).
|
||||
|
||||
duration_to_human($seconds, $short)
|
||||
Long: "2 hours and 30 minutes"
|
||||
@@ -206,6 +246,22 @@ RSX_TIME CLASS
|
||||
is_future($time) True if datetime is in the future
|
||||
is_today($time) True if datetime is today (in user's timezone)
|
||||
|
||||
Component Extractors
|
||||
Extract date/time components from a datetime. All require timezone
|
||||
parameter ($tz) or use user's timezone if omitted.
|
||||
Return null for invalid input.
|
||||
|
||||
day($time, $tz) Day of month (1-31)
|
||||
dow($time, $tz) Day of week integer (0=Sunday, 6=Saturday)
|
||||
dow_human($time, $tz) Full day name ("Monday", "Tuesday", etc.)
|
||||
dow_short($time, $tz) Short day name ("Mon", "Tue", etc.)
|
||||
month($time, $tz) Month number (1-12)
|
||||
month_human($time, $tz) Full month name ("January", "February")
|
||||
month_human_short($time, $tz) Short month name ("Jan", "Feb")
|
||||
year($time, $tz) Four-digit year (e.g., 2025)
|
||||
hour($time, $tz) Hour (0-23)
|
||||
minute($time, $tz) Minute (0-59)
|
||||
|
||||
Live Updates (JavaScript only)
|
||||
countdown($element, target_time, options)
|
||||
Live countdown to future time. Updates every second.
|
||||
@@ -224,8 +280,12 @@ RSX_TIME CLASS
|
||||
TIMEZONE INITIALIZATION
|
||||
User timezone is resolved in order:
|
||||
1. login_users.timezone (user's preference)
|
||||
2. config('rsx.datetime.default_timezone')
|
||||
3. 'America/Chicago' (hardcoded fallback)
|
||||
2. sites.timezone (site's default timezone)
|
||||
3. config('rsx.datetime.default_timezone')
|
||||
4. 'America/Chicago' (hardcoded fallback)
|
||||
|
||||
Timezone is cached per-request and auto-invalidates when user or site
|
||||
changes (e.g., after login/logout or site switch).
|
||||
|
||||
Page Load
|
||||
On page load, window.rsxapp includes:
|
||||
@@ -397,6 +457,7 @@ CONFIGURATION
|
||||
],
|
||||
|
||||
User timezone stored in login_users.timezone column.
|
||||
Site timezone stored in sites.timezone column (default: 'America/Chicago').
|
||||
|
||||
MODEL CASTING
|
||||
Model date/datetime columns are automatically cast to strings via custom
|
||||
@@ -434,4 +495,4 @@ SEE ALSO
|
||||
AUTHOR
|
||||
RSpade Framework
|
||||
|
||||
RSpade December 2025 TIME(7)
|
||||
RSpade January 2026 TIME(7)
|
||||
|
||||
@@ -376,6 +376,11 @@
|
||||
"created_at": "2025-12-27T22:53:05+00:00",
|
||||
"created_by": "root",
|
||||
"command": "php artisan make:migration:safe convert_tasks_polymorphic_to_type_refs"
|
||||
},
|
||||
"2026_01_12_073624_add_timezone_to_sites_table.php": {
|
||||
"created_at": "2026-01-12T07:36:24+00:00",
|
||||
"created_by": "root",
|
||||
"command": "php artisan make:migration:safe add_timezone_to_sites_table"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
database/migrations/2026_01_12_073624_add_timezone_to_sites_table.php
Executable file
23
database/migrations/2026_01_12_073624_add_timezone_to_sites_table.php
Executable file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* IMPORTANT: Use raw MySQL queries for clarity and auditability
|
||||
* ✅ DB::statement("ALTER TABLE sites ADD COLUMN new_field VARCHAR(255)")
|
||||
* ❌ Schema::table() with Blueprint
|
||||
*
|
||||
* Migrations must be self-contained - no Model/Service references
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
DB::statement("ALTER TABLE sites ADD COLUMN timezone VARCHAR(50) NOT NULL DEFAULT 'America/Chicago' AFTER name");
|
||||
}
|
||||
};
|
||||
@@ -964,7 +964,7 @@ Sessions persist 365 days. Never implement "Remember Me".
|
||||
|
||||
Pattern recognition:
|
||||
- `Rsx_Time::now()`, `Rsx_Time::format()`, `Rsx_Time::relative()`
|
||||
- `Rsx_Date::today()`, `Rsx_Date::format()`, `Rsx_Date::is_past()`
|
||||
- `Rsx_Date::today()`, `Rsx_Date::format()`, `Rsx_Date::relative()` - uses user → site → default timezone
|
||||
- `Rsx_Time::to_database()` for UTC storage
|
||||
- Functions throw if wrong type passed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user