Libs
object
%cookies
/phlo/libs/cookies.phlo
method
%cookies -> controller
line 7
Deze code wijst de inhoud van alle cookies toe aan een objecteigenschap, waardoor deze later in de code hergebruikt kan worden.
this->objData = $_COOKIEprop
%cookies -> lifetimeDays
line 5
Dit is een code die de waarde van het cookie's levensduur in dagen instelt op 180 dagen.
180method
%cookies -> __set ($key, $value)
line 9
Deze code zorgt ervoor dat een waarde wordt opgeslagen in de interne data, de globale cookies en via een HTTP-only, secure cookie met een bepaalde levensduur.
$this->objData[$key] = $value
$_COOKIE[$key] = $value
setcookie($key, $value, time() + $this->lifetimeDays * 86400, slash, $_SERVER['HTTP_HOST'], true, true)method
%cookies -> __unset ($key)
line 15
Verwijdert een cookie en de bijbehorende gegevens uit de objecten, door de waarde te unsetten en de cookie te verwijderen met een vervallen timestamp.
unset($this->objData[$key], $_COOKIE[$key])
setcookie($key, void, time() - 86400, slash, $_SERVER['HTTP_HOST'], true, true)object
%creds
/phlo/libs/creds.phlo
method
%creds -> __construct (?array $values = null)
line 5
In deze constructor worden, indien geen waarden worden meegegeven, gegevens uit een INI-bestand ingeladen. Vervolgens wordt voor elke sleutel-waarde combinatie uit dat array een dynamische eigenschap aangemaakt: als de waarde een array is, wordt er een nieuwe instantie van de klasse zelf gemaakt, anders wordt een SensitiveParameterValue-object gecreëerd.
$values ??= parse_ini_file(data.'creds.ini', true)
foreach ($values AS $key => $value){
$this->$key = is_array($value) ? new static($value) : new SensitiveParameterValue($value)
}method
%creds -> objGet ($key)
line 12
Deze code controleert of de parameter `$key` gelijk is aan `'toArray'`. In dat geval wordt een recursieve loop uitgevoerd over `objData`, waarbij waarden die van het type `'SensitiveParameterValue'` zijn, worden omgezet door hun `getValue()` methode. Als `$key` niet `'toArray'` is, wordt gecontroleerd of er een element bestaat onder `$key` in `objData` dat van het type `'SensitiveParameterValue'` is, en wordt dan de waarde via `getValue()` geretourneerd.
if ($key === 'toArray') return loop($this->objData, fn($value) => is_a($value, 'SensitiveParameterValue') ? $value->getValue() : $value)
if (isset($this->objData[$key]) && is_a($this->objData[$key], 'SensitiveParameterValue')) return $this->objData[$key]->getValue()method
%creds -> objInfo
line 17
Maakt door de invoer heen een lijst waarbij elke waarde wordt gecontroleerd op het type 'SensitiveParameterValue'. Als dat het geval is, wordt de waarde vervangen door een reeks van sterretjes met dezelfde lengte, anders blijft de waarde ongewijzigd.
loop($this->objData, fn($value) => is_a($value, 'SensitiveParameterValue') ? str_repeat('*', strlen($value->getValue())) : $value)object
%encryption
/phlo/libs/encryption.phlo
function
function encrypt ($data, $key):string
line 5
Deze code genereert een willekeurige nonce, versleutelt de data met behulp van een geheimsleutelfunctie, en encodeert het resultaat vervolgens in Base64 voor veilige transmissie of opslag.
base64_encode(($nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES)).sodium_crypto_secretbox($data, $nonce, hash('sha256', $key, true)))function
function decrypt ($encrypted, $key):string
line 6
Decodeert de base64-gecodeerde input en controleert of de decodering succesvol is en de lengte voldoende is. Zo ja, wordt de nonce uit de eerste bytes gehaald en wordt de rest gebruikt als ciphertext. Vervolgens wordt de geheime data ontsleuteld met een hash van de opgegeven sleutel en de sodium_crypto_secretbox_open-functie. Als een van de controles faalt, wordt false teruggegeven.
($d = base64_decode($encrypted, true)) !== false && strlen($d) >= SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ? sodium_crypto_secretbox_open(substr($d, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES), substr($d, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES), hash('sha256', $key, true)) : falseobject
%field
/phlo/libs/field.phlo
function
function field ($type, ...$args)
line 6
Dit definieert een functie die een nieuw veld aanmaakt, waarbij de functie naam dynamisch wordt opgebouwd op basis van het type. Het gebruikt het `$type` argument en extra argumenten om een veld van dat specifieke type te genereren.
phlo("field_$type", ...$args, type: $type)static
field :: __handle
line 8
Deze node geeft altijd een null-waarde terug, ongeacht de invoer of context.
nullprop
%field -> title
line 10
Het maakt de eerste letter van de waarde van de variabele $name hoofdletter.
ucfirst($this->name)method
%field -> input ($record, $CMS)
line 12
Maakt een invoerveld aan met het type, naam en andere attributen gebaseerd op de eigenschappen van het object en vult de waarde in uit het record of een standaardwaarde.
input(type: $this->type, name: $this->name, value: $record->{$this->name} ?? $this->default, maxlength: $this->length, placeholder: $this->placeholder, class: 'field')method
%field -> label ($record, $CMS)
line 13
Deze code geeft de waarde van een property terug uit het record, gebaseerd op de naam van het veld.
$record->{$this->name};object
%form_tags
/phlo/libs/form.tags.phlo
function
function button (...$args):string
line 5
Creëert een 'button'-element met de meegegeven arguments als attributen of inhoud.
tag('button', ...$args)function
function input (...$args):string
line 6
Deze node genereert een HTML `<input>` element met de opgegeven argumenten als attributen of inhoud.
tag('input', ...$args)function
function select (...$args):string
line 7
Maakt een 'select' element aan met de gegeven argumenten.
tag('select', ...$args)function
function textarea (...$args):string
line 8
Maakt een HTML `<textarea>` element aan met de meegegeven argumenten.
tag('textarea', ...$args)object
%lang
/phlo/libs/lang.phlo
function
function nl ($text, ...$args)
line 8
Translate de meegegeven tekst naar het Nederlands, eventueel met extra argumenten voor interpolatie of context.
%lang->translation('nl', $text, ...$args)function
function en ($text, ...$args)
line 9
Deze node vertaalt de invoertekst naar het Engels, gebruikmakend van dezelfde vertaalfunctie met de opgegeven tekst en argumenten.
%lang->translation('en', $text, ...$args)view
%lang ->
line 11
Dit stuk code voert een vertaling uit door de taalinstelling van de applicatie te gebruiken, waardoor de juiste taal wordt geladen voor de gebruiker.
%app->langprop
%lang -> browser
line 13
Deze code selecteert de meest geschikte taalcode uit de 'HTTP_ACCEPT_LANGUAGE'-header van de gebruiker door de eerste taal in de header te vergelijken met beschikbare talen in de applicatie. Als er geen geschikte taal wordt gevonden, wordt de standaardtaal uit de eerste overeenkomstige taal geselecteerd of null als er geen matches zijn.
last($langs = array_filter(explode(comma, $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? void), fn($lang) => isset(%app->langs[substr($lang, 0, 2)])), $langs ? substr(current($langs), 0, 2) : null)method
%lang -> cookie
line 14
Controleert of de taal uit de cookies bestaat in de beschikbare talen. Als dat het geval is, wordt de taalwaarde teruggegeven; anders wordt null geretourneerd.
($lang = %cookies->lang) && %app->langs[$lang] ? $lang : nullprop
%lang -> model
line 16
Een korte toelichting is dat deze node waarschijnlijk een instelling of parameter bevat voor het gebruik van een kleiner of geoptimaliseerd GPT-4 model, geschikt voor minder veeleisende taken of snellere verwerking.
'gpt-4o-mini'method
%lang -> translations
line 17
Initialiseert de vertalingen voor de huidige taal, met gebruik van de instellingen of gegevens uit '%app->lang' en 'langs'.
%INI(%app->lang, langs)method
%lang -> detect ($text, $fallback = 'en')
line 19
De code voert een chat-aanvraag uit bij een AI-model om de taal van een tekst te identificeren en die identificatie terug te geven als een ISO 639-1 code. Als de response geen geldige 2-karaktercode is, wordt een fallback-taal gebruikt.
$res = %OpenAI->chat (
model: $this->model,
system: 'Analyseer welke taal deze tekst is en geef alleen de ISO 639-1 code van de taal terug, zonder andere data!',
user: $text.lf.lf.'De ISO 639-1 code van de taal is: ',
temperature: 0,
)->answer
return strlen($res) === 2 ? strtolower($res) : $fallbackmethod
%lang -> hash ($from, $text)
line 29
Creëert een korte, unieke hash door eerst een samenvatting te maken van de tekst met een regex en hoofdletters, het eerste deel hiervan te gebruiken als prefix, en vervolgens een MD5-hash van de volledige tekst toe te voegen, zodat de resulterende code zowel herkenbaar als uniek is.
$from.($short = substr(implode(regex_all('/[A-z0-9]+/', ucwords($text))[0]), 0, 8)).substr(md5($text), 0, 10 - strlen($short))method
%lang -> translation ($from, $text, ...$args)
line 31
Deze methode vertaalt tekst van een opgegeven taal naar de actieve taal. Als de bron- en doeltaal gelijk zijn, wordt de tekst direct geretourneerd. Anders wordt de tekst opgesplitst in regels, voor elke regel wordt een hash gemaakt en gezocht naar een vertaling. Als die niet gevonden wordt, wordt een asynchrone vertaling aangevraagd. De vertalingen worden samengevoegd en teruggegeven, met optionele formattering als er argumenten zijn meegegeven.
if ($from === %app->lang) $translation = $text
else {
$translation = []
foreach (explode(lf, $text) AS $line){
if (trim($line)){
$hash = $this->hash($from, $line)
if (!$item = $this->translations->$hash) phlo_async('lang', 'asyncTranslation', $from, %app->lang, $item = $line)
}
else $item = void
$translation[] = $item
}
$translation = implode(lf, $translation)
}
$translation = strtr($translation, ['\n' => lf])
return $args ? sprintf($translation, ...$args) : $translationstatic
lang :: asyncTranslation ($from, $to, $text)
line 49
Deze code wijzigt de taal van de applicatie, genereert een unieke hash op basis van de originele taal en tekst, vertaalt de tekst en slaat de vertaling op in een translatiestructuur met de hash als sleutel.
%app->lang = $to
$hash = $this->hash($from, $text)
$translation = $this->translate($from, $to, $text)
return $this->translations->$hash = $translationmethod
%lang -> translate ($from, $to, $text)
line 56
Deze functie controleert eerst of de bron- en doeltaal hetzelfde zijn en retourneert in dat geval de oorspronkelijke tekst. Anders maakt het een chat-aanroep naar OpenAI, waarbij het model wordt geïnstrueerd om de tekst te vertalen tussen de opgegeven talen, met behoud van markdown, interpunctie en hoofdlettergebruik, en geeft alleen de vertaalde tekst terug.
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.",
user: $text,
temperature: 0,
)->answerobject
%model
/phlo/libs/model.phlo
static
model :: DB
line 8
Verbindt met een MySQL database.
%MySQLstatic
model :: objRecords
line 9
Een lege array, betekent dat er momenteel geen records of items in deze node aanwezig zijn.
[]static
model :: objLoaded
line 10
Het lijkt een lege array te retourneren, mogelijk bedoeld om aan te geven dat er geen gegevens geladen zijn of dat er geen actie is uitgevoerd.
[]static
model :: objCache
line 11
Deze code retourneert altijd de waarde 'false'.
falsestatic
model :: columns
line 13
Controleert of de statische property `$columns` bestaat; zo ja, wordt die geretourneerd. Als niet, wordt gekeken of er een methode `schema` bestaat; zo ja, wordt deze methode aangeroepen via `_columns()`. Als geen van beide het geval is, wordt de string '*' teruggegeven.
isset(static::$columns) ? static::$columns : (method_exists(static::class, 'schema') ? static::_columns() : '*')static
model :: _columns
line 14
Deze code genereert een gequoteerde lijst van kolomnamen voor databasegebruik, waarbij alleen velden met specifieke types ('child', 'many', 'virtual') worden uitgesloten. Het combineert velddefinities en voegt de juiste quotes toe voor gebruik in een SQL-query.
$fq = static::DB()->fieldQuotes
$list = array_merge(...array_values(array_filter(loop(static::fields(), fn($field, $column) => in_array($field->type, ['child', 'many', 'virtual']) ? null : ($field->columns ?: [static::$table."$fq.$fq".$column])))))
return $fq.implode("$fq,$fq", $list).$fqstatic
model :: fields
line 19
Controleert of er een 'schema'-methode bestaat in de klasse. Als dat het geval is, wordt de output van '_fields()' gebruikt; anders wordt gezocht naar de statische eigenschap '$fields' of een lege array als die niet bestaat.
method_exists(static::class, 'schema') ? static::_fields() : (static::$fields ?? [])static
model :: _fields
line 20
Loopt door de schema-velden en selecteert voor elk veld de laatste waarde uit een keten van alternatieven: of de naam, of het object (bij voorwaarden met type 'parent'), of het veld zelf.
loop(static::schema(), fn($field, $column) => last($field->name ??= $column, $field->type === 'parent' && $field->obj ??= $column, $field))static
model :: field ($name)
line 21
Geeft de veldconfiguratie terug uit de array van alle velden, gebaseerd op de opgegeven naam.
static::fields()[$name]static
model :: create (...$args)
line 23
Deze code maakt een nieuw record aan en retourneert dit vervolgens, mogelijk voor verdere verwerking of als resultaat van de functie.
static::record(id: static::createRecord(...$args))static
model :: createRecord (...$args)
line 24
Maakt een nieuw databaserecord aan in de opgegeven tabel met de meegegeven arguments.
static::DB()->create(static::$table, ...$args)static
model :: change ($where, ...$args)
line 25
Deze code roept een statische methode 'change' op die waarschijnlijk een wijziging doorvoert in de database voor een specifiek tabel. Het gebruikt de database-verbinding en tabelnaam uit statische eigenschappen en accepteert voorwaarden en extra argumenten om de wijziging uit te voeren.
static::DB()->change(static::$table, $where, ...$args)static
model :: delete ($where, ...$args)
line 26
Voert een delete-bewerking uit op de database voor de opgegeven tabel, gebaseerd op de meegegeven voorwaarden en eventuele extra argumenten.
static::DB()->delete(static::$table, $where, ...$args)method
%model -> objSave
line 28
Controleren of er een id is; indien niet, een foutmelding geven. Als een record met die id bestaat, wordt deze bijgewerkt en geretourneerd. Anders wordt een nieuw record aangemaakt en geretourneerd.
$this->id || error('Can\'t save '.static::class.' record without an id')
if (static::item(id: $this->id, columns: 'id')){
static::change('id=?', $this->id, ...$this)
return static::record(id: $this->id)
}
else return static::create(...$this)static
model :: column (...$args)
line 37
Laadt meerdere records en retourneert enkel de kolomwaarden, zonder Object-Of-Array structuur.
static::recordsLoad($args, 'fetchAll', [PDO::FETCH_COLUMN])static
model :: item (...$args)
line 38
Laadt records met de opgegeven argumenten, gebruikt de 'fetch' modus en haalt enkel één kolom op.
static::recordsLoad($args, 'fetch', [PDO::FETCH_COLUMN])static
model :: pair (...$args)
line 39
Laadt meerdere records en retourneert ze als een key-value paar, waarbij de fetch-modus is ingesteld op FETCH_KEY_PAIR voor snelle en eenvoudige mappen.
static::recordsLoad($args, 'fetchAll', [PDO::FETCH_KEY_PAIR])static
model :: records (...$args)
line 40
Laadt meerdere records uit de database en retourneert ze als unieke objecten van de class, met alle records samengevoegd in een enkele collectie.
static::recordsLoad($args, 'fetchAll', [PDO::FETCH_CLASS|PDO::FETCH_UNIQUE, static::class], true)static
model :: recordCount (...$args)
line 41
Deze code telt het aantal records door een telling uit te voeren op de kolom 'id'.
static::item(...$args, columns: 'COUNT(id)')static
model :: record (...$args)
line 42
Berekent het aantal records en geeft een foutmelding bij meer dan één record; anders retourneert het het eerste record of null als er geen records zijn.
count($records = static::records(...$args)) > 1 ? error('Multiple records for '.static::class) : (current($records) ?: null)static
model :: recordsLoad ($args, $fetch, $fetchMode, $saveRelations = false)
line 44
Deze code bouwt dynamisch query-parameters op voor het laden van records uit een database, inclusief condities, joins, selecties en groepsregels, afhankelijk van de statische eigenschappen en argumenten. Vervolgens wordt gebruik gemaakt van caching met APCu op basis van een cachekey en duur, waardoor herhaald laden efficiënter verloopt. Tot slot worden de geladen records opgeslagen voor hergebruik en geretourneerd.
$args['table'] ??= static::$table
$saveRelations && $args['columns'] ??= static::$table.'.id as _,'.static::columns()
isset(static::$joins) && $args['joins'] = static::$joins.(isset($args['joins']) ? " $args[joins]" : void)
method_exists(static::class, 'where') && $args['where'] = static::where().(isset($args['where']) ? " AND $args[where]" : void)
isset(static::$group) && $args['group'] ??= static::$group
isset(static::$order) && $args['order'] ??= static::$order
if ($cacheKey = $args['cacheKey'] ?? null) unset($args['cacheKey'])
if ($duration = static::$objCache) $records = apcu($cacheKey ?? static::class.slash.md5(json_encode($args)), fn() => static::DB()->load(...$args)->$fetch(...$fetchMode), $duration === true ? 86400 : $duration)
else $records = static::DB()->load(...$args)->$fetch(...$fetchMode)
if ($saveRelations && $records) self::$objRecords[static::class] = (self::$objRecords[static::class] ?? []) + array_column($records, null, 'id')
return $recordsstatic
model :: objRel ($key)
line 58
Geeft de waarde van een statisch eigenschap of methode of, indien niet aanwezig, een standaardwaarde (lege array).
static::$classProps[static::class][$key] ??= method_exists(static::class, $key) ? static::$key() : static::$$key ?? []prop
%model -> objState
line 60
Initialiseert een structuur met drie lege collecties: 'parents', 'children' en 'many'.
['parents' => [], 'children' => [], 'many' => []]method
%model -> objGet ($key)
line 61
Zoek eerst in de ouder, dan in de kinderen, en als geen resultaat wordt gevonden, zoek vervolgens meerdere items.
$this->getParent($key) ?? $this->getChildren($key) ?? $this->getMany($key)method
%model -> objIn ($ids)
line 62
Converteert een array van IDs naar een door komma's gescheiden string tussen aanhalingstekens, of retourneert 'NULL' als de array niet bestaat of leeg is.
$ids ? dq.implode(dq.comma.dq, $ids).dq : 'NULL'method
%model -> getParent ($key)
line 64
Retourneert het directe ouderobject gebaseerd op een sleutel, met controle op eerder geladen relaties en het dynamisch ophalen van gerelateerde records als die nog niet geladen zijn.
if (array_key_exists($key, $this->objState['parents'])) return $this->objState['parents'][$key]
$parents = self::objRel('objParents')
if (!$relation = $parents[$key] ?? null) return
$isArray = is_array($relation)
$class = $isArray ? $relation['obj'] : $relation
$column = $isArray ? $relation['key'] ?? $key : $key
if (!$parentId = $this->objData[$column] ?? null) return $this->objState['parents'][$key] = null
if (!isset(self::$objRecords[$class][$parentId])){
$idsToLoad = [$parentId => true]
$allObjData = array_map(fn($record) => $record->objData, self::$objRecords[static::class] ?? [])
foreach ($parents as $pKey => $pRelation){
$pIsArray = is_array($pRelation)
$pClass = $pIsArray ? $pRelation['obj'] : $pRelation
if ($pClass === $class) foreach (array_column($allObjData, $pIsArray ? $pRelation['key'] ?? $pKey : $pKey) as $pId) $pId && !isset(self::$objRecords[$class][$pId]) && $idsToLoad[$pId] = true
}
if ($idsToLoad = array_keys($idsToLoad)) $class::records(where: 'id IN ('.$this->objIn($idsToLoad).')')
}
$parentObject = self::$objRecords[$class][$parentId] ?? null
return $this->objState['parents'][$key] = $parentObjectmethod
%model -> getChildren ($key)
line 86
Verifieert of de children voor een opgegeven sleutel al zijn geladen; indien niet, wordt de relatie opgevraagd en worden de bijbehorende records ingeladen en in de state opgeslagen; tot slot wordt de children-array geretourneerd.
if (array_key_exists($key, $this->objState['children'])) return $this->objState['children'][$key]
if (!$relation = self::objRel('objChildren')[$key] ?? null) return
$isArray = is_array($relation)
$class = $isArray ? $relation['obj'] : $relation
$column = $isArray ? $relation['key'] : static::class
if (!isset(self::$objLoaded[static::class]['children'][$key])){
$parentIds = array_keys(self::$objRecords[static::class] ?? [])
if ($parentIds){
$children = $class::records(where: '`'.$column.'` IN ('.$this->objIn($parentIds).')')
foreach (self::$objRecords[static::class] AS $parentRecord) $parentRecord->objState['children'][$key] = []
foreach ($children AS $childId => $child) !is_null($pId = $child->objData[$column] ?? null) && isset(self::$objRecords[static::class][$pId]) && self::$objRecords[static::class][$pId]->objState['children'][$key][$childId] = $child
}
self::$objLoaded[static::class]['children'][$key] = true
}
return $this->objState['children'][$key] ?? []method
%model -> getMany ($key)
line 104
Laadt meerdere gerelateerde records uit de database indien deze nog niet eerder geladen zijn en cachet deze in de objectstaat, zodat bij volgende aanroepen de records direct beschikbaar zijn.
if (array_key_exists($key, $this->objState['many'])) return $this->objState['many'][$key]
if (!$relation = self::objRel('objMany')[$key] ?? null) return
$class = $relation['obj']
if (!isset(self::$objLoaded[static::class]['many'][$key])){
$parentIds = array_keys(self::$objRecords[static::class] ?? [])
if ($parentIds){
$targetTable = $class::$table
$records = $class::recordsLoad(arr(table: $relation['table'], columns: "`$targetTable`.*, `$relation[table]`.`$relation[localKey]` as _local_key", joins: "INNER JOIN `$targetTable` ON `$relation[table]`.`$relation[foreignKey]` = `$targetTable`.`id`", where: "`$relation[table]`.`$relation[localKey]` IN (".$this->objIn($parentIds).")"), 'fetchAll', [PDO::FETCH_CLASS, $class])
foreach (self::$objRecords[static::class] AS $parentRecord) $parentRecord->objState['many'][$key] = []
foreach ($records AS $record){
$recordId = $record->id
$parentId = $record->_local_key
unset($record->_local_key)
if (isset(self::$objRecords[static::class][$parentId])) self::$objRecords[static::class][$parentId]->objState['many'][$key][$recordId] = $record
}
}
self::$objLoaded[static::class]['many'][$key] = true
}
return $this->objState['many'][$key] ?? []method
%model -> getCount ($key)
line 126
Deze code controleert eerst of de telling al in de interne staat is opgeslagen, en retourneert die indien beschikbaar. Vervolgens identificeert het of de telling te maken heeft met een kind-relatie ('objChildren') of een meervoudige relatie ('objMany'). Als het een kind-relatie is, haalt het de IDs van gekoppelde records op, voert een geaggregeerde query uit om de telling te bepalen en cachet deze resultaten. Voor een 'many'-relatie wordt vergelijkbare logica toegepast, maar dan via een aparte tabel en kolom. Als geen relatie wordt gevonden, wordt 0 geretourneerd.
if (array_key_exists($key, $this->objState['counts'] ?? [])) return $this->objState['counts'][$key]
if ($relation = self::objRel('objChildren')[$key] ?? null){
if (!isset(self::$objLoaded[static::class]['children_count'][$key])){
$parentIds = array_keys(self::$objRecords[static::class] ?? [])
if ($parentIds){
$isArray = is_array($relation)
$class = $isArray ? $relation['obj'] : $relation
$column = $isArray ? $relation['key'] : static::class
$counts = $class::pair(columns: "`$column`, COUNT(*)", where: '`'.$column.'` IN ('.$this->objIn($parentIds).')', group: "`$column`")
foreach (self::$objRecords[static::class] as $id => $record) $record->objState['counts'][$key] = (int)($counts[$id] ?? 0)
}
self::$objLoaded[static::class]['children_count'][$key] = true
}
return $this->objState['counts'][$key] ?? 0
}
if ($relation = self::objRel('objMany')[$key] ?? null){
if (!isset(self::$objLoaded[static::class]['many_count'][$key])){
$parentIds = array_keys(self::$objRecords[static::class] ?? [])
if ($parentIds){
$counts = static::DB()->load(table: $relation['table'], columns: "`$relation[localKey]`,COUNT(*)", where: '`'.$relation['localKey'].'` IN ('.$this->objIn($parentIds).')', group: "`$relation[localKey]`")->fetchAll(PDO::FETCH_KEY_PAIR)
foreach (self::$objRecords[static::class] as $id => $record) $record->objState['counts'][$key] = (int)($counts[$id] ?? 0)
}
self::$objLoaded[static::class]['many_count'][$key] = true
}
return $this->objState['counts'][$key] ?? 0
}
return 0method
%model -> getLast ($key)
line 156
Zoekt in de objectstatus of de laatste kind gekoppeld aan de opgegeven sleutel al geladen is; indien niet, wordt via relatiegegevens en records de laatste kind opgevraagd, opgeslagen in de statestatus en teruggegeven.
if (array_key_exists($key, $this->objState['last_child'] ?? [])) return $this->objState['last_child'][$key]
if ($relation = self::objRel('objChildren')[$key] ?? null){
if (!isset(self::$objLoaded[static::class]['last_child'][$key])){
if ($parentIds = array_keys(self::$objRecords[static::class] ?? [])){
$isArray = is_array($relation)
$class = $isArray ? $relation['obj'] : $relation
$column = $isArray ? $relation['key'] : static::class
$childTable = $class::$table
$whereClause = "`$column` IN (".$this->objIn($parentIds).") AND `$childTable`.`id` = (SELECT `id` FROM `$childTable` AS lc WHERE lc.`$column`=`$childTable`.`$column` ORDER BY `id` DESC LIMIT 1)"
$lastChildren = $class::records(where: $whereClause)
foreach (self::$objRecords[static::class] as $record) $record->objState['last_child'][$key] = null
foreach ($lastChildren as $child) if (isset(self::$objRecords[static::class][$parentId = $child->objData[$column]])) self::$objRecords[static::class][$parentId]->objState['last_child'][$key] = $child
}
self::$objLoaded[static::class]['last_child'][$key] = true
}
return $this->objState['last_child'][$key] ?? null
}
return nullstatic
model :: objParents
line 177
Haalt de statische eigenschap op als deze bestaat; anders retourneert het een lege array als de ‘schema’ methode niet bestaat; indien wel, filtert het de velden op type ‘parent’ en maakt het een array, waarbij het voor elk veld een object en optioneel een sleutel teruggeeft of de objectnaam gebruikt.
if (property_exists(static::class, 'objParents')) return static::$objParents
if (!method_exists(static::class, 'schema')) return []
return loop(array_filter(static::fields(), fn($f) => $f->type === 'parent'), fn($f, $c) => $f->key ? arr(obj: $f->obj, key: $f->key) : ($f->obj ?? $c))static
model :: objChildren
line 183
Deze code controleert of de statische property 'objChildren' bestaat en retourneert deze indien aanwezig. Als de 'schema'-methode niet bestaat, geeft hij een lege array terug. Anders wordt een array gefilterd op velden met type 'child', en worden deze vervolgens verwerkt door een loop-functie die voor elk veld beslist of een object en mogelijk een sleutel retourneert, of gewoon het object zelf.
if (property_exists(static::class, 'objChildren')) return static::$objChildren
if (!method_exists(static::class, 'schema')) return []
return loop(array_filter(static::fields(), fn($f) => $f->type === 'child'), fn($f, $c) => $f->key ? arr(obj: $f->obj, key: $f->key) : ($f->obj ?? $c))static
model :: objMany
line 189
Geeft de waarde van de statische eigenschap `objMany` terug als die bestaat; anders, als er geen methode `schema` is, geeft het een lege array terug; anders filtert het de velden op het type 'many' en bouwt een array met object- en tabelleninformatie, inclusief de local en foreign keys.
if (property_exists(static::class, 'objMany')) return static::$objMany
if (!method_exists(static::class, 'schema')) return []
return loop(array_filter(static::fields(), fn($f) => $f->type === 'many'), fn($f) => arr(obj: $f->obj, table: $f->table, localKey: $f->localKey ?? static::class, foreignKey: $f->foreignKey ?? $f->obj))static
model :: createTable
line 195
Controleert of de methode schema() bestaat, anders wordt een fout gegooid. Daarna wordt een SQL CREATE TABLE statement opgebouwd door de veldgegevens te itereren en te formatteren, inclusief eventuele NOT NULL of NULL specificaties, en voegt een primaire sleutel op 'id' toe.
method_exists(static::class, 'schema') || error(static::class.' has no schema()')
return 'CREATE TABLE `'.static::$table.'` ('.lf.tab.implode(",\n\t", array_merge(...array_values(array_filter(loop(static::fields(), fn($field) => loop((array)$field->sql, fn($sql) => $sql.($field->required || $field->nullable === false ? ' NOT' : void).' NULL')))))).",\n\tPRIMARY KEY (`id`)\n)"object
%n8n
/phlo/libs/n8n.phlo
function
function n8n ($webhook, ?array $data = null, $test = false)
line 5
Maakt een POST-verzoek naar een webhook-URL, gebaseerd op servergegevens en testmodus, met optionele data mee.
HTTP(%creds->n8n->server.'webhook'.($test ? '-test' : '').'/'.$webhook, POST: $data)function
function n8n_test ($webhook, ?array $data = null)
line 6
Roep een functie genaamd 'n8n' aan met de webhook, een optionele data-array en een boolean parameter (waarschijnlijk voor een speciale modus of debug).
n8n($webhook, $data, true)object
%payload
/phlo/libs/payload.phlo
method
%payload -> controller
line 7
Parses JSON request bodies voor POST, PUT, PATCH methoden en voert import uit; behandelt multipart/form-data uploads met bestandsgegevens, slaat bestanden tijdelijk op, en stelt de data structureren in, inclusief array-collecties; verwerkt ook standaard $_FILES data door import.
f (in_array(method, ['POST', 'PUT', 'PATCH']) && str_starts_with($_SERVER['CONTENT_TYPE'] ?? void, 'application/json')) return $this->objData = get_object_vars(json_read('php://input'))
if ($_POST) $this->objImport(...$_POST)
elseif (method === 'PUT' && str_starts_with($_SERVER['CONTENT_TYPE'], 'multipart/form-data')){
$boundary = '--'.regex('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'])[1]
$arrays = []
foreach (explode($boundary, file_get_contents('php://input')) AS $part){
if (!trim($part) || $part === '--' || !str_contains($part, nl.nl)) continue
[$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) ?: null
if (str_ends_with($name, '[]')) $arrays[] = substr($name, 0, -2)
if (preg_match('/filename="([^"]*)"/', $headers['content-disposition'], $f)){
if ($f[1] === void || $body === null){
if (!str_ends_with($name, '[]')) $this->objData[$name] = null
continue
}
$filename = $f[1]
$file = %file(tempnam(sys_get_temp_dir(), 'phlo'), $filename, $body)
if (str_ends_with($name, '[]')) $this->objData[substr($name, 0, -2)][] = $file
else $this->objData[$name] = $file
}
else {
if (str_ends_with($name, '[]')) $this->objData[substr($name, 0, -2)][] = $body
else $this->objData[$name] = $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
%security
/phlo/libs/security.phlo
prop
%security -> nonce
line 5
Generert een willekeurige token van 8 tekens en slaat deze op in de property `$nonce`.
%app->nonce = token(8)method
%security -> full
line 7
Geeft een verzameling beveiligingsheaders of -instellingen door verschillende properties te controleren of toe te wijzen, vermoedelijk gericht op webbeveiliging en privacy.
$this->COOP
$this->CORP
$this->CORS
$this->CSP
$this->Referrer
$this->X_content
$this->X_framemethod
%security -> CSRF
line 17
Genereert en slaat een nieuwe CSRF-token van 12 tekens op in de sessie.
%session->csrf = token(12)method
%security -> COOP
line 18
Stelt een HTTP-header in die bepaalt dat de pagina alleen geopend mag worden door documenten van dezelfde oorsprong, wat beveiligingsverbeteringen biedt tegen bepaalde aanvalstechnieken zoals side-channel attacks.
header('Cross-Origin-Opener-Policy: same-origin')method
%security -> CORP
line 19
Voegt een HTTP-header toe die aangeeft dat Cross-Origin Resource Sharing alleen toestaat vanaf dezelfde oorsprong.
header('Cross-Origin-Resource-Policy: same-origin')method
%security -> CORS ($host = host)
line 20
Stelt de toegestane origin in voor cross-origin resource sharing (CORS) door een header te zetten die enkel toegang toestaat vanaf de opgegeven host.
header("Access-Control-Allow-Origin: https://$host")method
%security -> CSP
line 21
Verzendt een Content-Security-Policy header die beperkingen oplegt aan bronnen voor content, scripts, styles, afbeeldingen, fonts, connecties, en frame-ancestors, inclusief een nonce voor dynamisch geladen scripts.
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-$this->nonce' 'unsafe-inline'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'")method
%security -> Referrer
line 22
Het stelt een HTTP-header in die bepaalt dat de referrer-informatie alleen wordt meegestuurd naar dezelfde oorsprong of naar derden via een volledige URL, maar niet bij cross-origin requests, waardoor de privacy wordt verbeterd.
header('Referrer-Policy: strict-origin-when-cross-origin')method
%security -> X_content
line 23
Voegt een HTTP-header toe die voorkomt dat browsers het content-type van de respons gaan raden, wat bijdraagt aan verbeterde beveiliging door het voorkomen van content-sniffing.
header('X-Content-Type-Options: nosniff')method
%security -> X_frame
line 24
Deze code zet de HTTP-header 'X-Frame-Options' op 'DENY', waardoor voorkomt dat de pagina in een frame of iframe wordt geladen, wat bijdraagt aan beveiliging tegen clickjacking.
header('X-Frame-Options: DENY')object
%session
/phlo/libs/session.phlo
method
%session -> controller
line 5
Start de sessie en slaat de inhoud van de $_SESSION-variabele op in een property voor later gebruik.
ession_start()
$this->objData = $_SESSIONmethod
%session -> __set ($key, $value)
line 8
Deze code wijzigt twee arrays tegelijk: een globale sessie-array en een interne data-array van het object, door de opgegeven waarde toe te wijzen aan de gegeven sleutel in beide arrays.
$_SESSION[$key] = $this->objData[$key] = $valuemethod
%session -> __unset ($key)
line 9
Verwijdert een sleutel uit zowel de interne gegevensstructuur als uit de sessiegegevens.
unset($this->objData[$key], $_SESSION[$key])AI
object
%Claude
/phlo/libs/AI/Claude.phlo
static
Claude :: context (...$args)
line 7
Voegt 'assistant' en 'user' inhoud toe aan de 'messages'-array indien aanwezig, en verwijdert deze sleutels daarna uit de array.
$args['messages'] ??= []
if (isset($args['assistant']) && array_push($args['messages'], arr(role: 'assistant', content: $args['assistant']))) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], arr(role: 'user', content: $args['user']))) unset($args['user'])
return $argsmethod
%Claude -> chat (...$args)
line 14
Deze code maakt een geconfigureerde aanvraag naar een chatmodel, waarbij standaardwaarden voor het model en het maximale aantal tokens worden ingesteld. Vervolgens wordt een verzoek gedaan en wordt het antwoord als een object met een `answer`-veld geretourneerd.
$args = static::context(...$args)
$args['model'] ??= 'claude-3-5-sonnet-latest'
$args['max_tokens'] ??= 3333
$res = $this->request('messages', true, POST: $args)
return obj(answer: $res->content[0]->text)method
%Claude -> stream (...$args)
line 22
Deze functie zet een streaming-verbinding op met een API-endpoint, verstuurt een POST-verzoek met gespecificeerde parameters, en verwerkt de ontvangen data in real-time via een callback. Indien geen callback is meegegeven, wordt automatisch een event-stream opgezet en worden uitgaande data direct weergegeven. De response wordt opgebouwd uit de ontvangen delta-teksten en geretouneerd als volledige tekst.
%app->streaming = true
$args = static::context(...$args)
$args['model'] ??= 'claude-3-5-sonnet-latest'
$args['max_tokens'] ??= 3333
$args['stream'] = true
if (isset($args['cb'])){
$cb = $args['cb']
unset($args['cb'])
}
else {
cli || header('Content-Type: text/event-stream')
$cb = fn($data) => last(($text = $data->delta->text ?? void) === void || [print($text), cli || [ob_flush(), flush()]], $text)
}
$answer = void
$curl = curl_init('https://api.anthropic.com/v1/messages')
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST')
curl_setopt($curl, CURLOPT_POSTFIELDS, $data = json_encode($args))
curl_setopt($curl, CURLOPT_HTTPHEADER, ['anthropic-version: 2023-06-01', 'Content-Type: application/json', 'Content-Length: '.strlen($data), 'X-Api-Key: '.%creds->Claude])
curl_setopt($curl, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($cb, &$answer){
foreach (array_filter(explode(lf.lf, $data)) AS $chunk){
$obj = json_decode(substr($chunk, strpos($chunk, 'data: {') + 6))
$res = $cb($obj, $obj->delta->text ?? null)
if (is_string($res)) $answer .= $res
}
return strlen($data)
})
curl_exec($curl)
return $answermethod
%Claude -> request ($uri, $JSON = true, ...$args)
line 53
Verzendt een HTTP-verzoek naar een opgegeven API-endpoint met standaardheaders en API-sleutel, decodeert het JSON-resultaat, controleert op fouten, en retourneert het gedecodeerde object.
$res = json_decode(HTTP("https://api.anthropic.com/v1/$uri", ['anthropic-version: 2023-06-01', 'Content-Type: application/json', 'X-Api-Key: '.%creds->Claude], $JSON, ...$args))
if (isset($res->error)) error('Claude Request error: '.$res->error->message)
return $resobject
%DeepSeek
/phlo/libs/AI/DeepSeek.phlo
static
DeepSeek :: context (...$args)
line 7
Voegt standaardwaarden toe aan de 'messages' array en voegt gespreksrollen toe zoals 'system', 'assistant' en 'user' op basis van de meegegeven argumenten, waarna de originele argumenten worden opgeschoond.
$args['messages'] ??= []
if (isset($args['system']) && array_unshift($args['messages'], arr(role: 'system', content: $args['system']))) unset($args['system'])
if (isset($args['assistant']) && array_push($args['messages'], arr(role: 'assistant', content: $args['assistant']))) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], arr(role: 'user', content: $args['user']))) unset($args['user'])
return $argsmethod
%DeepSeek -> chat (...$args)
line 15
Verwerkt een chat-aanvraag door het voorbereiden van argumenten, verstuurt een request naar het chat-completions eindepunt, en retourneert een object met modelinformatie, finish-reden, tokengebruik en inhoud of tool-aanroepen.
$args = static::context(...$args)
$res = $this->request('chat/completions', POST: $args)
$return = obj(model: $res->model, finish: $res->choices[0]->finish_reason, tokens: $res->usage->total_tokens, tokens_in: $res->usage->prompt_tokens, tokens_out: $res->usage->completion_tokens)
if (isset($res->choices[0]->message->tool_calls)) $return->tools = loop($res->choices[0]->message->tool_calls, fn($tool) => obj(name: $tool->function->name, args: json_decode($tool->function->arguments, true)))
else $return->answer = $res->choices[0]->message->content
return $returnmethod
%DeepSeek -> stream (...$args)
line 24
Deze methode stelt een streamverbinding op met een API, verwerkt tijdelijke data-uploads via cURL, en gebruikt een callback om real-time data te verwerken en samen te voegen tot een eindantwoord.
%app->streaming = true
$args = static::context(...$args)
$args['stream'] = true
if (isset($args['cb'])){
$cb = $args['cb']
unset($args['cb'])
}
else {
cli || header('Content-Type: text/event-stream')
$cb = fn($data) => last(($text = $data->choices[0]->delta->content ?? void) === void || [print($text), cli || [ob_flush(), flush()]], $text)
}
$answer = void
$buffer = void
$curl = curl_init('https://api.deepseek.com/v1/chat/completions')
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST')
curl_setopt($curl, CURLOPT_POSTFIELDS, $data = json_encode($args))
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer '.%creds->DeepSeek, 'Content-Type: application/json', 'Content-Length: '.strlen($data)])
curl_setopt($curl, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($cb, &$buffer, &$answer){
$chunks = trim($buffer.$data)
$buffer = void
foreach (explode(lf.lf, $chunks) AS $chunk){
if (!str_starts_with($chunk, 'data: ')){
$buffer = $chunk
continue
}
if ($obj = json_decode(substr($chunk, 6))){
$res = $cb($obj)
if (is_string($res)) $answer .= $res
}
else $buffer = $chunk
}
return strlen($data)
})
curl_exec($curl)
return $answermethod
%DeepSeek -> request ($uri, $JSON = true, ...$args)
line 62
Verstuurt een HTTP-verzoek naar een DeepSeek API-endpoint met een opgegeven URI en authenticatie. Vervolgens decodeert het de JSON-respons en controleert op fouten; indien aanwezig, wordt een error gegenereerd. Daarna wordt de response teruggegeven.
$res = json_decode(HTTP("https://api.deepseek.com/v1/$uri", ['Authorization: Bearer '.%creds->DeepSeek], $JSON, ...$args))
if (isset($res->error)) error('DeepSeek Request error: '.$res->error->message)
return $resobject
%Gemini
/phlo/libs/AI/Gemini.phlo
method
%Gemini -> config ($modalities)
line 7
Stelt een configuratie samen met parameters voor taaloutput, zoals temperatuur, topK, topP, maximale tokens, en response-modi en -mime-type.
arr(temperature: 1, topK: 40, topP: .95, maxOutputTokens: 8192, response_modalities: $modalities, response_mime_type: 'text/plain')method
%Gemini -> change ($prompt, $base64, $type = 'image/jpeg')
line 9
Verstuurt een POST-verzoek naar de Google API voor generatief beeldmateriaal met het opgegeven prompt en base64-afbeelding, ontvangt de JSON-respons en geeft die als resultaat terug.
$this->respond (
json_decode (
HTTP (
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp-image-generation:generateContent?key='.%creds->Gemini,
JSON: true,
POST: arr (
contents: [
arr(role: 'user', parts: [arr(inlineData: arr(data: $base64, mimeType: $type))]),
arr(role: 'user', parts: [arr(text: $prompt)]),
],
generationConfig: $this->config(['image', 'text']),
),
),
),
)method
%Gemini -> create ($prompt)
line 25
Verstuurt een POST-verzoek naar de API van Google generative language voor het genereren van content op basis van een prompt, en verwerkt de JSON-respons om deze vervolgens te retourneren.
$this->respond (
json_decode (
HTTP (
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp-image-generation:generateContent?key='.%creds->Gemini,
JSON: true,
POST: arr (
contents: [
arr(role: 'user', parts: [arr(text: $prompt)]),
],
generationConfig: $this->config(['image', 'text']),
),
),
),
)method
%Gemini -> info ($prompt, $base64, $type = 'image/jpeg')
line 40
Verstuurt een POST-verzoek naar de Google API voor generatieve beeldcontent, met base64-afbeelding en prompt als input, en decodeert de JSON-respons voordat deze wordt geretourneerd.
$this->respond (
json_decode (
HTTP (
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp-image-generation:generateContent?key='.%creds->Gemini,
JSON: true,
POST: arr (
contents: [
arr(role: 'user', parts: [arr(inlineData: arr(data: $base64, mimeType: $type))]),
arr(role: 'user', parts: [arr(text: $prompt)]),
],
generationConfig: $this->config(['text']),
),
),
),
)method
%Gemini -> respond ($res)
line 56
Controleert of het antwoord eindigt op een 'IMAGE_SAFETY'-beoordeling en geeft een waarschuwing indien dat het geval is. Indien niet, retourneert het eerst gevonden tekstuele inhoud of inline data. Als er geen geldige informatie is, geeft het een foutmelding terug.
if ($res->candidates[0]->finishReason === 'IMAGE_SAFETY') return 'I\'m affraid I can\'t process your image or command, it seems unsafe.'
if ($text = $res->candidates[0]->content->parts[0]->text ?? null) return $text
if ($data = $res->candidates[0]->content->parts[0]->inlineData ?? null) return $data
return 'Some error occured processing your request, please try again later.'object
%OpenAI
/phlo/libs/AI/OpenAI.phlo
const
OpenAI :: model
line 7
Dit is een specificatie voor een modelinstelling die aangeeft dat het gebruik maakt van het GPT-4 mini-model.
'gpt-4o-mini'const
OpenAI :: voices
line 8
Dit is een lijst met string-elementen die verschillende stem- of voice-namen bevat, mogelijk voor gebruik in een tekst-naar-spraakfunctie of stemkeuzemodule.
['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']static
OpenAI :: context (...$args):array
line 10
Voegt berichten toe aan de berichtenlijst op basis van de inputparameters (system, assistant, user), zorgt ervoor dat de berichten correct worden geordend en verwijdert de oorspronkelijke parameters na het toevoegen.
$args['messages'] ??= []
if (isset($args['system']) && array_unshift($args['messages'], arr(role: 'system', content: $args['system']))) unset($args['system'])
if (isset($args['assistant']) && array_push($args['messages'], arr(role: 'assistant', content: $args['assistant']))) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], arr(role: 'user', content: $args['user']))) unset($args['user'])
return $argsstatic
OpenAI :: tool ($tool):array
line 18
Maakt een beschrijvende objectstructuur met naam, beschrijving en parameters, waarbij parameters worden gegenereerd uit een lijst met argumenten en alleen specifieke eigenschappen bevatten, en alle aanvullende eigenschappen worden uitgesloten.
arr (
type: 'function',
function: arr (
name: $tool->name,
description: $tool->desc,
parameters: arr (
type: 'object',
properties: loop($tool->args, fn($data, $arg) => array_filter($data, fn($key) => in_array($key, ['type', 'enum', 'desc']), ARRAY_FILTER_USE_KEY)),
additionalProperties: false,
required: array_keys($tool->args),
),
strict: true,
),
)method
%OpenAI -> chat (...$args):obj
line 33
Verwerkt een chat-aanvraag door model en context te bepalen, verstuurt een verzoek naar de API, en retourneert een object met details over het model, de reden van afronding, tokens en de inhoud of tool-aanroepen, afhankelijk van de response.
$args['model'] ??= static::model
$args = static::context(...$args)
$res = $this->request('chat/completions', POST: $args)
$return = obj(model: $res->model, finish: $res->choices[0]->finish_reason, tokens: $res->usage->total_tokens, tokens_in: $res->usage->prompt_tokens, tokens_out: $res->usage->completion_tokens)
if (isset($res->choices[0]->message->tool_calls)) $return->tools = loop($res->choices[0]->message->tool_calls, fn($tool) => obj(name: $tool->function->name, args: json_decode($tool->function->arguments, true)))
else $return->answer = $res->choices[0]->message->content
return $returnmethod
%OpenAI -> embedding ($input, $model = 'text-embedding-3-small')
line 43
Voert een POST-verzoek uit naar de 'embeddings'-endpoint met de invoer en het model, en retourneert de eerste embedding uit de respons.
$this->request('embeddings', POST: arr(input: $input, model: $model))->data[0]->embeddingmethod
%OpenAI -> stream (...$args):string
line 45
Stelt een streaming-verbinding op met de OpenAI API, verstuurt een chat-aanvraag, en verwerkt de ontvangen data door deze in realtime via een callback door te geven, waarbij tekstuele fragmenten worden opgebouwd en geretourneerd.
%app->streaming = true
$args['model'] ??= static::model
$args = static::context(...$args)
$args['stream'] = true
if (isset($args['cb'])){
$cb = $args['cb']
unset($args['cb'])
}
else {
cli || header('Content-Type: text/event-stream')
$cb = fn($text) => last(print($text), cli || [@ob_flush(), flush()], $text)
}
$answer = void
$buffer = void
$curl = curl_init('https://api.openai.com/v1/chat/completions')
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST')
curl_setopt($curl, CURLOPT_POSTFIELDS, $payload = json_encode($args))
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer '.%creds->OpenAI, 'Content-Type: application/json', 'Content-Length: '.strlen($payload)])
curl_setopt($curl, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($cb, &$buffer, &$answer){
$chunks = trim($buffer.$data)
$buffer = void
foreach (explode(lf.lf, $chunks) AS $chunk){
if ($obj = json_decode(substr($chunk, 6))) in_array($text = $obj->choices[0]->delta->content ?? null, [null, void]) || $answer .= $cb($text, $obj)
else $buffer = $chunk
}
return strlen($data)
})
curl_exec($curl)
return $answermethod
%OpenAI -> transcribe ($file, $model = 'whisper-1', ...$args):obj
line 77
Verwerkt een geluidsbestand door het te converteren naar CURLFile indien nodig, verstuurt het naar de API voor transcriptie met het gekozen model, en retourneert een object met modelnaam, duur, taal en transcriptie tekst.
if (is_string($file)) $file = new CURLFile($file)
elseif (is_a($file, 'file')) $file = $file->curl
$res = $this->request('audio/transcriptions', false, POST: arr(...$args, model: $model, file: $file, response_format: 'verbose_json'))
return obj (
model: $model,
duration: $res->duration,
lang: $res->language,
text: $res->text,
)method
%OpenAI -> vision ($text, $image, $stream = false, ...$args):obj
line 89
Stelt een bericht samen met tekst en optioneel een afbeelding, en kiest op basis van de stream-parameter voor een doorlopende stream of een chat-antwoord.
$args['model'] ??= static::model
$messages = [arr(role: 'user', content: [arr(type: 'text', text: $text), arr(type: 'image_url', image_url: arr(url: $image))])]
if ($stream) return $this->stream(...$args, messages: $messages)
else return $this->chat(...$args, messages: $messages)method
%OpenAI -> request ($uri, $JSON = true, ...$args)
line 96
Verstuurt een HTTP-verzoek naar de OpenAI API met gegeven URI en opties. Decodeert de JSON-response, controleert op fouten, en geeft het resultaat terug.
$res = json_decode(HTTP("https://api.openai.com/v1/$uri", ['Authorization: Bearer '.%creds->OpenAI], $JSON, ...$args))
if (isset($res->error)) error('OpenAI Request error:'.lf.$res->error->message)
return $resCSS
object
%basics
/phlo/libs/CSS/basics.phlo
view
style
line 5
Statisch toegewezen CSS-klasse-selector die verschillende stijlregels voor tekstuitlijning, floats, positionering, marges, paddings, overflow, display, cursor, tekstdecoratie en breedte bevat, bedoeld voor hergebruik en consistente styling.
.left: text-align: left
.center: text-align: center
.right: text-align: right
.float-left: float: left
.float-right: float: right
.absolute: position: absolute
.fixed: position: fixed
.relative: position: relative
.sticky: position: sticky
.padded: padding: 1rem
.margin: margin: 1rem
.margin-auto: margin: auto
.overflow: overflow: auto
.overflow-x: overflow-x: auto
.overflow-y: overflow-y: auto
.hide-x: overflow-x: hidden
.hide-y: overflow-y: hidden
.block: display: block
.hidden: display: none
.pointer: cursor: pointer
.underline: text-decoration: underline
.fit: width: fit-content
.full: width: 100%
.wide: max-width: 1200pxobject
%fixes
/phlo/libs/CSS/fixes.phlo
view
style
line 5
Past de box-sizing toe aan alle elementen en pseudo-elementen, zodat ze inclusief padding en border zijn. Verwijdert de standaard touch-acties voor elements zoals links en buttons. Verhelpt focus-styling door outline te verwijderen voor invoerelementen. Verwijdert de spin-buttons van numerieke invoeren in WebKit- en Gecko-browsers. Zorgt dat tabellen geen dubbele randen krijgen door border-collapse in te schakelen. Verwijdert de standaard uitvouwpijl van select-elementen in IE.
*, ::before, ::after: box-sizing: border-box
a, area, button, input, label, select, summary, textarea, [tabindex]: touch-action: manipulation
button:focus, input:focus, select:focus, textarea:focus, [contenteditable]:focus: outline: 0
input::-webkit-outer-spin-button, input::-webkit-inner-spin-button: -webkit-appearance: none
input[type="number"]: -moz-appearance: textfield
table: border-collapse: collapse
::-ms-expand: display: noneobject
%flex
/phlo/libs/CSS/flex.phlo
view
style
line 5
Stelt flexibele lay-outregels in met verschillende flex-eigenschappen, zoals display, flex-wrap, gap, en flex-direction, inclusief responsieve aanpassingen bij grotere schermen.
.flex: display: flex
.flex.auto > *: flex: auto
.flex.gap: gap: 1rem
.flex.row: flex-direction: column
.flex.wrap: flex-wrap: wrap
.grow: flex-grow: 1
@media (min-width:768px){
.flex.col: flex-direction: column
.flex.col.row: flex-direction: row
}object
%grid
/phlo/libs/CSS/grid.phlo
view
style
line 5
Deze code definieert een grid-lay-out die automatisch het aantal kolommen aanpast op basis van de beschikbare breedte, met minimaal 300px per kolom.
.grid {
display: grid
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr))
}DB
object
%DB
/phlo/libs/DB/DB.phlo
prop
%DB -> PDO
line 6
Geeft een foutmelding als er geen PDO-verbinding is gedefinieerd.
error('No PDO connector defined')prop
%DB -> fieldQuotes
line 7
Geeft een boolean waarde terug die aangeeft of er quotes (aanhalingstekens) rondom de gegevens staan, mogelijk om te controleren of het veld al gequoteerd is.
btmethod
%DB -> load (string $table, string $columns = '*', string $where = void, string $joins = void, string $group = void, string $limit = null, string $order = void, ...$args)
line 9
Constructs an SQL SELECT statement dynamically based on provided parameters and additional arguments, including automatic WHERE clause generation from key-value pairs if not specified.
!$where && $args && $where = loop(array_keys($args), fn($column) => "$table.$column=?", ' AND ')
$joins && $joins = " $joins"
$where && $where = " WHERE $where"
$group && $group = " GROUP BY $group"
$order && $order = " ORDER BY $order"
$limit && $limit = " LIMIT $limit"
$query = "SELECT $columns FROM $table$joins$where$group$order$limit"
return $this->query($query, ...array_values($args))method
%DB -> query ($query, ...$args)
line 20
Voert een databasequery uit met ondersteuning voor prepared statements bij invoerparameters, en bevat debugfunctionaliteit die querytype, tablename en optioneel de WHERE-clausule toont samen met het aantal geretourneerde rijen. Bij een fout wordt een gedetailleerde foutmelding inclusief trace gegenereerd.
try {
if (!$args) $stmt = $this->PDO->query($query)
else {
$stmt = $this->PDO->prepare($query)
$stmt->execute($args)
}
if (debug){
$match = regex('/\b(UPDATE|INSERT INTO|DELETE FROM|FROM)\b\s+([`"\[]?\w+[`"\]]?)/i', strtr($query, [$this->fieldQuotes => void]))
$where = strtr(regex('/\bWHERE (\b.+)/is', $query)[1] ?? void, [' ORDER BY' => void])
$match && debug("Q: $match[1] $match[2]".strtr(rtrim(" $where "), [dq => void])." (".$stmt->rowCount().")")
}
}
catch (PDOException $e){
error('Database error'.(debug ? colon.lf.$query.lf.lf.$e->getMessage().lf.lf.loop(array_slice(explode(lf, $e->getTraceAsString()), 2, -2), fn($line) => last($match = regex('/([^\/]+\.php)\(([0-9]+)\): (.*)/', $line), "$match[3] $match[1]:$match[2]"), lf) : void))
}
return $stmtmethod
%DB -> column (...$args)
line 39
Laadt de gegevens met de opgegeven argumenten en retourneert een lijst van één kolom uit alle rijen.
$this->load(...$args)->fetchAll(PDO::FETCH_COLUMN)method
%DB -> item (...$args)
line 40
Haalt een geladen database-gegevens op en retourneert de eerste kolom van de eerste rij, tenzij deze niet bestaat, dan null.
$this->load(...$args)->fetch(PDO::FETCH_COLUMN) ?: nullmethod
%DB -> pair (...$args)
line 41
Haalt gegevens op en geeft ze terug als een key-value paar array.
$this->load(...$args)->fetchAll(PDO::FETCH_KEY_PAIR)method
%DB -> group (...$args)
line 42
Deze code laadt gegevens met de meegegeven argumenten, haalt alle resultaten op en groepeert ze op basis van een kolom of veld, waarbij elke groep wordt omgezet in objecten van de klasse 'obj'.
$this->load(...$args)->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_CLASS, 'obj')method
%DB -> records (...$args)
line 43
Laadt gegevens met de opgegeven argumenten en haalt alle resultaten op, waarbij elke rij wordt omgezet in een object van het opgegeven klasse, met unieke resultaten op basis van de eerste kolom.
$this->load(...$args)->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_UNIQUE, 'obj')method
%DB -> record (...$args)
line 44
Levert een object op basis van geladen gegevens, of null als er geen resultaat is.
$this->load(...$args)->fetchObject('obj') ?: nullmethod
%DB -> create (string $table, ...$data)
line 46
Voert een insert-query uit waarbij indien de 'ignore'-waarde in de data waar is, de 'IGNORE' optie wordt toegevoegd. Bouwt kolomnamen en bijbehorende waarden correct op, vult de waarden aan met placeholders en vervangt deze door de daadwerkelijke data. Voert de query uit en geeft de laatst ingevoerde ID of een optionele ID terug.
if ($ignore = $data['ignore'] ?? false) unset($data['ignore'])
$columns = $this->fieldQuotes.implode($this->fieldQuotes.comma.$this->fieldQuotes, array_keys($data)).$this->fieldQuotes
$values = implode(comma, array_fill(0, count($data), qm))
$query = "INSERT".($ignore ? ' IGNORE' : void)." INTO $table ($columns) VALUES ($values)"
$this->query($query, ...array_values(loop($data, fn($value) => is_a($value, 'obj') ? $value->id : $value)))
return $this->PDO->lastInsertId() ?: ($data['id'] ?? null)method
%DB -> change (string $table, string $where, ...$data)
line 55
Deze functie bouwt dynamisch een SQL UPDATE statement op door de gegevens uit `$data` te gebruiken. Het telt het aantal placeholders in `$where` om te bepalen welke data voor de WHERE-voorwaarde en welke voor de SET-clausule zijn. Vervolgens worden de kolomnamen en waarden gescheiden en geordend, en wordt de query uitgevoerd met de juiste parameters, waarna het aantal gewijzigde rijen wordt geretourneerd.
$whereCount = substr_count($where, qm)
$updates = isset($data['updates']) ? $data['updates'] : void
unset($data['updates'])
$updates .= (($wheres = array_slice(array_keys($data), $whereCount)) && $updates ? comma : void).loop($wheres, fn($key) => $key.'=?', comma)
$query = "UPDATE $table SET $updates WHERE $where"
$args = array_values([...array_slice($data, $whereCount), ...array_slice($data, 0, $whereCount)])
return $this->query($query, ...$args)->rowCount()method
%DB -> delete (string $table, string $where, ...$args)
line 65
Voert een DELETE-query uit op een opgegeven tabel met een specifieke WHERE-voorwaarde en retourneert het aantal verwijderde rijen.
$this->query("DELETE FROM $table WHERE $where", ...$args)->rowCount()object
%MySQL
/phlo/libs/DB/MySQL.phlo
prop
%MySQL -> PDO
line 8
Creëert een nieuwe PDO-verbinding met de MySQL-database, waarbij de verbindingsgegevens worden gehaald uit een credentials-object.
new PDO('mysql:host='.%creds->mysql->host.';dbname='.%creds->mysql->database, %creds->mysql->user, %creds->mysql->password)object
%PostgreSQL
/phlo/libs/DB/PostgreSQL.phlo
prop
%PostgreSQL -> PDO
line 8
Maakt een nieuwe PDO-verbinding aan met een PostgreSQL-database, met gebruik van geconfigureerde inloggegevens.
new PDO('pgsql:host='.%creds->postgresql->host.';dbname='.%creds->postgresql->database, %creds->postgresql->user, %creds->postgresql->password)prop
%PostgreSQL -> fieldQuotes
line 9
Deze code definieert een functie die waarschijnlijk een tekstuele vervanging of bewerking toepast op de input, mogelijk met betrekking tot veld- of tabelnaamquotes voor PostgreSQL, om SQL-injecties en syntaxisfouten te voorkomen.
dqobject
%Qdrant
/phlo/libs/DB/Qdrant.phlo
method
%Qdrant -> get (string $input)
line 5
Slaat een embedding op in APCu-cache, met een key gebaseerd op de input, en haalt deze op of genereert en cachet deze voor 28 dagen.
apcu('embedding/'.token(input: $input), fn($input) => %OpenAI->embedding($input), 86400 * 28)method
%Qdrant -> collections
line 7
Haalt de namen op uit de collecties en geeft deze terug als een array.
array_column($this->request('collections')->result->collections, 'name')method
%Qdrant -> create ($collection, $size = 1536, $distance = 'Cosine')
line 8
Maakt een nieuwe collectie aan met opgegeven naam, vector grootte en afstandsmeting, en controleert of de aanmaak succesvol was.
$this->request("collections/$collection", PUT: arr(vectors: arr(size: $size, distance: $distance)))->status === 'ok'method
%Qdrant -> upsert ($collection, $id, $input, ...$payload)
line 9
Voert een HTTP PUT-verzoek uit naar de endpoint voor het updaten of toevoegen van een punt in een collectie. Het stuurt een array met puntgegevens, waaronder ID, vector en optioneel(payload). Het resultaat is de operatie-ID van de uitgevoerde operatie.
$this->request("collections/$collection/points", PUT: arr(points: [arr(id: $id, vector: $this->get($input), payload: $payload ?: null)]))->result->operation_idmethod
%Qdrant -> delete ($collection, ...$ids)
line 10
Verstuurt een POST-verzoek naar de API om specifieke punten te verwijderen uit een collectie door hun ID's te specificeren, met de endpoint 'collections/{collection}/points/delete'.
$this->request("collections/$collection/points/delete", POST: arr(points: $ids))->resultmethod
%Qdrant -> search ($collection, $input = null, $top = 100)
line 11
Voert een zoekactie uit in een verzameling door punten te versturen naar de API, met standaard- of meegegeven input. Resultaten worden gefilterd en teruggegeven met specifieke ID's en payload-gegevens, waarbij ontbrekende input wordt ingevuld met nullen in een vector van 1536 elementen.
create($this->request("collections/$collection/points/search", POST: arr(vector: is_null($input) ? array_fill(0, 1536, 0) : $this->get($input), top: $top, with_payload: true))->result, fn($record) => $record->id, fn($record) => last($record = array_merge(get_object_vars($record), get_object_vars($record->payload)), obj(...array_filter($record, fn($key) => $key !== 'payload', ARRAY_FILTER_USE_KEY))))method
%Qdrant -> drop ($collection)
line 12
Verwijdert een collectie met de opgegeven naam door een DELETE-verzoek naar de server te sturen en het resultaat te retourneren.
$this->request("collections/$collection", DELETE: true)->resultmethod
%Qdrant -> request ($uri, ...$data)
line 14
Verstuurt een HTTP-verzoek naar een Qdrant-server met de opgegeven URI en data, inclusief indien nodig een API-sleutel, en decodeert vervolgens de JSON-respons naar een associative array.
json_decode(HTTP(%creds->qdrant->server.$uri, %creds->qdrant->key ? ['api-key: '.%creds->qdrant->key] : [], true, ...$data))object
%SQLite
/phlo/libs/DB/SQLite.phlo
method
%SQLite -> controller
line 8
Maakt een verbinding met een SQLite-database met de bestandsnaam die in `$file` staat.
andle => "SQLite/$file"method
%SQLite -> __construct (private string $file)
line 9
Dit is de constructor van de class, die een padas van het type string als parameter ontvangt en waarschijnlijk gebruikt om het pad of de naam van de SQLite-database op te slaan of te initialiseren.
prop
%SQLite -> PDO
line 11
Maakt een nieuwe PDO-instantie aan met een SQLite-database, waarbij de databasebestandsnaam wordt gebruikt vanuit een eigenschap van het object.
new PDO('sqlite:'.$this->file)DOM
object
%CSS_var
/phlo/libs/DOM/CSS.var.phlo
view
script
line 5
Deze code definieert een property die via een proxy toegang biedt tot CSS-variabelen op het document. Bij lezen wordt de waarde van de CSS-variabele opgehaald, en bij schrijven wordt deze aangepast.
Object.defineProperty(app, 'var', {get(){return new Proxy({}, {get(_, key){return getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim()}, set(_, key, value){ return document.documentElement.style.setProperty(`--${key}`, value)}})}, configurable: true})object
%datatags
/phlo/libs/DOM/datatags.phlo
view
script
line 5
Deze code reageert op klikgebeurtenissen op elementen met specifieke data-attributes en bepaalt op basis daarvan de HTTP-methode en URI. Vervolgens wordt de juiste API-aanroep gedaan via een app-functie, met optioneel meegegeven data.
on('click', '[data-get], [data-post], [data-put], [data-patch], [data-delete]', (el, e) => {
if (el.dataset.confirm) return
e.preventDefault()
let method, uri, data = null
if ((uri = el.dataset.get) !== undefined) method = 'get'
else if ((uri = el.dataset.delete) !== undefined) method = 'delete'
else [data = {}, Object.keys(el.dataset).forEach((key) => key === 'post' || key === 'put' || key === 'patch' ? [method = key, uri = el.dataset[key]] : data[key] = el.dataset[key])]
app[method](uri, data)
})object
%dialog
/phlo/libs/DOM/dialog.phlo
view
script
line 5
Deze code definieert een set van dialoogfuncties (alert, confirm, prompt) die gebruik maken van een dynamisch aangemaakte HTML-dialog. De functie 'phlo.dialog' maakt een dialoogvenster op basis van het type en toont deze, wacht op de gebruiker, en geeft vervolgens de juiste waarde terug. Daarnaast wordt een click-eventlistener aangemaakt die, bij het aantreffen van een 'data-confirm'-attribuut, een bevestigingsdialoog toont en op basis van de reactie verdere acties onderneemt.
app.alert = (msg) => phlo.dialog('alert', msg)
app.confirm = (msg) => phlo.dialog('confirm', msg)
app.prompt = (msg, def) => phlo.dialog('prompt', msg, def)
phlo.dialog = async (type, message, defaultValue = '') => new Promise((resolve) => {
app.mod.append('body', ['<dialog>', '<form method="dialog">', `<p>${message}</p>`, type === 'prompt' ? `<input name="value" value="${defaultValue}">` : '', '<menu>', '<button value="1" autofocus>OK</button>', type !== 'alert' ? ' <button value="0">Cancel</button>' : '', '</menu>', '</form>', '</dialog>'].join(''))
const dialog = obj('dialog')
dialog.showModal()
dialog.addEventListener('close', () => {
const value = dialog.returnValue
const input = dialog.querySelector('input')
dialog.remove()
if (type === 'alert') return resolve()
if (type === 'confirm') return resolve(value === '1')
if (type === 'prompt') return resolve(value === '1' ? input.value : null)
})
})
on('click', '[data-confirm]', async (el, e) => {
e.preventDefault()
if (!await app.confirm(el.dataset.confirm)) return
delete el.dataset.confirm
app.update()
el.click()
})object
%exists
/phlo/libs/DOM/exists.phlo
view
script
line 5
Deze code definieert een functionaliteit om te detecteren of elementen bestaan: het houdt een lijst bij van elementen en hun bijbehorende callbacks, en voert deze callbacks uit wanneer de elementen nog niet eerder zijn geregistreerd als bestaand, waarbij het gebruikmaakt van een WeakMap om reeds geregistreerde elementen bij te houden.
phlo.exist = []
phlo.existing = new WeakMap
const onExist = (els, cb) => phlo.exist.push({els, cb})
app.updates.push(() => {
const existing = []
phlo.exist.forEach(item => objects(item.els).forEach(el => phlo.existing.has(el) || existing.push({el, cb: item.cb})))
existing.forEach(item => [phlo.existing.has(item.el) || phlo.existing.set(item.el, 'exist'), item.cb(item.el)])
})object
%form
/phlo/libs/DOM/form.phlo
view
script
line 5
Deze code bevat een functie die formuliergegevens verzendt via een AJAX-aanroep op basis van de methodenaam, en een event listener die bij invoer of verandering de formulierwaarden synchroniseert met attributen en de interne staat opslaat. Daarnaast verzorgt het een aangepaste submit-handler voor formulieren met de klasse 'async' om het standaard gedrag te voorkomen en de gegevens via de functie te verzenden.
const submitForm = (form) => {
const method = (form.attributes.method?.value ?? 'GET').toLowerCase()
app[method](new URL(form.action).pathname.substr(1), new FormData(form))
}
on('input change', 'input, select, textarea', input => {
if (input.tagName === 'SELECT') input.querySelectorAll('option').forEach((option, index) => option.selected ? option.setAttribute('selected', '') : option.removeAttribute('selected'))
if (input.type === 'checkbox') input.checked ? input.setAttribute('checked', '') : input.removeAttribute('checked')
if (input.type === 'text' && input.value !== input.getAttribute('value')) input.setAttribute('value', input.value)
if (input.type === 'textarea' && input.value !== input.innerHTML) input.innerHTML = input.value
phlo.state.save()
return false
})
on('submit', 'form.async', (form, e) => {
e.preventDefault()
submitForm(form)
})object
%image_resizer
/phlo/libs/DOM/image.resizer.phlo
view
script
line 5
Laadt een afbeelding, past de afmetingen aan op basis van maximale breedte en hoogte met behoud van aspect ratio, en genereert een geredimensioneerde datalink via een callback.
const imageResizer = (file, maxWidth, maxHeight, cb, quality = .8) => {
const img = new Image
img.onload = () => {
let width = img.width, height = img.height
const aspectRatio = width / height
if (width > maxWidth || height > maxHeight){
if (width > height){
width = maxWidth
height = Math.round(maxWidth / aspectRatio)
}
else {
height = maxHeight
width = Math.round(maxHeight * aspectRatio)
}
}
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(img, 0, 0, width, height)
cb(canvas.toDataURL(file.type, quality))
}
img.src = URL.createObjectURL(file)
}object
%link
/phlo/libs/DOM/link.phlo
view
script
line 5
Verkeert in de situatie dat bij het klikken op een link met de klasse 'async', afhankelijk van toetsaanslagen of bevestigingsstatus, de standaard navigatie wordt geblokkeerd en een asynchrone fetchactie wordt uitgevoerd.
on('click', 'a.async', (a, e) => e.ctrlKey || e.shiftKey || e.metaKey || a.dataset.confirm || [e.preventDefault(), app.get(a.attributes.href.value.substr(1))])object
%markdown
/phlo/libs/DOM/markdown.phlo
view
script
line 5
Parsetransformatie voor Markdown-tekst naar HTML met ondersteuning voor GFM-functionaliteit. Markeringen worden herkend en geconverteerd, zoals koppen, lijsten, tabellen, codeblokken, blokquotes en inline-elementen. Bij hyperlinks, afbeeldingen en tekstdecoraties wordt sanitatie toegepast en HTML wordt veilig ingebed. Ook worden referenties en ID-genereerlogic gebruikt voor koppen en tabellen.
function parse_markdown(md, opts = {}){
const o = {
gfm: opts.gfm !== false,
breaks: !!opts.breaks,
headerIds: opts.headerIds !== false,
headerPrefix: opts.headerPrefix || '',
smartypants: !!opts.smartypants
}
const unnull = x => (x == null ? '' : String(x))
let src = unnull(md).replace(/\r\n?/g, "\n")
const escHtml = s => s.replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c]))
const trimEndNL = s => s.replace(/\s+$/,'')
const isBlank = s => /^\s*$/.test(s)
const slugmap = new Map()
const slug = (t) => {
let s = t.toLowerCase().replace(/<\/?[^>]+>/g, '').replace(/[^\p{L}\p{N}\- _]+/gu, '').trim().replace(/[\s_]+/g, '-')
const base = o.headerPrefix + s
let k = base, i = 1
while (slugmap.has(k)) k = `${base}-${++i}`
slugmap.set(k, true)
return k
}
const smart = s => {
if (!o.smartypants) return s
return s.replace(/---/g, "—").replace(/--/g, "–").replace(/(^|[\s"(\[])(?=')/g, "$1‘").replace(/'/g, "’").replace(/(^|[\s(\[])(?=")/g, "$1“").replace(/"/g, "”").replace(/\.{3}/g, "…")
}
const refs = Object.create(null)
src = src.replace(
/^ {0,3}\[([^\]]+)\]:\s*<?([^\s>]+)>?(?:\s+(?:"([^"]*)"|'([^']*)'|\(([^)]+)\)))?\s*$/gm,
(_, label, url, t1, t2, t3) => {
const key = label.trim().replace(/\s+/g, ' ').toLowerCase()
if (!refs[key]) refs[key] = { href: url, title: t1 || t2 || t3 || '' }
return ''
}
)
const tokens = []
const lines = src.split("\n")
function takeWhile(start, pred){
let end = start
while (end < lines.length && pred(lines[end], end)) end++
return { start, end }
}
function pushParagraph(buf){
const text = buf.join("\n").trimEnd()
if (text) tokens.push({ type: "paragraph", text })
buf.length = 0
}
function parseBlock(start = 0, end = lines.length){
const para = []
let l = start
while (l < end){
const line = lines[l]
if (isBlank(line)){ pushParagraph(para); l++; continue; }
let m = line.match(/^ {0,3}(`{3,}|~{3,})([^\n]*)$/)
if (m){
pushParagraph(para)
const fenceLen = m[1].length
const info = (m[2] || '').trim()
let body = []
l++
while (l < end){
const s = lines[l]
const close = s.match(new RegExp(`^ {0,3}${m[1][0]}{${fenceLen},}\\s*$`))
if (close){ l++; break; }
body.push(s)
l++
}
tokens.push({ type: "code", lang: info.split(/\s+/)[0] || '', text: trimEndNL(body.join("\n")) })
continue
}
if (/^(?: {4}|\t)/.test(line)){
pushParagraph(para)
const { end: j } = takeWhile(l, s => /^(?: {4}|\t)/.test(s) || isBlank(s))
const block = lines.slice(l, j).map(s => s.replace(/^(?: {4}|\t)/, '')).join("\n")
tokens.push({ type: "code", lang: '', text: trimEndNL(block) })
l = j; continue
}
if (/^ {0,3}<(?:!--|\/?(?:html|head|body|pre|script|style|table|thead|tbody|tfoot|tr|td|th|div|p|h[1-6]|blockquote|ul|ol|li|section|article|aside|details|summary|figure|figcaption)\b)/i.test(line)){
pushParagraph(para)
const { end: j } = takeWhile(l, (s, idx) => !(idx > l && isBlank(lines[idx-1]) && isBlank(s)))
const html = lines.slice(l, j).join("\n")
tokens.push({ type: "html", text: html })
l = j; continue
}
if (/^ {0,3}(?:-+\s*|-{3,}|_{3,}|\*{3,})\s*$/.test(line)){
pushParagraph(para)
tokens.push({ type: "hr" })
l++; continue
}
m = line.match(/^ {0,3}(#{1,6})[ \t]*([^#\n]*?)[ \t#]*$/)
if (m){
pushParagraph(para)
tokens.push({ type: "heading", depth: m[1].length, text: m[2].trim() })
l++; continue
}
if (l + 1 < end && /^[^\s].*$/.test(line) && /^ {0,3}(=+|-+)\s*$/.test(lines[l + 1])){
pushParagraph(para)
const depth = lines[l + 1].trim().startsWith("=") ? 1 : 2
tokens.push({ type: "heading", depth, text: line.trim() })
l += 2; continue
}
if (/^ {0,3}>\s?/.test(line)){
pushParagraph(para)
const { end: j } = takeWhile(l, s => /^ {0,3}>\s?/.test(s) || isBlank(s))
const inner = lines.slice(l, j).map(s => s.replace(/^ {0,3}>\s?/, '')).join("\n")
const sub = parse_markdown(inner, { ...o })
tokens.push({ type: "blockquote", html: sub })
l = j; continue
}
m = line.match(/^ {0,3}((?:[*+-])|\d{1,9}[.)])\s+/)
if (m){
pushParagraph(para)
const bulletRe = /^ {0,3}((?:[*+-])|\d{1,9}[.)])\s+/
const { end: j } = takeWhile(l, (s, idx) =>
bulletRe.test(s) ||
(/^(?: {4}|\t)/.test(s)) ||
(!isBlank(s) && idx > l && !/^(?: {0,3}(?:[*+-]|\d{1,9}[.)])\s+)/.test(s))
)
const block = lines.slice(l, j)
const ordered = /^\d/.test(m[1])
const items = []
let cur = []
for (let k = 0; k < block.length; k++){
const ln = block[k]
const head = ln.match(bulletRe)
if (head){
if (cur.length) items.push(cur), cur = []
cur.push(ln.replace(bulletRe, ''))
} else {
cur.push(ln.replace(/^(?: {4}|\t)/, ''))
}
}
if (cur.length) items.push(cur)
const parsedItems = items.map(linesArr => {
let raw = linesArr.join("\n").replace(/\n\s+$/,'')
let checked = null
if (o.gfm){
const t = raw.match(/^\[([ xX])\][ \t]+/)
if (t){ checked = t[1].toLowerCase() === 'x'; raw = raw.replace(/^\[[ xX]\][ \t]+/, ''); }
}
const html = parse_markdown(raw, o)
return { html, checked }
})
tokens.push({ type: "list", ordered, items: parsedItems })
l = j; continue
}
if (o.gfm){
const hdr = line
const alignLn = lines[l + 1] || ''
if (/\|/.test(hdr) && /^ {0,3}\|? *:?-+:? *(?:\| *:?-+:? *)*\|? *$/.test(alignLn)){
pushParagraph(para)
const aligns = alignLn
.trim().replace(/^(\|)|(\|)$/g,'')
.split("|").map(s => s.trim()).map(s => s.startsWith(":-") && s.endsWith("-:") ? "center" : s.endsWith("-:") ? "right" : s.startsWith(":-") ? "left" : null)
const headerCells = hdr.trim().replace(/^(\|)|(\|)$/g,'').split("|").map(s => s.trim())
l += 2
const rows = []
while (l < end && /\|/.test(lines[l]) && !isBlank(lines[l])){
rows.push(lines[l].trim().replace(/^(\|)|(\|)$/g,'').split("|").map(s => s.trim()))
l++
}
tokens.push({ type: "table", header: headerCells, aligns, rows })
continue
}
}
para.push(line)
const next = lines[l + 1] || ''
const endPara =
isBlank(next) ||
/^ {0,3}(?:`{3,}|~{3,})/.test(next) ||
/^(?: {4}|\t)/.test(next) ||
/^ {0,3}((?:[*+-])|\d{1,9}[.)])\s+/.test(next) ||
/^ {0,3}(#{1,6})/.test(next) ||
/^ {0,3}>\s?/.test(next) ||
/^ {0,3}(?:-+\s*|-{3,}|_{3,}|\*{3,})\s*$/.test(next) ||
(o.gfm && /\|/.test(next) && /^ {0,3}\|? *:?-+:? *(?:\| *:?-+:? *)*\|? *$/.test(lines[l + 2] || ''))
if (endPara) pushParagraph(para)
l++
}
pushParagraph(para)
}
parseBlock(0, lines.length)
function renderInline(s){
if (!s) return ''
s = s.replace(/(`+)([^`]|[^`][\s\S]*?[^`])\1/g, (_, ticks, code) => `<code>${escHtml(code)}</code>`)
s = s.replace(/!\[([^\]]*)\]\(\s*<?([^\s)<>]+)>?\s*(?:(?:"([^"]*)"|'([^']*)'|\(([^)]+)\)))?\s*\)/g,
(_, alt, url, t1, t2, t3) => `<img src="${escHtml(url)}" alt="${escHtml(alt)}"${t1||t2||t3?` title="${escHtml(t1||t2||t3)}"`:''}>`)
s = s.replace(/!\[([^\]]*)\]\[([^\]]*)\]/g, (_, alt, id) => {
const ref = refs[(id || alt).trim().replace(/\s+/g,' ').toLowerCase()]
return ref ? `<img src="${escHtml(ref.href)}" alt="${escHtml(alt)}"${ref.title?` title="${escHtml(ref.title)}"`:''}>` : _
})
s = s.replace(/\[([^\]]+)\]\(\s*<?([^\s)<>]+)>?\s*(?:(?:"([^"]*)"|'([^']*)'|\(([^)]+)\)))?\s*\)/g,
(_, text, url, t1, t2, t3) => `<a href="${escHtml(url)}"${t1||t2||t3?` title="${escHtml(t1||t2||t3)}"`:''}>${text}</a>`)
s = s.replace(/\[([^\]]+)\]\s*\[([^\]]*)\]/g, (_, text, id) => {
const key = (id || text).trim().replace(/\s+/g,' ').toLowerCase()
const ref = refs[key]
return ref ? `<a href="${escHtml(ref.href)}"${ref.title?` title="${escHtml(ref.title)}"`:''}>${text}</a>` : _
})
s = s.replace(/<([a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^ <>"']+)>/g, (_, url) => `<a href="${escHtml(url)}">${escHtml(url)}</a>`)
s = s.replace(/<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})>/g, (_, mail) => `<a href="mailto:${escHtml(mail)}">${escHtml(mail)}</a>`)
if (o.gfm){
s = s.replace(/(?:(?<=\s)|^)(https?:\/\/[^\s<]+)(?=\s|$)/g, '<a href="$1">$1</a>')
s = s.replace(/(?:(?<=\s)|^)(www\.[^\s<]+)(?=\s|$)/g, '<a href="http://$1">$1</a>')
}
s = s.replace(/\*\*([\s\S]+?)\*\*/g, '<strong>$1</strong>').replace(/__([\s\S]+?)__/g, '<strong>$1</strong>')
s = s.replace(/\*([^*\n]+?)\*/g, '<em>$1</em>').replace(/_([^_\n]+?)_/g, '<em>$1</em>')
if (o.gfm) s = s.replace(/~~([\s\S]+?)~~/g, '<del>$1</del>')
s = s.replace(/ {2,}\n/g, "<br>\n")
if (o.breaks) s = s.replace(/\n/g, "<br>\n")
s = s.replace(/&(?!#?\w+;)/g, "&").replace(/<(?!\/?[A-Za-z][^>]*>)/g, "<")
return smart(s)
}
let out = ''
for (const t of tokens){
switch (t.type){
case "paragraph":
out += `<p>${renderInline(t.text)}</p>\n`
break
case "heading": {
const text = renderInline(t.text)
const id = o.headerIds ? slug(text.replace(/<[^>]+>/g, '')) : null
out += id ? `<h${t.depth} id="${id}">${text}</h${t.depth}>\n` : `<h${t.depth}>${text}</h${t.depth}>\n`
break
}
case "code": {
const cls = t.lang ? ` class="language-${escHtml(t.lang)}"` : ''
out += `<pre><code${cls}>${escHtml(t.text)}</code></pre>\n`
break
}
case "blockquote":
out += `<blockquote>\n${t.html.trim()}\n</blockquote>\n`
break
case "list": {
const tag = t.ordered ? "ol" : "ul"
out += `<${tag}>\n`
for (const it of t.items){
const task = it.checked === null ? '' : `<input ${it.checked ? 'checked="" ' : ''}disabled="" type="checkbox"> `
const body = it.html.trim().replace(/^<p>/, task + "<p>")
out += `<li>${body}</li>\n`
}
out += `</${tag}>\n`
break
}
case "table": {
const ths = t.header.map((h, i) => {
const a = t.aligns[i]
return a ? `<th align="${a}">${renderInline(h)}</th>` : `<th>${renderInline(h)}</th>`
}).join("\n")
let body = ''
for (const row of t.rows){
const tds = row.map((cell, i) => {
const a = t.aligns[i]
return a ? `<td align="${a}">${renderInline(cell)}</td>` : `<td>${renderInline(cell)}</td>`
}).join("\n")
body += `<tr>\n${tds}\n</tr>\n`
}
out += `<table>\n<thead>\n<tr>\n${ths}\n</tr>\n</thead>\n` + (body ? `<tbody>\n${body}</tbody>\n` : '') + `</table>\n`
break
}
case "hr":
out += "<hr>\n"
break
case "html":
out += t.text + "\n"
break
}
}
return out.trim()
}object
%shorthands
/phlo/libs/DOM/shorthands.phlo
view
script
line 5
Deze functies zijn korte hulpfuncties die eventlisteners toevoegen voor respectievelijk 'change', 'click' en 'input' gebeurtenissen, met minder argumenten nodig.
function onChange(els, cb){ on('change', els, cb) }
function onClick(els, cb){ on('click', els, cb) }
function onInput(els, cb){ on('input', els, cb) }object
%store
/phlo/libs/DOM/store.phlo
view
script
line 5
Deze code implementeert een reactief gegevensopslag- en koppelsysteem met observatie en automatische updates. Het bevat methoden voor het ophalen en instellen van gegevens via paden, het beheren van signalen, het afhandelen van afhankelijkheden tussen berekeningen, en het bijwerken van DOM-elementen op basis van de store. Daarnaast maakt het gebruik van proxies voor dynamische toegang en wijziging van gegevens, en zorgt het voor synchronisatie tussen DOM en store via data-attributes en eventhandlers. De structuur ondersteunt het automatisch evalueren van berekeningen en het efficient herberekenen bij wijzigingen.
phlo.store = {
signals: {},
listeners: {},
calcs: {},
calcDeps: {},
calcVals: {},
calcTick: false,
split: (path) => path.replace(/\]/g, '').split(/\.|\[/),
get(path){
if (!path) return undefined
let ctx = phlo.store.signals
const keys = phlo.store.split(path)
for (let i = 0; i < keys.length; i++){
if (ctx == null) return undefined
ctx = ctx[keys[i]]
}
return ctx
},
setPath(path, value){
let keys = phlo.store.split(path)
let ctx = phlo.store.signals
while (keys.length > 1){
const k = keys.shift()
ctx[k] ??= isNaN(keys[0]) ? {} : []
ctx = ctx[k]
}
const k = keys[0]
const old = ctx[k]
if (old === value) return false
ctx[k] = value
return true
},
set(path, value){
if (!phlo.store.setPath(path, value)) return
phlo.store.notify(path, phlo.store.get(path))
phlo.store.recalc(path)
phlo.store.schedule()
},
on(path, cb){ (phlo.store.listeners[path] ??= new Set).add(cb) },
off(path, cb){ phlo.store.listeners[path] && phlo.store.listeners[path].delete(cb) },
reset(){
phlo.store.signals = {}
phlo.store.listeners = {}
phlo.store.calcs = {}
phlo.store.calcDeps = {}
phlo.store.calcVals = {}
phlo.store.calcTick = false
},
signal(path, initial){
if (phlo.store.get(path) === undefined) phlo.store.set(path, initial)
return { subscribe: (cb) => phlo.store.on(path, cb), unsubscribe: (cb) => phlo.store.off(path, cb) }
},
notify(path, val){
const set = phlo.store.listeners[path]
if (set) set.forEach(cb => cb(val))
},
match(dep, changed){
if (!dep) return false
if (dep === changed) return true
return changed.startsWith(dep + '.') || changed.startsWith(dep + '[') || dep.startsWith(changed + '.') || dep.startsWith(changed + '[')
},
depsReady(list){
const arr = Array.isArray(list) ? list : (list ? [list] : [])
return arr.every(d => phlo.store.get(d) !== undefined)
},
evalCalc(name){
const fn = phlo.store.calcs[name]
if (!fn) return
let deps = []
let val
try {
const out = fn()
if (Array.isArray(out) && out.length === 2) deps = out[0], val = out[1]
else val = out
}
catch(e){
deps = []
val = undefined
}
const list = Array.isArray(deps) ? deps : (deps ? [deps] : [])
phlo.store.calcDeps[name] = list
if (!phlo.store.depsReady(list)) return
const old = phlo.store.calcVals[name]
if (old !== val){
phlo.store.calcVals[name] = val
const p = `calc.${name}`
phlo.store.setPath(p, val)
phlo.store.notify(p, val)
}
},
recalc(changed){
const names = Object.keys(phlo.store.calcs)
for (let i = 0; i < names.length; i++){
const name = names[i]
const deps = phlo.store.calcDeps[name] || []
for (let j = 0; j < deps.length; j++){
if (phlo.store.match(deps[j], changed)){
phlo.store.evalCalc(name)
break
}
}
}
},
recalcAll(){
const names = Object.keys(phlo.store.calcs)
for (let i = 0; i < names.length; i++) phlo.store.evalCalc(names[i])
},
schedule(){
if (phlo.store.calcTick) return
phlo.store.calcTick = true
setTimeout(() => {
phlo.store.calcTick = false
phlo.store.recalcAll()
})
},
proxy(base){
return new Proxy({}, {
get(t, k){
if (typeof k === 'symbol') return undefined
const seg = /^\d+$/.test(k) ? `[${k}]` : (base ? `.${k}` : String(k))
const path = base + seg
const v = phlo.store.get(path)
if (v !== undefined && (typeof v !== 'object' || v === null)) return v
return phlo.store.proxy(path)
},
set(t, k, v){
const seg = /^\d+$/.test(k) ? `[${k}]` : (base ? `.${k}` : String(k))
phlo.store.set(base + seg, v)
return true
},
has(t, k){ return phlo.store.get(base + (base ? '.' : '') + String(k)) !== undefined },
ownKeys(){ return Object.keys(phlo.store.get(base) || {}) },
getOwnPropertyDescriptor(){ return { enumerable: true, configurable: true } }
})
}
}
app.store = phlo.store.proxy('')
app.mod.store = (key, value) => {
const cur = phlo.store.get(key)
if (JSON.stringify(cur) === JSON.stringify(value)) return
const walk = (base, obj) => {
if (typeof obj !== 'object' || obj === null) return phlo.store.set(base, obj)
Object.entries(obj).forEach(([k, v]) => walk(isNaN(k) ? `${base}.${k}` : `${base}[${k}]`, v))
}
walk(key, value)
}
phlo.calc = new Proxy({}, {
set(t, k, fn){
if (typeof fn !== 'function') return false
phlo.store.calcs[k] = fn
phlo.store.evalCalc(k)
setTimeout(() => phlo.store.evalCalc(k))
return true
},
get(t, k){ return phlo.store.calcs[k] },
has(t, k){ return k in phlo.store.calcs },
deleteProperty(t, k){
delete phlo.store.calcs[k]
delete phlo.store.calcDeps[k]
delete phlo.store.calcVals[k]
return true
}
})
app.calc = new Proxy({}, {
get(t, k){ return phlo.store.calcVals[k] },
has(t, k){ return k in phlo.store.calcVals },
ownKeys(){ return Object.keys(phlo.store.calcVals) },
getOwnPropertyDescriptor(){ return { enumerable: true, configurable: true } }
})
onExist('[data-bind]', (el) => {
const key = el.dataset.bind
const isCalc = key.startsWith('calc.')
const isInput = el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA'
const fromDom = isInput ? el.value : el.textContent
const fromStore = phlo.store.get(key)
const domNonEmpty = (fromDom ?? '').trim() !== ''
const storeEmpty = fromStore === undefined || (typeof fromStore === 'string' && fromStore.trim() === '')
const domLeads = !isCalc && domNonEmpty && storeEmpty
const S = (v) => v == null ? '' : (typeof v === 'object' ? '' : String(v))
const apply = (v) => {
const s = S(v)
if (isInput) el.value = s
else el.textContent = s
}
phlo.store.on(key, apply)
if (domLeads) phlo.store.set(key, fromDom)
const initial = domLeads ? fromDom : fromStore
apply(initial)
if (!isCalc && isInput) el.oninput = (e) => phlo.store.set(key, e.target.value)
})
onExist('[data-bind-attr]', (el) => {
const spec = el.getAttribute('data-bind-attr')
if (!spec) return
const BOOL = new Set(['disabled','checked','hidden','required','readonly','selected','autofocus','multiple'])
let meta = phlo.existing.get(el)
if (!meta || typeof meta !== 'object'){
meta = { exist: true }
phlo.existing.set(el, meta)
}
meta.attr || (meta.attr = {})
meta.attr.cls || (meta.attr.cls = [])
spec.split(/\s*,\s*/).filter(Boolean).forEach(pair => {
const m = pair.match(/^\s*([^:]+)\s*:\s*(.+)\s*$/)
if (!m) return
const name = m[1]
const path = m[2]
const isCalc = path.startsWith('calc.')
const isInputVal = name === 'value' && (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA')
const domVal =
name === 'text' ? el.textContent :
name === 'html' ? el.innerHTML :
name === 'value' ? el.value :
(name === 'class' ? null : el.getAttribute(name))
const fromStore = phlo.store.get(path)
const domNonEmpty = (domVal ?? '').trim() !== ''
const storeEmpty = fromStore === undefined || (typeof fromStore === 'string' && fromStore.trim() === '')
const domLeads = !isCalc && name !== 'class' && domNonEmpty && storeEmpty
const S = (v) => v == null ? '' : (typeof v === 'object' ? '' : String(v))
const apply = (v) => {
if (name === 'text') el.textContent = S(v)
else if (name === 'html') app.mod.inner(el, S(v))
else if (name === 'value') app.mod.value(el, S(v))
else if (name === 'class'){
const next = Array.isArray(v) ? v : (v && typeof v === 'object') ? Object.keys(v).filter(k => v[k]) : String(v ?? '').split(/\s+/)
const uniq = [...new Set(next.filter(Boolean))]
const prev = meta.attr.cls
for (let i = 0; i < prev.length; i++) el.classList.remove(prev[i])
for (let i = 0; i < uniq.length; i++) el.classList.add(uniq[i])
meta.attr.cls = uniq
}
else if (BOOL.has(name)){
const on = !!v
app.mod.attr(el, { [name]: on ? '' : null })
if (name in el) el[name] = on
}
else app.mod.attr(el, { [name]: S(v) })
}
phlo.store.on(path, apply)
if (domLeads) phlo.store.set(path, domVal)
const initial = domLeads ? domVal : fromStore
apply(initial)
if (!isCalc && isInputVal) el.oninput = (e) => phlo.store.set(path, e.target.value)
})
})object
%template
/phlo/libs/DOM/template.phlo
view
script
line 6
Deze functie voert voor elke rij in 'rows' een templatefunctie uit, waarbij de waarden van de rij als argumenten worden doorgegeven.
app.mod.template = (template, rows) => rows.forEach(row => templates[template](...Object.values(row))),
const templates = {}object
%timestamps
/phlo/libs/DOM/timestamps.phlo
view
script
line 6
Deze code definieert een functie die de leeftijd van elementen met een bepaalde datalabel (dataset.ts) in seconden berekent en deze omzet naar een leesbare tijdsweergave, gebaseerd op een set tijdsintervallen. De functie vernieuwt dit elke seconde en bij het opstarten, waardoor live updates van tijdsago wordt getoond.
app.tsBase = {seconds: 60, minutes: 60, hours: 24, days: 7, weeks: 4, months: 13, years: 1}
const tsUpdate = () => (ranges = app.tsLabels && (tsValues = Object.values(app.tsBase)) ? Object.fromEntries(app.tsLabels.map((k, i) => [k, tsValues[i]])) : app.tsBase) && objects('[data-ts]').forEach(el => {
let age = Math.round(Date.now() / 1000) - Number(el.dataset.ts), text = ''
const future = age < 0
if (future) age = -age
for (const [range, multiplier] of Object.entries(ranges)){
if (text) continue
if (age / multiplier < 1.6583) text = `${Math.round(age)} ${range}`
age /= multiplier
}
text ||= `${Math.round(age)} ${Object.keys(ranges).at(-1)}`
text = `${future ? '-' : ''}${text}`
el.innerText === text || (el.innerText = text)
})
setInterval(() => app.state === 'active' && tsUpdate(), 1000)
setTimeout(tsUpdate, 1)object
%visible
/phlo/libs/DOM/visible.phlo
view
script
line 5
Deze code definieert een observatiesysteem dat reageert op zichtbaarheid van elementen. Het registreert observaties, controleert periodiek via een update callback, en gebruikt een IntersectionObserver om te detecteren wanneer elementen zichtbaar worden. Bij observatie wordt de juiste callback (in- of uit zicht) uitgevoerd.
phlo.observe = []
phlo.observing = new WeakMap
const onVisible = (els, cbIn, cbOut) => onVisibleIn(els, null, cbIn, cbOut)
const onVisibleIn = (els, root, cbIn, cbOut) => phlo.observe.push({els, root, cbIn, cbOut})
app.updates.push(() => {
const observers = []
phlo.observe.forEach(item => objects(item.els).forEach(el => phlo.observing.has(el) || observers.push({el, root: item.root, cbIn: item.cbIn, cbOut: item.cbOut})))
observers.forEach(item => [phlo.observing.has(item.el) || phlo.observing.set(item.el, 'observe'), (observer = new IntersectionObserver(entries => entries.forEach(entry => entry.isIntersecting ? !item.cbIn && item.cbOut ? [observer.unobserve(entry.target), item.cbOut(entry.target)] : item.cbIn(entry.target) : item.cbIn && item.cbOut && item.cbOut(entry.target)), {root: obj(item.root), threshold: .1})).observe(item.el)])
})Fields
object
%field_bool
/phlo/libs/Fields/bool.phlo
prop
%field_bool -> true
line 7
Deze node geeft altijd de waarde '✅' terug, ongeacht de invoer of context.
'✅'prop
%field_bool -> false
line 8
Geeft een foutmelding of negatieve status weer.
'❌'method
%field_bool -> label ($record, $CMS)
line 10
Toont een waarde gebaseerd op of het veld `$record->{$this->name}` waar (waarheid) is, door de 'true'-waarde te retourneren, anders de 'false'-waarde.
$record->{$this->name} ? $this->true : $this->falsemethod
%field_bool -> input ($record, $CMS)
line 11
Maakt een checkbox input met een label en een gestyleerde slider. De checkbox is gecontroleerd als de waarde van de record niet waar is.
tag('label', inner: input(type: 'checkbox', name: $this->name, value: 1, checked: $record->{$this->name} ? false : null).tag('span', class: 'slider', inner: void))method
%field_bool -> parse ($record)
line 12
Deze code converteert de waarde van een veld in `$payload` naar een boolean en wijst deze toe aan het corresponderende veld in `$record`.
$record->{$this->name} = (bool)%payload->{$this->name};method
%field_bool -> sql
line 14
Geeft een SQL-uitspraken terug die een kolom definieert met de naam uit de property en een datatype van tinyint(1) unsigned, passend voor booleaanse waarden.
"`$this->name` tinyint(1) unsigned"method
%field_bool -> nullable
line 15
Deze node retourneert altijd de waarde false, ongeacht de invoer of context.
falseobject
%field_child
/phlo/libs/Fields/child.phlo
prop
%field_child -> list
line 7
Het telt het aantal elementen in een lijst of collectie.
'count'prop
%field_child -> change
line 8
Deze code geeft altijd onwaar terug, ongeacht de invoer of situatie.
falseprop
%field_child -> create
line 9
Deze code geeft altijd een negatieve uitkomst terug, waardoor een bepaalde actie of creatie niet wordt uitgevoerd.
falseprop
%field_child -> record
line 10
Dit is een property of methode die een lijst retourneert, waarschijnlijk bestaande uit child-velden of gerelateerde records.
'list'method
%field_child -> count ($record, $CMS)
line 12
Creëert een HTML-link die verwijst naar een specifiek record, met dynamische URL gebaseerd op record- en objectgegevens, en toont het aantal gerelateerde items naast een titel.
tag('a', href: slash.$CMS->uriRecord.slash.$record->id.slash.$this->name, class: 'async', inner: $record->getCount($this->name).space.$this->title)method
%field_child -> last ($record)
line 13
Woont de laatste waarde op van een specifiek veld binnen een record, door de laatste waarde op te halen dat overeenkomt met de naam van het veld.
$record->getLast($this->name)method
%field_child -> label ($record, $CMS)
line 14
Deze code genereert een samengevoegde lijst van links voor elk element in een collectie, gescheiden door geen separator, en toont een streepje als de lijst leeg of onwaarachtig is.
implode(loop($record->{$this->name}, fn($child) => $this->link($child, $CMS))) ?: dashmethod
%field_child -> input ($record, $CMS)
line 15
Geeft het label van het record terug.
$this->label($record)method
%field_child -> link ($record, $CMS)
line 16
Maakt een URL op basis van de recordgegevens en voegt een HTML-link toe met de gegenereerde href, inclusief een CSS-klasse voor asynchrone functionaliteit.
$parentPath = $CMS->uriRecord.'/'.$record->{$CMS->model}->id;
$relationshipName = $this->name;
$href = "/{$parentPath}/{$relationshipName}/{$record->id}";
return tag('a', href: $href, class: 'async', inner: $record);object
%field_date
/phlo/libs/Fields/date.phlo
prop
%field_date -> handle
line 7
Deze node geeft altijd `true` terug, ongeacht de invoer of context.
truemethod
%field_date -> sql
line 9
Dit geeft aan dat de waarde een niet-negatieve datum is, met het type DATE in SQL en geen negatieve waarden toegestaan.
"`$this->name` DATE unsigned"object
%field_datetime
/phlo/libs/Fields/datetime.phlo
prop
%field_datetime -> handle
line 7
Deze node geeft altijd de waarde waar is (true).
trueprop
%field_datetime -> change
line 8
Controleert of de naam niet gelijk is aan 'created', 'changed' of 'updated'.
!in_array($this->name, ['created', 'changed', 'updated'])prop
%field_datetime -> create
line 9
Controleert of de naam niet gelijk is aan 'created', 'changed' of 'updated'.
!in_array($this->name, ['created', 'changed', 'updated'])method
%field_datetime -> label ($record, $CMS)
line 11
Toont een klok-icoon en een menselijke tijdsaanduiding als de waarde bestaat; anders wordt een streepje weergegeven.
($value = $record->{$this->name}) ? tag('i', class: 'icon clock-'.$this->labelIconClass(time() - $value), inner: void).tag('span', data_ts: $record->{$this->name}, inner: time_human($record->{$this->name})) : dashmethod
%field_datetime -> labelIconClass ($value)
line 12
Het codeblok bepaalt de kleur op basis van de waarde: rood als de waarde groter dan 86400 is, geel als hij tussen 3600 en 86400 ligt, en blauw voor lagere waarden.
$value > 86400 ? 'red' : ($value > 3600 ? 'yellow' : 'blue')method
%field_datetime -> input ($record, $CMS)
line 13
Maakt een datetime-invoerveld met type 'datetime-local', geeft een gestileerde invoer weer, waarbij de waarde wordt ingesteld op een geformatteerde datum- en tijdwaarde uit het record.
input(type: 'datetime-local', name: $this->name, value: date('Y-m-d\TH:i', $record->{$this->name}), class: 'field')method
%field_datetime -> parse ($record)
line 14
Deze code verwerkt datums voor een record. Als de veldnaam 'created' is en nog geen waarde heeft, wordt de huidige tijd ingesteld. Voor 'changed' of 'updated' wordt de huidige tijd toegewezen. Als er een payload-waarde bestaat, wordt deze omgezet naar een timestamp met strtotime en aan het record toegewezen.
if ($this->name === 'created') $record->created ??= time()
elseif (in_array($this->name, ['changed', 'updated'])) $record->{$this->name} = time()
elseif ($payload = %payload->{$this->name}) $record->{$this->name} = strtotime($payload)method
%field_datetime -> sql
line 20
De code definieert een databasekolom met de naam `$this->name`, van het type unsigned integer met een lengte van 10.
"`$this->name` int(10) unsigned"object
%field_email
/phlo/libs/Fields/email.phlo
view
%field_email -> label ($record, $CMS)
line 8
Deze code genereert een mailto-link met het e-mailadres uit de record, waardoor gebruikers direct een e-mail kunnen sturen door op de link te klikken.
<a href="mailto:{{ $record->{$this->name} }}">{{ $record->{$this->name} }}</a>object
%field_many
/phlo/libs/Fields/many.phlo
prop
%field_many -> list
line 7
Dit stuk code retourneert de tekst 'label'.
'label'prop
%field_many -> record
line 8
Deze node geeft eenvoudigweg de waarde van 'label' door, zonder verdere verwerking of context.
'label'prop
%field_many -> create
line 9
Deze code retourneert altijd true.
falseprop
%field_many -> change
line 10
Deze node geeft altijd de waarde `false` terug, ongeacht de invoer.
falsemethod
%field_many -> count ($record)
line 12
Deze code roept de methode `getCount` op het `$record` object, met als parameter de naam van het veld, en geeft daarmee waarschijnlijk het aantal item(s) of het aantal keer dat een bepaald veld voorkomt terug.
$record->getCount($this->name)method
%field_many -> label ($record, $CMS)
line 13
Loop door de elementen in de relatie, maak voor elk een link aan, en retourneer het resultaat; indien leeg, wordt een koppelteken weergegeven.
loop($record->{$this->name}, fn($relation) => $this->link($relation), lf) ?: dashmethod
%field_many -> link ($record)
line 14
Maakt een hyperlink aan met een dynamische URL gebaseerd op recordgegevens, inclusief een class voor asynchrone actie en toont het record als de linktekst.
tag('a', href: slash.($record::$uriRecord ?? $record::class).slash.$record->id, class: 'async', inner: $record)method
%field_many -> input ($record, $CMS)
line 15
Geeft een label weer gebaseerd op het record en CMS.
$this->label($record, $CMS)method
%field_many -> sql
line 17
Het resultaat is een lege array, wat aangeeft dat deze methode of functie geen data teruggeeft of geen bewerkingen uitvoert.
[]object
%field_number
/phlo/libs/Fields/number.phlo
prop
%field_number -> decimals
line 7
Returneert het getal met nul decimalen, oftewel een gehele waarde.
prop
%field_number -> length
line 8
Deze code geeft simpelweg het getal 5 terug, wat waarschijnlijk de lengte van een veld of een similar waarde aangeeft.
5prop
%field_number -> min
line 9
Deze code retourneert altijd 0.
method
%field_number -> label ($record, $CMS)
line 11
Formatteert een getal uit het record met een bepaald aantal decimalen, gebruikt komma als decimaalscheiding en punt als duizendtalseparator.
number_format($record->{$this->name}, $this->decimals, comma, dot)method
%field_number -> input ($record, $CMS)
line 12
Maakt een numeriek invoerveld met dynamische waarde, standaardwaarde, stapgrootte op basis van decimalen, minimale waarde en een CSS-klasse.
input(type: 'number', name: $this->name, value: $record->{$this->name} ?? $this->default, step: $this->decimals ? dot.str_repeat('0', $this->decimals - 1).'1' : null, min: $this->min, class: 'field')method
%field_number -> sql
line 14
Deze code genereert een SQL-velddefinitie dat een numeriek veld definieert, afhankelijk van de aanwezigheid van decimalen. Als er decimalen zijn, wordt een 'decimal' datatype gebruikt met een aangepaste precisie en schaal; anders wordt 'smallint' gebruikt. In beide gevallen wordt de unsigned-optie toegevoegd.
"`$this->name` ".($this->decimals ? 'decimal('.($this->length + $this->decimals).comma.$this->decimals.')' : "smallint($this->length)").' unsigned'object
%field_parent
/phlo/libs/Fields/parent.phlo
method
%field_parent -> label ($record, $CMS)
line 7
Controleert of een veld een modelobject is; indien ja, genereert het een link, anders wordt een streepje weergegeven.
is_a($obj = $record->{$this->name}, 'model') ? $this->link($obj) : dashmethod
%field_parent -> input ($record, $CMS)
line 8
Deze code genereert een HTML select-element met opties gebaseerd op een lijst van opties. Voor elke optie wordt gecontroleerd of deze overeenkomt met een waarde uit het record, en indien ja, wordt de 'selected'-attribuut toegevoegd.
select(name: $this->name, inner: loop($this->options, fn($parent) => '<option'.($parent->id === $record->{$this->name}?->id ? ' selected' : void).' value="'.$parent->id.'">'.$parent, void))method
%field_parent -> link ($record, $content = null)
line 9
Maakt een HTML-ankerlink met een dynamisch gegenereerde URL en voegt de inhoud toe of gebruikt het record als inhoud, met een CSS-klasse 'async'.
tag('a', href: slash.($record::$uriRecord ?? $record::class).slash.$record->id, class: 'async', inner: $content ?? $record)prop
%field_parent -> options
line 10
Haalt alle beschikbare records op binnen de context van de klasse of het object.
$this->obj::records()method
%field_parent -> sql
line 12
Definieert een varchar-veld met de naam `$this->name` dat maximaal 10 tekens kan bevatten.
"`$this->name` varchar(10)"object
%field_password
/phlo/libs/Fields/password.phlo
prop
%field_password -> list
line 7
Deze node geeft altijd de waarde false terug, ongeacht eventuele invoer of context.
falseprop
%field_password -> required
line 8
Deze node geeft altijd de waarde 'true' terug, wat betekent dat het veld altijd als verplicht wordt gemarkeerd.
trueprop
%field_password -> minlength
line 9
Een minimumlengte van 8 karakters voor een wachtwoord.
8prop
%field_password -> placeholder
line 10
Het retourneert een tekstlabel dat aangeeft dat het veld voor het nieuwe wachtwoord is.
'Nieuw wachtwoord'method
%field_password -> input ($record, $CMS)
line 12
Maakt een invoerveld aan met type, naam, placeholder en een vaste CSS-klasse.
input(type: $this->type, name: $this->name, placeholder: $this->placeholder, class: 'field')method
%field_password -> label ($record, $CMS)
line 13
Toont een vaste, versleutelde of verborgen wachtwoordlabel, ongeacht de invoer of context.
'••••••••'method
%field_password -> parse ($record)
line 14
Deze code haalt de wachtwoordwaarde op uit de payload, verwijdert eventuele spaties, en als er een wachtwoord is opgegeven, wordt het gehasht met bcrypt en opgeslagen in het record.
($password = trim(%payload->{$this->name})) && $record->{$this->name} = password_hash($password, PASSWORD_BCRYPT)method
%field_password -> sql
line 16
Geeft aan dat het veld 'name' wordt gedefinieerd als een tekenreeks met een maximale lengte van 60 karakters in een SQL-tabel.
"`$this->name` char(60)"object
%field_price
/phlo/libs/Fields/price.phlo
prop
%field_price -> decimals
line 7
Deze code geeft het getal 2 terug, wat waarschijnlijk aangeeft dat het getal 2 decimalen moet worden weergegeven of gebruikt.
2object
%field_select
/phlo/libs/Fields/select.phlo
method
%field_select -> input ($record, $CMS)
line 7
Genereert een `<select>` element met een dynamische naam en opties, waarbij het de optie markeert als geselecteerd op basis van het record.
select(name: $this->name, inner: loop($this->options, fn($option) => "<option".($record->{$this->name} === $option ? ' selected' : void).">$option", void))method
%field_select -> sql
line 9
Dit definieert een SQL-kolom met de naam `$this->name` die een ENUM-type is, inclusief opties die worden samengevoegd in een lijst zoals `'option1','option2',...`.
"`$this->name` enum('".implode("','", $this->options)."')"object
%field_text
/phlo/libs/Fields/text.phlo
prop
%field_text -> length
line 7
Deze node geeft de waarde 100 terug, waarschijnlijk de lengte van een tekst of een vergelijkbare numerieke waarde.
100prop
%field_text -> multiline
line 8
Deze eigenschap controleert of de lengte van een tekst meer dan 250 tekens is.
$this->length > 250method
%field_text -> input ($record, $CMS)
line 10
Selecteert op basis van een voorwaarde ofwel een meervoudig invoerveld of een enkel invoerveld te gebruiken.
$this->multiline ? $this->inputMulti($record) : $this->inputField($record)method
%field_text -> inputField ($record)
line 11
Maakt een HTML-inputveld aan met het type, naam, waarde, maximale lengte, placeholder en CSS-klasse die gebaseerd zijn op de objecteigenschappen. De waarde wordt geëscaped weergegeven als deze bestaat; anders wordt een standaardwaarde gebruikt.
input(type: $this->type, name: $this->name, value: ($value = $record->{$this->name}) ? esc($value) : $this->default, maxlength: $this->length, placeholder: $this->placeholder, class: 'field')method
%field_text -> inputMulti ($record)
line 12
Maakt een textarea-element met naam, placeholder en klasse. Als er een waarde in het record bestaat voor deze naam, wordt die ge-escape'd en ingevuld; anders wordt een default-waarde gebruikt indien aanwezig.
textarea(name: $this->name, inner: ($value = $record->{$this->name}) ? esc($value) : $this->default ?? void, placeholder: $this->placeholder, class: 'field')method
%field_text -> sql
line 14
Definieert een SQL-veld met de naam `$this->name`, datatype varchar en de lengte `$this->length`.
"`$this->name` varchar($this->length)"object
%field_token
/phlo/libs/Fields/token.phlo
prop
%field_token -> length
line 7
Geeft de lengte van de inhoud terug, in dit geval 8.
8prop
%field_token -> default
line 8
Haalt een token op met een lengte gelijk aan de waarde van `$this->length`.
token($this->length)prop
%field_token -> create
line 9
Deze code retourneert altijd false, ongeacht de input of omstandigheden.
falseprop
%field_token -> change
line 10
Deze code geeft altijd `false` terug, ongeacht de context of invoer.
falseprop
%field_token -> search
line 11
Deze code zorgt ervoor dat altijd een positie wordt teruggegeven, ongeacht de invoer. Het fungeert als een bevestiging dat de zoekactie succesvol is of dat het resultaat altijd waar is.
truemethod
%field_token -> label ($record, $CMS)
line 13
Maakt een HTML-div element aan met als inhoud de ge-escape-de waarde van een veld uit het record.
tag('div', inner: esc($record->{$this->name}))method
%field_token -> parse ($record)
line 15
Stelt de waarde van een veld in op de waarde in het record als deze nog niet bestaat, of wijzigt deze anders naar een standaardwaarde.
$record->{$this->name} ??= $this->defaultmethod
%field_token -> sql
line 17
Deze code definieert een varchar-kolom met een naam die wordt bepaald door de waarde van `$this->name` en een lengte die wordt bepaald door `$this->length`.
"`$this->name` char($this->length)"object
%field_virtual
/phlo/libs/Fields/virtual.phlo
prop
%field_virtual -> create
line 7
Deze methode of functie always false retourneert, waarschijnlijk bedoeld om een bepaalde bewerking of validatie te voorkomen of te markeren als niet succesvol.
falseprop
%field_virtual -> change
line 8
Deze code zal altijd onwaar teruggeven.
falsemethod
%field_virtual -> sql
line 10
Deze node retourneert een lege array zonder verdere bewerkingen of data.
[]Files
object
%CSV
/phlo/libs/Files/CSV.phlo
static
CSV :: __handle
line 5
Dit lijkt een property die een bestands- of padreferentie retourneert met een dynamisch pad dat wordt opgebouwd uit variabelen voor pad en bestandsnaam.
"CSV/$path$filename"method
%CSV -> __construct (string $filename, ?string $path = null)
line 6
Controleren of het pad is ingesteld, en indien niet, een standaardpad gebruiken; samenstellen van de bestandsnaam en extensie; controleren of het CSV-bestand leesbaar is en indien ja, de leesfunctie aanroepen.
$path ??= data
$this->objFile = $path.strtr($filename, [slash => dot]).'.csv'
if (is_readable($this->objFile)) $this->objRead()readonly
%CSV -> objFile:string
line 12
Deze node leest een CSV-bestand in en koppelt het aan een variabele, zodat de gegevens verder verwerkt kunnen worden.
method
%CSV -> objRead
line 14
Verwerkt een CSV-bestand door het te openen, de eerste rij (headers) te lezen, het juiste scheidingsteken te bepalen op basis van het voorkomen van komma's of puntkomma's, de headers te splitsen en vervolgens alle rijen in te lezen. Elke rij wordt gekoppeld aan de headers en opgeslagen in een array.
$fp = fopen($this->objFile, 'r+')
$headers = str_replace([dq, cr, lf], void, fgets($fp))
$delimiter = substr_count($headers, comma) > substr_count($headers, semi) ? comma : semi
$headers = explode($delimiter, $headers)
while ($row = fgetcsv($fp, null, $delimiter)) $this->objData[] = array_combine($headers, $row)
fclose($fp)object
%file
/phlo/libs/Files/file.phlo
static
file :: __handle
line 5
Deze code bouwt een pad op door 'file/' te combineren met een variabele '$file' en, indien aanwezig, een optionele naam '$name' die wordt toegevoegd met een schuine streep.
"file/$file".($name ? "/$name" : void)method
%file -> __construct (public string $file, ?string $name = null, $contents = null, ...$args)
line 6
Stelt een naam in als deze meegegeven is, schrijft inhoud naar het bestand als inhoud een string is, en importeert aanvullende objecten of argumenten als die aanwezig zijn.
$name && $this->name = $name
is_string($contents) && $this->write($contents)
$args && $this->objImport(...$args)method
%file -> append (string $data)
line 12
Voegt de opgegeven tekst toe aan het einde van het bestand zonder het te overschrijven.
file_put_contents($this->file, $data, FILE_APPEND)prop
%file -> basename
line 13
Haalt de basisnaam (bestandsnaam zonder pad) op van het opgegeven bestand.
pathinfo($this->file, PATHINFO_BASENAME)method
%file -> base64
line 14
Converteert de inhoud van het bestand naar een Base64-gecodeerde string.
base64_encode($this->contents)method
%file -> contents
line 15
Leest de inhoud van het bestand dat wordt aangegeven door de bestandsnaam in een property.
file_get_contents($this->file)method
%file -> contentsINI (bool $parse = true)
line 16
Maakt een INI-string aan, parseert de inhoud afhankelijk van de parameter en retourneert het resultaat met typetransparantie als `$parse` waar is.
parse_ini_string($this->contents, true, $parse ? INI_SCANNER_TYPED : INI_SCANNER_RAW)method
%file -> contentsJSON ($assoc = null)
line 17
Converteert de inhoud van een JSON-string naar een PHP-variabele, waarbij het resultaat als array of object wordt geretourneerd afhankelijk van de parameter.
json_decode($this->contents, $assoc)method
%file -> copy ($to)
line 18
Maakt een kopie van het bestand en slaat het op de opgegeven locatie op.
copy($this->file, $to)method
%file -> created
line 19
Geeft de creatietijd van het bestand terug.
filectime($this->file)method
%file -> createdAge
line 20
Deze node berekent de leeftijd sinds de creatie door de datum van creatie te vergelijken met de huidige datum.
age($this->created)method
%file -> createdHuman
line 21
Deze code zet een timestamp om naar een leesbaar formaat voor mensen.
time_human($this->created)method
%file -> curl ($type = null, $filename = null)
line 22
Creëert een nieuw CURLFile-object met de opgegeven bestandsnaam, type en optioneel een andere bestandsnaam voor de upload.
new CURLFile($this->file, $type, $filename)method
%file -> delete
line 23
Voert een controle uit of het bestand bestaat en probeert het te verwijderen. Vervolgens wordt een debug-bericht gelogd dat aangeeft of het verwijderen is gelukt of niet.
first($deleted = $this->exists && unlink($this->file), debug($deleted ? "Deleted $this->basename" : "Could not delete $this->basename"))method
%file -> exists
line 24
Controleert of het bestand dat in de variabele `$this->file` is opgeslagen, bestaat op het systeem.
file_exists($this->file)prop
%file -> ext
line 25
Haalt de bestandsextensie op uit de naam van het bestand.
pathinfo($this->name, PATHINFO_EXTENSION)prop
%file -> filename
line 26
Haalt de bestandsnaam zonder extensie op uit het pad of de bestandsnaam opgeslagen in de variabele.
pathinfo($this->file, PATHINFO_FILENAME)method
%file -> getLine
line 27
Haalt een regel uit het bestand, verwijdert eventuele witte ruimte aan het einde, en geeft deze terug; als het einde van het bestand is bereikt, geeft het false terug.
($line = fgets($this->pointer)) === false ? false : rtrim($line)method
%file -> getLength (int $length)
line 28
Leest een bepaald aantal bytes ($length) uit het bestand via de gespecificeerde pointer.
fread($this->pointer, $length)method
%file -> is (string $file)
line 29
Vergelijkt of de opgegeven bestandsnaam gelijk is aan de interne property `$file`.
$file === $this->filemethod
%file -> md5
line 30
Deze code berekent de MD5-hash van het bestand dat wordt weergegeven door de property `$file`.
md5_file($this->file)prop
%file -> mime
line 31
Bepaalt de MIME-type van een bestand op basis van de bestandsnaam.
mime($this->name)method
%file -> modified
line 32
Geeft de laatste wijzigingsdatum en -tijd van het bestand terug.
filemtime($this->file)method
%file -> modifiedAge
line 33
Geeft de leeftijd terug op basis van de gewijzigde datum.
age($this->modified)method
%file -> modifiedHuman
line 34
Deze node retourneert de meest recente wijziging in een menselijk leesbaar tijdformaat.
time_human($this->modified)method
%file -> move ($to)
line 35
Verplaatst het bestand naar een nieuwe locatie en update de internal referentie naar de gewijzigde bestandsnaam of pad.
rename($this->file, $to) && $this->file = $toprop
%file -> name
line 36
Dit haalt de bestandsnaam op zonder pad, meestal gebruikt om de naam zonder directory te verkrijgen.
$this->basenamemethod
%file -> output ($download = false)
line 37
Maakt een output met de inhoud, naam en optioneel downloaden van een bestand.
output($this->contents, $this->name, $download)prop
%file -> path
line 38
Deze code retourneert het absolute pad naar de directory waarin het bestand zich bevindt, gevolgd door een schuine streep.
realpath(pathinfo($this->file, PATHINFO_DIRNAME)).slashprop
%file -> pathRel
line 39
Controleert of het pad van het bestand begint met een bepaald relatieve rootpad, en indien ja, geeft het pad terug vanaf dat punt; anders wordt het volledige pad weergegeven.
str_starts_with($this->file, $relRoot = dirname(dirname($_SERVER['DOCUMENT_ROOT'])).slash) ? substr($this->file, strlen($relRoot)) : $this->fileprop
%file -> pointer
line 40
Deze code opent een bestand in lees- en schrijfmodes.
fopen($this->file, 'r+')method
%file -> readable
line 41
Controleert of het bestand dat gekoppeld is aan de variabele 'file' leesbaar is.
is_readable($this->file)method
%file -> src
line 42
Maakt een data-URI aan met het MIME-type en de base64-gegevens, geschikt voor gebruik in bijvoorbeeld afbeelding- of bestandsverwijzingen.
"data:$this->mime;base64,$this->base64"method
%file -> size
line 43
Returns de grootte van het bestand.
filesize($this->file)method
%file -> sizeHuman (int $precision = 0)
line 44
Converteert de omvang van een bestand naar een voor mensen leesbare string met een optionele precisie.
size_human($this->size, $precision)method
%file -> sha1
line 45
Deze methode berekent de SHA-1 hash van het bestand.
sha1_file($this->file)method
%file -> shortenTo (int $length)
line 46
Deze methode verkort een bestandsnaam tot een opgegeven lengte. Als de naam al korter is dan de lengte, wordt de originele naam teruggegeven. Anders wordt een afkapping toegevoegd met drie puntjes, zodat de uiteindelijke lengte niet groter is dan de opgegeven waarde, inclusief de bestandsextensie.
strlen($this->name) <= $length ? $this->name : substr($this->name, 0, $length - strlen($this->ext) - 3).dot.dot.dot.$this->extmethod
%file -> title
line 47
Convert de bestandsnaam zonder extensie naar een titel door de eerste letter hoofdletter te maken en underscores of andere niet-letters te vervangen door spaties.
ucfirst(strtr(pathinfo($this->name, PATHINFO_FILENAME), [us => space]))method
%file -> token ($length = 20)
line 48
Genereert een token met een opgegeven lengte, waarbij standaard de SHA-1 hashfunctie wordt gebruikt om het token te maken.
token($length, sha1: $this->sha1)method
%file -> type
line 49
Haalt het gedeelte van de MIME-waarde voordat de slash op, bijvoorbeeld het type zoals 'image' of 'video'.
substr($this->mime, 0, strpos($this->mime, slash))method
%file -> touch
line 50
Maakt het bestand met de opgegeven naam (gegevn door `$this->file`) aan als het nog niet bestaat of update de toegangstijd van het bestand.
touch($this->file)method
%file -> writeable
line 51
controleert of het bestand schrijfbaar is.
is_writeable($this->file)method
%file -> writeINI ($data, bool $deleteEmpty = false)
line 52
Schrijft data naar een INI-bestand, waarbij lege secties niet worden verwijderd tenzij aangegeven, en verwerkt de data door elke waarde te ontsnappen en te formatteren als key-value paren met dubbele quotes, gescheiden door nieuwe regels.
$this->write(!$deleteEmpty || $data ? loop($data, fn($value, $key) => $key.' = '.dq.strtr($value, [dq => bs.dq, lf => '\n']).dq, lf).lf : void, $deleteEmpty)method
%file -> writeJSON ($data, bool $deleteEmpty = false)
line 53
Deze code roept een methode aan om gegevens weg te schrijven. Als `$deleteEmpty` niet waar is of `$data` aanwezig is, wordt de data JSON-gecodeerd en doorgegeven; anders wordt er geen data geschreven.
$this->write(!$deleteEmpty || $data ? json_encode($data, jsonFlags) : void, $deleteEmpty)method
%file -> writeJSONplain ($data, bool $deleteEmpty = false)
line 54
Deze code controleert of lege data moet worden geschreven. Als $deleteEmpty valse is of $data niet leeg, wordt de data als JSON-code geëvalueerd en doorgegeven aan een write-functie; anders wordt niets geschreven.
$this->write(!$deleteEmpty || $data ? json_encode($data) : void, $deleteEmpty)method
%file -> write (string $data, bool $deleteEmpty = false)
line 55
Deze methode schrijft data naar een bestand en verwijdert het bestand indien de data leeg is en de parameter op true staat. Bij succesvolle schrijven wordt een debugbericht gelogd, anders wordt een fout gemeld.
if (!$data && $deleteEmpty) return $this->delete
if ($written = file_put_contents($this->file, $data) !== false) debug('Written '.$this->basename.' ('.$this->sizeHuman.')')
else error('Could not write '.$this->file)
return $writtenmethod
%file -> objInfo
line 62
Deze code maakt een array waarbij keys worden samengevoegd en de waarden worden afgeleid uit de huidige objecteigenschappen. Het voegt basisgegevens toe en, afhankelijk van een voorwaarde, ook aanvullende bestandsinformatie zoals grootte, aanmaak- en wijzigingsdatum, en MIME-type.
array_combine($keys = array_merge(['file', 'name', 'exists'], $this->exists ? ['sizeHuman', 'createdHuman', 'modifiedHuman', 'mime'] : []), loop($keys, fn($arg) => $this->$arg))object
%img
/phlo/libs/Files/img.phlo
static
img :: detect ($data)
line 5
Deze code detecteert het afbeeldingsformaat op basis van de eerste bytes van de gegevens en retourneert de overeenkomstige bestandsindeling ('jpg', 'png', 'gif', 'webp', 'bmp', 'tiff') door specifieke headers te vergelijken.
$header = substr($data, 0, 12)
if (substr($header, 0, 3) === "\xFF\xD8\xFF") return 'jpg'
if (substr($header, 0, 8) === "\x89PNG\x0D\x0A\x1A\x0A") return 'png'
if (substr($header, 0, 6) === 'GIF87a' || substr($header, 0, 6) === 'GIF89a') return 'gif'
if (substr($header, 0, 4) === 'RIFF' && substr($header, 8, 4) === 'WEBP') return 'webp'
if (substr($header, 0, 2) === "BM") return 'bmp'
if (substr($header, 0, 4) === "\x49\x49\x2A\x00" || substr($header, 0, 4) === "\x4D\x4D\x00\x2A") return 'tiff'static
img :: search ($search)
line 15
Zoekt naar afbeeldingsresultaten op Google op basis van een geformatteerde zoekterm, haalt meerdere base64-gecodeerde JPEG-gegevens op uit de HTML, sorteert deze op grootte, selecteert er een paar, kiest er één willekeurig uit en decodeert deze uiteindelijk naar een afbeeldingsbestand.
$q = strtr($search, [dash => '+'])
$DOM = HTTP("https://www.google.com/search?q=$q&source=lnms&tbm=isch&tbs=", agent: true)
$sources = regex_all('/var s=\'data:image\/jpeg;base64,([^\']{1000,})/', $DOM)[1]
usort($sources, fn($a, $b) => strlen($b) <=> strlen($a))
$sources = array_slice($sources, 0, 5)
shuffle($sources)
$source = current($sources)
$source = base64_decode($source)
return $sourcestatic
img :: __handle
line 27
Geeft de waarde van de variabele `$file` door, voorafgaand aan de string "img/".
"img/$file"method
%img -> __construct (public string $file)
line 28
De constructor accepteert een stringparameter die wordt opgeslagen in een publieke property, waarschijnlijk bedoeld om een bestandsnaam of pad te representeren voor de node.
prop
%img -> src:GdImage
line 30
Laadt een afbeelding vanuit een bestand door de inhoud te lezen en deze vervolgens te converteren naar een afbeeldingsresource.
imagecreatefromstring(file_get_contents($this->file))prop
%img -> width
line 31
Deze node geeft de breedte van de afbeelding weer door de breedte van de afbeelding te halen via de functie `imagesx` op de bronafbeelding.
imagesx($this->src)prop
%img -> height
line 32
Deze node geeft de brede (breedte) van een afbeelding terug door de breedte van de afbeelding te ophalen met `imagesx()` op de bron (`$this->src`).
imagesx($this->src)method
%img -> scale ($width, $height = false, $crop = true)
line 34
Maakt een schaalbewerking op basis van opgegeven breedte en hoogte, inclusief optionele cropping en verhoudingsbehoud, met correcte image resampling en transparantie ondersteuning.
if (!$width && !$height) return $this
$scale = 1
if ($width && $height) $scale = $crop ? max($width / $this->width, $height / $this->height) : min($width / $this->width, $height / $this->height)
elseif ($width) $scale = $width / $this->width
elseif ($height) $scale = $height / $this->height
if ($scale > 1) return $this
if ($width && $height && $this->width / $this->height != $width / $height){
if ($width / $height > $this->width / $this->height){
if ($crop){
$destX = $width
$destY = round($width / $this->width * $this->height)
if ($crop === 'top') $offsetY = 0
elseif ($crop === 'bottom') $offsetY = -$destY - -$height
else $offsetY = -round(($destY - $height) / 2)
}
else {
$destX = round($this->width * $height / $this->height)
$destY = $height
$width = $destX
$offsetY = 0
}
$offsetX = 0
}
else {
if ($crop){
$destX = round($height / $this->height * $this->width)
$destY = $height
$offsetX = -round (($destX - $width) / 2)
}
else {
$destX = $width
$destY = round($this->height * $width / $this->width)
$height = $destY
$offsetX = 0
}
$offsetY = 0
}
$destImg = imagecreatetruecolor($width, $height)
imagealphablending($destImg, false)
imagesavealpha($destImg, true)
imagecopyresampled($destImg, $this->src, $offsetX, $offsetY, 0, 0, $destX, $destY, $this->width, $this->height)
}
else {
if ($width){
$destX = $width
$destY = $width / $this->width * $this->height
}
elseif ($height){
$destX = $height / $this->height * $this->width
$destY = $height
}
$destImg = imagecreatetruecolor($destX, $destY)
imagealphablending($destImg, false)
imagesavealpha($destImg, true)
imagecopyresampled($destImg, $this->src, 0, 0, 0, 0, $destX, $destY, $this->width, $this->height)
}
$this->src = $destImg
return $thismethod
%img -> source
line 95
Captureert de uitvoer van de `imagejpeg()`-functie in een buffer en retourneert deze als een string, waardoor de afbeelding in geheugen beschikbaar wordt gesteld voor verdere verwerking.
ob_start()
imagejpeg($this->src)
return ob_get_clean()method
%img -> save ($file = null)
line 101
Slaat het afbeeldingsbestand op, afhankelijk van de extensie; bij 'png' en 'webp' wordt compressie toegepast, bij 'gif' wordt zonder compressie opgeslagen, en standaard wordt JPEG gebruikt met een kwaliteit van 85%.
$file && $this->file = $file
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION))
if ($ext === 'png') return imagepng($this->src, $this->file, 8)
if ($ext === 'gif') return imagegif($this->src, $this->file)
if ($ext === 'webp'){
imageistruecolor($this->src) || imagepalettetotruecolor($this->src)
return imagewebp($this->src, $this->file)
}
return imagejpeg($this->src, $this->file, 85)object
%INI
/phlo/libs/Files/INI.phlo
prop
%INI -> objFile:string
line 5
Deze node initialiseert mogelijk een bestand of object, afhankelijk van de context, en voert een setup of initialisatie uit voor het geselecteerde bestand of object.
static
INI :: __handle
line 7
Deze code bouwt een bestandsnaam door een pad en bestandsnaam te combineren, en voegt '/0' toe als de parameter `$parse` niet waar is.
"INI/$path$filename".(!$parse ? '/0' : void)method
%INI -> __construct (string $filename, ?string $path = null, bool $parse = true)
line 8
Deze constructor initialiseert een bestandsnaam op basis van een optioneel pad en de naam, door puntvervanging toe te passen. Vervolgens controleert het of het ini-bestand leesbaar is en leest het indien mogelijk, afhankelijk van de parse-parameter.
$path ??= data
$this->objFile = $path.strtr($filename, [slash => dot]).'.ini'
if (is_readable($this->objFile)) $this->objRead($parse)method
%INI -> objRead ($parse = true)
line 14
Deze code leest een INI-bestand met parse_ini_file, waarbij de inhoud wordt opgeslagen in objData. Vervolgens wordt de laatste waarde van die array geëvalueerd, en wordt de status van objChanged op false gezet. Het resultaat van de last-functie wordt geretourneerd.
last($this->objData = parse_ini_file($this->objFile, true, $parse ? INI_SCANNER_TYPED : INI_SCANNER_RAW), $this->objChanged = false, $this)method
%INI -> objWrite
line 15
Schrijft de genormaliseerde data naar een bestand door elke key-waarde combinatie te formatteren, correct te ontsnappen, en vervolgens alles samen met nieuwe regels weg te schrijven.
file_put_contents($this->objFile, loop($this->objData, fn($value, $key) => $key.' = '.dq.strtr($value, [dq => bs.dq, lf => '\n']).dq, lf).lf)method
%INI -> __destruct
line 17
Voert de write-methode uit als er wijzigingen zijn opgetreden.
$this->objChanged && $this->objWrite()object
%JSON
/phlo/libs/Files/JSON.phlo
static
JSON :: __handle
line 5
Voegt een pad en bestandsnaam samen met een voorwaarde dat, indien `$assoc` een booleaanse waarde is, er een schuine streep en de integer-versie van `$assoc` worden toegevoegd; anders gebeurt er niets.
"JSON/$path$filename".(is_bool($assoc) ? slash.(int)$assoc : void)method
%JSON -> __construct (string $filename, ?string $path = null, $assoc = null)
line 6
Controleert of de $path-variabele null is en stelt deze in op 'data' indien nodig. Bouwt het volledige pad naar het JSON-bestand. Als het bestand leesbaar is, wordt een leesfunctie aangeroepen met de $assoc-parameter.
$path ??= data
$this->objFile = "$path$filename.json"
if (is_readable($this->objFile)) $this->objRead($assoc)readonly
%JSON -> objFile:string
line 12
Deze code haalt een JSON-bestand op en converteert het naar een object, dat vervolgens wordt opgeslagen in een variabele of wordt doorgegeven voor verdere verwerking.
method
%JSON -> objTouch
line 14
Deze node markeert dat er veranderingen zijn aangebracht, waarschijnlijk om later te controleren of er updates nodig zijn of acties ondernomen moeten worden.
$this->objChanged = truemethod
%JSON -> objRead ($assoc = null)
line 15
Leest JSON-gegevens uit een bestand, slaat deze op in een klasse-eigenschap, en retourneert de klasse zelf voor verdere method chaining.
last($data = json_read($this->objFile, $assoc), $this->objData = $assoc || is_array($data) ? $data : get_object_vars($data), $this->objChanged = false, $this)method
%JSON -> objWrite ($data, $flags = null)
line 16
Voert een JSON-weergave weg naar een bestand en wijzigt de status van het object op basis van het resultaat van de schrijfbewerking.
first($written = json_write($this->objFile, $data, $flags), $written && $this->objChanged = false)method
%JSON -> __destruct
line 18
Controleert of er veranderingen zijn en schrijft vervolgens de data weg als dat het geval is.
$this->objChanged && $this->objWrite($this->objData)object%PDF
/phlo/libs/Files/PDF.phlo
static
PDF :: toText (string $file):string
line 5
Voert het 'pdftotext' commando uit op een PDF-bestand via een subprocess, leest de uitvoer en foutmeldingen, controleert op fouten, en geeft de tekst terug zonder achtergebleven form feed karakters.
$process = proc_open('pdftotext '.escapeshellarg($file).' -', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes)
if (!is_resource($process)) return null
fclose($pipes[0])
$text = stream_get_contents($pipes[1])
fclose($pipes[1])
$error = stream_get_contents($pipes[2])
fclose($pipes[2])
($code = proc_close($process)) && error("PDFToText Error: pdftotext command failed with code $code. Error: $error")
return rtrim($text, "\f")prop
%PDF -> title
line 17
Deze node geeft geen waarde door, mogelijk betekent dit dat er geen titel is of dat de titel niet is ingesteld.
nullprop
%PDF -> author
line 18
Deze code geeft altijd null terug, ongeacht de invoer.
nullprop
%PDF -> subject
line 19
Deze node geeft altijd null terug, ongeacht de invoer of andere voorwaarden.
nullprop
%PDF -> keywords
line 20
Deze node retourneert geen gegevens en geeft dus een null-waarde terug.
nullprop
%PDF -> creator
line 21
Deze node geeft een string terug die bestaat uit de tekst 'Phlo ' gevolgd door de inhoud van de variabele of property 'phlo', en dan de URL '(https://phlo.tech/)'.
'Phlo '.phlo.' (https://phlo.tech/)'prop
%PDF -> filename
line 23
Geeft een bestand met de naam "Download.pdf" terug voor download.
'Download.pdf'prop
%PDF -> mode
line 24
Deze node retourneert de waarde 'D'.
'D'method
%PDF -> fromHTML ($HTML)
line 26
Maakt een nieuwe mpdf-instantie aan, stelt metadata in op basis van aanwezige eigenschappen, schrijft de HTML-inhoud weg en geeft het gegenereerde PDF-bestand terug met opgegeven bestandsnaam en uitvoermodus.
$mpdf = new \Mpdf\Mpdf
$this->title && $mpdf->SetTitle($this->title)
$this->author && $mpdf->SetAuthor($this->author)
$this->subject && $mpdf->SetSubject($this->subject)
$this->keywords && $mpdf->SetKeywords($this->keywords)
$this->creator && $mpdf->SetCreator($this->creator)
$mpdf->WriteHTML($HTML)
return $mpdf->Output($this->filename, $this->mode)object
%XLSX
/phlo/libs/Files/XLSX.phlo
method
%XLSX -> __construct (string $file)
line 5
Laadt een XLSX-bestand door het uitpakken van de ZIP-structuur, leest celgegevens en gedeelde strings, en converteert de gegevens naar een arraystructuur met headers. Verwerken van werkbladen, rijen en cellen gebeurt met regex en string-manipulatie, waarbij gedeelde strings correct worden geëvalueerd en cellreferrers worden omgezet in indices.
$sheets = []
$shared = []
$sheetNames = []
$zip = new ZipArchive()
if ($zip->open($file) !== true) error('Error opening XLSX: '.esc($file))
for ($i = 0; $i < $zip->numFiles; $i++){
$name = $zip->getNameIndex($i)
if ($name === false) continue
if (dirname($name) === 'xl/worksheets') $sheets[filter_var($name, FILTER_SANITIZE_NUMBER_INT)] = $zip->getFromIndex($i)
elseif ($name === 'xl/sharedStrings.xml'){
$xml = $zip->getFromIndex($i)
if (!preg_match_all('/<t[^>]*>(.*?)<\/t>/s', $xml, $m)) error('Error reading shared lib in XLSX')
$shared = array_map(fn($t) => html_entity_decode($t, ENT_QUOTES | ENT_XML1, 'UTF-8'), $m[1])
}
elseif ($name === 'xl/workbook.xml'){
$xml = $zip->getFromIndex($i)
if (!preg_match_all('/<sheet[^>]*name="([^"]+)"[^>]*sheetId="([0-9]+)"/', $xml, $m)) error('Error reading XLSX workbook')
$sheetNames = $m[1]
}
}
$zip->close()
$toIndex = fn($letters) => array_reduce(str_split(strtoupper($letters)), fn($n, $c) => $n * 26 + ord($c) - 64, 0) - 1
$isShared = fn($attrs) => preg_match('/\bt="s"\b/', $attrs) === 1
foreach ($sheets AS $sheetID => $sheet){
$name = $sheetNames[$sheetID - 1] ?? 'Sheet '.$sheetID
if (!preg_match('/<row[^>]*>(.+)<\/row>/s', $sheet, $m)) error('Error parsing XLSX sheet')
$rowsXml = preg_split('/<\/row><row[^>]*>/', $m[1]) ?: []
$headerMap = []
$isHeader = true
foreach ($rowsXml AS $rowXml){
$rowXml = preg_replace('/<c([^>]*)\/>/', '<c$1></c>', $rowXml)
if (!preg_match_all('/<c r="([A-Z]+)[0-9]+"([^>]*)>(?:<f\b[^>]*\/?>)?(?:(?:<v>([^<]*)<\/v>)|(?:<is>.*?<t[^>]*>(.*?)<\/t>.*?<\/is>))?<\/c>/s', $rowXml, $mm)) error('Error parsing XLSX row')
if ($isHeader){
foreach (array_keys($mm[0]) AS $i){
$col = $toIndex($mm[1][$i])
$attrs = $mm[2][$i]
$valV = $mm[3][$i] ?? null
$valIS = $mm[4][$i] ?? null
$val = $valV !== null && $valV !== '' ? $valV : ($valIS !== null && $valIS !== '' ? html_entity_decode($valIS, ENT_QUOTES | ENT_XML1, 'UTF-8') : null)
$txt = $isShared($attrs) ? ($shared[$val] ?? null) : $val
$headerMap[$col] = $txt !== null && $txt !== '' ? $txt : 'col'.$col
}
$isHeader = false
}
else {
$rowArr = []
foreach (array_keys($mm[0]) AS $i){
$col = $toIndex($mm[1][$i])
$attrs = $mm[2][$i]
$valV = $mm[3][$i] ?? null
$valIS = $mm[4][$i] ?? null
$val = $valV !== null && $valV !== '' ? $valV : ($valIS !== null && $valIS !== '' ? html_entity_decode($valIS, ENT_QUOTES | ENT_XML1, 'UTF-8') : null)
$key = $headerMap[$col] ?? 'col'.$col
$rowArr[$key] = $isShared($attrs) ? ($shared[$val] ?? null) : $val
}
$this->objData[$name][] = $rowArr
}
}
}Lib Functions
function
button(...$args):string
/phlo/libs/form.tags.phlo line 5
Creëert een 'button'-element met de meegegeven arguments als attributen of inhoud.
tag('button', ...$args)function
decrypt($encrypted, $key):string
/phlo/libs/encryption.phlo line 6
Decodeert de base64-gecodeerde input en controleert of de decodering succesvol is en de lengte voldoende is. Zo ja, wordt de nonce uit de eerste bytes gehaald en wordt de rest gebruikt als ciphertext. Vervolgens wordt de geheime data ontsleuteld met een hash van de opgegeven sleutel en de sodium_crypto_secretbox_open-functie. Als een van de controles faalt, wordt false teruggegeven.
($d = base64_decode($encrypted, true)) !== false && strlen($d) >= SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ? sodium_crypto_secretbox_open(substr($d, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES), substr($d, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES), hash('sha256', $key, true)) : falsefunction
en($text, ...$args)
/phlo/libs/lang.phlo line 9
Deze node vertaalt de invoertekst naar het Engels, gebruikmakend van dezelfde vertaalfunctie met de opgegeven tekst en argumenten.
%lang->translation('en', $text, ...$args)function
encrypt($data, $key):string
/phlo/libs/encryption.phlo line 5
Deze code genereert een willekeurige nonce, versleutelt de data met behulp van een geheimsleutelfunctie, en encodeert het resultaat vervolgens in Base64 voor veilige transmissie of opslag.
base64_encode(($nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES)).sodium_crypto_secretbox($data, $nonce, hash('sha256', $key, true)))function
field($type, ...$args)
/phlo/libs/field.phlo line 6
Dit definieert een functie die een nieuw veld aanmaakt, waarbij de functie naam dynamisch wordt opgebouwd op basis van het type. Het gebruikt het `$type` argument en extra argumenten om een veld van dat specifieke type te genereren.
phlo("field_$type", ...$args, type: $type)function
input(...$args):string
/phlo/libs/form.tags.phlo line 6
Deze node genereert een HTML `<input>` element met de opgegeven argumenten als attributen of inhoud.
tag('input', ...$args)function
n8n($webhook, ?array $data = null, $test = false)
/phlo/libs/n8n.phlo line 5
Maakt een POST-verzoek naar een webhook-URL, gebaseerd op servergegevens en testmodus, met optionele data mee.
HTTP(%creds->n8n->server.'webhook'.($test ? '-test' : '').'/'.$webhook, POST: $data)function
n8n_test($webhook, ?array $data = null)
/phlo/libs/n8n.phlo line 6
Roep een functie genaamd 'n8n' aan met de webhook, een optionele data-array en een boolean parameter (waarschijnlijk voor een speciale modus of debug).
n8n($webhook, $data, true)function
nl($text, ...$args)
/phlo/libs/lang.phlo line 8
Translate de meegegeven tekst naar het Nederlands, eventueel met extra argumenten voor interpolatie of context.
%lang->translation('nl', $text, ...$args)function
select(...$args):string
/phlo/libs/form.tags.phlo line 7
Maakt een 'select' element aan met de gegeven argumenten.
tag('select', ...$args)function
textarea(...$args):string
/phlo/libs/form.tags.phlo line 8
Maakt een HTML `<textarea>` element aan met de meegegeven argumenten.
tag('textarea', ...$args)Phlo Functions
function
active(bool $cond, string $classList = void):string
/phlo/phlo.php line 265
Voert een voorwaarde uit waarbij, indien `$cond` waar is of `$classList` niet leeg, een class-attribuut wordt toegevoegd met de waarde van `$classList` en eventueel 'active' erachter, gescheiden door een spatie als `$classList` al bestaat en `$cond` waar is.
return $cond || $classList ? ' class="'.$classList.($cond ? ($classList ? space : void).'active' : void).'"' : void;function
age(int $time):int
/phlo/phlo.php line 266
Geeft het aantal seconden terug sinds het opgegeven tijdstip.
return time() - $time;function
age_human(int $age):string
/phlo/phlo.php line 267
Converteert een leeftijd in seconden naar een menselijk begrijpelijke weergave van leeftijd met behulp van een andere functie.
time_human(time() - $age);function
apcu($key, $cb, int $duration = 3600, bool $log = true):mixed
/phlo/phlo.php line 268
Deze code probeert een waarde op te halen uit APCu-cache of deze te genereren via een callback. Vervolgens wordt de waarde geretourneerd en indien loggen is ingeschakeld, wordt een debugbericht gestuurd met details over de cache sleutel en de inhoud of het type van de waarde.
return 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
apply(...$cmds):never
/phlo/phlo.php line 217
Voert een JSON-encoded response uit met de resultaten van `$cmds`, na het controleren van de CLI-modus, of wanneer headers al verzonden zijn, of bij streaming-context. Past debug toe op `$cmds` indien ingeschakeld, en stopt de verwerking daarna.
cli || headers_sent() || phlo('app')->streaming || [header('Content-Type: application/json'), header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'), header('Pragma: no-cache'), header('X-Content-Type-Options: nosniff')];
debug && $cmds = debug_apply($cmds);
die(json_encode($cmds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));function
arr(...$array):array
/phlo/phlo.php line 269
Verwijdert geen elementen en geeft de ontvangen array direct terug.
return $array;function
auth_log(string $user):int|false
/phlo/phlo.php line 270
Schrijft een regel naar het logbestand met de huidige datum en tijd, de gebruikersnaam en het IP-adres van de bezoeker, en voegt deze toe aan het einde van het bestand.
return file_put_contents(data.'access.log', date('j-n-Y H:i:s')." - $user - $_SERVER[REMOTE_ADDR]\n", FILE_APPEND);function
camel(string $text):string
/phlo/phlo.php line 271
Deze functie converteert een tekst naar camelCase door spaties te verwijderen en woorden met een hoofdletter te starten, waarna de eerste letter van het resultaat kleine wordt gemaakt.
return lcfirst(str_replace(space, void, ucwords(lcfirst($text))));function
chunk(...$cmds):void
/phlo/phlo.php line 222
Stelt een statische header in als deze nog niet is gezet, en stuurt vervolgens de meegegeven commando's als JSON-gestreamde data naar de client, gevolgd door het flushen van output buffers.
static $header;
$header ??= first(true, cli || headers_sent() || [http_response_code(206), header('Content-Type: text/event-stream'), header('Cache-Control: no-store'), header('X-Content-Type-Options: nosniff'), phlo('app')->streaming = true]);
echo json_encode($cmds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf;
cli || [@ob_flush(), flush()];function
create(iterable $items, Closure $keyCb, ?Closure $valueCb = null):array
/phlo/phlo.php line 272
Deze functie combineert de resultaten van twee iteraties: één met de key callback en één met de waarde callback (of de items zelf als geen waarde callback is opgegeven), en maakt daaruit een associatief array.
return array_combine(loop($items, $keyCb), $valueCb ? loop($items, $valueCb) : $items);function
debug(?string $msg = null)
/phlo/phlo.php line 273
Voegt de boodschap toe aan een statische debug-array als de debug-stand in- of uitgeschakeld is; retourneert de hele debug-collectie als geen bericht wordt meegegeven.
if (!debug) return;
static $debug = [];
if (is_null($msg)) return $debug;
$debug[] = $msg;function
dirs(string $path):array|false
/phlo/phlo.php line 279
Deze code retourneert een array van directorypaden die beginnen met de opgegeven `$path`, inclusief de afsluitende schuine streep voor elke directory.
return glob("$path*", GLOB_MARK | GLOB_ONLYDIR);function
DOM(string $body = void, string $head = void, string $lang = 'en', string $bodyAttrs = void):string
/phlo/phlo.php line 228
Deze node genereert een volledige HTML-pagina met doctype, html-tag met taal, head-sectie en body-sectie, waarbij de inhoud dynamisch wordt ingevuld via parameters.
return "<!DOCTYPE html>\n<html lang=\"$lang\">\n<head>\n$head</head>\n<body$bodyAttrs>\n$body\n</body>\n</html>";function
duration(int $decimals = 5, bool $float = false):string|float
/phlo/phlo.php line 280
Berekent de duur sinds het begin van de request en geeft deze weer met het aantal decimalen dat is aangegeven; optioneel wordt het resultaat afgerond en wordt een snelleheidsindicatie toegevoegd bij korte duur.
return last($d = microtime(true) - ($_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true)), $float ? round($d, $decimals) : rtrim(rtrim(sprintf("%.{$decimals}f", $d), '0'), dot).'s'.($d > 0 && $d < .5 ? ' ('.round(1 / $d).'/s)' : void));function
error(string $msg, int $code = 500):never
/phlo/phlo.php line 282
Worp een uitzondering op met een specifiek bericht en optioneel een code, standard op 500.
throw new PhloException($msg, $code);function
esc(string $string):string
/phlo/phlo.php line 281
Dit zorgt ervoor dat speciale HTML-tekens in de string worden gecodeerd, zodat ze veilig weergegeven worden in een HTML-context, inclusief quotes en met vervanging van ongeldige tekens.
return htmlspecialchars((string)$string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');function
files(string|array $paths, string $ext = '*.*'):array
/phlo/phlo.php line 283
Zoek alle bestanden met de opgegeven extensie in de gegeven paden en combineer de resultaten in één array.
return array_merge(...loop((array)$paths, fn($path) => glob("$path$ext")));function
first(...$args):mixed
/phlo/phlo.php line 284
Haalt het eerste element uit de lijst met argumenten.
return current($args);function
HTTP(string $url, array $headers = [], bool $JSON = false, $POST = null, $PUT = null, $PATCH = null, bool $DELETE = false, ?string $agent = null):string|false
/phlo/phlo.php line 247
Verwerkt de invoerparameters om een cURL-verzoek op te bouwen met de juiste HTTP-methode, headers, body en agent. Voert het verzoek uit en retourneert de response.
$curl = curl_init($url);
if ($POST || $PUT || $PATCH){
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 ? $_SERVER['HTTP_USER_AGENT'] : $agent);
curl_setopt_array($curl, [CURLOPT_HTTPHEADER => $headers, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 15, CURLOPT_ENCODING => void]);
$res = curl_exec($curl);
curl_close($curl);
return $res;function
indent(string $string, int $depth = 1):string
/phlo/phlo.php line 285
Voert tekst-indenting uit door herhaald tab-teken toe te voegen op basis van de diepte, en zorgt dat bestaande regels correct worden uitgelijnd zonder extra spaties aan het einde.
return ($tab = str_repeat(tab, $depth)).rtrim(strtr($string, [lf => lf.$tab]), tab);function
indentView(string $string, int $depth = 1):string
/phlo/phlo.php line 286
Voegt inspringingen toe aan een string op basis van de diepte, met extra tab-indelingen voor nieuwe regels die beginnen met een '<'.
return last($tab = str_repeat(tab, $depth), rtrim(preg_replace('/\n(\t*)</', "\n$1$tab<", $string), tab));function
json_read(string $file, ?bool $assoc = null):mixed
/phlo/phlo.php line 244
Laadt de inhoud van het opgegeven bestand, decodeert de JSON-gegevens en retourneert deze. Als het decoderen mislukt of het bestand niet gelezen kan worden, wordt een foutmelding gegenereerd.
return json_decode(file_get_contents($file), $assoc) ?? error('Error reading '.esc($file));function
json_write(string $file, $data, $flags = null):int|false
/phlo/phlo.php line 245
Schrijft de JSON-encoded data naar een bestand met exclusieve lock, zodat de gegevens veilig worden opgeslagen.
return file_put_contents($file, json_encode($data, $flags ?? jsonFlags), LOCK_EX);function
last(...$args):mixed
/phlo/phlo.php line 287
Geeft het laatste element van de opgegeven argumenten terug.
return end($args);function
location(?string $location = null):never
/phlo/phlo.php line 288
Deze code definieert een asynchrone functie die, afhankelijk van of een locatie is opgegeven, de gebruiker doorverwijst naar die locatie of terug naar de vorige pagina. Als geen locatie is meegegeven, wordt een standaardpad gebruikt.
async ? apply(location: $location ?? true) : [header('Location: '.($location ?? ($_SERVER['HTTP_REFERER'] ?? slash))), exit];function
loop(iterable $data, closure|array $cb, ?string $implode = null):mixed
/phlo/phlo.php line 289
Deze methode past een callback toe op elk element van een iterable, waarbij de callback kan bestaan uit een array met een object en een methode of gewoon een closure. Het resultaat is een array met de bewerkte waarden, of een geimplodeerde string als er een implode-parameter is opgegeven.
$return = [];
$isArray = is_array($cb);
foreach ($data AS $key => $value) $return[$key] = $isArray ? $cb[0]->{$cb[1]}($value, $key) : $cb($value, $key);
return is_null($implode) ? $return : implode($implode, $return);function
mime(string $filename):string
/phlo/phlo.php line 295
Geeft de MIME-type terug op basis van de bestandsextensie uit de naam, of via mime_content_type als het bestand bestaat; anders standaard 'application/octet-stream'.
return ['html' => 'text/html', 'css' => 'text/css', 'gif' => 'image/gif', 'ico' => 'image/x-icon', 'ini' => 'text/plain', 'js' => 'application/javascript', 'json' => 'application/json', 'jpg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jfif' => 'image/jpeg', 'ogg' => 'audio/ogg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'pdf' => 'application/pdf', 'phlo' => 'application/phlo', 'php' => 'application/x-httpd-php', 'png' => 'image/png', 'svg' => 'image/svg+xml', 'txt' => 'text/plain', 'webp' => 'image/webp'][pathinfo($filename, PATHINFO_EXTENSION)] ?? (is_file($filename) ? mime_content_type($filename) : 'application/octet-stream');function
obj(...$data):obj
/phlo/phlo.php line 296
Maakt een nieuw object aan met de meegegeven data als constructor-parameters.
return new obj(...$data);function
output(?string $content = null, ?string $filename = null, ?bool $attachment = null, ?string $file = null):never
/phlo/phlo.php line 229
Stelt de HTTP-headers in voor content-type, content-length en content-disposition (attachment of inline), en stuurt daarna de inhoud (bestand of string) naar de output voordat de script stopt.
header('Content-Type: '.mime($filename ?? basename($file ?? req)));
header('Content-Length: '.($file ? filesize($file) : strlen($content)));
if (is_bool($attachment) || $filename) header('Content-Disposition: '.($attachment ? 'attachment' : 'inline').';filename='.rawurlencode($filename ?? basename($file ?? req)));
$file ? readfile($file) : print($content);
exit;function
phlo(?string $phloName = null, ...$args):mixed
/phlo/phlo.php line 142
Deze functie beheert en retourneert objecten van een bepaalde naam, ondersteund door een cache. Als geen naam wordt doorgegeven, geeft het de lijst van beschikbare namen. Bij een specifieke naam wordt gecontroleerd of een speciale handle-methode bestaat en wordt deze aangeroepen, of wordt een nieuw object gemaakt en opgeslagen in de cache. Vervolgens wordt indien aanwezig de controller Methode aangeroepen voordat het object wordt geretourneerd.
static $list = [];
if (is_null($phloName)) return array_keys($list);
$phloName = strtr($phloName, [slash => us]);
$handle = method_exists($phloName, '__handle') ? $phloName::__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];
$phlo = new $phloName(...$args);
if ($handle) $list[$handle] = $phlo;
if ($phlo->hasMethod('controller') && (!cli || $phloName !== 'app')) $phlo->controller();
return $phlo;function
phlo_app(...$args)
/phlo/phlo.php line 112
Initialiseert standaardwaarden voor build, debug, data, php en www op basis van invoerparameters; definieert constante waarden en configureert autoloading van klassen via een class map en fallback naar bestandspaden; laadt afhankelijkheden zoals Composer autoload; stelt een requireredirectie en buildcontrole in; registreert fout- en uitzonderingshandlers; voert de 'app' node uit en behandelt CLI-uitvoer of uitzonderingen.
$args['build'] ??= false;
$args['debug'] ??= false;
$args['data'] ??= "$args[app]data/";
$args['php'] ??= $args['app'].($args['build'] ?? null ? 'php/' : void);
$args['www'] ??= "$args[app]www/";
foreach ($args AS $key => $value) define($key, $value);
spl_autoload_register(static function($class){
static $map;
$map ??= is_file($file = php.'classmap.php') ? require($file) : [];
if (isset($map[$class])) return require(php.$map[$class]);
if ($map) return build && debug && ($lib = phlo_find_lib('class', $class)) && phlo_activate_lib($lib);
if (is_file($file = php.strtr($class, [us => dot]).'.php')) return require($file);
});
defined('composer') && require_once(composer.'vendor/autoload.php');
define('req', cli ? implode(slash, $cli = array_slice($_SERVER['argv'], 1)) : rawurldecode(substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 1)));
debug && require(__DIR__.'/debug.php');
build && phlo_build_check() && (build['auto'] ?? true) && [require_once(__DIR__.'/build.php'), phlo_build()];
set_error_handler(static function($severity, $message, $file = null, $line = 0){
if (!(error_reporting() & $severity)) return false;
throw new PhloException($message, $severity, ['file' => $file, 'line' => $line]);
});
set_exception_handler('phlo_exception');
try {
phlo('app');
cli && print((strpos($cb = array_shift($cli), '::') ? $cb(...$cli) : [phlo($cb), array_shift($cli)](...$cli)).lf);
}
catch (Throwable $e){ phlo_exception($e); }function
phlo_app_jsonfile(string $app, string $file)
/phlo/phlo.php line 111
Laadt de inhoud van een bestand, vervangt een specifieke string om het pad aan te passen, decodeert de JSON-gegevens en roept vervolgens een functie aan met deze gegevens en de naam van de applicatie.
phlo_app(...json_decode(strtr(file_get_contents($file), ['"%app/' => dq.$app]), true), app: $app);function
phlo_async(string $obj, string $call, ...$args):bool
/phlo/phlo.php line 160
Voert een asynchrone bewerking uit door een externe 'phlo_exec' functie aan te roepen met de meegegeven objectnaam, call-naam en eventuele extra argumenten, zonder te wachten op het resultaat.
return phlo_exec(www, $obj, $call, false, ...$args);function
phlo_build_check():bool
/phlo/phlo.php line 162
Deze code controleert of het bestand 'app.php' niet bestaat of dat de laatst gewijzigde tijd van dit bestand ouder is dan de meest recente wijzigingstijd van de bronnen in 'phlo_sources()'.
return !is_file($app = php.'app.php') || filemtime($app) < array_reduce(phlo_sources(), fn($a, $f) => max($a, @filemtime($f)), 0);function
phlo_error_log(string $path, string $msg):int|false
/phlo/phlo.php line 95
Voegt een foutmelding toe aan een JSON-logbestand door een unieke ID te genereren op basis van het pad en bericht; houdt het aantal keren dezelfde fout voorkomt bij, en registreert de laatste keer dat de fout optrad.
$file = data.'errors.json';
$now = date('j-n-Y G:i:s');
$id = md5($path.preg_replace('/\s+/', void, trim(preg_replace('~(?:[A-Za-z]:)?[\\/](?:[^\s:/\\\\]+[\\/])*(?:([^\s:/\\\\]+\.[A-Za-z0-9]{1,8})|[^\s:/\\\\]+)(?::\d+)?~', '$1', $msg))));
$map = is_file($file) ? (json_read($file, true) ?: []) : [];
$row = $map[$id] ?? [];
$row['file'] = $path;
$row['req'] = req;
$row['msg'] = $msg;
$row['count'] = ($map[$id]['count'] ?? 0) + 1;
$row['lastOccured'] = $now;
unset($map[$id]);
$map = [...[$id => $row], ...$map];
return json_write($file, $map);function
phlo_exception(Throwable $e):never
/phlo/phlo.php line 67
Foutafhandelingsfunctie die een Throwable verwerkt door melding te loggen, condities voor debugging en ontwikkelomgeving, en vervolgens de juiste output (console, API, of HTML-pagina) te genereren op basis van de context.
$msg = $e->getMessage();
static $retried = false;
if (build && debug && preg_match('/^Call to undefined function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/', $msg, $m) && !$retried && ($lib = phlo_find_lib('function', $m[1])) && phlo_activate_lib($lib)) location(slash.req);
$code = (int)$e->getCode();
$payload = $e instanceof PhloException ? $e->payload() : ['error' => $msg, 'code' => ($code ?: 500), 'type' => get_class($e), 'data' => ['file' => $e->getFile(), 'line' => $e->getLine()]];
if (phlo('app')->hasMethod('errorPage')) phlo('app')->errorPage($msg, (int)($payload['code'] ?? 500));
$d = is_array($payload['data'] ?? null) ? $payload['data'] : [];
$file = $d['file'] ?? $e->getFile();
$line = (int)($d['line'] ?? $e->getLine());
$short = shortpath($file).colon.$line;
phlo_error_log($short, $msg);
if (debug) debug_error($e);
if (cli || async){
$text = ($payload['type'] ?? 'Error').colon.space.$msg;
if (async) apply(error: $text);
fwrite(STDERR, $text.lf);
exit(1);
}
http_response_code($code = $payload['code'] ?? 500);
header('X-Content-Type-Options: nosniff');
$title = "Phlo $code Error";
$CSS = 'body{background:black;color:white;font-family:sans-serif;text-align:center;margin-top:18dvh}pre{white-space:pre-wrap}';
$body = '<h1>'.esc($title).'</h1><pre>'.esc(($payload['type'] ?? 'Error').colon.space.$msg).'</pre>';
print(DOM($body, tag('title', $title).lf.tag('style', $CSS)));
exit(1);function
phlo_exec(string $path, string $obj, string $call, bool $sync = true, ...$args):string|bool
/phlo/phlo.php line 158
Voert een PHP-script uit via de commandoregel met opgegeven parameters, en retourneert de output of voert de opdracht asynchroon uit zonder output.
return last(exec('/usr/bin/php '.escapeshellarg(rtrim($path, slash).'/app.php').' '.escapeshellarg($obj).space.escapeshellarg($call).loop($args, fn($arg) => space.escapeshellarg((string)$arg), void).($sync ? void : ' > /dev/null 2>&1 &'), $res), $sync ? implode(lf, $res) : true);function
phlo_exists(string $obj):bool
/phlo/phlo.php line 161
Controleert of er een PHP-bestand bestaat dat overeenkomt met de naam, door de punt-vervanging uit te voeren en vervolgens te controleren op het bestaan van het bestand.
return is_file(php.strtr($obj, [us => dot]).'.php');function
phlo_sources():array
/phlo/phlo.php line 163
Verzamelt .phlo-bestanden uit een opgegeven bronmap of app-map, voegt vervolgens extra bibliotheekbestanden toe als ze bestaan, sorteert de lijst hoofdletterongevoelig en geeft de geordende lijst terug.
$sources = files(isset(build['sources']) ? build['sources'] : app, '*.phlo');
foreach (build['libs'] AS $lib) $sources[] = is_file($file = __DIR__."/libs/$lib.phlo") ? $file : error('Build Error: Library not found '.esc($lib));
natcasesort($sources);
return $sources;function
phlo_sync(string $obj, string $call, ...$args):string
/phlo/phlo.php line 159
Dit is een functie die een andere functie of methode aanroept met een gespecificeerd object en call, door gebruik te maken van een globale uitvoerfunctie.
return phlo_exec(www, $obj, $call, true, ...$args);function
regex(string $pattern, string $subject, int $flags = 0, int $offset = 0):array
/phlo/phlo.php line 297
Voert een reguliere expressie uit op een string en geeft de matches terug als array; retourneert een lege array als geen match wordt gevonden.
return preg_match($pattern, $subject, $match, $flags, $offset) ? $match : [];function
regex_all(string $pattern, string $subject, int $flags = 0, int $offset = 0):array
/phlo/phlo.php line 298
Voert een reguliere expressie uit op de tekst en retourneert alle gevonden matches in een array; indien geen matches worden gevonden, wordt een lege array geretourneerd.
return preg_match_all($pattern, $subject, $matches, $flags, $offset) ? $matches : [];function
req(int $index, ?int $length = null):mixed
/phlo/phlo.php line 299
Haalt een specifiek deel uit een URL door de string op te splitsen, en geeft dat deel terug. Als een lengte is opgegeven, worden meerdere delen samengevoegd tot één string.
static $parts;
$parts ??= explode(slash, req);
return is_null($length) ? ($parts[$index] ?? null) : (implode(slash, array_slice($parts, $index, $length < 0 ? null : $length)) ?: null);function
route(?string $method = null, string $path = void, ?bool $async = null, ?string $data = null, ?string $cb = null)
/phlo/phlo.php line 304
De code valideert en verwerkt een inkomend verzoek op basis van method, asynchronous vlag, payload-gegevens en URL-paadonderdelen. Het filtert het verzoek, haalt variabelen uit het pad, controleert op patronen zoals parameters met default-waarden, vaste waarden, of gedefinieerde lijsten, en verzamelt deze in argumenten voor een callback. Als alle controles slagen, wordt de callback aangeroepen met de verzamelde argumenten.
if ($method && $method !== method) return;
if (!is_null($async) && $async !== async) return;
if ($data && phlo('payload')->objKeys !== explode(comma, $data)) return;
$req = array_filter(explode(slash, req));
$cbArgs = [];
$index = -1;
foreach (array_filter(explode(space, $path)) AS $index => $item){
$reqItem = req($index);
if (strpos($item, '$') === 0){
$item = substr($item, 1);
if (str_ends_with($item, '=*')){
$cbArgs[substr($item, 0, -2)] = implode(slash, array_slice($req, $index));
$index = count($req) - 1;
break;
}
elseif (str_ends_with($item, qm)){
$item = substr($item, 0, -1);
if ($reqItem && $item !== $reqItem) return;
$reqItem = $item === $reqItem;
}
elseif (str_contains($item, eq)){
list ($item, $default) = explode(eq, $item, 2);
$default = $default ?: null;
}
elseif (is_null($reqItem)) return;
if (str_contains($item, dot) && (list($item, $length) = explode(dot, $item, 2)) && strlen($reqItem) != $length) return false;
if (str_contains($item, colon)){
(list ($item, $list) = explode(colon, $item, 2)) && $list = explode(comma, $list);
if (!$reqItem || in_array($reqItem, $list)) $cbArgs[$item] = $reqItem ?: $default ?? null;
else return;
}
else $cbArgs[$item] = $reqItem ?? $default;
}
elseif ($item !== $reqItem) return;
}
if (isset($req[$index + 1])) return;
if (!$cb) return obj(...$cbArgs);
if ($cb(...$cbArgs) === false) return;
exit;function
shortpath(?string $file):string
/phlo/phlo.php line 345
Het haalt het tweede laatst gebruikte deel en het laatste deel van een pad op, gescheiden door een slash, tenzij het pad minder dan twee onderdelen heeft, dan wordt alleen het laatste deel geretourneerd. Bij lege invoer wordt 'unknown' teruggegeven.
if (!$file) return 'unknown';
$p = explode(slash, str_replace(bs, slash, $file));
$n = count($p);
return $n >= 2 ? $p[$n - 2].slash.$p[$n - 1] : end($p);function
size_human(int $size, int $precision = 0):string
/phlo/phlo.php line 351
Deze functie converteert een grootte in bytes naar een menselijke leesbare formaat, door het getal te delen door 1024 totdat het binnen het juiste bereik valt, en voegt vervolgens de juiste schaal (B, KB, MB, GB, TB) toe met de opgegeven precisie.
foreach (['b', 'Kb', 'Mb', 'Gb', 'Tb'] AS $range){
if ($size / 1024 < 1) break;
$size /= 1024;
}
return round($size, $precision).$range;function
slug(string $text):string
/phlo/phlo.php line 358
Deze code converteert tekst naar een URL-vriendelijke slug: het zet tekst om naar kleine letters, verwijdert accenten, vervangt niet-alfanumerieke tekens door koppeltekens en verwijdert overbodige koppeltekens aan beide zijden.
return trim(preg_replace('/[^a-z0-9]+/', dash, strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text))), dash);function
tag(string $tagName, ?string $inner = null, ...$args):string
/phlo/phlo.php line 236
Maakt een HTML-tag met opgegeven tagnaam en optionele inhoud, en voegt niet-NULL extra attributen toe als tag- of attribuutwaarden.
return "<$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
time_human(?int $time = null):string
/phlo/phlo.php line 359
Deze code berekent een menselijke tijdweergave door een gegeven timestamp te vergelijken met de huidige tijd en deze om te zetten in een geschikt tijdsindeling (zoals dagen, weken, maanden). Het gebruikt een lijst van labels en bijbehorende multipliers om de leeftijd te schalen naar een begrijpelijke eenheid, en stopt wanneer de leeftijd onder een bepaalde drempel ligt.
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
title(?string $title = null, string $implode = ' - '):string
/phlo/phlo.php line 237
Voert een statische lijst van titels bij, voegt een meegegeven titel toe als die aanwezig is, of gebruikt de huidige app-titel of een standaardwaarde en combineert de lijst tot een enkele string met een gespecificeerd scheidingsteken.
static $titles = [];
if ($title) return $titles[] = $title;
$titles[] = phlo('app')->title ?: 'Phlo '.phlo;
return implode($implode, $titles);function
token(int $length = 8, ?string $input = null, ?string $sha1 = null):string
/phlo/phlo.php line 369
Genereert een willekeurig token van een opgegeven lengte door eerst een SHA1-hash te creëren op basis van input, een getal, of een random getal, en vervolgens letters te concatenëren op basis van de hash-waarden totdat de gewenste lengte bereikt is.
$sha1 ??= sha1($input ?? random_int(date('Y'), PHP_INT_MAX), true);
$token = void;
for ($i = 0; strlen($token) < $length; $i++) $token .= chr(ord('a') + (ord($sha1[$i % 20]) % 26));
return $token;function
view(?string $body = null, ?string $title = null, array|string $css = [], array|string $js = [], array|string $defer = [], array|string $options = [], array $settings = [], ?string $ns = null, bool|string $uri = req, ...$cmds):void
/phlo/phlo.php line 170
Deze node handelt het genereren van een HTML-pagina en het configureren van resources en metadata. Het voegt CSS- en JS-bestanden toe, stelt meta-titels en andere head-elementen in, handelt preloading en defer opties af, en verzorgt de HTML-structuur inclusief body-attributen. Bij async verwerking wordt belangrijke conta
nt via `$cmds` meegegeven en toegepast.
if (cli) return;
!async && !is_bool($uri) && $uri !== req && location("/$uri");
$app = phlo('app');
$title && title($title);
$css = array_merge((array)$css, (array)$app->css);
$js = array_merge((array)$js, (array)$app->js);
$defer = array_merge((array)$defer, (array)$app->defer);
$options = implode(space, array_merge((array)$options, (array)$app->options, debug ? ['debug'] : []));
$settings = array_merge($settings, (array)$app->settings);
if (async){
$uri !== false && $cmds['uri'] = $uri;
$cmds['trans'] ??= true;
$cmds['title'] = title();
$css && $cmds['css'] = $css;
$js && $cmds['js'] = $js;
$defer && $cmds['defer'] = $defer;
$cmds['options'] = $options;
$cmds['settings'] = $settings;
!is_null($body) && $cmds['inner']['body'] = $body;
apply(...$cmds);
}
$body ??= $cmds['main'] ?? void;
debug && $body .= lf.debug_render();
$ns ??= $app->ns ?? 'app';
$link = $app->link ?: [];
$head = tag('title', inner: title()).lf;
$head .= '<meta name="viewport" content="'.($cmds['viewport'] ?? $app->viewport ?? 'width=device-width').'">'.lf;
$app->description && $head .= "<meta name=\"description\" content=\"$app->description\">\n";
$app->themeColor && $head .= "<meta name=\"theme-color\" content=\"$app->themeColor\">\n";
$app->image && $head .= "<meta property=\"og:image\" content=\"$app->image\">\n";
is_file(www.$filename = 'favicon.ico') && $head .= "<link rel=\"favicon\" href=\"/$filename?".version."\">\n";
is_file(www.$filename = 'manifest.json') && $head .= "<link rel=\"manifest\" href=\"/$filename?".version."\">\n";
is_file(www.$filename = 'icons.png') && $link[] = "</$filename?".version.">; rel=preload; as=image";
$app->head && $head .= $app->head.lf;
foreach ($css AS $item) $head .= '<link rel="stylesheet" href="'.esc($item).'">'.lf;
is_file(www.$filename = "$ns.css") && [$link[] = "</$filename?".version.">; rel=preload; as=style", $head .= '<link rel="stylesheet" href="'.esc(slash.$filename.qm.version).'">'.lf];
foreach ($js AS $item) $head .= '<script src="'.esc($item).'"></script>'.lf;
foreach ($defer AS $item) $head .= '<script src="'.esc($item).'" defer></script>'.lf;
is_file(www.$filename = "$ns.js") && [$link[] = "</$ns.js?".version.">; rel=preload; as=script", $head .= '<script src="'.esc(slash.$filename.qm.version).'" defer></script>'.lf];
!build && $link && header('Link: '.implode(comma, $link), false);
if ($lang = $cmds['lang'] ?? $app->lang ?? 'en') unset($cmds['lang']);
$bodyAttrs = void;
$options && $bodyAttrs .= " class=\"$options\"";
$settings && $bodyAttrs .= loop($settings, fn($value, $key) => ' data-'.$key.'="'.esc($value).'"', void);
die(DOM($body, $head, $lang, $bodyAttrs));