Core

object

%cookies

/phlo/resources/cookies.phlo
version 1.0
creator q-ai.nl
summary Cookies data object
package web
frontend false
backend true
tags cookies session browser web
method

%cookies -> controller

line 9
Deze controller haalt de huidige staat van cookies op en wijst deze toe aan de objData-eigenschap.
this->objData = $_COOKIE
prop

%cookies -> lifetimeDays

line 11
Stelt de levensduur van cookies in dagen in.
180
method

%cookies -> objSet ($key, $value, array $options = [])

line 13
Stelt een cookie in met de opgegeven sleutel en waarde, samen met optionele parameters voor vervaldatum, pad, beveiliging en SameSite-attributen.
$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 true
method

%cookies -> __unset ($key)

line 21
Verwijdert een cookie door deze uit de lokale objectgegevens en de globale $_COOKIE-array te verwijderen, en stelt de vervaldatum in op het verleden.
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
version 1.0
creator q-ai.nl
summary Language and translation resource
package i18n
frontend false
backend true
requires @cookies @AI @INI phlo.async
advice Use %lang in views to show current app lang (for example in links)
tags lang translation i18n locale ai
function

function nl ($text, ...$args)

line 11
Vertaal de gegeven tekst naar het Nederlands met de opgegeven argumenten voor opmaak.
%lang->translation('nl', $text, ...$args)
function

function en ($text, ...$args)

line 12
Deze functie haalt een vertaling op voor de opgegeven tekst in het Engels, en kan optioneel worden opgemaakt met extra argumenten.
%lang->translation('en', $text, ...$args)
static

lang :: asyncBatch ($from, $to, $json)

line 14
Voert een batchvertaling asynchroon uit, decodeert JSON-invoer en slaat de vertalingen op als deze succesvol zijn.
%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
Deze functie haalt de huidige taalinstelling van de applicatie op, waardoor lokalisatie van views mogelijk is.
%app->lang
prop

%lang -> model

line 23
Deze functie haalt het model op dat is gekoppeld aan de opgegeven taalidentificator.
'gpt-4o-mini'
prop

%lang -> instructions

line 24
Definieert een set instructies voor de Phlo programmeertaal.
void
static

lang :: fileCache

line 25
lang::$fileCache is een statische eigenschap die gecachete taaldocumenten opslaat voor efficiënte toegang tijdens runtime.
[]
method

%lang -> file ($lang)

line 27
Haal het configuratiebestand op voor de opgegeven taal, met het formaat 'langs.$lang.ini'.
langs.$lang.'.ini'
method

%lang -> escape ($value)

line 29
Escaped speciale tekens in een string voor veilige uitvoer in HTML, vervangend backslashes, dubbele aanhalingstekens en regeleinden met hun respectieve escape-sequenties.
strtr((string)$value, [bs => bs.bs, dq => bs.dq, lf => '\n'])
method

%lang -> unescape ($value)

line 30
Deze functie ontsnapt een gegeven string door escape-sequenties te vervangen door hun overeenkomstige karakters.
strtr(strtr($value, [bs.bs => "\x01", bs.dq => dq, '\n' => lf]), ["\x01" => bs])
method

%lang -> lineValue ($line, $eq)

line 32
Extraheert en verwerkt een lijnwaarde uit een gegeven string, verwijdert omringende aanhalingstekens indien aanwezig en ontsnapt speciale tekens.
$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 38
Leest alle sleutel-waarde paren uit een opgegeven bestand en retourneert deze als een associatieve array. Als het bestand niet bestaat of geen geldig bestand is, retourneert het een lege 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 $items
method

%lang -> search ($file, $hash)

line 49
Zoekt naar een specifieke hash in een bestand en retourneert de bijbehorende waarde als deze is gevonden.
$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 $value
method

%lang -> lookup ($hash)

line 95
Zoekt een waarde in de taalbestanden cache op basis van de opgegeven hash en werkt de cache bij als het bestand is gewijzigd.
$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] = $value
method

%lang -> save ($lang, $pairs)

line 106
Slaat een set van sleutel-waardeparen op in een taaldocument, waarbij de sleutels worden gesorteerd en de bestandsrechten correct worden ingesteld.
$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 120
Haal de context op van de app-auteur met betrekking tot het doel en het domein, indien beschikbaar; anders retourneert het void.
($instr = trim($this->instructions ?? void)) !== void ? lf.'Context from the app author about purpose and domain: '.$instr : void
prop

%lang -> browser

line 122
Haal de voorkeurstaal uit de 'Accept-Language' HTTP-header, en retourneer de eerste overeenkomende taalcodes uit de ondersteunde talen van de applicatie.
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 123
Haal de taalvoorkeur uit cookies en controleer of het een geldige optie is in de beschikbare talen van de applicatie, waarbij de taal wordt geretourneerd als deze geldig is of null anders.
($lang = %cookies->lang) && %app->langs[$lang] ? $lang : null
method

%lang -> detect ($text, $fallback = 'en')

line 124
Detecteert de taal van de gegeven tekst en retourneert de ISO 639-1 code. Als de detectie mislukt, retourneert het een opgegeven fallback-taalcode, standaard 'en'.
$res = %AI->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) : $fallback
method

%lang -> hash ($from, $text)

line 133
Genereert een hash op basis van de opgegeven tekst en een voorvoegsel van de opgegeven taal.
$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 134
Vertaling van de gegeven tekst van een opgegeven taal naar de huidige taal van de applicatie, waarbij ontbrekende vertalingen asynchroon worden afgehandeld.
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) : $translation
method

%lang -> translate ($from, $to, $text)

line 153
Vertaal een gegeven tekst van de ene ISO 639-1 taal naar de andere met behulp van AI, terwijl markdown-opmaak en hoofdletters behouden blijven.
if ($from === $to) return $text
return %AI->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,
)->answer
method

%lang -> translateBatch ($from, $to, $texts)

line 162
Vertaal een batch tekst van de ene naar de andere taal met behulp van AI, en geef de vertalingen in genummerd formaat terug.
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 = %AI->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 $result
object

%payload

/phlo/resources/payload.phlo
version 1.0
creator q-ai.nl
summary POST, PUT, PATCH, QUERY and file-upload data object
package web
frontend false
backend true
requires @file
tags payload request upload post put patch query
method

%payload -> controller

line 10
contentType = %req->contentType
if (in_array(phlo('req')->method, ['POST', 'PUT', 'PATCH', 'QUERY']) && 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', 'QUERY']) && 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', 'QUERY']) && 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

%seo

/phlo/resources/seo.phlo
version 1.0
creator q-ai.nl
summary Multilingual SEO: sitemap.xml, robots.txt, hreflang + head meta (description/OG/Twitter/canonical)
package seo
frontend false
backend true
requires output
tags seo sitemap robots hreflang opengraph multilingual
route

route GET sitemap.xml

line 10
Deze route genereert een sitemap voor SEO-doeleinden wanneer deze via een GET-verzoek wordt benaderd.
output($this)
route

route GET robots.txt

line 12
Definieert een route die reageert op GET-verzoeken door de inhoud van het robots-bestand in platte tekstformaat met UTF-8-charset uit te geven.
output($this->robots(), type: 'text/plain; charset=utf-8')
method

%seo -> robots

line 14
Genereert de inhoud van een robots.txt-bestand op basis van de instellingen van de applicatie, waarbij wordt gespecificeerd welke gebruikersagenten zijn toegestaan of geweerd bij het toegang krijgen tot bepaalde paden, en voegt een link naar de sitemap toe.
if (!(defined('indexable') && indexable)) return 'User-agent: *'.lf.'Disallow: /'.lf
$lines = ['User-agent: *', 'Allow: /']
foreach ((array)(%app->robotsDisallow ?? []) AS $path) $lines[] = 'Disallow: '.$path
$lines[] = 'Sitemap: '.%req->base.slash.'sitemap.xml'
return implode(lf, $lines).lf
method

%seo -> intl ($uri)

line 22
Haal de geïndividualiseerde versie van een URI op uit de slugs van de applicatie, en retourneer de originele URI als er geen vertaling wordt gevonden.
(%app->slugs ?? [])[$uri] ?? $uri
method

%seo -> locale

line 24
Deze functie haalt de locale-code op die overeenkomt met de taalinstelling van de applicatie, met 'en' als standaard als deze niet is ingesteld.
$lang = %app->lang ?? 'en'
$map = ['nl' => 'nl_NL', 'en' => 'en_US', 'de' => 'de_DE', 'fr' => 'fr_FR', 'es' => 'es_ES', 'it' => 'it_IT', 'pt' => 'pt_PT', 'pl' => 'pl_PL', 'ru' => 'ru_RU', 'el' => 'el_GR', 'tr' => 'tr_TR', 'zh' => 'zh_CN']
return $map[$lang] ?? $lang
prop

%seo -> ogTitle

line 30
Dit haalt de Open Graph-titel op voor SEO-doeleinden met behulp van de title() functie.
title()
prop

%seo -> ogDescr

line 31
Haal de beschrijving van de applicatie op, retourneert void als deze niet is ingesteld.
%app->description ?? void
prop

%seo -> ogImage

line 32
Deze expressie haalt de Open Graph-afbeeldings-URL op voor SEO-doeleinden, met 'icon.webp' als standaard als er geen afbeelding is opgegeven.
%req->base.slash.ltrim(%app->image ?? 'icon.webp', slash)
prop

%seo -> canonical

line 33
Dit haalt de canonical URL op voor SEO-doeleinden uit het aanvraagobject.
%req->url
prop

%seo -> ogType

line 34
Stelt het Open Graph-type in voor SEO-doeleinden, specifiek op 'website'.
'website'
prop

%seo -> siteName

line 35
Deze expressie wijst de waarde van de titel van de app of de id toe aan de variabele $siteName voor SEO-doeleinden.
%app->title ?? id
prop

%seo -> twitterCard

line 36
De seo->$twitterCard eigenschap wordt gebruikt om de Twitter Card metadata voor een view te configureren, waardoor een betere integratie met Twitter deelfunctionaliteiten mogelijk is.
false
prop

%seo -> noIndex

line 37
De seo->$noIndex expressie evalueert naar true als zowel %app->noLink als %app->noIndex niet zijn ingesteld, anders retourneert het false.
%app->noLink ?? %app->noIndex ?? false
view

%seo -> view

line 39
Genereert een XML-sitemap voor de applicatie door over de gedefinieerde pagina's te itereren en hun URL's op te nemen.
<?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

%seo -> page ($uri)

line 47
Genereert een SEO-vriendelijke XML-sitemapvermelding voor een view, inclusief gelokaliseerde URL's voor elke taal die door de applicatie wordt ondersteund.
<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
line 58
Genereert een alternatieve linkelement voor SEO-doeleinden, waarbij de taal en URL voor de huidige weergave worden gespecificeerd.
<xhtml:link rel=alternate hreflang="$lang" href="%req->base$uri"{{ slash }}>
view
line 59
Genereert een alternatieve link-tag voor SEO-doeleinden, die de taal en de URL van de bron specificeert.
<link rel=alternate hreflang="$lang" href="%req->base$uri">
view

%seo -> head

line 61
Genereert SEO-gerelateerde meta-tags voor een view, inclusief Open Graph- en Twitter Card-tags, op basis van de opgegeven eigenschappen zoals beschrijving, titel en afbeelding.
<if $this->noIndex>
	<meta name=robots content=noindex,follow>
</if>
<if $this->ogDescr>
	<meta name=description content="$this->ogDescr">
</if>
<meta property=og:site_name content="$this->siteName">
<meta property=og:title content="$this->ogTitle">
<meta property=og:description content="$this->ogDescr">
<meta property=og:type content="$this->ogType">
<meta property=og:url content="$this->canonical">
<meta property=og:image content="$this->ogImage">
<meta property=og:locale content="$this->locale">
<if $this->twitterCard>
	<meta name=twitter:card content=summary_large_image>
	<meta name=twitter:title content="$this->ogTitle">
	<meta name=twitter:description content="$this->ogDescr">
	<meta name=twitter:image content="$this->ogImage">
</if>
<if !$this->noIndex>
	<link rel=canonical href="$this->canonical">
</if>
object

%session

/phlo/resources/session.phlo
version 1.0
creator q-ai.nl
summary Session data object
package web
frontend false
backend true
tags session web state
method

%session -> controller

line 9
Initialiseert de sessie en wijst de sessiegegevens toe aan de objData-eigenschap.
ession_start()
$this->objData = $_SESSION
method

%session -> __set ($key, $value)

line 12
Stelt een sessievariabele in met de opgegeven sleutel op de gegeven waarde.
$_SESSION[$key] = $this->objData[$key] = $value
method

%session -> __unset ($key)

line 13
Verwijdert de opgegeven sleutel uit de sessiegegevens en de interne objectgegevens.
unset($this->objData[$key], $_SESSION[$key])
method

%session -> __isset ($key)

line 14
Controleert of een sessievariabele die door de gegeven sleutel is geïdentificeerd, is ingesteld en niet null is.
isset($this->objData[$key])
method

%session -> objRegenerateId ($deleteOld = true)

line 16
Regenerates de sessie-ID voor de huidige sessie, waarbij optioneel de oude sessiegegevens worden verwijderd op basis van de $deleteOld-parameter.
session_regenerate_id($deleteOld)
$this->objData = $_SESSION
object

%stream

/phlo/resources/stream.phlo
version 1.0
creator q-ai.nl
summary Raw data stream beside the JSON command channel: stream() emits text or binary chunks under any content type, app.stream() consumes them via fetch dispatching on the response type
package runtime
frontend true
backend true
provides app.stream
tags stream binary raw data download
function

function stream ($data = null, string $type = 'application/octet-stream', ?string $name = null):void

line 10
$res = %res
$cli = %req->cli
!$res->streaming && $res->done && error('Output already started, invalid stream()')
if (!$res->streaming){
	$res->streaming = true
	$res->type = $type
	$res->header('Cache-Control', 'no-store')
	$res->header('X-Content-Type-Options', 'nosniff')
	$res->header('X-Accel-Buffering', 'no')
	$name === null || $res->header('Content-Disposition', 'attachment; filename="'.str_replace('"', '', $name).'"')
	$res->render()
}
if ($data === null) return
foreach (is_iterable($data) ? $data : [$data] as $part){
	print((string)$part)
	$cli || [@ob_flush(), flush()]
}
view

script

line 30
app.stream = async (path, onData = null, async = false, type = null, data = null) => {
	const url = `${location.origin}/${path}`
	const headers = {}
	const csrf = obj('meta[name="csrf"]')?.content
	csrf && (headers['X-CSRF-Token'] = csrf)
	async && (headers['X-Requested-With'] = 'phlo')
	let body = data
	if (body !== null && !(body instanceof FormData) && !(body instanceof Blob)) [body = JSON.stringify(body), headers['Content-Type'] = 'application/json']
	phlo.log(`⇣ APP.STREAM ${url}`)
	const res = await fetch(url, {method: body === null ? 'GET' : 'POST', credentials: 'same-origin', headers, body})
	if (!res.ok) throw new Error(`stream ${url} 🔴 ${res.status}`)
	const mime = (res.headers.get('content-type') ?? '').split(';')[0].trim()
	const kind = type ?? (mime === 'application/x-ndjson' ? 'ndjson' : mime === 'application/json' ? 'json' : mime === 'text/event-stream' ? 'sse' : mime.startsWith('text/') ? 'text' : 'raw')
	const emit = value => onData && onData(value)
	if (kind === 'json'){
		const value = await res.json()
		emit(value)
		return value
	}
	if (kind === 'blob'){
		const value = await res.blob()
		emit(value)
		return value
	}
	const reader = res.body.getReader()
	const decoder = new TextDecoder()
	const out = []
	const parts = []
	let text = ''
	let buffer = ''
	const line = raw => {
		if (!raw) return
		const value = JSON.parse(raw)
		out.push(value)
		emit(value)
	}
	const frame = raw => {
		if (!raw.trim()) return
		let event = 'message'
		const data = []
		raw.split('\n').forEach(l => l.startsWith('event:') ? event = l.slice(6).trim() : l.startsWith('data:') && data.push(l.slice(5).replace(/^ /, '')))
		const value = {event, data: data.join('\n')}
		out.push(value)
		emit(value)
	}
	while (true){
		const {done, value} = await reader.read()
		if (done) break
		if (kind === 'raw'){
			parts.push(value)
			emit(value)
			continue
		}
		const piece = decoder.decode(value, {stream: true})
		if (kind === 'text'){
			text += piece
			emit(piece)
			continue
		}
		buffer += piece
		if (kind === 'sse'){
			const hold = buffer.endsWith('\r') ? '\r' : ''
			buffer = buffer.slice(0, buffer.length - hold.length).replace(/\r\n?/g, '\n') + hold
		}
		const pieces = buffer.split(kind === 'sse' ? '\n\n' : '\n')
		buffer = pieces.pop()
		pieces.forEach(kind === 'sse' ? frame : line)
	}
	if (kind === 'raw') return new Blob(parts, {type: mime || 'application/octet-stream'})
	const tail = decoder.decode()
	if (kind === 'text'){
		tail && [text += tail, emit(tail)]
		return text
	}
	buffer += tail
	buffer && (kind === 'sse' ? frame(buffer.replace(/\r\n?/g, '\n')) : line(buffer))
	return out
}
object

%tasks

/phlo/resources/tasks.phlo
version 1.0
creator q-ai.nl
summary Cron runner for %app->tasks. One cron entry per app triggers this every minute.
package scheduling
frontend false
backend true
tags cron schedule tasks scheduler
static

tasks :: dir

line 9
Toegang tot het pad van de directory voor taken, specifiek verwijzend naar 'tasks/'.
data.'tasks/'
static

tasks :: run

line 11
Voert geplande taken uit door hun vervaldatum te controleren en ze te vergrendelen om gelijktijdige uitvoering te voorkomen. Het slaat de uitvoeringsdetails op en markeert de taak als voltooid na uitvoering.
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
Slaat de uitvoergegevens op in een JSON-bestand in de opgegeven map, met de opgegeven naam, do, schema en return-waarden.
json_write(static::dir().$name.'.json', arr(do: $do, schedule: $schedule, return: $return))
static

tasks :: due ($name, $task, $now)

line 28
Bepaalt of een geplande taak moet worden uitgevoerd op basis van de frequentie-instellingen, zoals 'elke', 'dagelijks' of 'wekelijks'. Het controleert de laatste uitvoeringstijd ten opzichte van de huidige tijd om te beslissen of de taak moet worden uitgevoerd.
$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, $now))) return false
	return $last < strtotime('monday this week', $now)
}
return false
static

tasks :: fire ($do)

line 46
Voert een taak uit die is gedefinieerd door een Closure, een 'Class::method' string of een resource-naam string, en retourneert het resultaat van de uitvoering.
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
Haal de laatste uitvoeringstijdstempel van een taak op uit een bestand, en retourneer 0 als het bestand niet bestaat.
$file = static::dir().$name.'.last'
return is_file($file) ? (int)file_get_contents($file) : 0
static

tasks :: markRun ($name, $ts)

line 61
Schrijft de tijdstempel van de laatste uitvoering van een taak naar een bestand met de naam van de taak in de opgegeven directory, met gebruik van exclusieve vergrendeling om gelijktijdige schrijfbewerkingen te voorkomen.
file_put_contents(static::dir().$name.'.last', (string)$ts, LOCK_EX)
static

tasks :: lock ($name)

line 63
Maakt een lockbestand voor een taak aan als het nog niet bestaat of ouder is dan een uur.
$file = static::dir().$name.'.lock'
if (is_file($file) && (time() - filemtime($file)) < 3600) return false
touch($file)
return true
static

tasks :: unlock ($name)

line 70
Verwijdert het vergrendelingsbestand dat aan een taak is gekoppeld, zodat deze opnieuw kan worden uitgevoerd.
@unlink(static::dir().$name.'.lock')
object

%useragent

/phlo/resources/useragent.phlo
version 1.0
creator q-ai.nl
summary User agent information
package web
frontend false
backend true
tags useragent browser os device web
prop

%useragent -> source

line 9
Deze expressie haalt de user agent-string op uit het aanvraagobject en retourneert null als deze niet is ingesteld.
%req->userAgent ?: null
prop

%useragent -> os

line 11
Bepaalt het besturingssysteem op basis van de user agent-string door deze te vergelijken met vooraf gedefinieerde patronen.
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
Haalt de versie van het besturingssysteem uit de user agent-string als deze beschikbaar is, en retourneert deze in een schone indeling.
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 void
prop

%useragent -> osFull

line 38
Geeft de volledige naam van het besturingssysteem terug, samen met de versie als deze beschikbaar is; anders retourneert het 'Onbekend' of alleen de naam van het besturingssysteem.
if (!$this->OS) return 'Unknown'
if ($this->OS === 'Windows') return 'Windows'
$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.space.$short)
prop

%useragent -> name

line 48
Bepaalt de naam van de webbrowser op basis van de gebruikersagentstring die in de source is opgegeven. Het controleert verschillende patronen om populaire browsers zoals Chrome, Firefox en Safari te identificeren en retourneert 'Onbekend' als er geen overeenkomst wordt gevonden.
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 65
Haalt het versienummer uit de user agent-string als deze overeenkomt met specifieke browserpatronen, en retourneert het schoongemaakte versienummer of void als er geen overeenkomst is.
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 void
prop

%useragent -> full

line 76
Geeft de volledige user agent-string terug, inclusief de naam en versie van de user agent, geformatteerd om onnodige delen uit te sluiten.
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.space.$short)
prop

%useragent -> device

line 85
Bepaalt het type apparaat (Tablet, Telefoon of Desktop) op basis van de user agent-string die is opgeslagen in de source-eigenschap.
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
version 1.0
creator q-ai.nl
summary Visitor tracking via heartbeat
extends model
package analytics
frontend false
backend true
requires @payload @model token useragent
tags visitors analytics heartbeat tracking
static

visitors :: table

line 11
De 'visitors::$table' verwijst naar de database tabel die is gekoppeld aan de 'visitors' resource in Phlo.
'visitors'
static

visitors :: columns

line 12
Definieert de kolommen voor de visitors resource, waarbij de te gebruiken attributen voor gegevensbewerkingen worden gespecificeerd.
'id,token,host,page,lang,IP,browser,os,device,active_seconds,state,width,height,referrer,created,changed'
static

visitors :: history

line 14
Haal een geschiedenis op van bezoekersaantallen, gegroepeerd op datum en tel unieke tokens en totale bezoeken.
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
Dit haalt het aantal unieke online bezoekers op die in de afgelopen 9 seconden zijn veranderd.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 9)')
static

visitors :: lastHour

line 16
Haal het aantal unieke bezoekers (tokens) op die in het afgelopen uur zijn veranderd.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 3600)')
static

visitors :: isBot (?string $ua):bool

line 18
Bepaalt of de user agent-string aangeeft dat de bezoeker een bot is door deze te vergelijken met een vooraf gedefinieerd regex-patroon.
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
Analyseert de referrer-URL om de gebruikte zoekmachine te identificeren en retourneert een geformatteerde string die de zoekmachine aangeeft of de host als er geen overeenkomst wordt gevonden.
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\./', void, (string)(parse_url($url, PHP_URL_HOST) ?? void)))
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,s,pp,ps

line 30
Behandelt het PUT-verzoek voor het bijwerken van hartslaginformatie van bezoekers, inclusief toestemming, sessieduur en pagina-interacties, terwijl de gegevens in de database worden opgeslagen.
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.%cookies->token.space.%useragent->source) : token(20, $n.space.date('Ymd').space.%cookies->token.space.%req->ip)
$delta = max(0, min(120, (int)%payload->s))
$prevDelta = max(0, min(120, (int)%payload->ps))
$lang = strlen(%payload->l) === 2 ? %payload->l : %app->lang ?? 'en'
$referrer = ($r = (string)%payload->r) && !str_contains($r, host) ? static::parseReferrer($r) : null
static::DB()->query('INSERT INTO '.static::$table.' (id, token, host, page, lang, IP, browser, os, device, active_seconds, state, width, height, referrer, created, changed) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE token = VALUES(token), host = VALUES(host), page = VALUES(page), lang = VALUES(lang), IP = VALUES(IP), browser = VALUES(browser), os = VALUES(os), device = VALUES(device), active_seconds = active_seconds + VALUES(active_seconds), state = VALUES(state), width = VALUES(width), height = VALUES(height), referrer = IF(referrer IS NULL OR referrer = '.sq.sq.', VALUES(referrer), referrer), changed = VALUES(changed)', $id, token(20, (string)(%cookies->token ?? %payload->n)), host, %payload->u, $lang, $consent ? %req->ip : void, $consent ? %useragent->full.(%payload->a ? ' App' : void) : substr(md5((string)%useragent->source), 0, 8), $consent ? %useragent->osFull : void, $consent ? %useragent->device : void, $delta + $prevDelta, %payload->v, %payload->w, %payload->h, $referrer, time(), time())
$pages = preg_replace('/visitors$/', 'visitor_pages', (string)static::$table)
if (strlen((string)%payload->p) >= 8){
	$pv = token(20, $id.space.%payload->p)
	static::DB()->query('INSERT INTO '.$pages.' (id, visitor, host, page, lang, active_seconds, beats, created, changed) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE active_seconds = active_seconds + VALUES(active_seconds), beats = beats + 1, changed = VALUES(changed)', $pv, $id, host, %payload->u, $lang, $delta, 1, time(), time())
}
if (strlen((string)%payload->pp) >= 8){
	$ppv = token(20, $id.space.%payload->pp)
	static::DB()->query('UPDATE '.$pages.' SET active_seconds = active_seconds + ?, beats = beats + 1, changed = ? WHERE id = ?', $prevDelta, time(), $ppv)
}
view

script

line 51
Volgt gebruikersactiviteit op de pagina door actieve tijd te meten en heartbeat-gegevens naar de server te verzenden, inclusief gebruikersstatus en zichtbaarheidstoestand.
let curpath = app.path
let pv = phlo.token(12)
let hbTimer, activeMs = 0, activeAt = null, prev = null
const visible = () => document.visibilityState === 'visible'
const accrue = () => {
	if (activeAt === null) return
	activeMs += performance.now() - activeAt
	activeAt = visible() ? performance.now() : null
}
const flush = beacon => {
	accrue()
	const consent = document.cookie.includes('cookieChoice=all')
	window.name ||= phlo.token(8)
	const s = Math.floor(activeMs / 1000)
	activeMs -= s * 1000
	const 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: pv, r: (r = document.referrer) ? (r === `${location.origin}/` ? null : r) : null, c: consent ? 1 : 0, s, pp: prev ? prev.p : null, ps: prev ? prev.s : null})
	prev = null
	fetch('/heartbeat', {method: 'PUT', headers: {'Content-Type': 'application/json'}, body, keepalive: !!beacon})
}
const heartbeat = () => delay('heartbeat', 333, () => {
	clearTimeout(hbTimer)
	flush()
	if (visible()) hbTimer = setTimeout(heartbeat, 20000)
})
document.addEventListener('visibilitychange', () => {
	if (!visible()) return flush(true)
	activeAt = performance.now()
	heartbeat()
})
addEventListener('resize', heartbeat)
addEventListener('pagehide', () => flush(true))
app.updates.push(() => {
	if (curpath === app.path) return
	if (prev) flush()
	accrue()
	prev = {p: pv, s: Math.floor(activeMs / 1000)}
	pv = phlo.token(12)
	activeMs = 0
	curpath = app.path
	heartbeat()
})
if (visible()) activeAt = performance.now()
heartbeat()
object

%websocket

/phlo/resources/websocket.phlo
version 1.0
creator q-ai.nl
summary Server-side WebSocket handler via phloWS
advice Enable this class only when websockets are configured for the host
package realtime
frontend false
backend true
tags websocket realtime ws server
static

websocket :: connect ($wsHost, $wsToken, $wsSocket)

line 10
Stelt een WebSocket-verbinding tot stand met behulp van de opgegeven host, token en socketparameters.
!function_exists('wsConnect') || wsConnect($wsHost, $wsToken, $wsSocket)
static

websocket :: auth ($wsHost, $wsToken, $wsSocket)

line 11
Controleert of de 'wsAuth'-functie bestaat en roept deze, indien niet, aan met de opgegeven parameters voor WebSocket-authenticatie.
!function_exists('wsAuth') || wsAuth($wsHost, $wsToken, $wsSocket)
static

websocket :: receive ($wsHost, $wsToken, $wsSocket, $data)

line 12
Ontvangt gegevens van een WebSocket-verbinding met behulp van de opgegeven host, token en socket, en verwerkt de binnenkomende gegevens als JSON.
function_exists('wsReceive') && wsReceive($wsHost, $wsToken, $wsSocket, ...json_decode($data, true))
static

websocket :: close ($wsHost, $wsToken, $wsSocket)

line 13
Sluit een WebSocket-verbinding met behulp van de opgegeven host, token en socket.
function_exists('wsClose') && wsClose($wsHost, $wsToken, $wsSocket)
object

%WhatsApp

/phlo/resources/WhatsApp.phlo
version 1.0
creator q-ai.nl
summary WhatsApp client for phloWA using whatsapp-web.js
package messaging
frontend false
backend true
requires HTTP
tags whatsapp messaging api
method

%WhatsApp -> __construct (public string $url, public string $secret)

line 10
Initialiseert een WhatsApp-instantie met een opgegeven URL en geheim, waarbij wordt gegarandeerd dat de URL eindigt met een schuine streep.
$this->url = rtrim($url, slash).slash
static

WhatsApp :: channel ($channel)

line 12
Maakt een nieuwe instantie van het WhatsApp-kanaal met de opgegeven URL en geheim, standaard 'http://localhost:8081' en 'void' als niet gespecificeerd.
new static($channel->configData->url ?? 'http://localhost:8081', $channel->secretData->secret ?? void)
method

%WhatsApp -> number ($contact)

line 14
Extraheert het telefoonnummer uit een WhatsApp-contactstring en retourneert een fout als het formaat ongeldig is.
($pos = strpos($contact, '@')) ? substr($contact, 0, $pos) : error('Invalid contact: '.esc($contact))
method

%WhatsApp -> isGroup ($contact)

line 15
Controleert of de opgegeven contactpersoon een WhatsApp-groep is door te verifiëren of de contactpersoon een '@g' achtervoegsel bevat.
last($this->number($contact), (bool)strpos($contact, '@g'))
method

%WhatsApp -> status

line 17
Haal de huidige status van WhatsApp op met een GET-verzoek.
$this->request('status', GET: true)
method

%WhatsApp -> health

line 18
Controleert de gezondheidsstatus van de WhatsApp-service door een GET-verzoek te verzenden.
$this->request('health', GET: true)
method

%WhatsApp -> qr

line 19
Stuurt een verzoek om de QR-code voor WhatsApp-authenticatie op te halen.
$this->request('qr', GET: true)
method

%WhatsApp -> disconnect

line 20
Verbreekt de huidige WhatsApp-sessie door een verbreekverzoek te sturen.
$this->request('disconnect')
method

%WhatsApp -> read ($chat)

line 22
Stuurt een verzoek om berichten te lezen uit een opgegeven WhatsApp-chat.
$this->request('read', chat: $chat)
method

%WhatsApp -> reaction ($msg, $emoji)

line 23
Stuurt een reactie-emoticon naar een specifiek bericht in WhatsApp.
$this->request('reaction', msg: $msg, emoji: $emoji)
method

%WhatsApp -> text ($to, $text)

line 25
Verzendt een tekstbericht naar de opgegeven ontvanger via WhatsApp.
$this->request('text', to: $to, text: $text)
method

%WhatsApp -> image ($to, file $file, $text = void)

line 26
Verzendt een afbeeldingsbericht via WhatsApp naar de opgegeven ontvanger, met de optie om een tekstbericht toe te voegen.
$this->request('image', to: $to, filename: $file->name, image: $file->src, text: $text)
method

%WhatsApp -> location ($to, $lat, $lon, $text)

line 27
Verzendt een locatiebericht via WhatsApp naar de opgegeven ontvanger met breedtegraad, lengtegraad en optionele tekst.
$this->request('location', to: $to, lat: $lat, lon: $lon, text: $text)
method

%WhatsApp -> document ($to, file $file, $text = void)

line 28
Verzendt een document via WhatsApp naar de opgegeven ontvanger, inclusief een optioneel tekstbericht.
$this->request('document', to: $to, filename: $file->name, document: $file->src, text: $text)
method

%WhatsApp -> audio ($to, file $file)

line 30
Verzendt een audiobericht naar een opgegeven ontvanger met het opgegeven audiobestand.
$this->request('audio', to: $to, audio: $file->src)
method

%WhatsApp -> voice ($to, file $file)

line 31
Verzendt een spraakbericht naar een opgegeven ontvanger met behulp van het opgegeven audiobestand.
$this->request('voice', to: $to, audio: $file->src)
method

%WhatsApp -> poll ($to, $name, array $options, bool $multi = false)

line 33
Verzendt een peilingbericht naar een opgegeven WhatsApp-ontvanger met de gegeven opties, met de mogelijkheid voor meerdere selecties indien opgegeven.
$this->request('poll', to: $to, name: $name, options: $options, multi: $multi)
method

%WhatsApp -> startTyping ($to)

line 35
Start de typindicator voor een opgegeven ontvanger in WhatsApp.
$this->request('typing/start', to: $to)
method

%WhatsApp -> stopTyping ($to)

line 36
Stop de typindicator voor een specifieke ontvanger in WhatsApp.
$this->request('typing/stop', to: $to)
method

%WhatsApp -> request ($action, ...$data)

line 38
Verzendt een verzoek naar de WhatsApp API met de opgegeven actie en gegevens, en retourneert een responsobject dat succes of falen aangeeft.
$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 8
Genereert een class-attribuutstring voor HTML-elementen, waarbij 'active' aan de opgegeven classlijst wordt toegevoegd als de voorwaarde waar is.
$cond || $classList ? ' class="'.$classList.($cond ? ($classList ? space : void).'active' : void).'"' : void
function

age(int $time)

/phlo/resources/age.phlo line 8
Berechnet de leeftijd door de gegeven tijd van de huidige tijd af te trekken.
time() - $time
function

age_human(int $age)

/phlo/resources/age.human.phlo line 9
Berechnet een leesbare tijdsduur op basis van de gegeven leeftijd in seconden.
time_human(time() - $age)
function

apcu($key, $cb, int $duration = 3600, bool $log = true)

/phlo/resources/apcu.phlo line 9
Cache een waarde met behulp van APCu met een opgegeven sleutel en callback, met een duur en optionele 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
Wacht op meerdere taken om te voltooien, beheert hun uitvoer en fouten, en retourneert de resultaten. Het beheert kindprocessen en zorgt ervoor dat ze niet eindeloos blokkeren.
	if (daemon) return daemon::await($jobs)
	$children = []
	$open = []
	foreach ($jobs AS $i => $job){
		[$cb, $args] = is_array($job) ? [$job[0], array_slice($job, 1)] : [$job, []]
		$cmd = cli.space.escapeshellarg($_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])
		stream_set_blocking($pipes[1], false)
		stream_set_blocking($pipes[2], false)
		$children[$i] = obj(proc: $proc, out: $pipes[1], err: $pipes[2], stdout: void, stderr: void)
		$open['o'.$i] = $pipes[1]
		$open['e'.$i] = $pipes[2]
	}
	// Drain every child's stdout AND stderr together: reading one stream to EOF before the
	// other deadlocks a child that fills the unread pipe. Bound the whole wait so a hung
	// child cannot block the caller forever.
	$deadline = time() + (defined('await_timeout') ? await_timeout : 300)
	while ($open){
		$read = $open
		$write = $except = []
		if (@stream_select($read, $write, $except, 1) === false) break
		foreach ($read AS $key => $stream){
			$chunk = fread($stream, 65536)
			if ($chunk === void || $chunk === false){
				feof($stream) && $open = array_diff_key($open, [$key => 1])
				continue
			}
			$i = substr($key, 1)
			if ($key[0] === 'o') $children[$i]->stdout .= $chunk
			else $children[$i]->stderr .= $chunk
		}
		if (time() >= $deadline) break
	}
	// Terminate any child still running (deadline or a stream_select error). SIGKILL cannot
	// be ignored, so proc_close below will not block on a process that drops SIGTERM.
	foreach ($children AS $child) (proc_get_status($child->proc)['running'] ?? false) && proc_terminate($child->proc, 9)
	$results = []
	foreach ($children AS $i => $child){
		fclose($child->out)
		fclose($child->err)
		$code = proc_close($child->proc)
		$err = trim($child->stderr)
		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($child->stdout, true)
		$results[$i] = json_last_error() === JSON_ERROR_NONE ? $json : $child->stdout
	}
	return $results
function

button(...$args):string

/phlo/resources/tags.form.phlo line 10
Maakt een knop element met de opgegeven argumenten die als props worden doorgegeven.
tag('button', ...$args)
function

camel(string $text)

/phlo/resources/camel.phlo line 8
Converteert een gegeven string naar camel case door de eerste letter van elk woord te kapitaliseren en spaties te verwijderen.
lcfirst(str_replace(space, void, ucwords(lcfirst($text))))
function

chunk(...$cmds):void

/phlo/resources/chunk.phlo line 9
Deze functie verzendt een gegevenschunk naar de client, waarbij streaming wordt geïnitialiseerd als dit nog niet is gebeurd en de juiste headers voor de respons worden ingesteld.
	$res = %res
	$cli = %req->cli
	!$res->streaming && $res->done && error('Output already started, invalid chunk()')
	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 = 'application/x-ndjson'
		$res->header('Cache-Control', 'no-store')
		$res->header('X-Content-Type-Options', 'nosniff')
		$res->render()
	}
	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 8
Maakt een associatieve array door de waarden uit de iterable als sleutels te gebruiken en de optionele waarde callback te gebruiken om de bijbehorende waarden te bepalen.
array_combine(loop($items, $keyCb), $valueCb ? loop($items, $valueCb) : $items)
function

en($text, ...$args)

/phlo/resources/lang.phlo line 12
Deze functie haalt een vertaling op voor de opgegeven tekst in het Engels, en kan optioneel worden opgemaakt met extra argumenten.
%lang->translation('en', $text, ...$args)
function

exec_stream(string $cmd, ?int $timeoutSec = 0)

/phlo/resources/exec.stream.phlo line 9
Voert een opdracht uit in een apart proces en streamt de uitvoer en foutmeldingen asynchroon, waarbij ze als objecten worden opgeleverd. Het ondersteunt ook een time-outfunctie om het proces te beëindigen als het de opgegeven duur overschrijdt.
	$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, $QUERY = null, bool $DELETE = false, string|bool|null $agent = null, string|bool $cookies = false, int $timeout = 15, &$response = null)

/phlo/resources/HTTP.phlo line 9
	$curl = curl_init($url)
	if ($POST !== null || $PUT !== null || $PATCH !== null || $QUERY !== 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]
		elseif (!is_null($QUERY)) [$method = 'QUERY', $content = $QUERY]
		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)
	if ($cookies !== false) [$jar = $cookies === true ? data.'cookies.txt' : $cookies, curl_setopt($curl, CURLOPT_COOKIEFILE, $jar), curl_setopt($curl, CURLOPT_COOKIEJAR, $jar)]
	$resHeaders = []
	curl_setopt_array($curl, [CURLOPT_HTTPHEADER => $headers, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => $timeout, CURLOPT_ENCODING => void, CURLOPT_HEADERFUNCTION => function($ch, $line) use (&$resHeaders){
		$parts = explode(colon, $line, 2)
		count($parts) === 2 && $resHeaders[strtolower(trim($parts[0]))] = trim($parts[1])
		return strlen($line)
	}])
	$res = curl_exec($curl)
	$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE)
	$response = obj(ok: $res !== false && $status >= 200 && $status < 300, status: $status, headers: $resHeaders, error: $res === false ? curl_error($curl) : null)
	if ($res === false) error('HTTP error: '.curl_error($curl))
	return $res
function

input(...$args):string

/phlo/resources/tags.form.phlo line 11
Maakt een invoerelement in de view met de opgegeven argumenten.
tag('input', ...$args)
function

n8n($webhook, ?array $data = null, $test = false)

/phlo/resources/n8n.phlo line 9
Stuurt een HTTP POST-verzoek naar een n8n webhook met optionele gegevens en een testvlag.
HTTP(%creds->n8n->server.'webhook'.($test ? '-test' : '').'/'.$webhook, POST: $data)
function

nl($text, ...$args)

/phlo/resources/lang.phlo line 11
Vertaal de gegeven tekst naar het Nederlands met de opgegeven argumenten voor opmaak.
%lang->translation('nl', $text, ...$args)
function

notify(string $title, string $body = void, string $type = 'info', string $level = 'info', ?string $user = null):void

/phlo/resources/notify.phlo line 8
Verzendt een melding met een opgegeven titel, inhoud, type, niveau en optionele gebruiker naar een geconfigureerde URL via HTTP.
	$cfg = %creds->notify ?? null
	if (!$cfg) return
	$url = $cfg->url ?? void
	$secret = $cfg->secret ?? void
	if ($url === void || $secret === void) return
	try {
		HTTP($url, ['secret: '.$secret], true, [
			'app' => (string)($cfg->app ?? (defined('id') ? id : 'app')),
			'server' => $cfg->server ?? 'local',
			'host' => %req->host ?? void,
			'type' => $type,
			'level' => $level,
			'title' => $title,
			'body' => $body,
			'user' => $user,
		])
	}
	catch (\Throwable $e){}
function

phlo(?string $phloName = null, ...$args):mixed

/phlo/phlo.php line 240
Maakt of haalt een instantie van een Phlo-object op op basis van de opgegeven naam en argumenten, en beheert een statische lijst van objecten voor hergebruik.
static $list = [];
if ($phloName === 'tech/reset'){
	obj::$classProps = [];
	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
Initialiseert een Phlo-applicatie door de omgeving in te stellen, noodzakelijke klassen en functies te laden, en verschillende runtime-parameters te configureren op basis van de opgegeven argumenten.
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['daemon']    ??= 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['daemon']) require_once __DIR__.'/classes/daemon.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 9
Voert een callbackfunctie asynchroon uit, waarbij eventuele extra argumenten worden doorgegeven, en retourneert de proces-ID als het succesvol is.
	if (daemon) return daemon::fire($cb, $args)
	$cmd = cli.space.escapeshellarg($_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void).' > /dev/null 2>&1 & echo $!'
	exec($cmd, $r)
	return isset($r[0]) && ctype_digit($r[0]) && (int)$r[0] > 0
function

phlo_cli(array $args):void

/phlo/phlo.php line 190
Verwerkt commandoregelargumenten, dispatcht een doelcommando en drukt het resultaat af in JSON-indeling.
if (!$args) return;
$target = array_shift($args);
$result = phlo_dispatch($target, $args);
if (isset($result)) print(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
function

phlo_dispatch(string $target, array $args = []):mixed

/phlo/phlo.php line 177
Verzendt een methode-aanroep naar een object of een statische methode van een klasse op basis van de opgegeven doelsstring, met optioneel doorgegeven argumenten.
if (str_contains($target, dot)){
	[$object, $method] = explode(dot, $target, 2);
	$handle = phlo($object);
	return $args ? $handle->$method(...$args) : ($handle->hasMethod($method) ? $handle->$method() : $handle->$method);
}
if (str_contains($target, '::')){
	[$class, $method] = explode('::', $target, 2);
	return $class::$method(...$args);
}
return $target(...$args);
function

phlo_exception(Throwable $e):void

/phlo/phlo.php line 33
Behandelt uitzonderingen door het Throwable-object door te geven aan de phlo_error_handle functie voor verwerking.
require_once engine.'error.php';
phlo_error_handle($e);
function

phlo_exists(string $obj)

/phlo/resources/phlo.exists.phlo line 8
Controleert of een specifiek PHP-bestand bestaat op het opgegeven objectpad.
is_file(php.strtr($obj, [us => dot]).'.php')
function

phlo_load(bool $http):void

/phlo/phlo.php line 153
Laadt de noodzakelijke runtime-bestanden voor de applicatie, waarbij ervoor wordt gezorgd dat de applicatie slechts eenmaal wordt geladen en dat het juiste contenttype wordt ingesteld voor 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_serve():void

/phlo/phlo.php line 197
Start een Phlo-server die luistert naar binnenkomende berichten, deze verwerkt en antwoorden terugstuurt in JSON-formaat. Het beheert zowel synchrone als asynchrone verzoeken, inclusief outputstreaming en foutrapportage.
ini_set('display_errors', 'stderr');
stream_set_blocking(STDIN, true);
fwrite(STDOUT, json_encode(['t' => 'ready']).lf);
while (($line = fgets(STDIN)) !== false){
	$line = trim($line);
	if ($line === void) continue;
	$msg    = json_decode($line, true) ?: [];
	$id     = $msg['id']     ?? null;
	$target = (string)($msg['target'] ?? void);
	$args   = (array)($msg['args'] ?? []);
	$stream = (bool)($msg['stream'] ?? false);
	$lineBuf = void;
	$emit = static function(string $chunk) use (&$lineBuf, $id):string {
		$lineBuf .= $chunk;
		while (($pos = strpos($lineBuf, lf)) !== false){
			$out = substr($lineBuf, 0, $pos);
			$lineBuf = substr($lineBuf, $pos + 1);
			fwrite(STDOUT, json_encode(['id' => $id, 't' => 'line', 'data' => $out], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
		}
		return void;
	};
	try {
		if ($target === void) error('No target');
		if ($stream){
			ob_start($emit, 1);
			$result = phlo_dispatch($target, $args);
			while (ob_get_level()) ob_end_flush();
			if ($lineBuf !== void) fwrite(STDOUT, json_encode(['id' => $id, 't' => 'line', 'data' => $lineBuf], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
		}
		else $result = phlo_dispatch($target, $args);
		fwrite(STDOUT, json_encode(['id' => $id, 't' => 'done', 'result' => $result], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
	}
	catch (Throwable $e){
		while (ob_get_level()) ob_end_clean();
		fwrite(STDOUT, json_encode(['id' => $id, 't' => 'error', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
	}
	phlo('tech/reset');
	if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
	gc_collect_cycles();
}
function

phlo_stream(string $cb, ...$args)

/phlo/resources/phlo.stream.phlo line 10
Stroomt uitvoer van een callbackfunctie, waardoor asynchrone verwerking van gegevens mogelijk is. Het kan in een daemon-modus werken of een opdracht in de CLI-context uitvoeren.
	if (daemon){
		yield from daemon::stream($cb, $args)
		return
	}
	yield from exec_stream(cli.space.escapeshellarg($_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 9
Voert een callbackfunctie synchronisch uit, waarbij eventuele extra argumenten worden doorgegeven, en retourneert het resultaat als een JSON-object of een foutmelding als de uitvoering mislukt.
	if (daemon) return daemon::run($cb, $args)
	$cmd = cli.space.escapeshellarg($_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)
	if ($code !== 0) error('Could not execute "'.esc($cb).'" via CLI')
	$j = json_decode($out, true)
	if (json_last_error() !== JSON_ERROR_NONE) return $out
	if (is_array($j) && isset($j['error'])) error($j['error'])
	return $j
function

phlo_thread():void

/phlo/phlo.php line 115
Beheert de hoofd uitvoeringsstroom van een Phlo-applicatie, inclusief verzoeken voor zowel CLI- als webomgevingen, met inbegrip van authenticatie en dashboardweergave.
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') return;
	phlo_exception($e);
}
catch (Throwable $e){
	phlo_exception($e);
}
function

select(...$args):string

/phlo/resources/tags.form.phlo line 12
Maakt een 'select' HTML-element met de opgegeven argumenten als attributen en opties.
tag('select', ...$args)
function

setting(?string $key = null, $value = null)

/phlo/resources/settings.phlo line 10
	$store = %JSON('settings', assoc: true)
	if ($key === null) return $store->objData
	if (func_num_args() > 1){
		$store->$key = $value
		$store->objChanged && $store->objWrite($store->objData)
	}
	return $store->$key
function

slug(string $text)

/phlo/resources/slug.phlo line 8
Converteert een gegeven string naar een URL-vriendelijke slug door niet-alfanumerieke tekens te verwijderen, naar kleine letters om te zetten en spaties door streepjes te vervangen.
trim(preg_replace('/[^a-z0-9]+/', dash, strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text))), dash)
function

stream($data = null, string $type = 'application/octet-stream', ?string $name = null):void

/phlo/resources/stream.phlo line 10
	$res = %res
	$cli = %req->cli
	!$res->streaming && $res->done && error('Output already started, invalid stream()')
	if (!$res->streaming){
		$res->streaming = true
		$res->type = $type
		$res->header('Cache-Control', 'no-store')
		$res->header('X-Content-Type-Options', 'nosniff')
		$res->header('X-Accel-Buffering', 'no')
		$name === null || $res->header('Content-Disposition', 'attachment; filename="'.str_replace('"', '', $name).'"')
		$res->render()
	}
	if ($data === null) return
	foreach (is_iterable($data) ? $data : [$data] as $part){
		print((string)$part)
		$cli || [@ob_flush(), flush()]
	}
function

tag(string $tagName, ?string $inner = null, ...$args)

/phlo/resources/tag.phlo line 9
Genereert een HTML-tag met de opgegeven naam, optionele inhoud en extra attributen.
"<$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
Maakt een 'textarea' HTML-element met de opgegeven argumenten.
tag('textarea', ...$args)
function

time_human(?int $time = null)

/phlo/resources/time.human.phlo line 8
Converteert een gegeven tijdstempel naar een leesbaar tijdsverschil formaat, zoals '2 dagen' of '3 uur'. Als er geen tijdstempel wordt opgegeven, wordt de huidige tijd gebruikt.
	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 = daemon, ...$data)

/phlo/resources/wsCast.phlo line 9
Verzendt een WebSocket-bericht naar een opgegeven doel of naar alle doelen met behulp van de opgegeven host en poort.
HTTP (
	'http://127.0.0.1:'.$wsPort.'/message',
	JSON: true,
	POST: arr (
		host: $wsHost,
		target: $wsTarget,
		data: $data,
	),
)

We gebruiken essentiële cookies om deze site te laten werken. Met uw toestemming gebruiken we ook analytics om de site te verbeteren.