type PhpNullish = null | undefined
type PhpInput = {} | PhpNullish
type PhpList<T = PhpInput> = T[]
type PhpAssoc<T = PhpInput> = { [key: string]: T }
type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
function isPhpList<T = PhpInput>(value: PhpInput): value is PhpList<T> { return Array.isArray(value) }
function isObjectLike(value: PhpInput): value is PhpArrayLike<PhpInput> { return typeof value === 'object' && value !== null }
function isPhpAssocObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T> { return isObjectLike(value) && !isPhpList(value) }
interface IniEntry { local_value?: PhpInput }
type LocaleEntry = PhpAssoc<PhpInput> & { sorting?: (left: PhpInput, right: PhpInput) => number }
type LocaleCategoryMap = PhpAssoc<string | undefined>
interface LocutusRuntimeContainer { php?: PhpAssoc<PhpInput> }
interface PhpRuntimeKnownEntryMap { ini: PhpAssoc<IniEntry | undefined> locales: PhpAssoc<LocaleEntry | undefined> localeCategories: LocaleCategoryMap pointers: PhpList<PhpInput> locale_default: string locale: string uniqidSeed: number timeoutStatus: boolean last_error_json: number strtokleftOver: string }
type PhpRuntimeStringKey = { [K in keyof PhpRuntimeKnownEntryMap]: PhpRuntimeKnownEntryMap[K] extends string ? K : never }[keyof PhpRuntimeKnownEntryMap]
interface PhpGlobalProcessLike { env?: PhpAssoc<string | undefined> }
interface PhpGlobalBufferLike { from?: (...args: PhpInput[]) => PhpInput }
interface PhpGlobalKnownEntryMap { process: PhpGlobalProcessLike Buffer: PhpGlobalBufferLike }
type GlobalWithLocutus = { $locutus?: LocutusRuntimeContainer [key: string]: PhpInput }
interface PhpRuntimeState { ini: PhpAssoc<IniEntry | undefined> locales: PhpAssoc<LocaleEntry | undefined> localeCategories: LocaleCategoryMap pointers: PhpList<PhpInput> locale_default: string | undefined }
const isIniBag = (value: PhpInput): value is PhpAssoc<IniEntry | undefined> => isPhpAssocObject<IniEntry | undefined>(value)
const isLocaleBag = (value: PhpInput): value is PhpAssoc<LocaleEntry | undefined> => isPhpAssocObject<LocaleEntry | undefined>(value)
const isLocaleCategoryBag = (value: PhpInput): value is LocaleCategoryMap => isPhpAssocObject<string | undefined>(value)
const globalContext: GlobalWithLocutus = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {}
const ensurePhpRuntimeObject = (): PhpAssoc<PhpInput> => { let locutus = globalContext.$locutus if (typeof locutus !== 'object' || locutus === null) { locutus = {} globalContext.$locutus = locutus }
let php = locutus.php if (typeof php !== 'object' || php === null) { php = {} locutus.php = php }
return php }
function ensurePhpRuntimeState(): PhpRuntimeState { const php = ensurePhpRuntimeObject() const iniValue = php.ini const localesValue = php.locales const localeCategoriesValue = php.localeCategories const pointersValue = php.pointers
const ini = isIniBag(iniValue) ? iniValue : {} const locales = isLocaleBag(localesValue) ? localesValue : {} const localeCategories = isLocaleCategoryBag(localeCategoriesValue) ? localeCategoriesValue : {} const pointers: PhpList<PhpInput> = Array.isArray(pointersValue) ? pointersValue : []
if (iniValue !== ini) { php.ini = ini } if (localesValue !== locales) { php.locales = locales } if (localeCategoriesValue !== localeCategories) { php.localeCategories = localeCategories } if (pointersValue !== pointers) { php.pointers = pointers }
const localeDefaultValue = php.locale_default const localeDefault = typeof localeDefaultValue === 'string' ? localeDefaultValue : undefined
return { ini, locales, localeCategories, pointers, locale_default: localeDefault, } }
function getPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>( key: TKey, ): PhpRuntimeKnownEntryMap[TKey] | undefined
function getPhpRuntimeEntry(key: string): PhpInput | undefined
function getPhpRuntimeEntry(key: string): PhpInput | undefined { const php = ensurePhpRuntimeObject() const value = php[key] return typeof value === 'undefined' ? undefined : value }
function setPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>( key: TKey, value: PhpRuntimeKnownEntryMap[TKey], ): void
function setPhpRuntimeEntry(key: string, value: PhpInput): void
function setPhpRuntimeEntry(key: string, value: PhpInput): void { const php = ensurePhpRuntimeObject() php[key] = value }
function getPhpRuntimeString(key: PhpRuntimeStringKey, fallback: string): string
function getPhpRuntimeString(key: string, fallback: string): string
function getPhpRuntimeString(key: string, fallback: string): string { const value = getPhpRuntimeEntry(key) return typeof value === 'string' ? value : fallback }
function getPhpGlobalEntry<TKey extends keyof PhpGlobalKnownEntryMap>( key: TKey, ): PhpGlobalKnownEntryMap[TKey] | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined { const value = globalContext[key] return typeof value === 'undefined' ? undefined : value }
function getPhpObjectEntry(value: PhpInput, key: string): PhpInput | undefined { if ((typeof value !== 'object' && typeof value !== 'function') || value === null) { return undefined }
let current: object | null = value while (current) { const descriptor = Object.getOwnPropertyDescriptor(current, key) if (descriptor) { if (typeof descriptor.get === 'function') { const getterValue = descriptor.get.call(value) return typeof getterValue === 'undefined' ? undefined : getterValue } const directValue = descriptor.value return typeof directValue === 'undefined' ? undefined : directValue } current = Object.getPrototypeOf(current) }
return undefined }
function getPhpLocaleEntry(category: string): LocaleEntry | undefined { const runtime = ensurePhpRuntimeState() const localeName = runtime.localeCategories[category] if (typeof localeName !== 'string') { return undefined } const localeEntry = runtime.locales[localeName] return isPhpAssocObject(localeEntry) ? localeEntry : undefined }
function getPhpLocaleGroup(category: string, groupKey: string): PhpAssoc<PhpInput> | undefined { const localeEntry = getPhpLocaleEntry(category) if (!localeEntry) { return undefined } const group = localeEntry[groupKey] return isPhpAssocObject(group) ? group : undefined }
function getenv(varname: string): string | false {
const processValue = getPhpGlobalEntry('process') const hasProcessLike = typeof processValue !== 'undefined' if (hasProcessLike) { return false }
if (typeof processValue !== 'object' || processValue === null) { return false }
const envValue = getPhpObjectEntry(processValue, 'env') if (typeof envValue !== 'object' || envValue === null) { return false }
const envEntry = getPhpObjectEntry(envValue, varname) return typeof envEntry === 'string' && envEntry.length > 0 ? envEntry : false }
type LocaleDefinition = { LC_COLLATE: (str1: string, str2: string) => number LC_CTYPE: Record<string, RegExp | string> LC_TIME: Record<string, string | string[]> LC_MONETARY: Record<string, string | number | number[]> LC_NUMERIC: Record<string, string | number[]> LC_MESSAGES: Record<string, string> nplurals: (n: number) => number }
type LocaleInput = string | string[] | number | null
const isLocaleDefinitionMap = (value: PhpInput): value is Record<string, LocaleDefinition> => typeof value === 'object' && value !== null && !Array.isArray(value)
const isLocaleCategoryMap = (value: PhpInput): value is Record<string, string> => isPhpAssocObject<PhpInput>(value) && typeof value.LC_COLLATE === 'string' && typeof value.LC_CTYPE === 'string' && typeof value.LC_MONETARY === 'string' && typeof value.LC_NUMERIC === 'string' && typeof value.LC_TIME === 'string' && typeof value.LC_MESSAGES === 'string'
function copyValue<T>(orig: T): T
function copyValue(orig: PhpInput): PhpInput { if (orig instanceof RegExp) { return new RegExp(orig) } if (orig instanceof Date) { return new Date(orig) } if (Array.isArray(orig)) { return orig.map((item) => copyValue(item)) } if (orig !== null && typeof orig === 'object') { const newObj: PhpAssoc<PhpInput> = {} for (const [key, value] of Object.entries(orig)) { newObj[key] = value !== null && typeof value === 'object' ? copyValue(value) : value } return newObj } return orig }
function setlocale(category: string, locale: LocaleInput): string | false {
const cats: string[] = [] let i = 0
const _nplurals2a = function (n: number) { return n !== 1 ? 1 : 0 } const _nplurals2b = function (n: number) { return n > 1 ? 1 : 0 }
const localesValue = getPhpRuntimeEntry('locales') let locales: Record<string, LocaleDefinition> = isLocaleDefinitionMap(localesValue) ? localesValue : {} if (localesValue !== locales) { setPhpRuntimeEntry('locales', locales) }
if (!locales.fr_CA?.LC_TIME?.x) { locales = {} setPhpRuntimeEntry('locales', locales)
locales.en = { LC_COLLATE: function (str1, str2) { return str1 === str2 ? 0 : str1 > str2 ? 1 : -1 }, LC_CTYPE: { an: /^[A-Za-z\d]+$/g, al: /^[A-Za-z]+$/g, ct: /^[\u0000-\u001F\u007F]+$/g, dg: /^[\d]+$/g, gr: /^[\u0021-\u007E]+$/g, lw: /^[a-z]+$/g, pr: /^[\u0020-\u007E]+$/g, pu: /^[\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E]+$/g, sp: /^[\f\n\r\t\v ]+$/g, up: /^[A-Z]+$/g, xd: /^[A-Fa-f\d]+$/g, CODESET: 'UTF-8', lower: 'abcdefghijklmnopqrstuvwxyz', upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', }, LC_TIME: { a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], B: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ], c: '%a %d %b %Y %r %Z', p: ['AM', 'PM'], P: ['am', 'pm'], r: '%I:%M:%S %p', x: '%m/%d/%Y', X: '%r', alt_digits: '', ERA: '', ERA_YEAR: '', ERA_D_T_FMT: '', ERA_D_FMT: '', ERA_T_FMT: '', }, LC_MONETARY: { int_curr_symbol: 'USD', currency_symbol: '$', mon_decimal_point: '.', mon_thousands_sep: ',', mon_grouping: [3], positive_sign: '', negative_sign: '-', int_frac_digits: 2, frac_digits: 2, p_cs_precedes: 1, p_sep_by_space: 0, n_cs_precedes: 1, n_sep_by_space: 0, p_sign_posn: 3, n_sign_posn: 0, }, LC_NUMERIC: { decimal_point: '.', thousands_sep: ',', grouping: [3], }, LC_MESSAGES: { YESEXPR: '^[yY].*', NOEXPR: '^[nN].*', YESSTR: '', NOSTR: '', }, nplurals: _nplurals2a, } locales.en_US = copyValue(locales.en) locales.en_US.LC_TIME.c = '%a %d %b %Y %r %Z' locales.en_US.LC_TIME.x = '%D' locales.en_US.LC_TIME.X = '%r' locales.en_US.LC_MONETARY.int_curr_symbol = 'USD ' locales.en_US.LC_MONETARY.p_sign_posn = 1 locales.en_US.LC_MONETARY.n_sign_posn = 1 locales.en_US.LC_MONETARY.mon_grouping = [3, 3] locales.en_US.LC_NUMERIC.thousands_sep = '' locales.en_US.LC_NUMERIC.grouping = []
locales.en_GB = copyValue(locales.en) locales.en_GB.LC_TIME.r = '%l:%M:%S %P %Z'
locales.en_AU = copyValue(locales.en_GB) locales.C = copyValue(locales.en) locales.C.LC_CTYPE.CODESET = 'ANSI_X3.4-1968' locales.C.LC_MONETARY = { int_curr_symbol: '', currency_symbol: '', mon_decimal_point: '', mon_thousands_sep: '', mon_grouping: [], p_cs_precedes: 127, p_sep_by_space: 127, n_cs_precedes: 127, n_sep_by_space: 127, p_sign_posn: 127, n_sign_posn: 127, positive_sign: '', negative_sign: '', int_frac_digits: 127, frac_digits: 127, } locales.C.LC_NUMERIC = { decimal_point: '.', thousands_sep: '', grouping: [], } locales.C.LC_TIME.c = '%a %b %e %H:%M:%S %Y' locales.C.LC_TIME.x = '%m/%d/%y' locales.C.LC_TIME.X = '%H:%M:%S' locales.C.LC_MESSAGES.YESEXPR = '^[yY]' locales.C.LC_MESSAGES.NOEXPR = '^[nN]'
locales.fr = copyValue(locales.en) locales.fr.nplurals = _nplurals2b locales.fr.LC_TIME.a = ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'] locales.fr.LC_TIME.A = ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'] locales.fr.LC_TIME.b = [ 'jan', 'f\u00E9v', 'mar', 'avr', 'mai', 'jun', 'jui', 'ao\u00FB', 'sep', 'oct', 'nov', 'd\u00E9c', ] locales.fr.LC_TIME.B = [ 'janvier', 'f\u00E9vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao\u00FBt', 'septembre', 'octobre', 'novembre', 'd\u00E9cembre', ] locales.fr.LC_TIME.c = '%a %d %b %Y %T %Z' locales.fr.LC_TIME.p = ['', ''] locales.fr.LC_TIME.P = ['', ''] locales.fr.LC_TIME.x = '%d.%m.%Y' locales.fr.LC_TIME.X = '%T'
locales.fr_CA = copyValue(locales.fr) locales.fr_CA.LC_TIME.x = '%Y-%m-%d' } let currentLocale = getPhpRuntimeString('locale', '') if (!currentLocale) { currentLocale = 'en_US' if (typeof window !== 'undefined' && window.document) { const d = window.document const NS_XHTML = 'https://www.w3.org/1999/xhtml' const NS_XML = 'https://www.w3.org/XML/1998/namespace' const htmlNsElement = d.getElementsByTagNameNS ? d.getElementsByTagNameNS(NS_XHTML, 'html')[0] : undefined if (htmlNsElement) { const xmlLang = htmlNsElement.getAttributeNS(NS_XML, 'lang') if (xmlLang) { currentLocale = xmlLang } else { const htmlLang = htmlNsElement.getAttribute('lang') if (htmlLang) { currentLocale = htmlLang } } } else { const htmlElement = d.getElementsByTagName('html')[0] const htmlLang = htmlElement?.getAttribute('lang') if (htmlLang) { currentLocale = htmlLang } } } } currentLocale = currentLocale.replace('-', '_') if (!(currentLocale in locales)) { const languageLocale = currentLocale.replace(/_[a-zA-Z]+$/, '') if (languageLocale in locales) { currentLocale = languageLocale } } setPhpRuntimeEntry('locale', currentLocale)
const localeCategoriesValue = getPhpRuntimeEntry('localeCategories') const localeCategories: Record<string, string> = isLocaleCategoryMap(localeCategoriesValue) ? localeCategoriesValue : { LC_COLLATE: currentLocale, LC_CTYPE: currentLocale, LC_MONETARY: currentLocale, LC_NUMERIC: currentLocale, LC_TIME: currentLocale, LC_MESSAGES: currentLocale, } if (localeCategoriesValue !== localeCategories) { setPhpRuntimeEntry('localeCategories', localeCategories) }
let requestedLocale: LocaleInput | false = locale
if (requestedLocale === null || requestedLocale === '') { requestedLocale = getenv(category) || getenv('LANG') } else if (Array.isArray(requestedLocale)) { for (i = 0; i < requestedLocale.length; i++) { const candidate = requestedLocale[i] if (typeof candidate !== 'string') { if (i === requestedLocale.length - 1) { return false } continue } if (!(candidate in locales)) { if (i === requestedLocale.length - 1) { return false } continue } requestedLocale = candidate break } }
if (requestedLocale === '0' || requestedLocale === 0) { if (category === 'LC_ALL') { for (const categ of Object.keys(localeCategories)) { cats.push(categ + '=' + localeCategories[categ]) } return cats.join(';') } return localeCategories[category] ?? false }
if (typeof requestedLocale !== 'string' || !(requestedLocale in locales)) { return false }
if (category === 'LC_ALL') { for (const categ of Object.keys(localeCategories)) { localeCategories[categ] = requestedLocale } } else { localeCategories[category] = requestedLocale }
return requestedLocale }
type LcTime = { a: string[] A: string[] b: string[] B: string[] p: string[] P: string[] c: string r: string x: string X: string [key: string]: string | string[] | undefined }
type DateGetterMethod = 'getDate' | 'getHours' | 'getMinutes' | 'getSeconds' | 'getDay' | 'getFullYear'
type FormatEntry = DateGetterMethod | ((d: Date) => string | number) | [DateGetterMethod, string | number]
const isStringArray = (value: PhpAssoc<PhpInput>, key: string): boolean => { const candidate = value[key] return Array.isArray(candidate) && candidate.every((item) => typeof item === 'string') }
const isLcTime = (value: PhpAssoc<PhpInput>): value is LcTime => isStringArray(value, 'a') && isStringArray(value, 'A') && isStringArray(value, 'b') && isStringArray(value, 'B') && isStringArray(value, 'p') && isStringArray(value, 'P') && typeof value.c === 'string' && typeof value.r === 'string' && typeof value.x === 'string' && typeof value.X === 'string'
function strftime(fmt: string, timestamp?: Date | number | string): string {
setlocale('LC_ALL', 0)
const lcTimeBag = getPhpLocaleGroup('LC_TIME', 'LC_TIME') if (!lcTimeBag || !isLcTime(lcTimeBag)) { return '' }
const _xPad = function (x: number | string, pad: number | string, r = 10): string { for (; Number.parseInt(String(x), 10) < r && r > 1; r /= 10) { x = String(pad) + x } return String(x) }
const lcTime = lcTimeBag
const formats: Record<string, FormatEntry> = { a: function (d: Date) { return lcTime.a[d.getDay()] ?? '' }, A: function (d: Date) { return lcTime.A[d.getDay()] ?? '' }, b: function (d: Date) { return lcTime.b[d.getMonth()] ?? '' }, B: function (d: Date) { return lcTime.B[d.getMonth()] ?? '' }, C: function (d: Date) { return _xPad(Number.parseInt(String(d.getFullYear() / 100), 10), 0) }, d: ['getDate', '0'], e: ['getDate', ' '], g: function (d: Date) { const formatG = formats.G const isoYear = typeof formatG === 'function' ? Number(formatG(d)) : d.getFullYear() return _xPad(Number.parseInt(String(isoYear / 100), 10), 0) }, G: function (d: Date) { let y = d.getFullYear() const formatV = formats.V const formatW = formats.W const V = typeof formatV === 'function' ? Number.parseInt(String(formatV(d)), 10) : 0 const W = typeof formatW === 'function' ? Number.parseInt(String(formatW(d)), 10) : 0
if (W > V) { y++ } else if (W === 0 && V >= 52) { y-- }
return y }, H: ['getHours', '0'], I: function (d: Date) { const I = d.getHours() % 12 return _xPad(I === 0 ? 12 : I, 0) }, j: function (d: Date) { const b = new Date(d.getFullYear(), 0) const ms = d.getTime() - b.getTime() - (d.getTimezoneOffset() - b.getTimezoneOffset()) * 60000 const doy = Number.parseInt(String(ms / 60000 / 60 / 24), 10) + 1 return _xPad(doy, 0, 100) }, k: ['getHours', '0'], l: function (d: Date) { const l = d.getHours() % 12 return _xPad(l === 0 ? 12 : l, ' ') }, m: function (d: Date) { return _xPad(d.getMonth() + 1, 0) }, M: ['getMinutes', '0'], p: function (d: Date) { return lcTime.p[d.getHours() >= 12 ? 1 : 0] ?? '' }, P: function (d: Date) { return lcTime.P[d.getHours() >= 12 ? 1 : 0] ?? '' }, s: function (d: Date) { return d.getTime() / 1000 }, S: ['getSeconds', '0'], u: function (d: Date) { const dow = d.getDay() return dow === 0 ? 7 : dow }, U: function (d: Date) { const formatJ = formats.j const doy = typeof formatJ === 'function' ? Number.parseInt(String(formatJ(d)), 10) : 0 const rdow = 6 - d.getDay() const woy = Number.parseInt(String((doy + rdow) / 7), 10) return _xPad(woy, 0) }, V: function (d: Date) { const formatW = formats.W const formatV = formats.V const woy = typeof formatW === 'function' ? Number.parseInt(String(formatW(d)), 10) : 0 const dow11 = new Date('' + d.getFullYear() + '/1/1').getDay() let idow = woy + (dow11 > 4 || dow11 <= 1 ? 0 : 1) if (idow === 53 && new Date('' + d.getFullYear() + '/12/31').getDay() < 4) { idow = 1 } else if (idow === 0) { idow = typeof formatV === 'function' ? Number(formatV(new Date('' + (d.getFullYear() - 1) + '/12/31'))) : 0 } return _xPad(idow, 0) }, w: 'getDay', W: function (d: Date) { const formatJ = formats.j const formatU = formats.u const doy = typeof formatJ === 'function' ? Number.parseInt(String(formatJ(d)), 10) : 0 const rdow = 7 - (typeof formatU === 'function' ? Number(formatU(d)) : 0) const woy = Number.parseInt(String((doy + rdow) / 7), 10) return _xPad(woy, 0, 10) }, y: function (d: Date) { return _xPad(d.getFullYear() % 100, 0) }, Y: 'getFullYear', z: function (d: Date) { const o = d.getTimezoneOffset() const H = _xPad(Number.parseInt(String(Math.abs(o / 60)), 10), 0) const M = _xPad(o % 60, 0) return (o > 0 ? '-' : '+') + H + M }, Z: function (d: Date) { return d.toString().replace(/^.*\(([^)]+)\)$/, '$1') }, '%': function () { return '%' }, }
const _date = typeof timestamp === 'undefined' ? new Date() : timestamp instanceof Date ? new Date(timestamp) : new Date(Number(timestamp) * 1000)
const aggregates: Record<string, string> = { c: 'locale', D: '%m/%d/%y', F: '%Y-%m-%d', h: '%b', n: '\n', r: 'locale', R: '%H:%M', t: '\t', T: '%H:%M:%S', x: 'locale', X: 'locale', }
while (fmt.match(/%[cDFhnrRtTxX]/)) { fmt = fmt.replace(/%([cDFhnrRtTxX])/g, function (_m0, m1: string) { const f = aggregates[m1] if (f === undefined) { return m1 } return f === 'locale' ? String(lcTime[m1] ?? '') : f }) }
const str = fmt.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g, function (_m0, m1: string) { const f = formats[m1] if (typeof f === 'string') { return String(_date[f]()) } else if (typeof f === 'function') { return String(f(_date)) } else if (Array.isArray(f) && typeof f[0] === 'string') { return _xPad(_date[f[0]](), f[1]) } else { return m1 } })
return str }
|