Core
object
%cookies
/phlo/resources/cookies.phlo
method
%cookies -> controller
line 9
This controller retrieves the current state of cookies and assigns it to the objData property.
this->objData = $_COOKIEprop
%cookies -> lifetimeDays
line 11
Sets the lifetime of cookies in days.
180method
%cookies -> objSet ($key, $value, array $options = [])
line 13
Sets a cookie with the specified key and value, along with optional parameters for expiration, path, security, and SameSite attributes.
$this->objData[$key] = $value
$_COOKIE[$key] = $value
$defaults = ['expires' => time() + $this->lifetimeDays * 86400, 'path' => slash, 'secure' => %req->secure, 'httponly' => true, 'samesite' => 'Lax']
setcookie($key, $value, array_merge($defaults, $options))
return truemethod
%cookies -> __unset ($key)
line 21
Removes a cookie by unsetting it from the local object data and the global $_COOKIE array, and sets its expiration date to the past.
unset($this->objData[$key], $_COOKIE[$key])
$options = ['expires' => time() - 86400, 'path' => slash, 'secure' => %req->secure, 'httponly' => true, 'samesite' => 'Lax']
setcookie($key, void, $options)object
%lang
/phlo/resources/lang.phlo
function
function nl ($text, ...$args)
line 11
Translates the given text into Dutch using the specified arguments for formatting.
%lang->translation('nl', $text, ...$args)function
function en ($text, ...$args)
line 12
This function retrieves a translation for the specified text in English, optionally formatting it with additional arguments.
%lang->translation('en', $text, ...$args)static
lang :: asyncBatch ($from, $to, $json)
line 14
Executes a batch translation asynchronously, decoding JSON input and saving the translations if successful.
%app->lang = $to
$texts = json_decode($json, true)
$translations = $this->translateBatch($from, $to, $texts)
if ($translations) $this->save($to, $translations)view
%lang -> view
line 21
This function retrieves the current language setting for the application, allowing for localization of views.
%app->langprop
%lang -> model
line 23
This function retrieves the model associated with the specified language identifier.
'gpt-4o-mini'static
lang :: fileCache
line 24
lang::$fileCache is a static property that stores cached language files for efficient retrieval during runtime.
[]method
%lang -> file ($lang)
line 26
Retrieves the configuration file for the specified language, using the format 'langs.$lang.ini'.
langs.$lang.'.ini'method
%lang -> escape ($value)
line 28
Escapes special characters in a string for safe output in HTML, replacing backslashes, double quotes, and line feeds with their respective escape sequences.
strtr((string)$value, [bs => bs.bs, dq => bs.dq, lf => '\n'])method
%lang -> unescape ($value)
line 29
This function unescapes a given string by replacing escape sequences with their corresponding characters.
strtr(strtr($value, [bs.bs => "\x01", bs.dq => dq, '\n' => lf]), ["\x01" => bs])method
%lang -> lineValue ($line, $eq)
line 31
Extracts and processes a line value from a given string, removing surrounding quotes if present and unescaping any special characters.
$value = rtrim(substr($line, $eq + 3), cr.lf)
if (strlen($value) > 1 && $value[0] === dq && substr($value, -1) === dq) $value = substr($value, 1, -1)
return $this->unescape($value)method
%lang -> readAll ($file)
line 37
Reads all key-value pairs from a specified file and returns them as an associative array. If the file does not exist or is not a valid file, it returns an empty array.
$items = []
if (!is_file($file)) return $items
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] AS $line){
$eq = strpos($line, ' = ')
if ($eq === false) continue
$items[substr($line, 0, $eq)] = $this->lineValue($line, $eq)
}
return $itemsmethod
%lang -> search ($file, $hash)
line 48
Searches for a specific hash in a file and returns its associated value if found.
$size = (int)@filesize($file)
if (!$size) return null
$h = @fopen($file, 'rb')
if (!$h) return null
$lo = 0
$hi = $size
while ($hi - $lo > 4096){
$mid = intdiv($lo + $hi, 2)
fseek($h, $mid)
fgets($h)
$pos = ftell($h)
if ($pos >= $hi){
$hi = $mid
continue
}
$line = (string)fgets($h)
$eq = strpos($line, ' = ')
if ($eq === false){
$hi = $mid
continue
}
$cmp = strcmp(substr($line, 0, $eq), $hash)
if ($cmp < 0) $lo = ftell($h)
elseif ($cmp > 0) $hi = $pos
else {
fclose($h)
return $this->lineValue($line, $eq)
}
}
fseek($h, $lo)
$value = null
while (ftell($h) < $hi && ($line = fgets($h)) !== false){
$eq = strpos($line, ' = ')
if ($eq === false) continue
$cmp = strcmp(substr($line, 0, $eq), $hash)
if ($cmp > 0) break
if ($cmp === 0){
$value = $this->lineValue($line, $eq)
break
}
}
fclose($h)
return $valuemethod
%lang -> lookup ($hash)
line 94
Looks up a value in the language file cache based on the provided hash, updating the cache if the file has changed.
$file = $this->file(%app->lang)
$mtime = (int)@filemtime($file)
$cache =& static::$fileCache[$file]
if (!$cache || $cache['mtime'] !== $mtime) $cache = ['mtime' => $mtime, 'items' => []]
if (array_key_exists($hash, $cache['items'])) return $cache['items'][$hash]
$value = $this->search($file, $hash)
if ($value === null && $mtime) $value = $this->readAll($file)[$hash] ?? null
return $cache['items'][$hash] = $valuemethod
%lang -> save ($lang, $pairs)
line 105
Saves a set of key-value pairs to a language file, ensuring the keys are sorted and the file permissions are set appropriately.
$file = $this->file($lang)
$items = $this->readAll($file)
foreach ($pairs AS $hash => $value) $items[$hash] = $value
ksort($items, SORT_STRING)
$out = void
foreach ($items AS $hash => $value) $out .= $hash.' = '.dq.$this->escape($value).dq.lf
$tmp = $file.'.'.getmypid().'.tmp'
file_put_contents($tmp, $out, LOCK_EX)
@chmod($tmp, 0664)
rename($tmp, $file)
unset(static::$fileCache[$file])method
%lang -> transContext
line 119
Retrieves the translation context from the app author, providing information about the purpose and domain if available.
($instr = trim((string)(%app->transInstr ?? void))) !== void ? lf.'Context from the app author about purpose and domain: '.$instr : voidprop
%lang -> browser
line 121
Extracts the preferred language from the 'Accept-Language' HTTP header, returning the first matching language code from the application's supported languages.
last($langs = array_filter(explode(comma, %req->acceptLanguage), fn($lang) => isset(%app->langs[substr($lang, 0, 2)])), $langs ? substr(current($langs), 0, 2) : null)method
%lang -> cookie
line 122
Retrieves the language preference from cookies and checks if it is a valid option in the application's available languages, returning the language if valid or null otherwise.
($lang = %cookies->lang) && %app->langs[$lang] ? $lang : nullmethod
%lang -> detect ($text, $fallback = 'en')
line 123
Detects the language of the given text and returns its ISO 639-1 code, defaulting to 'en' if detection fails.
$res = %OpenAI->chat (
model: $this->model,
system: 'Analyse which language this text is in and return only the ISO 639-1 code of the language, no other data!',
user: $text.lf.lf.'The ISO 639-1 code of the language is: ',
temperature: 0,
)->answer
return strlen($res) === 2 ? strtolower($res) : $fallbackmethod
%lang -> hash ($from, $text)
line 132
Generates a hash based on the provided text and a prefix from the specified language.
$from.($short = substr(implode(regex_all('/[A-Za-z0-9]+/', ucwords($text))[0]), 0, 8)).substr(md5($text), 0, 10 - strlen($short))method
%lang -> translation ($from, $text, ...$args)
line 133
Translates the given text from a specified language to the application's current language, handling missing translations asynchronously.
if ($from === %app->lang) $translation = strtr($text, ['\n' => lf])
else {
$translation = []
$missing = []
foreach (explode(lf, $text) AS $line){
if (trim($line)){
$hash = $this->hash($from, $line)
$item = $this->lookup($hash)
if ($item === null) [$missing[$hash] = $item = $line, debug(%app->lang.': '.(strlen($line) > 20 ? substr($line, 0, 18).'...' : $line))]
}
else $item = void
$translation[] = $item
}
if ($missing) phlo_async('lang::asyncBatch', $from, %app->lang, json_encode($missing))
$translation = implode(lf, $translation)
}
return $args ? sprintf($translation, ...$args) : $translationmethod
%lang -> translate ($from, $to, $text)
line 152
Translates a given text from one ISO 639-1 language to another using the OpenAI API, while preserving markdown formatting and capitalization.
if ($from === $to) return $text
return %OpenAI->chat (
model: $this->model,
system: "You will be provided with a word, sentence or (markdown) text in ISO 639-1 language $from, and your task is to translate this string into ISO 639-1 language $to. Respect markdown, missing interpunction and specific use of capitals. Give only the translation.".$this->transContext(),
user: $text,
temperature: 0,
)->answermethod
%lang -> translateBatch ($from, $to, $texts)
line 161
Translates a batch of texts from one language to another using the OpenAI chat model, returning the translations in a numbered format.
if ($from === $to) return $texts
$hashes = array_keys($texts)
$numbered = implode(lf, array_map(fn($i, $t) => ($i + 1).'. '.$t, array_keys($values = array_values($texts)), $values))
$answer = %OpenAI->chat (
model: $this->model,
system: "You will be provided with numbered lines in ISO 639-1 language $from. Translate each line into ISO 639-1 language $to. Return only the numbered translations in the same format. Respect markdown, missing interpunction and specific use of capitals.".$this->transContext(),
user: $numbered,
temperature: 0,
)->answer
$result = []
foreach (explode(lf, trim($answer)) AS $line){
if (preg_match('/^(\d+)\.\s*(.+)/', $line, $m))
$result[$hashes[(int)$m[1] - 1]] = $m[2]
}
return $resultobject
%payload
/phlo/resources/payload.phlo
method
%payload -> controller
line 10
Processes incoming request payloads based on the content type, handling JSON, URL-encoded, and multipart form data, and populates the objData property accordingly.
contentType = %req->contentType
if (in_array(phlo('req')->method, ['POST', 'PUT', 'PATCH']) && str_starts_with($contentType, 'application/json')){
$data = json_read('php://input')
return $this->objData = is_object($data) ? get_object_vars($data) : (is_array($data) ? $data : [])
}
if ($_POST) $this->objImport(...$_POST)
elseif (in_array(phlo('req')->method, ['PUT', 'PATCH']) && str_starts_with($contentType, 'application/x-www-form-urlencoded')){
$body = file_get_contents('php://input')
$data = []
parse_str($body, $data)
if ($data) $this->objImport(...$data)
}
elseif (in_array(phlo('req')->method, ['PUT', 'PATCH']) && str_starts_with($contentType, 'multipart/form-data')){
$match = regex('/boundary="?([^";]+)"?/', $contentType)
if (!$match) return
$boundary = '--'.$match[1]
$arrays = []
$raw = file_get_contents('php://input')
foreach (explode($boundary, $raw) AS $part){
if (!trim($part) || $part === '--' || !str_contains($part, nl.nl)) continue
$headers = []
[$rawHeaders, $body] = explode(nl.nl, $part, 2)
foreach (explode(nl, trim($rawHeaders)) AS $header){
if (str_contains($header, colon)){
[$key, $value] = explode(colon, $header, 2)
$headers[strtolower(trim($key))] = trim($value)
}
}
if (!isset($headers['content-disposition'])) continue
if (!preg_match('/name="([^"]+)"/', $headers['content-disposition'], $match)) continue
$name = $match[1]
$body = rtrim($body, nl)
if ($body === void) $body = null
$base = $name
$keys = []
$hasEmptyIndex = false
if (preg_match('/^([^\[]+)((?:\[[^\]]*\])*)$/', $name, $m)){
$base = $m[1]
$brackets = $m[2]
if ($brackets){
preg_match_all('/\[([^\]]*)\]/', $brackets, $mm)
$keys = $mm[1]
$hasEmptyIndex = in_array(void, $keys, true)
}
}
if ($hasEmptyIndex) $arrays[] = $base
$assign = function($value) use ($base, $keys){
if ($keys){
if (!isset($this->objData[$base]) || !is_array($this->objData[$base])) $this->objData[$base] = []
$ref =& $this->objData[$base]
$count = count($keys)
foreach ($keys AS $i => $k){
$last = $i === $count - 1
if ($k === void){
if ($last) $ref[] = $value
else {
$ref[] = []
end($ref)
$idx = key($ref)
$ref =& $ref[$idx]
}
}
else {
if ($last) $ref[$k] = $value
else {
if (!isset($ref[$k]) || !is_array($ref[$k])) $ref[$k] = []
$ref =& $ref[$k]
}
}
}
}
else $this->objData[$base] = $value
};
if (preg_match('/filename="([^"]*)"/', $headers['content-disposition'], $f)){
if ($f[1] === void || $body === null){
if (!$hasEmptyIndex) $assign(null)
continue
}
$filename = $f[1]
$file = %file(tempnam(sys_get_temp_dir(), 'phlo'), $filename, $body)
$assign($file)
}
else $assign($body)
}
foreach ($this->objData AS $key => $val){
if (str_ends_with($key, '[]')){
unset($this->objData[$key])
$this->objData[substr($key, 0, -2)] = is_array($val) ? array_values(array_filter($val, fn($v) => $v !== null)) : [$val]
}
elseif (!is_array($val) && substr($key, -2) === '[]') $this->objData[$key] = [$val]
}
foreach (array_unique($arrays) AS $key) if (!isset($this->objData[$key])) $this->objData[$key] = []
}
if ($_FILES) $this->objImport(...loop($_FILES, fn($f) => is_array($f['name']) ? loop(array_keys($f['name']), fn($i) => $f['error'][$i] ? null : %file($f['tmp_name'][$i], $f['name'][$i], mime: $f['type'][$i], size: $f['size'][$i])) : ($f['error'] ? null : %file($f['tmp_name'], $f['name'], mime: $f['type'], size: $f['size']))))object
%session
/phlo/resources/session.phlo
method
%session -> controller
line 9
Initializes the session and assigns the session data to the objData property.
ession_start()
$this->objData = $_SESSIONmethod
%session -> __set ($key, $value)
line 12
Sets a session variable with the specified key to the given value.
$_SESSION[$key] = $this->objData[$key] = $valuemethod
%session -> __unset ($key)
line 13
Removes the specified key from the session data and the internal object data.
unset($this->objData[$key], $_SESSION[$key])method
%session -> __isset ($key)
line 14
Checks if a session variable identified by the given key is set and not null.
isset($this->objData[$key])method
%session -> objRegenerateId ($deleteOld = true)
line 16
Regenerates the session ID for the current session, optionally deleting the old session data based on the $deleteOld parameter.
session_regenerate_id($deleteOld)
$this->objData = $_SESSIONobject
%sitemap
/phlo/resources/sitemap.phlo
route
route GET sitemap.xml
line 10
Outputs the current instance of the route for the GETSitemap method.
output($this)method
%sitemap -> intl ($uri)
line 12
Retrieves the internationalized slug for a given URI from the app's slugs, or returns the URI itself if no slug is found.
(%app->slugs ?? [])[$uri] ?? $uriview
%sitemap -> view
line 14
Generates an XML sitemap for the application by iterating over the defined pages and including their URLs.
<?xml version=1.0 encoding="UTF-8"?>
<urlset xmlns=http://www.sitemaps.org/schemas/sitemap/0.9 xmlns:xhtml=http://www.w3.org/1999/xhtml xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation=http://www.sitemaps.org/schemas/sitemap/0.9+http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd>
<foreach %app->pages AS $uri>
{{ $this->page($uri) }}
</foreach>
</urlset>view
%sitemap -> page ($uri)
line 22
Generates a sitemap entry for a specific view, including localized URLs for each language supported by the application.
<url>
<loc>%req->base{( $uri ?: slash )}</loc>
<foreach array_keys(%app->langs) AS $lang>
<if $lang === %app->lang>
{{ $this->xlink('x-default', $uri ?: slash) }}
</if>
{{ $this->xlink($lang, $lang === %app->lang ? ($uri ?: slash) : "/$lang".($this->intl($uri) ?: void)) }}
</foreach>
</url>view
%sitemap -> xlink ($lang, $uri)
line 33
Generates an alternate link for a sitemap entry, specifying the language and the base URI of the request.
<xhtml:link rel=alternate hreflang="$lang" href="%req->base$uri"{{ slash }}>view
%sitemap -> link ($lang, $uri)
line 34
Generates a link element for alternate language versions of a page in the sitemap, using the specified language and request URI.
<link rel=alternate hreflang="$lang" href="%req->base$uri">object
%tasks
/phlo/resources/tasks.phlo
static
tasks :: dir
line 9
Accesses the directory path for tasks, specifically pointing to 'tasks/'.
data.'tasks/'static
tasks :: run
line 11
Executes scheduled tasks by checking their due status and locking them to prevent concurrent execution. It saves the run details and marks the task as completed after execution.
is_dir(static::dir()) || mkdir(static::dir(), 0755, true)
$now = time()
foreach (%app->tasks ?? [] AS $name => $task){
$task = (object)$task
if (!static::due($name, $task, $now)) continue
if (!static::lock($name)) continue
$schedule = array_intersect_key((array)$task, array_flip(['every', 'daily', 'weekly']))
$do = is_string($task->do) ? $task->do : null
static::saveRun($name, $do, $schedule, static::fire($task->do))
static::markRun($name, $now)
static::unlock($name)
}static
tasks :: saveRun ($name, $do, $schedule, $return)
line 26
Saves the run data to a JSON file in the specified directory, using the provided name, do, schedule, and return values.
json_write(static::dir().$name.'.json', arr(do: $do, schedule: $schedule, return: $return))static
tasks :: due ($name, $task, $now)
line 28
Determines if a scheduled task is due to run based on its frequency settings, such as 'every', 'daily', or 'weekly'. It checks the last run time against the current time to decide if the task should be executed.
$last = static::lastRun($name)
if (isset($task->every)){
$every = preg_match('/^\d/', $task->every) ? $task->every : '1 '.$task->every
$seconds = strtotime("+$every", 0) ?: 0
return $seconds > 0 && ($now - $last) >= $seconds
}
if (isset($task->daily)){
if (date('H:i', $now) !== $task->daily) return false
return $last < strtotime('today 00:00', $now)
}
if (isset($task->weekly)){
if (date('D H:i', $now) !== date('D H:i', strtotime("$task->weekly today"))) return false
return $last < strtotime('monday this week', $now)
}
return falsestatic
tasks :: fire ($do)
line 46
Executes a task defined by a Closure, a 'Class::method' string, or a resource-name string, returning the result of the execution.
if ($do instanceof \Closure) return $do()
if (is_string($do) && str_contains($do, '::')){
[$class, $method] = explode('::', $do, 2)
return $class::$method()
}
if (is_string($do)) return phlo($do)
error('Task do must be Closure, "Class::method" string, or resource-name string')static
tasks :: lastRun ($name)
line 56
Retrieves the last run timestamp of a task from a file, returning 0 if the file does not exist.
$file = static::dir().$name.'.last'
return is_file($file) ? (int)file_get_contents($file) : 0static
tasks :: markRun ($name, $ts)
line 61
Writes the timestamp of the last run of a task to a file named after the task in the specified directory, using exclusive locking to prevent concurrent writes.
file_put_contents(static::dir().$name.'.last', (string)$ts, LOCK_EX)static
tasks :: lock ($name)
line 63
Creates a lock file for a task if it does not already exist or is older than one hour.
$file = static::dir().$name.'.lock'
if (is_file($file) && (time() - filemtime($file)) < 3600) return false
touch($file)
return truestatic
tasks :: unlock ($name)
line 70
Removes the lock file associated with a task, allowing it to be executed again.
@unlink(static::dir().$name.'.lock')object
%useragent
/phlo/resources/useragent.phlo
prop
%useragent -> source
line 9
This expression retrieves the user agent string from the request object, returning null if it is not set.
%req->userAgent ?: nullprop
%useragent -> os
line 11
Determines the operating system from the user agent string by matching it against predefined patterns.
if (!$this->source) return 'Unknown'
$list = [
'Android' => '/Android/i',
'iPadOS' => '/iPad.*OS/i',
'iOS' => '/iPhone|iPod/i',
'Windows' => '/Windows NT/i',
'macOS' => '/Mac OS X/i',
'ChromeOS' => '/CrOS/i',
'Linux' => '/Linux/i',
]
foreach ($list AS $n => $r) if (preg_match($r, $this->source)) return $n
if (preg_match('/iPad/i',$this->source) && preg_match('/Mac OS X/i',$this->source)) return 'iPadOS'
return 'Unknown'prop
%useragent -> osV
line 27
Extracts the operating system version from the user agent string if available, returning it in a cleaned format.
if (!$this->source) return void
if (preg_match('/(?:Android|OS X|OS|Windows NT)\s*([0-9._]+)/i', $this->source, $m)){
$v = strtr($m[1], [us => dot])
$v = preg_replace('/[^0-9.].*/', void, $v)
$v = preg_replace('/(?:\.0)+$/', void, $v)
return $v
}
return voidprop
%useragent -> osFull
line 38
This method returns the operating system name along with its version, formatted to exclude minor version numbers if they are zero.
if (!$this->OS) return 'Unknown'
$v = $this->osV
if (!$v) return $this->OS
$short = preg_replace('/^(\d+\.\d+).*/','$1',$v)
if (preg_match('/\.0$/',$short)) $short = preg_replace('/\.0$/', void, $short)
return trim($this->OS.' '.$short)prop
%useragent -> name
line 47
Determines the name of the web browser based on the user agent string provided in the source. It checks for various patterns to identify popular browsers like Chrome, Firefox, and Safari, returning 'Unknown' if no match is found.
if (!$this->source) return 'Unknown'
if (preg_match('/\bwv\b/',$this->source) || (preg_match('/Version\/\d+\.\d+/',$this->source) && strpos($this->source,'Chrome/')!==false && strpos($this->source,'Safari/')!==false && strpos($this->source,' Mobile ')!==false)) return 'Android WebView'
if (preg_match('/CriOS\/([0-9.]+)/',$this->source)) return 'Chrome'
if (preg_match('/FxiOS\/([0-9.]+)/',$this->source)) return 'Firefox'
$list = [
'Edge' => '/Edg\/([0-9.]+)/',
'Opera' => '/OPR\/([0-9.]+)/',
'Samsung Internet' => '/SamsungBrowser\/([0-9.]+)/i',
'Chrome' => '/Chrome\/([0-9.]+)/',
'Firefox' => '/Firefox\/([0-9.]+)/',
'Safari' => '/Version\/([0-9.]+).*Safari/i',
]
foreach ($list AS $n => $r) if (preg_match($r, $this->source)) return $n
return 'Unknown'prop
%useragent -> version
line 64
Extracts the version number from the user agent string if it matches specific browser patterns, returning the cleaned version or void if no match is found.
if (!$this->source) return void
if (preg_match('/(?:Edg|OPR|Chrome|Firefox|Version|CriOS|FxiOS|SamsungBrowser)\/([0-9.]+)/', $this->source, $m)){
$v = $m[1]
$v = preg_replace('/[^0-9.].*/', void, $v)
$v = preg_replace('/(?:\.0)+$/', void, $v)
return $v
}
return voidprop
%useragent -> full
line 75
Returns the full user agent string, including the name and version of the user agent, formatted to remove unnecessary parts.
if (!$this->name) return 'Unknown'
$v = $this->version
if (!$v) return $this->name
$short = preg_replace('/^(\d+\.\d+).*/','$1',$v)
if (preg_match('/\.0$/',$short)) $short = preg_replace('/\.0$/', void, $short)
return rtrim($this->name.' '.$short)prop
%useragent -> device
line 84
Determines the type of device (Tablet, Phone, or Desktop) based on the user agent string stored in the source property.
if (!$this->source) return 'Unknown'
if (preg_match('/iPad|Tablet|Tab|SM-T|Nexus 7|Nexus 10/i', $this->source)) return 'Tablet'
if (preg_match('/Mobile|iPhone|Android.*Mobile|SM-G|Pixel [0-9]/i', $this->source)) return 'Phone'
return 'Desktop'object
%visitors
/phlo/resources/visitors.phlo
static
visitors :: table
line 11
The 'visitors::$table' refers to the database table associated with the 'visitors' resource in Phlo.
'visitors'static
visitors :: columns
line 12
Defines the columns for the 'visitors' resource, specifying the attributes to be included in the data structure.
'id,token,host,page,lang,IP,browser,os,device,requests,state,width,height,referrer,created,changed'static
visitors :: history
line 14
Retrieves a history of visitor counts, grouping by date and counting distinct tokens and total visits.
static::records(columns: 'FROM_UNIXTIME(changed, "%Y-%m-%d") AS date,COUNT(DISTINCT token) AS visitors,COUNT(id) AS visits', group: 'date', order: 'date DESC')static
visitors :: online
line 15
This retrieves the count of distinct online visitors who have changed within the last 9 seconds.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 9)')static
visitors :: lastHour
line 16
Retrieves the count of distinct visitors (tokens) who have changed within the last hour.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 3600)')static
visitors :: isBot (?string $ua):bool
line 18
Determines if the user agent string indicates that the visitor is a bot by matching it against a predefined regex pattern.
if (!$ua) return false
return (bool)preg_match('/bot|crawl|spider|slurp|baiduspider|facebookexternalhit|twitterbot|linkedinbot|curl|wget|python-requests|go-http-client|java\//i', $ua)static
visitors :: parseReferrer (string $url):string
line 23
Parses the referrer URL to identify the search engine used, returning a formatted string indicating the search engine or the host name if no match is found.
static $engines = ['google' => 'Google', 'bing' => 'Bing', 'duckduckgo' => 'DuckDuckGo', 'yahoo' => 'Yahoo', 'baidu' => 'Baidu', 'yandex' => 'Yandex', 'ecosia' => 'Ecosia', 'startpage' => 'Startpage', 'brave' => 'Brave', 'kagi' => 'Kagi']
$host = strtolower(preg_replace('/^www\./', '', (string)(parse_url($url, PHP_URL_HOST) ?? '')))
foreach ($engines AS $key => $name) if (str_contains($host, $key)) return 'search:'.$name
return $host ?: substr($url, 0, 100)route
route PUT heartbeat @n,v,l,u,w,h,a,p,r,c
line 30
This route handles PUT requests to log heartbeat data from visitors, recording user consent, device information, and page details, while managing unique identifiers and referrer parsing.
if (static::isBot(%useragent->source)) return
$consent = (bool)%payload->c
$n = strlen(%payload->n) === 8 ? %payload->n : date('Ymd')
$id = $consent ? token(20, $n.space.%app->token.space.%useragent->source) : token(20, $n.space.date('Ymd').space.%app->token.space.%req->ip)
$data = arr (
token: token(20, (string)(%app->token ?? error('No app token available'))),
host: host,
page: %payload->u,
lang: strlen(%payload->l) === 2 ? %payload->l : %app->lang ?? 'en',
IP: $consent ? %req->ip : void,
browser: $consent ? %useragent->full.(%payload->a ? ' App' : void) : substr(md5((string)%useragent->source), 0, 8),
os: $consent ? %useragent->osFull : void,
device: $consent ? %useragent->device : void,
requests: %payload->p,
state: %payload->v,
width: %payload->w,
height: %payload->h,
changed: time(),
)
$record = static::record(id: $id, columns: 'id,page,referrer')
if (($referrer = %payload->r) && !$record?->referrer && !str_contains($referrer, host)) $data['referrer'] = static::parseReferrer($referrer)
if ($record) static::change('id=?', $id, ...$data)
else static::create(...$data, id: $id, created: time())view
script
line 56
This script manages a heartbeat mechanism that sends visitor data to the server, including consent status and app state, while also handling cookie creation and updates for visitor tracking.
let curpath = app.path
let heartbeatTimeout
const heartbeat = () => delay('heartbeat', 333, () => {
clearTimeout(heartbeatTimeout)
const consent = document.cookie.includes('cookieChoice=all')
if (consent){
const m = document.cookie.match(/phlo_visitor=([a-z0-9]{8})/)
if (m) window.name = m[1]
else {
window.name ||= phlo.token(8)
document.cookie = `phlo_visitor=${window.name};path=/;max-age=${365 * 86400};SameSite=Lax`
}
}
else window.name ||= phlo.token(8)
fetch('/heartbeat', {method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({n: window.name, v: app.state, l: obj('html').lang ?? 'en', u: app.path, w: innerWidth, h: innerHeight, a: app.mode, p: phlo.state.index, r: (r = document.referrer) ? (r === `${location.origin}/` ? null : r) : null, c: consent ? 1 : 0})})
heartbeatTimeout = setTimeout(heartbeat, 20000)
})
document.addEventListener('visibilitychange', heartbeat)
;['focus', 'blur', 'resize'].forEach(e => addEventListener(e, heartbeat))
app.updates.push(() => curpath !== app.path && (heartbeat(), curpath = app.path))
heartbeat()object
%websocket
/phlo/resources/websocket.phlo
static
websocket :: connect ($host, $token, $socket)
line 10
function_exists('wsConnect') && wsConnect($host, $token, $socket)static
websocket :: auth ($host, $token, $socket)
line 11
function_exists('wsAuth') && wsAuth($host, $token, $socket)static
websocket :: receive ($host, $token, $socket, $data)
line 12
function_exists('wsReceive') && wsReceive($host, $token, $socket, ...json_decode($data, true))static
websocket :: close ($host, $token, $socket)
line 13
function_exists('wsClose') && wsClose($host, $token, $socket)object%WhatsApp
/phlo/resources/WhatsApp.phlo
method
%WhatsApp -> __construct (public string $url, public string $secret)
line 10
Initializes a WhatsApp instance with a specified URL and secret, ensuring the URL ends with a slash.
$this->url = rtrim($url, slash).slashstatic
WhatsApp :: channel ($channel)
line 12
Creates a new instance of the WhatsApp channel using the provided URL and secret, defaulting to 'http://localhost:8081' and 'void' if not specified.
new static($channel->configData->url ?? 'http://localhost:8081', $channel->secretData->secret ?? void)method
%WhatsApp -> number ($contact)
line 14
Extracts the phone number from a WhatsApp contact string, returning an error if the format is invalid.
($pos = strpos($contact, '@')) ? substr($contact, 0, $pos) : error('Invalid contact: '.esc($contact))method
%WhatsApp -> isGroup ($contact)
line 15
Checks if the specified contact is a WhatsApp group by verifying if the contact contains a '@g' suffix.
last($this->number($contact), (bool)strpos($contact, '@g'))method
%WhatsApp -> status
line 17
Retrieves the current status from WhatsApp using a GET request.
$this->request('status', GET: true)method
%WhatsApp -> health
line 18
Checks the health status of the WhatsApp service by sending a GET request.
$this->request('health', GET: true)method
%WhatsApp -> qr
line 19
Sends a request to retrieve the QR code for WhatsApp authentication.
$this->request('qr', GET: true)method
%WhatsApp -> disconnect
line 20
Disconnects the current WhatsApp session by sending a disconnect request.
$this->request('disconnect')method
%WhatsApp -> read ($chat)
line 22
Sends a request to read messages from a specified WhatsApp chat.
$this->request('read', chat: $chat)method
%WhatsApp -> reaction ($msg, $emoji)
line 23
Sends a reaction emoji to a specified message in WhatsApp.
$this->request('reaction', msg: $msg, emoji: $emoji)method
%WhatsApp -> text ($to, $text)
line 25
Sends a text message to the specified recipient using WhatsApp.
$this->request('text', to: $to, text: $text)method
%WhatsApp -> image ($to, file $file, $text = void)
line 26
Sends an image message via WhatsApp to the specified recipient, optionally including a text message.
$this->request('image', to: $to, filename: $file->name, image: $file->src, text: $text)method
%WhatsApp -> location ($to, $lat, $lon, $text)
line 27
Sends a location message via WhatsApp to the specified recipient with latitude, longitude, and optional text.
$this->request('location', to: $to, lat: $lat, lon: $lon, text: $text)method
%WhatsApp -> document ($to, file $file, $text = void)
line 28
Sends a document via WhatsApp to the specified recipient, including an optional text message.
$this->request('document', to: $to, filename: $file->name, document: $file->src, text: $text)method
%WhatsApp -> audio ($to, file $file)
line 30
Sends an audio message to a specified recipient using the provided audio file.
$this->request('audio', to: $to, audio: $file->src)method
%WhatsApp -> voice ($to, file $file)
line 31
Sends a voice message to a specified recipient using the provided audio file.
$this->request('voice', to: $to, audio: $file->src)method
%WhatsApp -> poll ($to, $name, array $options, bool $multi = false)
line 33
Sends a poll message to a specified WhatsApp recipient with given options, allowing for multiple selections if specified.
$this->request('poll', to: $to, name: $name, options: $options, multi: $multi)method
%WhatsApp -> startTyping ($to)
line 35
Starts the typing indicator for a specified recipient in WhatsApp.
$this->request('typing/start', to: $to)method
%WhatsApp -> stopTyping ($to)
line 36
Stops the typing indicator for a specific recipient in WhatsApp.
$this->request('typing/stop', to: $to)method
%WhatsApp -> request ($action, ...$data)
line 38
Sends a request to the WhatsApp API with the specified action and data, returning a response object indicating success or failure.
$get = $data['GET'] ?? false
unset($data['GET'])
$raw = trim((string)HTTP($this->url.$action, ['secret: '.$this->secret], true, $get ? null : $data))
if (strtolower($raw) === 'ok') return obj(ok: true)
$res = json_decode($raw)
if (!$res && $raw) return obj(ok: false, error: $raw)
return $res ?: obj(ok: false, error: 'Empty WhatsApp response')Functions
function
active(bool $cond, string $classList = void)
/phlo/resources/active.phlo line 7
Generates a class attribute string for HTML elements, adding 'active' to the specified class list if the condition is true.
$cond || $classList ? ' class="'.$classList.($cond ? ($classList ? space : void).'active' : void).'"' : voidfunction
age(int $time)
/phlo/resources/age.phlo line 7
Calculates the age by subtracting the given time from the current time.
time() - $timefunction
age_human(int $age)
/phlo/resources/age.human.phlo line 8
Calculates a human-readable time duration from the given age in seconds.
time_human(time() - $age)function
apcu($key, $cb, int $duration = 3600, bool $log = true)
/phlo/resources/apcu.phlo line 8
Caches a value using APCu with a specified key and callback, allowing for a duration and optional logging.
first($value = apcu_entry($key, $cb, $duration), $log && debug('C: '.(strlen($key) > 58 ? substr($key, 0, 55).'...' : $key).(is_array($value) ? ' ('.count($value).')' : (is_numeric($value) ? ":$value" : (is_string($value) ? ':string:'.strlen($value) : colon.gettype($value))))))function
await(...$jobs)
/phlo/resources/await.phlo line 10
Executes multiple jobs asynchronously and collects their results, handling any errors that occur during execution.
$children = []
foreach ($jobs AS $i => $job){
[$cb, $args] = is_array($job) ? [$job[0], array_slice($job, 1)] : [$job, []]
$cmd = cli.space.escapeshellarg((string)$_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void)
$desc = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']]
$proc = proc_open($cmd, $desc, $pipes)
fclose($pipes[0])
$children[$i] = obj(proc: $proc, out: $pipes[1], err: $pipes[2])
}
$results = []
foreach ($children AS $i => $child){
$out = stream_get_contents($child->out)
$err = stream_get_contents($child->err)
fclose($child->out)
fclose($child->err)
$code = proc_close($child->proc)
$err = trim((string)$err)
if ($err !== void){
$ej = json_decode($err, true)
$results[$i] = json_last_error() === JSON_ERROR_NONE ? $ej : $err
continue
}
if ($code !== 0){
$results[$i] = obj(error: 'CLI process failed', code: $code)
continue
}
$json = json_decode($out, true)
$results[$i] = json_last_error() === JSON_ERROR_NONE ? $json : $out
}
return $resultsfunction
button(...$args):string
/phlo/resources/tags.form.phlo line 10
Creates a button element with the specified arguments passed as props.
tag('button', ...$args)function
camel(string $text)
/phlo/resources/camel.phlo line 7
Converts a given string to camel case by capitalizing the first letter of each word and removing spaces.
lcfirst(str_replace(space, void, ucwords(lcfirst($text))))function
chunk(...$cmds):void
/phlo/resources/chunk.phlo line 8
This function processes a set of commands, handling debugging information and managing the response for streaming output in a specific format.
$res = %res
$cli = %req->cli
if (debug){
$res->dump && [$cmds['dump'] = $res->dump, $res->dump = []]
$res->debug && [$cmds['debug'] = $res->debug, $res->debug = []]
}
if (!$res->streaming){
$res->streaming = true
$res->type = 'text/event-stream'
$res->header('Cache-Control', 'no-store')
$res->header('X-Content-Type-Options', 'nosniff')
$res->render(206)
}
print(json_encode($cmds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf)
$cli || [@ob_flush(), flush()]function
create(iterable $items, \Closure $keyCb, ?\Closure $valueCb = null)
/phlo/resources/create.phlo line 7
Creates an associative array by using the values from the iterable as keys and the optional value callback to determine the corresponding values.
array_combine(loop($items, $keyCb), $valueCb ? loop($items, $valueCb) : $items)function
en($text, ...$args)
/phlo/resources/lang.phlo line 12
This function retrieves a translation for the specified text in English, optionally formatting it with additional arguments.
%lang->translation('en', $text, ...$args)function
exec_stream(string $cmd, ?int $timeoutSec = 0)
/phlo/resources/exec.stream.phlo line 9
Executes a command in a separate process and streams its output and error messages asynchronously, yielding them as objects. It also supports a timeout feature to terminate the process if it exceeds the specified duration.
$desc = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']]
$proc = proc_open($cmd, $desc, $pipes)
if (!is_resource($proc)) return
stream_set_blocking($pipes[1], false)
stream_set_blocking($pipes[2], false)
$bufOut = void
$bufErr = void
while (true){
$status = proc_get_status($proc)
$running = $status['running']
$read = []
$w = null
$e = null
if (!feof($pipes[1])) $read[] = $pipes[1]
if (!feof($pipes[2])) $read[] = $pipes[2]
if ($read) @stream_select($read, $w, $e, 0, 200000)
foreach ($read AS $r){
$chunk = fread($r, 8192)
if ($chunk === void || $chunk === false) continue
if ($r === $pipes[1]){
$bufOut .= $chunk
while (($pos = strpos($bufOut, lf)) !== false){
$line = substr($bufOut, 0, $pos)
$bufOut = substr($bufOut, $pos + 1)
yield obj(data: $line)
}
}
else {
$bufErr .= $chunk
while (($pos = strpos($bufErr, lf)) !== false){
$line = substr($bufErr, 0, $pos)
$bufErr = substr($bufErr, $pos + 1)
yield obj(data: $line, error: true)
}
}
}
if (!$running) break
if ($timeoutSec > 0 && ($status['running_time'] ?? 0) > $timeoutSec){
proc_terminate($proc)
yield obj(data: 'process timeout', error: true)
break
}
}
if ($bufOut !== void) yield obj(data: $bufOut)
if ($bufErr !== void) yield obj(data: $bufErr, error: true)
foreach ($pipes AS $p) @fclose($p)
proc_close($proc)function
HTTP(string $url, array $headers = [], bool $JSON = false, $POST = null, $PUT = null, $PATCH = null, bool $DELETE = false, ?string $agent = null)
/phlo/resources/HTTP.phlo line 8
Sends an HTTP request to the specified URL with optional headers and supports various methods including GET, POST, PUT, PATCH, and DELETE.
$curl = curl_init($url)
if ($POST !== null || $PUT !== null || $PATCH !== null){
if (!is_null($POST)) [$method = 'POST', $content = $POST]
elseif (!is_null($PUT)) [$method = 'PUT', $content = $PUT]
elseif (!is_null($PATCH)) [$method = 'PATCH', $content = $PATCH]
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method)
if ($JSON) [!is_string($content) && $content = json_encode($content), array_push($headers, 'Content-Type: application/json', 'Content-Length: '.strlen($content))]
curl_setopt($curl, CURLOPT_POSTFIELDS, $content)
}
elseif ($DELETE) curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE')
$agent && curl_setopt($curl, CURLOPT_USERAGENT, $agent === true ? phlo('req')->userAgent : $agent)
curl_setopt_array($curl, [CURLOPT_COOKIEFILE => data.'cookies.txt', CURLOPT_COOKIEJAR => data.'cookies.txt', CURLOPT_HTTPHEADER => $headers, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 15, CURLOPT_ENCODING => void])
$res = curl_exec($curl)
if ($res === false) error('HTTP error: '.curl_error($curl))
return $resfunction
input(...$args):string
/phlo/resources/tags.form.phlo line 11
Creates an input element in the view with the specified arguments.
tag('input', ...$args)function
n8n($webhook, ?array $data = null, $test = false)
/phlo/resources/n8n.phlo line 8
Sends an HTTP POST request to an n8n webhook with optional data and a test flag.
HTTP(%creds->n8n->server.'webhook'.($test ? '-test' : '').'/'.$webhook, POST: $data)function
n8n_test($webhook, ?array $data = null)
/phlo/resources/n8n.test.phlo line 8
This function triggers an n8n workflow using the specified webhook and optional data array, returning the result of the n8n call.
n8n($webhook, $data, true)function
nl($text, ...$args)
/phlo/resources/lang.phlo line 11
Translates the given text into Dutch using the specified arguments for formatting.
%lang->translation('nl', $text, ...$args)function
notify(string $title, string $body = '', string $type = 'info', string $level = 'info', ?string $user = null):void
/phlo/resources/notify.phlo line 9
Sends a notification with a specified title, body, type, level, and optional user to a configured URL using HTTP.
$cfg = %creds->notify ?? null
if (!$cfg) return
$url = (string)($cfg->url ?? void)
$secret = (string)($cfg->secret ?? void)
if ($url === void || $secret === void) return
try {
HTTP($url, ['secret: '.$secret], true, [
'app' => (string)($cfg->app ?? (defined('id') ? id : 'app')),
'server' => (string)($cfg->server ?? 'local'),
'host' => (string)(%req->host ?? ''),
'type' => $type,
'level' => $level,
'title' => $title,
'body' => $body,
'user' => $user,
])
}
catch (\Throwable $e){}function
phlo(?string $phloName = null, ...$args):mixed
/phlo/phlo.php line 190
Creates or retrieves an instance of a Phlo object based on the provided name and arguments, managing a static list of objects for efficient access.
static $list = [];
if ($phloName === 'tech/reset') return array_keys($list = array_filter($list, static fn($obj) => $obj->objPers));
if ($phloName === null) return array_keys($list);
$class = strtr($phloName, [slash => us]);
$handle = method_exists($class, '__handle') ? $class::__handle(...$args) : ($args ? null : $phloName);
if ($handle === true){
if (isset($list[$phloName])) return $list[$phloName]->objImport(...$args);
$handle = $phloName;
}
elseif ($handle && isset($list[$handle])) return $list[$handle];
$object = new $class(...$args);
if ($handle) $list[$handle] = $object;
if ($object->hasMethod('controller') && (!phlo('req')->cli || $phloName !== 'app')) $object->controller();
return $object;function
phlo_app(...$args):void
/phlo/phlo.php line 38
Initializes the Phlo application with specified arguments, setting up necessary configurations, autoloading classes, and handling errors and exceptions.
if ($args['trace'] ??= false) require_once __DIR__.'/classes/trace.php';
require_once __DIR__.'/functions'.($args['trace'] ? '.trace.php' : '.php');
require_once __DIR__.'/classes/obj.php';
require_once __DIR__.'/classes/req.php';
require_once __DIR__.'/classes/res.php';
$args['app'] ?? error('No "app" path defined');
$args['debug'] ??= false;
$args['build'] ??= false;
$args['host'] ??= null;
$args['control'] ??= ($args['build'] && $args['debug']) ? 'phlo' : false;
$args['auth'] ??= false;
$args['data'] ??= $args['app'].'data/';
$args['php'] ??= $args['app'].'php/';
$args['www'] ??= $args['app'].'www/';
$args['cli'] ??= ZEND_THREAD_SAFE ? 'php-zts' : 'php';
$args['thread'] ??= false;
$args['build'] && $args['thread'] && error('Phlo build and thread mode cannot be combined');
$args['build'] && !is_file($args['data'].'app.json') && error('Phlo build mode requires data/app.json');
$args['auth'] && !$args['build'] && error('Auth requires build mode');
foreach ($args as $key => $value) define($key, $value);
define('engine', __DIR__.slash);
if ($args['debug']) require_once __DIR__.'/debug.php';
if ($args['build']) require_once __DIR__.'/classes/changed.php';
if ($args['trace']) trace::boot($args['app']);
set_error_handler(static function(int $level, string $msg, string $file = '', int $line = 0):bool {
if (!(error_reporting() & $level)) return false;
throw new ErrorException($msg, 0, $level, $file, $line);
});
set_exception_handler('phlo_exception');
spl_autoload_register(static function(string $class):void {
static $map = null, $mtime = null;
$file = php.'classmap.php';
if ($map === null || $mtime !== (is_file($file) ? filemtime($file) : null)){
$map = is_file($file) ? require $file : [];
$mtime = is_file($file) ? filemtime($file) : null;
}
if (isset($map[$class])){ require_once php.$map[$class]; return; }
});
if ($args['build']){
$engineMap = ['build' => 'build', 'reflect' => 'reflect', 'build_file' => 'file', 'build_node' => 'node', 'build_builder' => 'builder', 'build_css' => 'css', 'build_icons' => 'icons'];
spl_autoload_register(static function(string $class) use ($engineMap):void {
$name = $engineMap[strtolower($class)] ?? null;
if ($name !== null) require_once engine.'classes/'.$name.'.php';
});
}
defined('composer') && spl_autoload_register(static function(string $class):void {
static $loaded = false;
if ($loaded) return;
$loaded = true;
require_once composer.'vendor/autoload.php';
foreach (spl_autoload_functions() as $fn){
if (is_array($fn) && ($fn[0] ?? null) instanceof \Composer\Autoload\ClassLoader){
spl_autoload_unregister($fn);
spl_autoload_register($fn);
$fn[0]->loadClass($class);
return;
}
}
});
if ($args['thread'] !== false && PHP_SAPI !== 'cli'){
ignore_user_abort(true);
$handle = static function():void { phlo_thread(); };
for ($i = 1; !$args['thread'] || $i <= $args['thread']; ++$i){
$keepRunning = frankenphp_handle_request($handle);
phlo('tech/reset');
if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
gc_collect_cycles();
if (!$keepRunning) break;
}
return;
}
phlo_thread();function
phlo_async(string $cb, ...$args)
/phlo/resources/phlo.async.phlo line 8
Executes a callback function asynchronously in the background, passing any additional arguments to it, and returns the process ID of the spawned process.
last($cmd = cli.space.escapeshellarg((string)$_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void).' > /dev/null 2>&1 & echo $!', exec($cmd, $r), isset($r[0]) && ctype_digit($r[0]) && (int)$r[0] > 0)function
phlo_cli(array $args):void
/phlo/phlo.php line 174
Executes a method or function specified in the `$args` array and outputs the result as a JSON string.
if (!$args) return;
$target = array_shift($args);
if (str_contains($target, dot)){
[$object, $method] = explode(dot, $target, 2);
$handle = phlo($object);
$result = $args ? $handle->$method(...$args) : ($handle->hasMethod($method) ? $handle->$method() : $handle->$method);
}
elseif (str_contains($target, '::')){
[$class, $method] = explode('::', $target, 2);
$result = $class::$method(...$args);
}
else $result = $target(...$args);
if (isset($result)) print(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);function
phlo_exception(Throwable $e):void
/phlo/phlo.php line 33
Handles exceptions by passing the Throwable object to the phlo_error_handle function for processing.
require_once engine.'error.php';
phlo_error_handle($e);function
phlo_exists(string $obj)
/phlo/resources/phlo.exists.phlo line 7
Checks if a specified PHP file exists in the given object path.
is_file(php.strtr($obj, [us => dot]).'.php')function
phlo_load(bool $http):void
/phlo/phlo.php line 151
Loads the necessary runtime files for the application, ensuring that the application is only loaded once and that the correct content type is set for HTTP responses.
static $loaded = false, $loadedApp = null;
if ($loaded && $loadedApp === app){
if ($http && !phlo('res')->type) phlo('res')->type = 'text/html; charset=UTF-8';
return;
}
if (build && (!is_file(php.'functions.php') || !is_file(php.'app.php') || build_base::changed())){
debug('Builder started');
$changed = build::run();
$changed && debug('Built '.implode(', ', array_map('basename', $changed)).' ('.count($changed).')');
}
if (!is_file(php.'functions.php') || !is_file(php.'app.php')) error('Compiled runtime not available');
if (!$loaded){
require_once php.'functions.php';
$loaded = true;
}
if ($loadedApp !== app){
require_once php.'app.php';
$loadedApp = app;
}
if ($http && !phlo('res')->type) phlo('res')->type = 'text/html; charset=UTF-8';function
phlo_stream(string $cb, ...$args)
/phlo/resources/phlo.stream.phlo line 10
Executes a callback in a streaming manner, passing additional arguments to it while yielding the output.
yield from exec_stream(cli.space.escapeshellarg((string)$_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void))function
phlo_sync(string $cb, ...$args)
/phlo/resources/phlo.sync.phlo line 8
Executes a specified callback function in the CLI context with provided arguments and returns the result as a JSON object, handling errors appropriately.
$cmd = cli.space.escapeshellarg((string)$_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void)
exec($cmd.' 2>&1', $r, $code)
$out = implode(lf, $r)
$j = json_decode($out, true)
if ($code !== 0) error('Could not execute "'.esc($cb).'" via CLI')
if (json_last_error() !== JSON_ERROR_NONE) return $out
if (is_array($j) && isset($j['error'])) error((string)$j['error'])
return $jfunction
phlo_thread():void
/phlo/phlo.php line 113
Handles the main execution flow of a Phlo application, managing requests for both CLI and web environments, including authentication and dashboard rendering.
try {
$req = phlo('req');
if ($req->cli){
$target = $req->args[0] ?? void;
if (str_starts_with($target, 'build::') || str_starts_with($target, 'reflect::')){
phlo_cli($req->args);
return;
}
phlo_load(false);
phlo('app');
phlo_cli($req->args);
return;
}
$isDashboard = build && debug && control && str_starts_with($req->path.slash, control.slash);
if (auth && !$isDashboard){
phlo_auth('site', 'Phlo App - '.host);
if (phlo('res')->done) return;
}
if ($isDashboard){
require_once engine.'control.php';
phlo_dashboard::handle(substr($req->path, strlen(control) + 1));
phlo('res')->render();
return;
}
phlo_load(true);
phlo('app');
phlo('res')->render();
}
catch (RuntimeException $e){
if ($e->getMessage() === 'PhloDump' || $e->getMessage() === 'PhloStop') return;
phlo_exception($e);
}
catch (Throwable $e){
phlo_exception($e);
}function
select(...$args):string
/phlo/resources/tags.form.phlo line 12
Creates a 'select' HTML element with the provided arguments as attributes and options.
tag('select', ...$args)function
slug(string $text)
/phlo/resources/slug.phlo line 7
Converts a given string into a URL-friendly slug by removing non-alphanumeric characters, converting to lowercase, and replacing spaces with dashes.
trim(preg_replace('/[^a-z0-9]+/', dash, strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text))), dash)function
tag(string $tagName, ?string $inner = null, ...$args)
/phlo/resources/tag.phlo line 8
Generates an HTML tag with the specified name, optional inner content, and additional attributes.
"<$tagName".loop(array_filter($args, fn($value) => !is_null($value)), fn($value, $key) => space.strtr($key, [us => dash]).($value === true ? void : '="'.esc($value).'"'), void).'>'.(is_null($inner) ? void : "$inner</$tagName>")function
textarea(...$args):string
/phlo/resources/tags.form.phlo line 13
Creates a 'textarea' HTML element with the specified arguments.
tag('textarea', ...$args)function
time_human(?int $time = null)
/phlo/resources/time.human.phlo line 7
Converts a given timestamp into a human-readable time difference format, such as '2 days' or '3 hours'. If no timestamp is provided, it uses the current time.
static $labels
$labels ??= last($labels = arr(seconds: 60, minutes: 60, hours: 24, days: 7, weeks: 4, months: 13, years: 1), defined('tsLabels') && $labels = array_combine(tsLabels, $labels), $labels)
$age = time() - $time
foreach ($labels AS $range => $multiplier){
if ($age / $multiplier < 1.6583) break
$age /= $multiplier
}
return round($age)." $range"function
wsCast($wsTarget = 'all', $wsHost = host, $wsPort = websocket, ...$data)
/phlo/resources/wsCast.phlo line 8
Sends a message to a specified WebSocket target, allowing for data to be transmitted over the WebSocket connection.
HTTP (
'http://127.0.0.1:'.$wsPort.'/message',
JSON: true,
POST: arr (
host: $wsHost,
target: $wsTarget,
data: $data,
),
)