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
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user