Array
(
[message] => Vvveb\prepareJson(): Argument #1 ($array) must be of type array, null given, called in /home/www/vvveb/demo/system/component/component.php on line 575
[file] => /home/www/vvveb/demo/system/functions.php
[line_no] => 2945
[line] => function prepareJson(array $array): array { // <==
[lines] => Array
(
[0] => * Encodes attribute markers (@, @@), preserves empty arrays/objects, booleans, integers, and nulls
[1] => * using type suffix markers (--array, --boolean, --int, --null, --object).
[2] => *
[3] => * @param array $array The array to prepare
[4] => *
[5] => * @return array The prepared array with encoded keys
[6] => */
[7] => function prepareJson(array $array): array { // <==
[8] => if (! is_array($array)) {
[9] => //return;
[10] => }
[11] => $helper = [];
[12] =>
[13] => foreach ($array as $key => $value) {
)
[trace] => #0 /home/www/vvveb/demo/system/component/component.php(575): Vvveb\prepareJson()
#1 /home/www/vvveb/demo/system/component/component.php(709): Vvveb\System\Component\Component->loadTemplateDocument()
#2 /home/www/vvveb/demo/system/component/component.php(127): Vvveb\System\Component\Component->generateRequiredComponents()
#3 /home/www/vvveb/demo/system/core/view.php(521): Vvveb\System\Component\Component->__construct()
#4 /home/www/vvveb/demo/plugins/seo/app.php(81): Vvveb\System\Core\View->render()
#5 /home/www/vvveb/demo/plugins/seo/app.php(178): Vvveb\Plugins\Seo\App->setSchema()
#6 /home/www/vvveb/demo/plugins/seo/app.php(213): Vvveb\Plugins\Seo\App->pageType()
#7 [internal function]: Vvveb\Plugins\Seo\App->page()
#8 /home/www/vvveb/demo/system/event.php(105): call_user_func_array()
#9 /home/www/vvveb/demo/app/controller/content/post.php(182): Vvveb\System\Event::trigger()
#10 [internal function]: Vvveb\Controller\Content\Post->index()
#11 /home/www/vvveb/demo/system/core/frontcontroller.php(256): call_user_func()
#12 /home/www/vvveb/demo/system/core/frontcontroller.php(329): Vvveb\System\Core\FrontController::call()
#13 /home/www/vvveb/demo/system/core/frontcontroller.php(403): Vvveb\System\Core\FrontController::redirect()
#14 /home/www/vvveb/demo/system/core/startup.php(541): Vvveb\System\Core\FrontController::dispatch()
#15 /home/www/vvveb/demo/index.php(170): Vvveb\System\Core\start()
#16 /home/www/vvveb/demo/public/index.php(28): include('...')
#17 {main}
[code] => Array
(
[0] =>
[2] => /**
[3] => * Vvveb
[4] => *
[5] => * Copyright (C) 2022 Ziadin Givan
[6] => *
[7] => * This program is free software: you can redistribute it and/or modify
[8] => * it under the terms of the GNU Affero General Public License as
[9] => * published by the Free Software Foundation, either version 3 of the
[10] => * License, or (at your option) any later version.
[11] => *
[12] => * This program is distributed in the hope that it will be useful,
[13] => * but WITHOUT ANY WARRANTY; without even the implied warranty of
[14] => * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
[15] => * GNU Affero General Public License for more details.
[16] => *
[17] => * You should have received a copy of the GNU Affero General Public License
[18] => * along with this program. If not, see .
[19] => *
[20] => */
[21] =>
[22] => namespace Vvveb;
[23] =>
[24] => /**
[25] => * Generate a URL from a route name or array of parameters.
[26] => *
[27] => * @param string|array $parameters Route name or URL parameters
[28] => * @param array|bool $mergeParameters Parameters to merge with existing URL parameters
[29] => * @param bool $useCurrentUrl Whether to use the current URL as base
[30] => *
[31] => * @return string The generated URL
[32] => */
[33] => function url(string|array $parameters, array|bool $mergeParameters = false, bool $useCurrentUrl = true): string {
[34] => if (is_string($parameters) && $parameters) {
[35] => $result = '';
[36] =>
[37] => if (isset($mergeParameters['scheme'])) {
[38] => $result .= $mergeParameters['scheme'] . ':';
[39] => unset($mergeParameters['scheme']);
[40] => }
[41] =>
[42] => if (isset($mergeParameters['host'])) {
[43] => $result .= '//' . \Vvveb\System\Sites::url($mergeParameters['host']);
[44] => unset($mergeParameters['host']);
[45] =>
[46] => if (isset($mergeParameters['scheme'])) {
[47] => $result = $mergeParameters['scheme'] . ":$result";
[48] => unset($mergeParameters['scheme']);
[49] => }
[50] => }
[51] =>
[52] => $path = $mergeParameters['path'] ?? SITE_PATH;
[53] =>
[54] => $url = System\Routes::url($parameters, $mergeParameters);
[55] => $result .= (V_SUBDIR_INSTALL ? V_SUBDIR_INSTALL : '') . $path . ($url ?? '');
[56] => } else {
[57] => static $url = '';
[58] => static $urlParams = [];
[59] =>
[60] => if ($useCurrentUrl && ! $url) {
[61] => $url = parse_url($_SERVER['REQUEST_URI'] ?? '');
[62] =>
[63] => if (isset($url['query'])) {
[64] => parse_str($url['query'], $urlParams);
[65] => }
[66] => }
[67] =>
[68] => if (! is_array($parameters)) {
[69] => $parameters = [];
[70] => }
[71] =>
[72] => if ($mergeParameters) {
[73] => if (is_array($mergeParameters)) {
[74] => $mergeParameters = array_merge($urlParams, $mergeParameters);
[75] => } else {
[76] => $mergeParameters = $urlParams;
[77] => }
[78] =>
[79] => if (is_array($mergeParameters)) {
[80] => $parameters = array_merge($mergeParameters, $parameters);
[81] => }
[82] => }
[83] =>
[84] => $result = '';
[85] => $path = $parameters['path'] ?? SITE_PATH;
[86] =>
[87] => if (isset($parameters['host'])) {
[88] => $result .= '//' . \Vvveb\System\Sites::url($parameters['host']);
[89] => unset($parameters['host']);
[90] =>
[91] => if (isset($parameters['scheme'])) {
[92] => $result = $parameters['scheme'] . ":$result";
[93] => unset($parameters['scheme']);
[94] => }
[95] => } else {
[96] => if (! $useCurrentUrl) {
[97] => $result .= (V_SUBDIR_INSTALL ? V_SUBDIR_INSTALL : '') . $path;
[98] => }
[99] => }
[100] =>
[101] => $result .= ($useCurrentUrl ? $url['path'] ?? '' : '') . ($parameters ? '?' . urldecode(http_build_query($parameters)) : '');
[102] => }
[103] =>
[104] => return $result;
[105] => }
[106] =>
[107] => /**
[108] => * Get a configuration value or the Config instance.
[109] => *
[110] => * @param string|null $key Config key to retrieve, or null for the Config instance
[111] => * @param mixed $default Default value if key is not found
[112] => *
[113] => * @return mixed Configuration value or Config instance
[114] => */
[115] => function config(?string $key = null, mixed $default = null): mixed {
[116] => if (is_null($key)) {
[117] => return System\Config::getInstance();
[118] => }
[119] =>
[120] => return System\Config::getInstance()->get($key, $default);
[121] => }
[122] =>
[123] => /**
[124] => * Get a configuration value.
[125] => *
[126] => * @param string|null $key Config key to retrieve, or null for all config
[127] => * @param mixed $default Default value if key is not found
[128] => *
[129] => * @return mixed Configuration value
[130] => */
[131] => function getConfig(?string $key = null, mixed $default = null): mixed {
[132] => return System\Config::getInstance()->get($key, $default);
[133] => }
[134] =>
[135] => /**
[136] => * Set a configuration value.
[137] => *
[138] => * @param string $key Config key to set
[139] => * @param mixed $value Value to set
[140] => *
[141] => * @return bool True on success
[142] => */
[143] => function setConfig(string $key, mixed $value = null): bool {
[144] => return System\Config::getInstance()->set($key, $value);
[145] => }
[146] =>
[147] => /**
[148] => * Remove a configuration value.
[149] => *
[150] => * @param string $key Config key to remove
[151] => *
[152] => * @return mixed Result of the unset operation
[153] => */
[154] => function unsetConfig(string $key): mixed {
[155] => return System\Config::getInstance()->unset($key);
[156] => }
[157] =>
[158] => /**
[159] => * Get a setting value by namespace and key.
[160] => *
[161] => * @param string $namespace Setting namespace
[162] => * @param string|array|null $key Setting key or array of keys
[163] => * @param mixed $default Default value if key is not found
[164] => * @param int $site_id Site ID
[165] => *
[166] => * @return mixed Setting value
[167] => */
[168] => function getSetting(string $namespace, string|array|null $key = null, mixed $default = null, int $site_id = SITE_ID): mixed {
[169] => return System\Setting::getInstance()->get($namespace, $key, $default, $site_id);
[170] => }
[171] =>
[172] => /**
[173] => * Set a setting value.
[174] => *
[175] => * @param string $namespace Setting namespace
[176] => * @param string|null $key Setting key
[177] => * @param mixed $value Value to set
[178] => * @param int $site_id Site ID
[179] => *
[180] => * @return mixed Result of the set operation
[181] => */
[182] => function setSetting(string $namespace, ?string $key = null, mixed $value = null, int $site_id = SITE_ID): mixed {
[183] => return System\Setting::getInstance()->set($namespace, $key, $value, $site_id);
[184] => }
[185] =>
[186] => /**
[187] => * Delete a setting.
[188] => *
[189] => * @param string $namespace Setting namespace
[190] => * @param string|null $key Setting key
[191] => * @param int $site_id Site ID
[192] => *
[193] => * @return mixed Result of the delete operation
[194] => */
[195] => function deleteSetting(string $namespace, ?string $key = null, int $site_id = SITE_ID): mixed {
[196] => return System\Setting::getInstance()->delete($namespace, $key, $site_id);
[197] => }
[198] =>
[199] => /**
[200] => * Set multiple settings at once.
[201] => *
[202] => * @param string $namespace Setting namespace
[203] => * @param array $settings Array of key-value pairs to set
[204] => * @param int $site_id Site ID
[205] => *
[206] => * @return mixed Result of the set operation
[207] => */
[208] => function setMultiSetting(string $namespace, array $settings, int $site_id = SITE_ID): mixed {
[209] => return System\Setting::getInstance()->setMulti($namespace, $settings, $site_id);
[210] => }
[211] =>
[212] => /**
[213] => * Get multiple setting content values.
[214] => *
[215] => * @param int $site_id Site ID
[216] => * @param string $namespace Setting namespace
[217] => * @param array|string|null $key Setting key or array of keys
[218] => * @param array $default Default value if keys are not found
[219] => * @param int|false $language_id Language ID or false for default
[220] => *
[221] => * @return mixed Setting content values
[222] => */
[223] => function getMultiSettingContent(int $site_id, string $namespace, array|string|null $key = null, array $default = [], int|false $language_id = false): mixed {
[224] => return System\Meta\SettingContent::getInstance()->getMulti($site_id, $namespace, $key, $default, $language_id);
[225] => }
[226] =>
[227] => /**
[228] => * Set multiple setting content values.
[229] => *
[230] => * @param int $site_id Site ID
[231] => * @param mixed $meta Setting content data to set
[232] => *
[233] => * @return mixed Result of the set operation
[234] => */
[235] => function setMultiSettingContent(int $site_id, mixed $meta): mixed {
[236] => return System\Meta\SettingContent::getInstance()->setMulti($site_id, $meta);
[237] => }
[238] =>
[239] => /**
[240] => * Get post metadata.
[241] => *
[242] => * @param int $post_id Post ID
[243] => * @param string $namespace Metadata namespace
[244] => * @param string|null $key Metadata key
[245] => * @param mixed $default Default value if key is not found
[246] => *
[247] => * @return mixed Metadata value
[248] => */
[249] => function getPostMeta(int $post_id, string $namespace, string|null $key = null, mixed $default = null): mixed {
[250] => return System\Meta\PostMeta::getInstance()->get($post_id, $namespace, $key, $default);
[251] => }
[252] =>
[253] => /**
[254] => * Set post metadata.
[255] => *
[256] => * @param int $post_id Post ID
[257] => * @param string $namespace Metadata namespace
[258] => * @param string|null $key Metadata key
[259] => * @param mixed $value Value to set
[260] => *
[261] => * @return mixed Result of the set operation
[262] => */
[263] => function setPostMeta(int $post_id, string $namespace, string|null $key = null, mixed $value = null): mixed {
[264] => return System\Meta\PostMeta::getInstance()->set($post_id, $namespace, $key, $value);
[265] => }
[266] =>
[267] => /**
[268] => * Delete post metadata.
[269] => *
[270] => * @param int $post_id Post ID
[271] => * @param string $namespace Metadata namespace
[272] => * @param string|null $key Metadata key
[273] => *
[274] => * @return mixed Result of the delete operation
[275] => */
[276] => function deletePostMeta(int $post_id, string $namespace, string|null $key = null): mixed {
[277] => return System\Meta\PostMeta::getInstance()->delete($post_id, $namespace, $key);
[278] => }
[279] =>
[280] => /**
[281] => * Set multiple post metadata values at once.
[282] => *
[283] => * @param int $post_id Post ID
[284] => * @param string $namespace Metadata namespace
[285] => * @param mixed $meta Metadata data to set
[286] => *
[287] => * @return mixed Result of the set operation
[288] => */
[289] => function setMultiPostMeta(int $post_id, string $namespace, mixed $meta): mixed {
[290] => return System\Meta\PostMeta::getInstance()->setMulti($post_id, $namespace, $meta);
[291] => }
[292] =>
[293] => /**
[294] => * Get post content metadata.
[295] => *
[296] => * @param int $post_id Post ID
[297] => * @param string $namespace Metadata namespace
[298] => * @param string|null $key Metadata key
[299] => * @param mixed $default Default value if key is not found
[300] => * @param int|false $language_id Language ID or false for default
[301] => *
[302] => * @return mixed Metadata value
[303] => */
[304] => function getPostContentMeta(int $post_id, string $namespace, string|null $key = null, mixed $default = null, int|false $language_id = false): mixed {
[305] => return System\Meta\PostContentMeta::getInstance()->get($post_id, $namespace, $key, $default, $language_id);
[306] => }
[307] =>
[308] => /**
[309] => * Get multiple post content metadata values.
[310] => *
[311] => * @param int $post_id Post ID
[312] => * @param string $namespace Metadata namespace
[313] => * @param array|string|null $key Metadata key or array of keys
[314] => * @param array $default Default value if keys are not found
[315] => * @param int|false $language_id Language ID or false for default
[316] => *
[317] => * @return mixed Metadata values
[318] => */
[319] => function getMultiPostContentMeta(int $post_id, string $namespace, array|string|null $key = null, array $default = [], int|false $language_id = false): mixed {
[320] => return System\Meta\PostContentMeta::getInstance()->getMulti($post_id, $namespace, $key, $default, $language_id);
[321] => }
[322] =>
[323] => /**
[324] => * Get multiple product content metadata values.
[325] => *
[326] => * @param int $product_id Product ID
[327] => * @param string $namespace Metadata namespace
[328] => * @param array|string|null $key Metadata key or array of keys
[329] => * @param array $default Default value if keys are not found
[330] => * @param int|false $language_id Language ID or false for default
[331] => *
[332] => * @return mixed Metadata values
[333] => */
[334] => function getMultiProductContentMeta(int $product_id, string $namespace, array|string|null $key = null, array $default = [], int|false $language_id = false): mixed {
[335] => return System\Meta\ProductContentMeta::getInstance()->getMulti($product_id, $namespace, $key, $default, $language_id);
[336] => }
[337] =>
[338] => /**
[339] => * Set post content metadata.
[340] => *
[341] => * @param int $post_id Post ID
[342] => * @param string $namespace Metadata namespace
[343] => * @param string|null $key Metadata key
[344] => * @param mixed $value Value to set
[345] => * @param int|false $language_id Language ID or false for default
[346] => *
[347] => * @return mixed Result of the set operation
[348] => */
[349] => function setPostContentMeta(int $post_id, string $namespace, string|null $key = null, mixed $value = null, int|false $language_id = false): mixed {
[350] => return System\Meta\PostContentMeta::getInstance()->set($post_id, $namespace, $key, $value, $language_id);
[351] => }
[352] => /**
[353] => * Set product content metadata.
[354] => *
[355] => * @param int $product_id Product ID
[356] => * @param string $namespace Metadata namespace
[357] => * @param string|null $key Metadata key
[358] => * @param mixed $value Value to set
[359] => * @param int|false $language_id Language ID or false for default
[360] => *
[361] => * @return mixed Result of the set operation
[362] => */
[363] => function setProductContentMeta(int $product_id, string $namespace, string|null $key = null, mixed $value = null, int|false $language_id = false): mixed {
[364] => return System\Meta\ProductContentMeta::getInstance()->set($product_id, $namespace, $key, $value, $language_id);
[365] => }
[366] =>
[367] => /**
[368] => * Delete post content metadata.
[369] => *
[370] => * @param int $post_id Post ID
[371] => * @param string $namespace Metadata namespace
[372] => * @param string|null $key Metadata key
[373] => * @param int|false $language_id Language ID or false for default
[374] => *
[375] => * @return mixed Result of the delete operation
[376] => */
[377] => function deletePostContentMeta(int $post_id, string $namespace, string|null $key = null, int|false $language_id = false): mixed {
[378] => return System\Meta\PostContentMeta::getInstance()->delete($post_id, $namespace, $key, $language_id);
[379] => }
[380] =>
[381] => /**
[382] => * Delete product content metadata.
[383] => *
[384] => * @param int $product_id Product ID
[385] => * @param string $namespace Metadata namespace
[386] => * @param string|null $key Metadata key
[387] => * @param int|false $language_id Language ID or false for default
[388] => *
[389] => * @return mixed Result of the delete operation
[390] => */
[391] => function deleteProductContentMeta(int $product_id, string $namespace, string|null $key = null, int|false $language_id = false): mixed {
[392] => return System\Meta\ProductContentMeta::getInstance()->delete($product_id, $namespace, $key, $language_id);
[393] => }
[394] =>
[395] => /**
[396] => * Set multiple post content metadata values at once.
[397] => *
[398] => * @param int $post_id Post ID
[399] => * @param mixed $meta Metadata data to set
[400] => *
[401] => * @return mixed Result of the set operation
[402] => */
[403] => function setMultiPostContentMeta(int $post_id, mixed $meta): mixed {
[404] => return System\Meta\PostContentMeta::getInstance()->setMulti($post_id, $meta);
[405] => }
[406] =>
[407] => /**
[408] => * Set multiple product content metadata values at once.
[409] => *
[410] => * @param int $product_id Product ID
[411] => * @param mixed $meta Metadata data to set
[412] => *
[413] => * @return mixed Result of the set operation
[414] => */
[415] => function setMultiProductContentMeta(int $product_id, mixed $meta): mixed {
[416] => return System\Meta\ProductContentMeta::getInstance()->setMulti($product_id, $meta);
[417] => }
[418] =>
[419] => /**
[420] => * Get the current template name.
[421] => *
[422] => * @return string|false|null Template name, or false/null if not set
[423] => */
[424] => function getCurrentTemplate(): string|false|null {
[425] => return System\Core\View :: getInstance()->template();
[426] => }
[427] =>
[428] => /**
[429] => * Get route data for a given URL.
[430] => *
[431] => * @param string $url The URL to look up
[432] => *
[433] => * @return array|false Route data array, or false if not found
[434] => */
[435] => function getUrlRoute(string $url): array|false {
[436] => $urlData = \Vvveb\System\Routes::getUrlData($url);
[437] =>
[438] => return $urlData;
[439] => }
[440] =>
[441] => /**
[442] => * Get the current request URI.
[443] => *
[444] => * @return string Current request URI
[445] => */
[446] => function getCurrentUrl(): string {
[447] => return $_SERVER['REQUEST_URI'] ?? '';
[448] => }
[449] =>
[450] => /**
[451] => * Get the admin path URL.
[452] => *
[453] => * @return string Admin path with leading and trailing slashes
[454] => */
[455] => function adminPath(): string {
[456] => return (V_SUBDIR_INSTALL ? V_SUBDIR_INSTALL : '') . '/' . \Vvveb\config('admin.path', 'admin') . '/';
[457] => }
[458] =>
[459] => /**
[460] => * Get the public URL path.
[461] => *
[462] => * @return string Public URL path
[463] => */
[464] => function publicUrlPath(): string {
[465] => $public_path = PUBLIC_PATH;
[466] =>
[467] => if (V_SUBDIR_INSTALL) {
[468] => $public_path = str_replace(V_SUBDIR_INSTALL, '', $public_path);
[469] => }
[470] =>
[471] => if ($public_path == '/public/' || $public_path == '/public/admin/') {
[472] => return PUBLIC_PATH;
[473] => } else {
[474] => return '/';
[475] => }
[476] => }
[477] =>
[478] => /**
[479] => * Get the current theme URL path.
[480] => *
[481] => * @return string Theme URL path
[482] => */
[483] => function themeUrlPath(): string {
[484] => $theme = \Vvveb\System\Core\View::getInstance()->getTheme();
[485] => //$theme = 'landing';
[486] => if (APP == 'app') {
[487] => $path = PUBLIC_THEME_PATH . 'themes/' . $theme . '/';
[488] => } else {
[489] => $path = PUBLIC_THEME_PATH . $theme . '/';
[490] => }
[491] =>
[492] => return $path;
[493] => }
[494] =>
[495] => /**
[496] => * Escape a URL for HTML output.
[497] => *
[498] => * @param string $url URL to escape
[499] => *
[500] => * @return string Escaped URL
[501] => */
[502] => function escUrl(string $url): string {
[503] => return htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
[504] => }
[505] =>
[506] => /**
[507] => * Escape an attribute value for HTML output.
[508] => *
[509] => * @param string $attr Attribute value to escape
[510] => *
[511] => * @return string Escaped attribute value
[512] => */
[513] => function escAttr(string $attr): string {
[514] => return htmlspecialchars($attr);
[515] => }
[516] =>
[517] => /**
[518] => * Escape a string for HTML output.
[519] => *
[520] => * @param string $url String to escape
[521] => *
[522] => * @return string Escaped string
[523] => */
[524] => function escHtml(string $url): string {
[525] => return htmlspecialchars($url);
[526] => }
[527] =>
[528] => /**
[529] => * Get an environment variable value.
[530] => *
[531] => * @param string $key Environment variable name
[532] => * @param mixed $default Default value if variable is not set
[533] => *
[534] => * @return mixed Environment variable value or default
[535] => */
[536] => function env(string $key, mixed $default = null): mixed {
[537] => if ($env = getenv($key)) {
[538] => return $env;
[539] => } else {
[540] => return $default;
[541] => }
[542] => }
[543] =>
[544] => /**
[545] => * Convert a date to a human-friendly relative format.
[546] => *
[547] => * @param string|int $date Date string or Unix timestamp
[548] => *
[549] => * @return string|int Human-friendly date string, or original timestamp if too far in the future
[550] => */
[551] => function friendlyDate(string|int $date): string|int {
[552] => $fileformats = [
[553] => 1 => static fn ($i) => __('%d second', '%d seconds', $i),
[554] => 60 => static fn ($i) => __('%d minute', '%d minutes', $i),
[555] => 3600 => static fn ($i) => __('%d hour', '%d hours', $i),
[556] => 86400 => static fn ($i) => __('%d day', '%d days', $i),
[557] => 604800 => static fn ($i) => __('%d week', '%d weeks', $i),
[558] => 2592000 => static fn ($i) => __('%d month', '%d months', $i),
[559] => 31536000 => static fn ($i) => __('%d year', '%d years', $i),
[560] => 315360000 => static fn ($i) => __('%d decade', '%d decades', $i),
[561] => 3153600000 => static fn ($i) => __('%d century', '%d centuries', $i),
[562] => ];
[563] =>
[564] => $time_direction = __(' ago');
[565] =>
[566] => if (is_string($date)) {
[567] => $date = strtotime($date);
[568] => }
[569] =>
[570] => $diff = time() - $date + 10;
[571] =>
[572] => if ($diff < 0) {
[573] => $time_direction = __(' from now');
[574] => $diff = abs($diff);
[575] => }
[576] =>
[577] => $lastTime = 1;
[578] => $lastText = $fileformats[1]($diff);
[579] =>
[580] => foreach ($fileformats as $time => $text) {
[581] => if ($diff < $time) {
[582] => $units = floor($diff / $lastTime);
[583] =>
[584] => return sprintf($lastText($units), $units) . $time_direction;
[585] => }
[586] => $lastText = $text;
[587] => $lastTime = $time;
[588] => }
[589] =>
[590] => return $date;
[591] => }
[592] =>
[593] => /**
[594] => * Convert dot notation to array key notation.
[595] => *
[596] => * Converts var.key1.key2 to var['key1']['key2'].
[597] => *
[598] => * @param string $key Dot-notation key string
[599] => *
[600] => * @return string Array key notation string
[601] => */
[602] => function dotToArrayKey(string $key): string {
[603] => //var.key1.key2 > var['key1']['key2']
[604] => //var.key1 > var['key1']
[605] =>
[606] => return preg_replace_callback('/\.([-_\w]+)/', function ($matches) {
[607] => return "['" . str_replace("'", "\'", $matches[1]) . "']";
[608] => }, $key);
[609] => }
[610] =>
[611] => /**
[612] => * Convert form name bracket notation to array key notation.
[613] => *
[614] => * Converts name[key] to ['name']['key'].
[615] => *
[616] => * @param string $key Form name bracket notation string
[617] => *
[618] => * @return string Array key notation string
[619] => */
[620] => function postNameToArrayKey(string $key): string {
[621] => $key = str_replace(['][', '[', ']'] , ['.', '.', ''], $key);
[622] =>
[623] => return '[\'' . str_replace('.', '\'][\'', $key) . '\']';
[624] => }
[625] =>
[626] => /**
[627] => * Prefix all array keys with a given prefix.
[628] => *
[629] => * @param string $prefix Prefix to add to each key
[630] => * @param array &$array Array to modify by reference
[631] => *
[632] => * @return array Modified array with prefixed keys
[633] => */
[634] => function prefixArrayKeys(string $prefix, array &$array): array {
[635] => if (is_array($array)) {
[636] => foreach ($array as $k => $v) {
[637] => $array["$prefix$k"] = $v;
[638] => unset($array[$k]);
[639] => }
[640] => }
[641] =>
[642] => return $array;
[643] => }
[644] =>
[645] => /**
[646] => * Sanitize text by encoding special characters.
[647] => *
[648] => * @param string $data Text to sanitize
[649] => *
[650] => * @return string Sanitized text
[651] => */
[652] => function filterText(string $data): string {
[653] => return urldecode(filter_var($data, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_ENCODE_HIGH | FILTER_FLAG_ENCODE_LOW));
[654] => }
[655] =>
[656] => /**
[657] => * Get or set session data.
[658] => *
[659] => * When a string is passed, retrieves the session value for that key. When an array is passed, sets all key-value pairs.
[660] => *
[661] => * @param string|array $data Session key to get, or array of key-value pairs to set
[662] => * @param mixed $default Default value to return if key is not found
[663] => *
[664] => * @return mixed Session value when getting, or default value
[665] => */
[666] => function session(string|array $data, mixed $default = null): mixed {
[667] => $session = System\Session::getInstance();
[668] =>
[669] => if (! $session) {
[670] => return $default;
[671] => }
[672] =>
[673] => if (is_array($data)) {
[674] => foreach ($data as $key => $value) {
[675] => $session->set($key, $value);
[676] => }
[677] => } else {
[678] => $value = $session->get($data);
[679] =>
[680] => if ($value === null) {
[681] => return $default;
[682] => }
[683] =>
[684] => return $value;
[685] => }
[686] =>
[687] => return $default;
[688] => }
[689] =>
[690] => /**
[691] => * Regex filter to allow only matched content.
[692] => *
[693] => * @param string $regex Regular expression pattern
[694] => * @param string $input Input string to filter
[695] => * @param int $maxInputSize Maximum input size to test
[696] => *
[697] => * @return string|false Matched content, or false if no match
[698] => */
[699] => function filter(string $regex, string $input, int $maxInputSize = 100): string|false {
[700] => $matches = [];
[701] =>
[702] => if (preg_match($regex, substr($input, 0, $maxInputSize), $matches)) {
[703] => return $matches[0];
[704] => } else {
[705] => return false;
[706] => }
[707] => }
[708] =>
[709] => /**
[710] => * Shortcut for preg_match to return the matched parameter directly.
[711] => *
[712] => * @param string $regex
[713] => * @param string $input
[714] => * @param integer $level [false]
[715] => *
[716] => * @return mixed
[717] => */
[718] => function pregMatch(string $regex, string $input, int|false $level = false): mixed {
[719] => $matches = [];
[720] =>
[721] => //if (preg_match($regex, $input, $matches, PREG_UNMATCHED_AS_NULL | PREG_PATTERN_ORDER)) {
[722] => if (preg_match($regex, $input, $matches, PREG_UNMATCHED_AS_NULL)) {
[723] => if ($level !== false) {
[724] => return $matches[$level];
[725] => }
[726] =>
[727] => return $matches;
[728] => } else {
[729] => return false;
[730] => }
[731] => }
[732] =>
[733] => /**
[734] => * Shortcut for preg_match_all to return the matched parameters directly.
[735] => *
[736] => * @param string $regex
[737] => * @param string $input
[738] => * @param integer $level [false]
[739] => *
[740] => * @return mixed
[741] => */
[742] => function pregMatchAll(string $regex, string $input, int|false $level = false): mixed {
[743] => $matches = [];
[744] =>
[745] => if (preg_match_all($regex, $input, $matches, PREG_UNMATCHED_AS_NULL | PREG_PATTERN_ORDER)) {
[746] => if ($level !== false) {
[747] => return $matches[$level];
[748] => }
[749] =>
[750] => return $matches;
[751] => } else {
[752] => return false;
[753] => }
[754] => }
[755] =>
[756] => /**
[757] => * Validates that input is in the allowed values list.
[758] => *
[759] => * @param mixed $input The input value to validate.
[760] => * @param array $allowedValues The list of allowed values.
[761] => *
[762] => * @return mixed The input value if valid, null otherwise.
[763] => */
[764] => function arrayAllowValues(mixed $input, array $allowedValues): mixed {
[765] => if (! in_array($input, $allowedValues)) {
[766] => return null;
[767] => } else {
[768] => return $input;
[769] => }
[770] => }
[771] =>
[772] => /**
[773] => * Get values from multidimensional arrays based on path.
[774] => * For example for array ['item' => ['desc' => ['name' => 'test']]] the path "item.desc.name" will return "test".
[775] => *
[776] => * @param array $a The array to search.
[777] => * @param string $path The dot-separated path to the value.
[778] => * @param mixed $default The default value if path is not found.
[779] => * @param string $token The token used to separate path segments.
[780] => *
[781] => * @return mixed The value at the given path or the default value.
[782] => */
[783] => function arrayPath(array $a, string $path, mixed $default = null, string $token = '.'): mixed {
[784] => $p = strtok($path, $token);
[785] =>
[786] => while ($p !== false) {
[787] => if (! isset($a[$p])) {
[788] => return $default;
[789] => }
[790] =>
[791] => $a = $a[$p];
[792] => $p = strtok($token);
[793] => }
[794] =>
[795] => return $a;
[796] => }
[797] =>
[798] => /**
[799] => * Converts slug/text to human readable format.
[800] => *
[801] => * @param string $text The text to convert.
[802] => *
[803] => * @return string The human readable text.
[804] => */
[805] => function humanReadable(string $text): string {
[806] => return ucfirst(str_replace(['_', '-', '/', '[', ']', '.'], [' ', ' ', ' - ', ' ', ' ', ' '], trim($text, ' /\-_')));
[807] => }
[808] =>
[809] => /**
[810] => * Cleans text for URL usage without transliteration.
[811] => *
[812] => * @param string $text The text to clean.
[813] => * @param string $divider The divider character to use.
[814] => *
[815] => * @return string The cleaned URL string.
[816] => */
[817] => function cleanUrl(string $text, string $divider = '-'): string {
[818] => // replace non letter or digits by divider
[819] => $text = preg_replace('/[^\pL\d]+/u', $divider, $text);
[820] =>
[821] => // remove unwanted characters
[822] => $text = preg_replace('/[^\\-\w]+/', '', $text);
[823] =>
[824] => $text = trim($text, $divider);
[825] =>
[826] => return $text;
[827] => }
[828] =>
[829] => /**
[830] => * Creates URL-safe slug from text with transliteration.
[831] => *
[832] => * @param string $text The text to slugify.
[833] => * @param string $divider The divider character to use.
[834] => *
[835] => * @return string The URL-safe slug.
[836] => */
[837] => function slugify(string $text, string $divider = '-'): string {
[838] => if (! $text) {
[839] => return $text;
[840] => }
[841] => // replace non letter or digits by divider
[842] => $text = preg_replace('/[^\pL\d]+/u', $divider, $text);
[843] =>
[844] => // transliterate
[845] => if (function_exists('iconv')) {
[846] => $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
[847] => }
[848] =>
[849] => // remove unwanted characters
[850] => $text = preg_replace('/[^-\w]+/', '', $text);
[851] =>
[852] => // trim
[853] => $text = trim($text, $divider);
[854] =>
[855] => // remove duplicate divider
[856] => $text = preg_replace('/-+/', $divider, $text);
[857] =>
[858] => // lowercase
[859] => $text = strtolower($text);
[860] =>
[861] => return $text;
[862] => }
[863] =>
[864] => if ((! defined('GETTEXT') || GETTEXT == true) && function_exists('_')) {
[865] => /**
[866] => * Translates text using gettext.
[867] => *
[868] => * @param string $text The text to translate.
[869] => * @param string|false $plural The plural form of the text.
[870] => * @param int|false $count The count for plural translation.
[871] => * @param bool $nocache Whether to bypass the translation cache.
[872] => *
[873] => * @return string The translated text.
[874] => */
[875] => function __($text, string|false $plural = false, int|false $count = false, bool $nocache = false): string {
[876] => if ($plural) {
[877] => $translation = ngettext($text, $plural, $count);
[878] => } else {
[879] => $translation = gettext($text);
[880] => }
[881] =>
[882] => //list($text, $plural, $count, $translation) = System\Event::trigger(__NAMESPACE__, '__', $text, $plural, $count, $translation);
[883] =>
[884] => return $translation;
[885] => }
[886] => } else {
[887] => /**
[888] => * Translates text using custom PO file parser.
[889] => *
[890] => * @param string $text The text to translate.
[891] => * @param string|false $plural The plural form of the text.
[892] => * @param int|false $count The count for plural translation.
[893] => * @param bool $nocache Whether to bypass the translation cache.
[894] => *
[895] => * @return string The translated text.
[896] => */
[897] => function __($text, string|false $plural = false, int|false $count = false, bool $nocache = false): string {
[898] => static $translations;
[899] =>
[900] => if (($translations === null || $nocache) && ! isEditor()) {
[901] => $translations = [];
[902] => $lang = System\Locale::getLanguage();
[903] =>
[904] => $cacheFile = DIR_CACHE . $lang . '-translations.php';
[905] =>
[906] => if (file_exists($cacheFile)) {
[907] => $translations = require $cacheFile;
[908] => } else {
[909] => $folder = DIR_ROOT . 'locale' . DS . $lang . DS . 'LC_MESSAGES' . DS;
[910] => if (is_dir($folder)) {
[911] => require_once DIR_SYSTEM . 'functions' . DS . 'php-mo.php';
[912] =>
[913] => foreach (['vvveb', 'user'] as $domain) {
[914] => $poFile = $folder . $domain . '.po';
[915] => if (file_exists($poFile) && ($t = phpmo_parse_po_file($poFile) ?: []) && is_array($t)) {
[916] => $translations = array_merge($translations, $t);
[917] => }
[918] => }
[919] =>
[920] => foreach ($translations as $id => &$msg) {
[921] => $msg = $msg['msgstr'];
[922] => }
[923] =>
[924] => file_put_contents($cacheFile, ' }
[926] => }
[927] => }
[928] =>
[929] => if (isset($translations[$text])) {
[930] => if ($count && $plural) {
[931] => $translation = $translations[$text][1] ?? $translations[$text][0] ?? $text;
[932] => } else {
[933] => $translation = $translations[$text][0] ?? $text;
[934] => }
[935] => } else {
[936] => $translation = $text;
[937] => }
[938] =>
[939] => //list($text, $plural, $count, $translation) = System\Event::trigger(__NAMESPACE__, '__', $text, $plural, $count, $translation);
[940] =>
[941] => return $translation;
[942] => }
[943] => }
[944] =>
[945] => /**
[946] => * Checks if the current user is an admin.
[947] => *
[948] => * @return bool True if the current user is an admin, false otherwise.
[949] => */
[950] => function isAdmin(): bool {
[951] => return System\User\Admin::current() ? true : false;
[952] => }
[953] =>
[954] => /**
[955] => * Converts a CSS selector to an XPath query.
[956] => *
[957] => * @param string $selector The CSS selector to convert.
[958] => *
[959] => * @return string The resulting XPath query.
[960] => */
[961] => function cssToXpath(string $selector): string {
[962] => //if already xpath don't transform
[963] => //if (substr_compare($selector,'vtpl_xpath', 0, 11) == 0) return substr($selector, 12, -1);
[964] =>
[965] => $selector = (string) $selector;
[966] =>
[967] => //convert , to | union operator to allow multiple queries
[968] => $selector = str_replace(',', '|', $selector);
[969] =>
[970] => $cssSelector = [
[971] => // E > F: Matches any F element that is a child of an element E
[972] => '/\s*>\s*/',
[973] =>
[974] => // E + F: Matches any F element immediately preceded by an element
[975] => '/\s*\+\s*/',
[976] =>
[977] => // E F: Matches any F element that is a descendant of an E element
[978] => '/([a-zA-Z\*="\[\]#._-])\s+([a-zA-Z\*="\[\]#._-])/', //'/([a-zA-Z\*="\[\]#._-])\s+([a-zA-Z\*#._-])/',
[979] =>
[980] => // E:first-child: Matches element E when E is the first child of its parent
[981] => '/([a-z#\.]\w*):first-child/',
[982] =>
[983] => // E:nth-child() Matches the nth child element
[984] => '/([a-z#\.]\w*):nth-child\((\d+)\)/',
[985] =>
[986] => // E:first: Matches the first element from the set
[987] => '/([a-z#\.]\w*):first/',
[988] =>
[989] => // E:nth(2): Matches the nth element from the set
[990] => '/([a-z#\.]\w*):nth\((\d+)\)/',
[991] =>
[992] => // E[foo="warning"]: Matches any E element whose "foo" attribute value is exactly equal to "warning"
[993] => '/([a-z]\w*)\[([a-z][\w\-_]*)\="([^"]*)"]/',
[994] =>
[995] => // E[foo]: Matches any E element with the "foo" attribute set (whatever the value)
[996] => '/([a-z]\w*)\[([a-z][\w_\-]*)\]/',
[997] =>
[998] => // E[!foo]: Matches any E element without the "foo" attribute set
[999] => '/([a-z]\w*)\[!([a-z][\w\-_]*)\]/',
[1000] =>
[1001] => // [foo="warning"]: Matches any element whose "foo" attribute value is exactly equal to "warning"
[1002] => '/\[([a-z][\w\-_]*)\=\"(.*)\"\]/',
[1003] =>
[1004] => // [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning" and has other attributes
[1005] => '/(?<=\])\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/',
[1006] =>
[1007] => // [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning"
[1008] => '/\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/',
[1009] =>
[1010] => // [foo^="warning"]: Matches any element whose "foo" attribute value begins with the string "warning"
[1011] => '/\[([a-z][\w_\-]*)\^\=\"([^"]+)\"\]/',
[1012] =>
[1013] => // [foo$="warning"]: Matches any element whose "foo" attribute value ends with the string "warning"
[1014] => '/\[([a-z][\w_\-]*)\$\=\"([^"]+)\"\]/',
[1015] =>
[1016] => // [foo][baz]: Matches any element with the "foo" attribute set (whatever the value)
[1017] => '/(?<=\])(?
[1019] => // [foo]: Matches any element with the "foo" attribute set (whatever the value)
[1020] => '/\[([a-z][\w_\-]*)\]/',
[1021] =>
[1022] => // element[foo*]: Matches any element that starts with "foo" attribute (whatever the value)
[1023] => '/(\w+)\[([a-z][\w\-]*)\*\]/',
[1024] =>
[1025] => // [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and has other attributes
[1026] => '/(?<=\])\[([a-z][\w\-]*)\*\]/',
[1027] =>
[1028] => // [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and is a single attribute
[1029] => '/(?
[1031] => // div.warn*: HTML only. The same as DIV[class*="warning"]
[1032] => '/([a-z]\w*|\*)\.([a-z][\w\-_]*)\*/',
[1033] =>
[1034] => // div.warning: HTML only. The same as DIV[class~="warning"]
[1035] => '/([a-z]\w*|\*)\.([a-z][\w\-_]*)+/',
[1036] =>
[1037] => // .warn*: HTML only. The same as [class*="warning"]
[1038] => '/\.([a-z][\w\-\_]*)\*/',
[1039] =>
[1040] => // .warning: HTML only. The same as [class~="warning"]
[1041] => '/\.([a-z][\w\-\_]*)+/',
[1042] =>
[1043] => // E#myid: Matches any E element with id-attribute equal to "myid"
[1044] => '/([a-z]\w*)\#([a-z][\w\-_]*)/',
[1045] =>
[1046] => // #myid: Matches any E element with id-attribute equal to "myid"
[1047] => '/\#([a-z][\w\-_]*)/',
[1048] => ];
[1049] =>
[1050] => $xpathQuery = [
[1051] => '/', //element > child
[1052] => '/following-sibling::*[1]/self::', // element + precedent
[1053] => '\1//\2', //element descendent
[1054] => '*[1]/self::\1', //element:first-child
[1055] => '*[\2]/self::\1', //element:nth-child(2)
[1056] => '\1[1]', //element:first
[1057] => '\1[\2]', //element:nth(2)
[1058] => '\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]', //element[attribute="string"]
[1059] => '\1 [ @\2 ]', //element[attribute]
[1060] => '\1 [ not(@\2) ]', //element[!attribute]
[1061] => '[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo="warning"]
[1062] => '*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo="warning"]
[1063] => '*[ contains( concat( " ", @\1, " " ), "\2" ) ]', //[foo*="warning"]
[1064] => '*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo^="warning"] not implemented
[1065] => '*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo$="warning"] not implemented
[1066] => '[ @\1 ]', //[attribute][attribute]
[1067] => '*[ @\1 ]', //[attribute]
[1068] => '\1 [ @*[starts-with(name(), "\2")] ]', //element[attr*]
[1069] => '[ @*[starts-with(name(), "\1")] ]', //[attr*] - attribute with other attributes
[1070] => '*[ @*[starts-with(name(), "\1")] ]', //[attr*] - single attribute
[1071] => '\1[ contains( concat( " ", @class, " " ), concat( " ", "\2") ) ]', //element[class*="string"]
[1072] => '\1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]', //element[class~="string"]
[1073] => '*[ contains( concat( " ", @class, " " ), concat( " ", "\1") ) ]', //[class*="string"]
[1074] => '*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]', //element[class~="string"]
[1075] => '\1[ @id = "\2" ]', //element#id
[1076] => '*[ @id = "\1" ]', //#id
[1077] => ];
[1078] =>
[1079] => $result = (string) '//' . preg_replace($cssSelector, $xpathQuery, $selector);
[1080] =>
[1081] => return $result;
[1082] => }
[1083] =>
[1084] => /**
[1085] => * Imports an HTML string into a DOM document and returns the node.
[1086] => *
[1087] => * @param string $html The HTML string to import.
[1088] => * @param string|null $selector An optional CSS selector to find a specific node.
[1089] => *
[1090] => * @return \DOMNode|null The imported DOM node or null.
[1091] => */
[1092] => function importHtml(string $html, ?string $selector = null): ?\DOMNode {
[1093] => $dom = new \DomDocument();
[1094] => $dom->preserveWhiteSpace = true;
[1095] => $dom->recover = true;
[1096] => $dom->strictErrorChecking = false;
[1097] => $dom->substituteEntities = false;
[1098] => $dom->formatOutput = false;
[1099] => $dom->resolveExternals = false;
[1100] => $dom->validateOnParse = false;
[1101] => $dom->xmlStandalone = true;
[1102] => $dom->loadHTML($html);
[1103] =>
[1104] => if ($selector) {
[1105] => $xpath = new \DOMXpath($dom);
[1106] => $xpathQuery = cssToXpath($selector);
[1107] => $result = $xpath->query($xpathQuery);
[1108] =>
[1109] => $nodeToImport = $result->item(0)?->firstChild;
[1110] => } else {
[1111] => $body = $dom->getElementsByTagName('body');
[1112] => $nodeToImport = $body->item(0)?->firstChild;
[1113] => }
[1114] =>
[1115] => return $nodeToImport;
[1116] => }
[1117] =>
[1118] => /**
[1119] => * Converts a dash-separated string to CamelCase.
[1120] => *
[1121] => * @param string $string The string to convert.
[1122] => * @param string $dash The dash character used as separator.
[1123] => *
[1124] => * @return string The CamelCase string.
[1125] => */
[1126] => function dashesToCamelCase(string $string, string $dash = '-'): string {
[1127] => return str_replace($dash, '', ucwords($string, $dash));
[1128] => }
[1129] =>
[1130] => /**
[1131] => * Recursively converts underscore-separated array keys to camelCase.
[1132] => *
[1133] => * @param array $array The array whose keys to convert.
[1134] => *
[1135] => * @return array The array with converted keys.
[1136] => */
[1137] => function arrayKeysToCamelCase(array &$array): array {
[1138] => foreach ($array as $key => &$value) {
[1139] => if (is_array($value)) {
[1140] => $value = arrayKeysToCamelCase($value);
[1141] => }
[1142] =>
[1143] => $newKey = lcfirst(dashesToCamelCase($key, '_'));
[1144] =>
[1145] => if ($newKey != $key) {
[1146] => $array[$newKey] = $value;
[1147] => unset($array[$key]);
[1148] => }
[1149] => }
[1150] =>
[1151] => return $array;
[1152] => }
[1153] =>
[1154] => /**
[1155] => * Removes extra spaces from a string.
[1156] => *
[1157] => * @param string $string The string to process.
[1158] => *
[1159] => * @return string The string with extra spaces removed.
[1160] => */
[1161] => function stripExtraSpaces(string $string): string {
[1162] => foreach (['\t', '\n', '\r', ' '] as $space) {
[1163] => $string = preg_replace('/(' . $space . ')' . $space . '+/', '\1', $string);
[1164] => }
[1165] =>
[1166] => return $string;
[1167] => }
[1168] =>
[1169] => /**
[1170] => * Reads the last N lines from a file.
[1171] => *
[1172] => * @param string $filename The path to the file.
[1173] => * @param int $lines The number of lines to read from the end.
[1174] => *
[1175] => * @return string|false The last lines of the file, or false on failure.
[1176] => */
[1177] => function tail(string $filename, int $lines = 1000): string|false {
[1178] => $file = @fopen($filename, 'rb');
[1179] =>
[1180] => if ($file === false) {
[1181] => return false;
[1182] => }
[1183] =>
[1184] => $buffer = 4096;
[1185] => $output = '';
[1186] => $chunk = '';
[1187] =>
[1188] => @fseek($file, -1, SEEK_END);
[1189] =>
[1190] => if (fread($file, 1) != "\n") {
[1191] => $lines -= 1;
[1192] => }
[1193] =>
[1194] => while (ftell($file) > 0 && $lines >= 0) {
[1195] => $seek = min(ftell($file), $buffer);
[1196] => fseek($file, -$seek, SEEK_CUR);
[1197] => $output = ($chunk = fread($file, $seek)) . $output;
[1198] => //fseek($file, -mb_strlen($chunk, '8bit'), SEEK_CUR);
[1199] => fseek($file, strlen($chunk), SEEK_CUR);
[1200] => $lines -= substr_count($chunk, "\n");
[1201] => }
[1202] =>
[1203] => while ($lines++ < 0) {
[1204] => $output = substr($output, strpos($output, "\n") + 1);
[1205] => }
[1206] =>
[1207] => fclose($file);
[1208] =>
[1209] => return trim($output);
[1210] => }
[1211] =>
[1212] => /**
[1213] => * Return current module name.
[1214] => *
[1215] => * @return string
[1216] => */
[1217] => function getModuleName(): string {
[1218] => return strtolower(\Vvveb\System\Core\FrontController::getModuleName());
[1219] => }
[1220] =>
[1221] => /**
[1222] => * Return current action name.
[1223] => *
[1224] => * @return string
[1225] => */
[1226] => function getActionName(): string {
[1227] => return strtolower(\Vvveb\System\Core\FrontController::getActionName());
[1228] => }
[1229] =>
[1230] => /**
[1231] => * Inserts a new key/value before the key in the array.
[1232] => *
[1233] => * @param string $key The key to insert before.
[1234] => * @param array $array An array to insert in to.
[1235] => * @param string $new_key The key to insert.
[1236] => * @param mixed $new_value An value to insert.
[1237] => *
[1238] => * @return array The new array if the key exists, otherwise the unchanged array.
[1239] => *
[1240] => * @see arrayInsertAfter()
[1241] => */
[1242] => function arrayInsertBefore(string $key, array &$array, string $new_key, mixed $new_value): array {
[1243] => if (array_key_exists($key, $array)) {
[1244] => $new = [];
[1245] =>
[1246] => foreach ($array as $k => $value) {
[1247] => if ($k === $key) {
[1248] => $new[$new_key] = $new_value;
[1249] => }
[1250] => $new[$k] = $value;
[1251] => }
[1252] =>
[1253] => return $new;
[1254] => }
[1255] =>
[1256] => return $array;
[1257] => }
[1258] =>
[1259] => /**
[1260] => * Inserts a new array before the key in the array.
[1261] => *
[1262] => * @param string $key The key to insert before.
[1263] => * @param array $array An array to insert in to.
[1264] => * @param array $new_array An array to insert.
[1265] => *
[1266] => * @return array The new array if the key exists, otherwise the unchanged array.
[1267] => *
[1268] => * @see arrayInsertAfter()
[1269] => */
[1270] => function arrayInsertArrayBefore(string $key, array &$array, array $new_array): array {
[1271] => if (array_key_exists($key, $array)) {
[1272] => $new = [];
[1273] =>
[1274] => foreach ($array as $k => $value) {
[1275] => if ($k === $key) {
[1276] => $new += $new_array;
[1277] => }
[1278] => $new[$k] = $value;
[1279] => }
[1280] =>
[1281] => return $new;
[1282] => }
[1283] =>
[1284] => return $array;
[1285] => }
[1286] =>
[1287] => /**
[1288] => * Inserts a new key/value after the key in the array.
[1289] => *
[1290] => * @param string $key The key to insert after.
[1291] => * @param array $array An array to insert in to.
[1292] => * @param string $new_key The key to insert.
[1293] => * @param mixed $new_value An value to insert.
[1294] => *
[1295] => * @return array The new array if the key exists, otherwise the unchanged array.
[1296] => *
[1297] => * @see arrayInsertBefore()
[1298] => */
[1299] => function arrayInsertAfter(string $key, array &$array, string $new_key, mixed $new_value): array {
[1300] => if (array_key_exists($key, $array)) {
[1301] => $new = [];
[1302] =>
[1303] => foreach ($array as $k => $value) {
[1304] => $new[$k] = $value;
[1305] =>
[1306] => if ($k === $key) {
[1307] => $new[$new_key] = $new_value;
[1308] => }
[1309] => }
[1310] =>
[1311] => return $new;
[1312] => }
[1313] =>
[1314] => return $array;
[1315] => }
[1316] =>
[1317] => /**
[1318] => * Inserts a new array after the key in the array.
[1319] => *
[1320] => * @param string $key The key to insert after.
[1321] => * @param array $array An array to insert in to.
[1322] => * @param array $new_array An array to insert.
[1323] => *
[1324] => * @return array The new array if the key exists, otherwise the unchanged array.
[1325] => *
[1326] => * @see arrayInsertBefore()
[1327] => */
[1328] => function arrayInsertArrayAfter(string $key, array &$array, array $new_array): array {
[1329] => if (array_key_exists($key, $array)) {
[1330] => $new = [];
[1331] =>
[1332] => foreach ($array as $k => $value) {
[1333] => $new[$k] = $value;
[1334] =>
[1335] => if ($k === $key) {
[1336] => $new += $new_array;
[1337] => }
[1338] => }
[1339] =>
[1340] => return $new;
[1341] => }
[1342] =>
[1343] => return $array;
[1344] => }
[1345] =>
[1346] => /**
[1347] => * Get a request parameter value.
[1348] => *
[1349] => * @param string $key The request parameter key.
[1350] => *
[1351] => * @return mixed The parameter value, or false if not set.
[1352] => */
[1353] => function get(string $key): mixed {
[1354] => return System\Core\Request::getInstance()->get[$key] ?? false;
[1355] => }
[1356] =>
[1357] => /**
[1358] => * Check if the page is loaded in the editor.
[1359] => *
[1360] => * @return boolean
[1361] => */
[1362] => function isEditor(): bool {
[1363] => return isset($_GET['r']);
[1364] => }
[1365] =>
[1366] => /**
[1367] => * Log an error message.
[1368] => *
[1369] => * @param string $message The error message to log.
[1370] => *
[1371] => * @return void
[1372] => */
[1373] => function logError(string $message): void {
[1374] => error_log($message);
[1375] => }
[1376] =>
[1377] => /**
[1378] => * Return list of theme folders.
[1379] => *
[1380] => * @param string|false $theme The theme name, or false to use the current theme.
[1381] => *
[1382] => * @return array The list of theme folders.
[1383] => */
[1384] => function getThemeFolderList(string|false $theme = false): array {
[1385] => $skipFolders = ['src', 'source', 'backup', 'sections', 'blocks', 'inputs', 'css', 'scss', 'fonts', 'img', 'import', 'node_modules', 'screenshots', 'video', 'js'];
[1386] =>
[1387] => if (! $theme) {
[1388] => $theme = \Vvveb\System\Sites::getTheme(SITE_ID) ?? 'default';
[1389] => }
[1390] =>
[1391] => $themeFolder = DIR_THEMES . $theme;
[1392] => $files = glob("$themeFolder/*", GLOB_ONLYDIR) ?: [];
[1393] => $pages['/'] = ['name' => '/', 'title' => '/', 'filename' => '/', 'file' => '/', 'path' => '/', 'folder' => $theme];
[1394] =>
[1395] => foreach ($files as $file) {
[1396] => $folder = preg_replace('@^.*/themes/[^/]+/@', '', $file);
[1397] => $filename = basename($file);
[1398] =>
[1399] => if (in_array($folder, $skipFolders)) {
[1400] => continue;
[1401] => }
[1402] =>
[1403] => $pages[$file] = ['name' => $filename, 'title' => "/$filename", 'filename' => $filename, 'file' => $filename, 'folder' => $theme];
[1404] => }
[1405] =>
[1406] => return $pages;
[1407] => }
[1408] =>
[1409] => /**
[1410] => * Return all available field type definitions.
[1411] => *
[1412] => * @return array Associative array of field types grouped by category.
[1413] => */
[1414] => function getFieldTypes(): array {
[1415] => return
[1416] => [
[1417] => 'basic' => [
[1418] => 'text' => [
[1419] => 'name' => __('Text'),
[1420] => 'icon' => 'icon-text-outline',
[1421] => ],
[1422] => 'textarea' => [
[1423] => 'name' => __('Textarea'),
[1424] => 'icon' => 'icon-document-text-outline',
[1425] => ],
[1426] => 'number' => [
[1427] => 'name' => __('Number'),
[1428] => 'icon' => 'icon-document-text-outline',
[1429] => ],
[1430] => 'range' => [
[1431] => 'name' => __('Range'),
[1432] => 'icon' => 'icon-git-commit-outline',
[1433] => ],
[1434] => 'email' => [
[1435] => 'name' => __('Email'),
[1436] => 'icon' => 'icon-mail-outline',
[1437] => ],
[1438] => 'url' => [
[1439] => 'name' => __('Url'),
[1440] => 'icon' => 'icon-link-outline',
[1441] => ],
[1442] => 'tel' => [
[1443] => 'name' => __('Telephone'),
[1444] => 'icon' => 'icon-call-outline',
[1445] => ],
[1446] => 'password' => [
[1447] => 'name' => __('Password'),
[1448] => 'icon' => 'icon-lock-open-outline',
[1449] => ],
[1450] => 'select' => [
[1451] => 'name' => __('Select'),
[1452] => 'icon' => 'icon-chevron-down-circle-outline',
[1453] => ],
[1454] => ],
[1455] => 'content' => [
[1456] => 'image' => [
[1457] => 'name' => __('Image'),
[1458] => 'icon' => 'icon-image-outline',
[1459] => ],
[1460] => 'file' => [
[1461] => 'name' => __('File'),
[1462] => 'icon' => 'icon-document-outline',
[1463] => ],
[1464] => 'wysiwyg' => [
[1465] => 'name' => __('Wysiwyg Editor'),
[1466] => 'icon' => 'icon-create-outline',
[1467] => ],
[1468] => 'oembed' => [
[1469] => 'name' => __('oEmbed'),
[1470] => 'icon' => 'icon-videocam-outline',
[1471] => ],
[1472] => 'gallery' => [
[1473] => 'name' => __('Gallery'),
[1474] => 'icon' => 'icon-images-outline',
[1475] => ],
[1476] => ],
[1477] => 'choice' => [
[1478] => 'select' => [
[1479] => 'name' => __('Select'),
[1480] => 'icon' => 'icon-chevron-down-circle-outline',
[1481] => ],
[1482] => 'checkbox' => [
[1483] => 'name' => __('Checkbox'),
[1484] => 'icon' => 'icon-checkbox-outline',
[1485] => ],
[1486] => 'radio' => [
[1487] => 'name' => __('Radio Button'),
[1488] => 'icon' => 'icon-checkmark-circle-outline',
[1489] => ],
[1490] => 'toggle' => [
[1491] => 'name' => __('Toggle'),
[1492] => 'icon' => 'icon-toggle-outline',
[1493] => ],
[1494] => ],
[1495] => 'relational' => [
[1496] => 'link' => [
[1497] => 'name' => __('Link'),
[1498] => 'icon' => 'icon-link-outline',
[1499] => ],
[1500] => 'post' => [
[1501] => 'name' => __('Post'),
[1502] => 'icon' => 'icon-document-text-outline',
[1503] => ],
[1504] => 'product' => [
[1505] => 'name' => __('Product'),
[1506] => 'icon' => 'icon-cube-outline',
[1507] => ],
[1508] => 'relationship' => [
[1509] => 'name' => __('Relationship'),
[1510] => 'icon' => 'icon-link-outline',
[1511] => ],
[1512] => 'taxonomy' => [
[1513] => 'name' => __('Taxonomy'),
[1514] => 'icon' => 'icon-apps-outline',
[1515] => ],
[1516] => 'user' => [
[1517] => 'name' => __('User'),
[1518] => 'icon' => 'icon-person-outline',
[1519] => ],
[1520] => ],
[1521] => 'widgets' => [
[1522] => 'google_map' => [
[1523] => 'name' => __('Google Map'),
[1524] => 'icon' => 'icon-map-outline',
[1525] => ],
[1526] => 'color' => [
[1527] => 'name' => __('Color'),
[1528] => 'icon' => 'icon-color-palette-outline',
[1529] => ],
[1530] => 'time' => [
[1531] => 'name' => __('Time'),
[1532] => 'icon' => 'icon-time-outline',
[1533] => ],
[1534] => 'date' => [
[1535] => 'name' => __('Date'),
[1536] => 'icon' => 'icon-today-outline',
[1537] => ],
[1538] => 'date_time' => [
[1539] => 'name' => __('Date time'),
[1540] => 'icon' => 'icon-calendar-clear-outline',
[1541] => ],
[1542] => 'month' => [
[1543] => 'name' => __('Month'),
[1544] => 'icon' => 'icon-calendar-outline',
[1545] => ],
[1546] => 'week' => [
[1547] => 'name' => __('week'),
[1548] => 'icon' => 'icon-calendar-number-outline',
[1549] => ],
[1550] => ],
[1551] => 'advanced' => [
[1552] => 'message' => [
[1553] => 'name' => __('Message'),
[1554] => 'icon' => 'icon-chevron-down-circle-outline',
[1555] => ],
[1556] => 'accordion' => [
[1557] => 'name' => __('Accordion'),
[1558] => 'icon' => 'icon-chevron-down-circle-outline',
[1559] => ],
[1560] => 'tab' => [
[1561] => 'name' => __('Tab'),
[1562] => 'icon' => 'icon-chevron-down-circle-outline',
[1563] => ],
[1564] => ],
[1565] => ];
[1566] => }
[1567] =>
[1568] => /**
[1569] => * Return list of default template filenames.
[1570] => *
[1571] => * @return array List of default template filenames.
[1572] => */
[1573] => function getDefaultTemplateList(): array {
[1574] => return [
[1575] => 'index.html',
[1576] => 'index.coming-soon.html',
[1577] => 'index.maintenance.html',
[1578] => 'blank.html',
[1579] => 'error404.html',
[1580] => 'error500.html',
[1581] => 'contact.html',
[1582] => 'search/index.html',
[1583] => 'content/post.html',
[1584] => 'content/page.html',
[1585] => 'content/index.html',
[1586] => 'content/category.html',
[1587] => 'content/tag.html',
[1588] => 'product/index.html',
[1589] => 'product/product.html',
[1590] => 'product/category.html',
[1591] => ];
[1592] => }
[1593] =>
[1594] => /**
[1595] => * Return list of available templates for a theme.
[1596] => *
[1597] => * @param string|null $theme The theme name, or null to use the current theme.
[1598] => * @param array $skip List of folder names to skip.
[1599] => *
[1600] => * @return array Associative array of templates.
[1601] => */
[1602] => function getTemplateList(?string $theme = null, array $skip = []): array {
[1603] => $friendlyNames = [
[1604] => 'index' => ['name' => __('Home page'), 'description' => __('Website homepage'), 'global' => true],
[1605] => 'index.coming-soon' => ['name' => __('Coming soon'), 'description' => __('Coming soon message page'), 'global' => true],
[1606] => 'index.maintenance' => ['name' => __('Under maintenance'), 'description' => __('Website under maintenance message page'), 'global' => true],
[1607] => 'contact' => ['name' => __('Contact us page'), 'description' => __('Contact us page')],
[1608] => 'blank' => ['name' => __('Blank page'), 'description' => __('Template page used for new pages')],
[1609] => 'product' => ['name' => __('Product page'), 'description' => __('Used to display a product'), 'editor' => ['template' => 'product'], 'global' => true],
[1610] => 'error404' => ['name' => __('Page not found'), 'description' => __('Shows when a page is not available'), 'global' => true],
[1611] => 'error500' => ['name' => __('Server error'), 'description' => __('Site error display page'), 'global' => true],
[1612] => 'content-index' => ['name' => __('Blog homepage'), 'description' => __('Blog page with latest posts'), 'global' => true],
[1613] => 'content-post' => ['name' => __('Blog post'), 'description' => __('Blog post'), 'editor' => ['template' => 'post'], 'global' => true],
[1614] => 'content-page' => ['name' => __('Page'), 'description' => __('Page'), 'editor' => ['template' => 'page'], 'global' => true],
[1615] => 'product-index' => ['name' => __('Shop page'), 'description' => __('Shop homepage'), 'global' => true],
[1616] => 'search-index' => ['name' => __('Search page'), 'description' => __('Search page'), 'global' => true],
[1617] => 'user-index' => ['name' => __('Dashboard'), 'description' => __('User dashboard'), 'global' => true],
[1618] => ];
[1619] =>
[1620] => $skipFolders = array_merge(['src', 'source', 'backup', 'sections', 'blocks', 'inputs', 'css', 'scss', 'screenshots', 'locale', 'node_modules'], $skip);
[1621] =>
[1622] => if (! $theme) {
[1623] => $theme = \Vvveb\System\Sites::getTheme(SITE_ID) ?? 'default';
[1624] => }
[1625] => $pages = [];
[1626] => $themeFolder = DIR_THEMES . $theme;
[1627] => //$files = glob("$themeFolder/{,*/*/,*/}*.html", GLOB_BRACE);
[1628] => $glob = ['', '*/*/', '*/'];
[1629] => $files = globBrace($themeFolder . DS, $glob, '*.html');
[1630] =>
[1631] => foreach ($files as $file) {
[1632] => $file = preg_replace('@^.*[\\\/]themes[\\\/][^\\\/]+[\\\/]@', '', $file);
[1633] => $filename = basename($file);
[1634] =>
[1635] => $folder = \Vvveb\System\Functions\Str::match('@(\w+)/.*?$@', $file) ?? DS;
[1636] => $path = \Vvveb\System\Functions\Str::match('@(\w+)/.*?$@', $file);
[1637] =>
[1638] => if (in_array($folder, $skipFolders)) {
[1639] => continue;
[1640] => }
[1641] => $name = $title = str_replace('.html', '', $filename);
[1642] => $description = '';
[1643] => $name = (! empty($folder) && $folder != DS) ? "$folder-$name" : $name;
[1644] =>
[1645] => if (isset($friendlyNames[$name])) {
[1646] => if (isset($friendlyNames[$name]['description'])) {
[1647] => $description = $friendlyNames[$name]['description'];
[1648] => }
[1649] =>
[1650] => $title = $friendlyNames[$name]['name'];
[1651] => }
[1652] =>
[1653] => $url = PUBLIC_PATH . "themes/$theme/$file";
[1654] => $title = str_replace('index', 'home', $title);
[1655] =>
[1656] => $pages[$name] = ['name' => $name, 'filename' => $filename, 'file' => $file, 'url' => $url, 'title' => humanReadable($title), 'folder' => $path, 'description' => $description, 'global' => $friendlyNames[$name]['global'] ?? false];
[1657] =>
[1658] => if (isset($friendlyNames[$name]['editor'])) {
[1659] => $pages[$name]['editor'] = $friendlyNames[$name]['editor'];
[1660] => }
[1661] => }
[1662] =>
[1663] => //ksort($pages);
[1664] =>
[1665] => return $pages;
[1666] => }
[1667] =>
[1668] => /**
[1669] => * Sanitize filename by removing dangerous characters.
[1670] => *
[1671] => * @param string $file The filename to sanitize.
[1672] => * @param bool $normalizePath Whether to normalize directory separators to the OS separator.
[1673] => *
[1674] => * @return string The sanitized filename.
[1675] => */
[1676] => function sanitizeFileName(string $file, bool $normalizePath = true): string {
[1677] => if (! $file) {
[1678] => return $file;
[1679] => }
[1680] =>
[1681] => //remove null bytes
[1682] => $file = str_replace(chr(0), '', $file);
[1683] => //sanitize, remove double dot .. and remove get parameters if any
[1684] => $prev = '';
[1685] => while ($prev != $file) {
[1686] => $prev = $file;
[1687] => $file = preg_replace('@\?.*$|\.{2,}|[^\w\-\./: \\\]@' , '', $file);
[1688] => }
[1689] => //replace directory separators with OS specific separator
[1690] => if ($normalizePath) {
[1691] => $file = str_replace(['\\', '/'], DS, $file);
[1692] => }
[1693] =>
[1694] => return $file;
[1695] => }
[1696] =>
[1697] => /**
[1698] => * Format byte count to human-readable string (KB, MB, etc.).
[1699] => *
[1700] => * @param int $bytes The number of bytes.
[1701] => *
[1702] => * @return string The formatted string with appropriate unit.
[1703] => */
[1704] => function formatBytes(int $bytes): string {
[1705] => $i = 0;
[1706] => $units = ['', 'K', 'M', 'G', 'T', 'P', 'E'];
[1707] => $bytes = abs($bytes);
[1708] =>
[1709] => while (($remainder = ($bytes / 1024)) > 1) {
[1710] => $bytes = $remainder;
[1711] => $i++;
[1712] => }
[1713] =>
[1714] => return round($bytes, 2) . ' ' . $units[$i] . 'B';
[1715] => }
[1716] =>
[1717] => /**
[1718] => * Parse quantity string with K/M/G suffix to integer bytes.
[1719] => *
[1720] => * @param string $string The quantity string to parse (e.g. "10M", "512K").
[1721] => *
[1722] => * @return int The parsed integer value in bytes.
[1723] => */
[1724] => function parseQuantity(string $string): int {
[1725] => if (function_exists('\ini_parse_quantity')) {
[1726] => return \ini_parse_quantity($string);
[1727] => }
[1728] =>
[1729] => $string = trim($string);
[1730] => $multiplier = lcfirst($string[strlen($string) - 1]);
[1731] => $number = intval($string);
[1732] =>
[1733] => return match ($multiplier) {
[1734] => 'k' => $number * 1024,
[1735] => 'm' => $number * 1048576,
[1736] => 'g' => $number * 1073741824,
[1737] => default => $number,
[1738] => };
[1739] => }
[1740] =>
[1741] => /**
[1742] => * Check if a controller file exists.
[1743] => *
[1744] => * @param string $name The controller name.
[1745] => * @param string $app The application directory name.
[1746] => *
[1747] => * @return bool True if the controller file exists.
[1748] => */
[1749] => function isController(string $name, string $app = APP): bool {
[1750] => $file = DIR_ROOT . $app . DS . 'controller' . DS . strtolower($name) . '.php';
[1751] => $exists = file_exists($file);
[1752] =>
[1753] => return $exists;
[1754] => }
[1755] =>
[1756] => /**
[1757] => * Check if a model SQL file exists.
[1758] => *
[1759] => * @param string $name The model name.
[1760] => * @param string $app The application directory name.
[1761] => *
[1762] => * @return bool True if the model SQL file exists.
[1763] => */
[1764] => function isModel(string $name, string $app = APP): bool {
[1765] => $file = DIR_ROOT . $app . DS . 'sql' . DS . DB_ENGINE . DS . $name . '.sql';
[1766] => $exists = file_exists($file);
[1767] =>
[1768] => return $exists;
[1769] => }
[1770] =>
[1771] => /**
[1772] => * Instantiate and return a model object by name.
[1773] => *
[1774] => * @param string $model The model name.
[1775] => *
[1776] => * @return object The instantiated model object.
[1777] => */
[1778] => function model(string $model): object {
[1779] => $modelClass = 'Vvveb\Sql\\' . ucwords($model) . 'SQL';
[1780] =>
[1781] => return new $modelClass();
[1782] => }
[1783] =>
[1784] => /**
[1785] => * Instantiate and return a controller object by name.
[1786] => *
[1787] => * @param string $name The controller name.
[1788] => *
[1789] => * @return object The instantiated controller object.
[1790] => */
[1791] => function controller(string $name): object {
[1792] => $controllerClass = 'Vvveb\Controller\\' . ucwords($name);
[1793] =>
[1794] => return new $controllerClass();
[1795] => }
[1796] =>
[1797] => /**
[1798] => * Return the fully qualified class name for a field type.
[1799] => *
[1800] => * @param string $fieldType The field type identifier.
[1801] => *
[1802] => * @return string|null The fully qualified class name, or null if not found.
[1803] => */
[1804] => function fieldTypeClass(string $fieldType): ?string {
[1805] => $field = ucfirst($fieldType);
[1806] => $fieldClass = "Vvveb\System\Fields\\$field";
[1807] =>
[1808] => if (! class_exists($fieldClass)) {
[1809] => return null;
[1810] => }
[1811] =>
[1812] => return $fieldClass;
[1813] => }
[1814] =>
[1815] => /**
[1816] => * Returns available post types for a given type category.
[1817] => *
[1818] => * @param string $type The type category (e.g. 'post', 'product').
[1819] => * @return array
[1820] => */
[1821] => function postTypes(string $type): array {
[1822] => $defaultTypes = [
[1823] => 'post' => [
[1824] => 'post' => [
[1825] => 'name' => 'Post',
[1826] => 'source' => 'default',
[1827] => 'site_id' => '0',
[1828] => 'type' => 'post',
[1829] => 'plural' => 'posts',
[1830] => 'icon' => 'icon-document-text-outline',
[1831] => 'comments'=> true,
[1832] => ],
[1833] => 'page' => [
[1834] => 'name' => 'Page',
[1835] => 'source' => 'default',
[1836] => 'site_id' => '0',
[1837] => 'type' => 'page',
[1838] => 'plural' => 'pages',
[1839] => 'icon' => 'icon-document-outline',
[1840] => 'comments'=> false,
[1841] => ],
[1842] => ],
[1843] => 'product' => [
[1844] => 'product' => [
[1845] => 'name' => 'Product',
[1846] => 'type' => 'product',
[1847] => 'site_id' => '0',
[1848] => 'source' => 'default',
[1849] => 'plural' => 'products',
[1850] => 'icon' => 'icon-cube-outline',
[1851] => ],
[1852] => ],
[1853] => ];
[1854] =>
[1855] => $default = $defaultTypes[$type] ?? [];
[1856] => $typeName = ucfirst($type);
[1857] => list($pluginTypes) = System\Event::trigger('Vvveb\postTypes', "custom$typeName", []);
[1858] => array_walk($pluginTypes, function (&$type,$key) {
[1859] => $type['source'] = 'plugin';
[1860] => $type['name'] = ucfirst($key);
[1861] => });
[1862] =>
[1863] => $userTypes = getSetting($type, 'types', []);
[1864] =>
[1865] => $params = ['module' => "settings/{$type}-type"];
[1866] => $paramsList = ['module' => "settings/{$type}-types"];
[1867] =>
[1868] => array_walk($userTypes, function (&$type,$key) use ($params, $paramsList) {
[1869] => $type['source'] = 'user';
[1870] => $type['name'] = ucfirst($key);
[1871] => $type['url'] = \Vvveb\url($params + ['type' => $type['type']]);
[1872] => $type['delete-url'] = \Vvveb\url($paramsList + ['action' => 'delete', 'type[]' => $type['type']]);
[1873] => });
[1874] =>
[1875] => $types = $default + $pluginTypes + $userTypes;
[1876] =>
[1877] => return $types;
[1878] => }
[1879] =>
[1880] => /**
[1881] => * Debug output function - dumps variables with syntax highlighting.
[1882] => *
[1883] => * @param mixed ...$variables The variables to dump.
[1884] => * @return void
[1885] => */
[1886] => function d(mixed ...$variables): void {
[1887] => foreach ($variables as $variable) {
[1888] => echo highlight_string(" }
[1890] => }
[1891] =>
[1892] => /**
[1893] => * Debug and die - dumps variables with syntax highlighting and stops execution.
[1894] => *
[1895] => * @param mixed ...$variables The variables to dump.
[1896] => * @return never
[1897] => */
[1898] => function dd(mixed ...$variables): never {
[1899] => foreach ($variables as $variable) {
[1900] => echo highlight_string(" }
[1902] =>
[1903] => die();
[1904] => }
[1905] =>
[1906] => /**
[1907] => * Encrypts a string using OpenSSL with specified cipher and digest.
[1908] => *
[1909] => * @param string $key The encryption key.
[1910] => * @param string $value The plaintext string to encrypt.
[1911] => * @param string $cipher The cipher method (default 'aes256').
[1912] => * @param string $digest The digest method for key derivation (default 'sha256').
[1913] => * @param string|null &$tag The authentication tag output.
[1914] => * @param string $aad Additional authenticated data.
[1915] => * @param int $tag_length The authentication tag length (default 16).
[1916] => * @return string The base64-encoded encrypted string.
[1917] => */
[1918] => function encrypt(string $key, string $value, string $cipher = 'aes256', string $digest = 'sha256', ?string &$tag = null, string $aad = '', int $tag_length = 16): string {
[1919] => $key = openssl_digest($key, $digest, true);
[1920] => $iv_length = openssl_cipher_iv_length($cipher);
[1921] => $iv = openssl_random_pseudo_bytes($iv_length);
[1922] =>
[1923] => return base64_encode($iv . openssl_encrypt($value, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, $aad, $tag_length));
[1924] => }
[1925] =>
[1926] => /**
[1927] => * Decrypts an OpenSSL-encrypted string.
[1928] => *
[1929] => * @param string $key The decryption key.
[1930] => * @param string $value The base64-encoded encrypted string.
[1931] => * @param string $cipher The cipher method (default 'aes256').
[1932] => * @param string $digest The digest method for key derivation (default 'sha256').
[1933] => * @param string|null &$tag The authentication tag output.
[1934] => * @param string $aad Additional authenticated data.
[1935] => * @return string|false The decrypted string, or false on failure.
[1936] => */
[1937] => function decrypt(string $key, string $value, string $cipher = 'aes256', string $digest = 'sha256', ?string &$tag = null, string $aad = ''): string|false {
[1938] => $result = false;
[1939] =>
[1940] => $key = openssl_digest($key, $digest, true);
[1941] => $iv_length = openssl_cipher_iv_length($cipher);
[1942] => $value = base64_decode($value);
[1943] => $iv = substr($value, 0, $iv_length);
[1944] => $value = substr($value, $iv_length);
[1945] =>
[1946] => if (strlen($iv) == $iv_length) {
[1947] => $result = openssl_decrypt($value, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, $aad);
[1948] => }
[1949] =>
[1950] => return $result;
[1951] => }
[1952] =>
[1953] => /**
[1954] => * Converts CamelCase string to dash/underscore-separated lowercase.
[1955] => *
[1956] => * @param string $string The CamelCase string to convert.
[1957] => * @param string $us The separator character (default '-').
[1958] => * @return string The converted string.
[1959] => */
[1960] => function camelToUnderscore(string $string, string $us = '-'): string {
[1961] => return strtolower(preg_replace(
[1962] => '/(?<=\d)(?=[A-Za-z])|(?<=[A-Za-z])(?=\d)|(?<=[a-z])(?=[A-Z])/', $us, $string));
[1963] => }
[1964] =>
[1965] => /**
[1966] => * Recursively converts camelCase array keys to dash-separated lowercase.
[1967] => *
[1968] => * @param array &$array The array to convert keys for.
[1969] => * @param string $us The separator character (default '-').
[1970] => * @return array The array with converted keys.
[1971] => */
[1972] => function arrayKeysToUnderscore(array &$array, string $us = '-'): array {
[1973] => foreach ($array as $key => &$value) {
[1974] => if (is_array($value)) {
[1975] => $value = arrayKeysToUnderscore($value, $us);
[1976] => }
[1977] =>
[1978] => $newKey = camelToUnderscore($key, $us);
[1979] =>
[1980] => if ($newKey != $key) {
[1981] => $array[$newKey] = $value;
[1982] => unset($array[$key]);
[1983] => }
[1984] => }
[1985] =>
[1986] => return $array;
[1987] => }
[1988] =>
[1989] => /**
[1990] => * Removes specified HTML tags from a string.
[1991] => *
[1992] => * @param string $string The string to process.
[1993] => * @param array $tags The tag names to strip (default ['script', 'iframe', 'applet']).
[1994] => * @return string The string with tags removed.
[1995] => */
[1996] => function stripTags(string $string, array $tags = ['script', 'iframe', 'applet']): string {
[1997] => if ($string && $tags) {
[1998] => foreach ($tags as $tag) {
[1999] => $string = preg_replace('@<\s*' . $tag . '.*?>.*?' . $tag . '\s*>@im', '', $string);
[2000] => }
[2001] => }
[2002] =>
[2003] => return $string;
[2004] => }
[2005] =>
[2006] => /**
[2007] => * Recursively strips tags from all string values in an array.
[2008] => *
[2009] => * @param array $array The array to process.
[2010] => * @param array $tags The tag names to strip.
[2011] => * @return array The array with tags stripped from string values.
[2012] => */
[2013] => function stripTagsArray(array $array, array $tags): array {
[2014] => if ($tags) {
[2015] => foreach ($array as $key => &$value) {
[2016] => if (is_string($value)) {
[2017] => $value = stripTags($value, $tags);
[2018] => } elseif (is_array($value)) {
[2019] => $value = stripTagsArray($value, $tags);
[2020] => }
[2021] => }
[2022] => unset($value);
[2023] => }
[2024] =>
[2025] => return $array;
[2026] => }
[2027] =>
[2028] => /**
[2029] => * Removes specified characters from a string.
[2030] => *
[2031] => * @param string $string The string to process.
[2032] => * @param array $chars The characters to remove.
[2033] => * @return string The string with characters removed.
[2034] => */
[2035] => function stripChars(string $string, array $chars = ['(', ')', '{', '}', ':', '<', '>']): string {
[2036] => return str_replace($chars, '', $string);
[2037] => }
[2038] =>
[2039] => /**
[2040] => * Sanitizes HTML by removing dangerous elements and attributes.
[2041] => *
[2042] => * @param mixed $string The string to sanitize. Non-string values are returned as-is.
[2043] => * @return mixed The sanitized string, or the original value if not a string.
[2044] => */
[2045] => function sanitizeHTML(mixed $string): mixed {
[2046] => if (! is_string($string)) {
[2047] => return $string;
[2048] => }
[2049] =>
[2050] => // Fix &entity\n;
[2051] => //add <> before tag entities to allow regex matching
[2052] => $string = str_replace(['<', '>'], ['<<', '>>'], $string);
[2053] => $string = str_replace(['&', '<', '>'], ['&', '<', '>'], $string);
[2054] => $string = preg_replace('/(*\w+)[\x00-\x20]+;/u', '$1;', $string);
[2055] => $string = preg_replace('/(*[0-9A-F]+);*/iu', '$1;', $string);
[2056] => $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8');
[2057] =>
[2058] => // Remove any attribute starting with "on" or xmlns
[2059] => $string = preg_replace('#(<[^>]+?[\x00-\x20"\'/])(?:on|xmlns)[^>]*+>#iu', '$1>', $string);
[2060] => $string = preg_replace('#[\x00-\x20"\'/](on|xmlns)[:\w]+[\x00-\x20"\'/]*=[\x00-\x20"\'/]*([\'"]+).+?\2#iu', '', $string);
[2061] =>
[2062] => // Remove javascript: and vbscript: protocols
[2063] => $string = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $string);
[2064] => $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $string);
[2065] => $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $string);
[2066] =>
[2067] => // Only works in IE:
[2068] => $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $string);
[2069] => $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $string);
[2070] => $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $string);
[2071] =>
[2072] => // Remove namespaced elements (we do not need them)
[2073] => $string = preg_replace('#*\w+:\w[^>]*+>#i', '', $string);
[2074] =>
[2075] => do {
[2076] => // Remove really unwanted tags
[2077] => $old_data = $string;
[2078] => //$string = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $string);
[2079] => $string = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $string);
[2080] => } while ($old_data !== $string);
[2081] =>
[2082] => //restore entities
[2083] => $string = str_replace(['<<', '>>'], ['<', '>'], $string);
[2084] => return $string;
[2085] => }
[2086] =>
[2087] =>
[2088] => /**
[2089] => * Returns site settings with caching and optional language filtering.
[2090] => *
[2091] => * @param int $site_id The site ID (default SITE_ID).
[2092] => * @param int|false $language_id The language ID to filter by, or false to skip filtering.
[2093] => * @return array The site settings array.
[2094] => */
[2095] => function siteSettings(int $site_id = SITE_ID, int|false $language_id = false): array {
[2096] => $cache = System\Cache::getInstance();
[2097] => $site = $cache->cache(APP,'site.' . $site_id, function () use ($site_id) {
[2098] => $siteSql = new Sql\SiteSQL();
[2099] => $site = $siteSql->get(['site_id' => $site_id]);
[2100] =>
[2101] => if ($site && isset($site['settings'])) {
[2102] => $settings = json_decode($site['settings'], true);
[2103] => $siteData = $siteSql->getSiteData(['site' => $settings]);
[2104] =>
[2105] => foreach (['favicon', 'logo', 'logo-sticky', 'logo-dark', 'logo-dark-sticky', 'webbanner'] as $img) {
[2106] => if (isset($settings[$img])) {
[2107] => $settings["$img-src"] = $settings[$img];
[2108] => $settings[$img] = System\Images::image($settings[$img], '');
[2109] => }
[2110] => }
[2111] =>
[2112] => return $settings + $siteData;
[2113] => }
[2114] =>
[2115] => return [];
[2116] => }, 259200);
[2117] =>
[2118] => if ($language_id && $site) {
[2119] => $site['description'] = $site['description'][$language_id] ?? $site['description'][1] ?? '';
[2120] => }
[2121] =>
[2122] => return $site;
[2123] => }
[2124] =>
[2125] => /**
[2126] => * Check php code for syntax errors, return false on errors.
[2127] => *
[2128] => * @param string $source
[2129] => * @return bool
[2130] => */
[2131] => function checkPhpSyntax(string $source): bool {
[2132] => try {
[2133] => $tokens = token_get_all($source, TOKEN_PARSE);
[2134] => } catch (\ParseError $e) {
[2135] => return false;
[2136] => }
[2137] =>
[2138] => return $tokens !== false;
[2139] => }
[2140] =>
[2141] => /**
[2142] => * Truncates text to specified number of characters.
[2143] => *
[2144] => * @param string $text The text to truncate.
[2145] => * @param int $limit The maximum number of characters.
[2146] => * @return string The truncated text.
[2147] => */
[2148] => function truncateWords(string $text, int $limit): string {
[2149] => $parts = preg_split('/(\s+)/ms', substr($text, 0, $limit * 2), 0, PREG_SPLIT_DELIM_CAPTURE);
[2150] => $partsCount = count($parts);
[2151] =>
[2152] => $length = 0;
[2153] => $lastPart = 0;
[2154] => for (; $lastPart < $partsCount; ++$lastPart) {
[2155] => $partlen = strlen($parts[$lastPart]);
[2156] => $length += $partlen;
[2157] => if ($length >= $limit) {
[2158] => //don't include less than two letter last word or spaces
[2159] => $partlen = strlen($parts[$lastPart - 1]);
[2160] => while (($partlen < 3) && $lastPart > 3) {
[2161] => $lastPart--;
[2162] => $partlen = strlen($parts[$lastPart - 1]);
[2163] => }
[2164] => break;
[2165] => }
[2166] => }
[2167] =>
[2168] => return implode(array_slice($parts, 0, $lastPart));
[2169] => }
[2170] =>
[2171] => /**
[2172] => * Send email using template.
[2173] => *
[2174] => * @param string|array $to
[2175] => * @param mixed $template
[2176] => * @param string $subject
[2177] => * @param array $data
[2178] => * @param mixed $config
[2179] => * @param null|mixed $app
[2180] => * @param mixed $site_id
[2181] => * @return bool
[2182] => */
[2183] => function email(string|array $to, string $subject, string|array $template, array $data = [], array $config = [], ?string $app = null, int $site_id = SITE_ID): bool {
[2184] => $email = System\Email::getInstance();
[2185] =>
[2186] => if (is_array($template)) {
[2187] => $html = $template['html'] ?? '';
[2188] => $txt = $template['txt'] ?? '';
[2189] => } else {
[2190] => $htmlView = new System\Core\View($app);
[2191] => $htmlView->setTheme();
[2192] => $htmlView->set($data);
[2193] => //get email html template
[2194] => $htmlView->template("email/$template.html");
[2195] => $html = $htmlView->render(true, false, true);
[2196] =>
[2197] => //get email text template
[2198] => $txtView = new System\Core\View($app);
[2199] => $txtView->setTheme();
[2200] => $txtView->set($data);
[2201] => $txtView->template("email/$template.txt.html");
[2202] => $txt = $txtView->render(true, false, true);
[2203] => $txt = htmlToText($txt);
[2204] => /*
[2205] => $htmlView->template("email/$template.txt.html");
[2206] => $txt = $htmlView->render(true, false, true);
[2207] => */
[2208] => }
[2209] =>
[2210] => //get site contact email for sender and reply to
[2211] => $site = siteSettings($site_id, session('language_id') ?? 1);
[2212] => $sender = $config['sender'] ?? $site['description']['title'];
[2213] => $from = $config['from'] ?? $site['contact-email'];
[2214] => $reply = $config['reply'] ?? $site['contact-email'];
[2215] =>
[2216] => $email->setHtml($html);
[2217] => $email->setText($txt);
[2218] => $email->setTo($to);
[2219] => $email->setSender($sender);
[2220] => $email->setFrom($from);
[2221] => $email->setReplyTo($reply);
[2222] => $email->setSubject($subject);
[2223] =>
[2224] => return $email->send();
[2225] => }
[2226] =>
[2227] => /**
[2228] => * Recursively removes a directory.
[2229] => *
[2230] => * @param string $src The directory path to remove.
[2231] => * @param array $skip Files or directories to skip (currently unused).
[2232] => * @param mixed $keepParent
[2233] => * @return bool True on success, false on failure.
[2234] => */
[2235] => function rrmdir(string $src, array $skip = [], $keepParent = false): bool {
[2236] => if (! is_dir($src)) {
[2237] => return false;
[2238] => }
[2239] =>
[2240] => $dir = @opendir($src);
[2241] => $result = true;
[2242] =>
[2243] => if ($dir) {
[2244] => while (false !== ($file = readdir($dir))) {
[2245] => $full = $src . DS . $file;
[2246] =>
[2247] => if (($file != '.') &&
[2248] => ($file != '..') &&
[2249] => (! in_array($full, $skip))) {
[2250] => if (is_dir($full)) {
[2251] => $result = rrmdir($full, $skip, false);
[2252] => if ($result) {
[2253] => $result = rmdir($full);
[2254] => }
[2255] => } else {
[2256] => $result = @unlink($full);
[2257] => }
[2258] =>
[2259] => if (! $result) {
[2260] => break;
[2261] => }
[2262] => }
[2263] => }
[2264] =>
[2265] => closedir($dir);
[2266] =>
[2267] => if ($result && ! $keepParent) {
[2268] => //rmdir($full);
[2269] => }
[2270] => }
[2271] =>
[2272] => return $result;
[2273] => }
[2274] =>
[2275] => /**
[2276] => * Recursively copies a directory.
[2277] => *
[2278] => * @param string $src The source directory path.
[2279] => * @param string $dst The destination directory path.
[2280] => * @param array $skip Files or directories to skip.
[2281] => * @param bool $overwrite Whether to overwrite existing destination (default true).
[2282] => * @return bool True on success, false on failure.
[2283] => */
[2284] => function rcopy(string $src, string $dst, array $skip = [], bool $overwrite = true): bool {
[2285] => if ($overwrite && file_exists($dst)) {
[2286] => //rrmdir($dst);
[2287] => }
[2288] =>
[2289] => $result = true;
[2290] =>
[2291] => if (is_dir($src)) {
[2292] => if (! file_exists($dst)) {
[2293] => $result = @mkdir($dst);
[2294] =>
[2295] => if (! $result) {
[2296] => return false;
[2297] => }
[2298] => }
[2299] =>
[2300] => @chmod($dst, 0755);
[2301] =>
[2302] => $files = scandir($src);
[2303] =>
[2304] => foreach ($files as $file) {
[2305] => $full = $src . DS . $file;
[2306] =>
[2307] => if ($file != '.' &&
[2308] => $file != '..' &&
[2309] => ! in_array($file, $skip)) {
[2310] => if (is_dir($full)) {
[2311] => $result = rcopy($full, $dst . DS . $file);
[2312] => } else {
[2313] => $result = @copy($full, $dst . DS . $file);
[2314] =>
[2315] => if (! $result) {
[2316] => if (@chmod($dst . DS . $file, 0644)) {
[2317] => $result = @copy($full, $dst . DS . $file);
[2318] => }
[2319] => }
[2320] => }
[2321] =>
[2322] => if (! $result) {
[2323] => return false;
[2324] => }
[2325] => }
[2326] => }
[2327] => } else {
[2328] => if (file_exists($src)) {
[2329] => $result = @copy($src, $dst);
[2330] =>
[2331] => if (! $result) {
[2332] => if (@chmod($dst, 0644)) {
[2333] => $result = copy($src, $dst);
[2334] => }
[2335] => }
[2336] =>
[2337] => if (! $result) {
[2338] => return false;
[2339] => }
[2340] => }
[2341] => }
[2342] =>
[2343] => return $result;
[2344] => }
[2345] =>
[2346] => /**
[2347] => * Recursively renames/moves a directory.
[2348] => *
[2349] => * @param string $src The source directory path.
[2350] => * @param string $dst The destination directory path.
[2351] => * @param array $skip Files or directories to skip.
[2352] => * @return void
[2353] => */
[2354] => function rrename(string $src, string $dst, array $skip = []): void {
[2355] => if (file_exists($dst)) {
[2356] => rrmdir($dst);
[2357] => }
[2358] =>
[2359] => if (is_dir($src)) {
[2360] => if (! is_dir($dst)) {
[2361] => mkdir($dst, 0755, true);
[2362] => }
[2363] => $files = scandir($src);
[2364] =>
[2365] => foreach ($files as $file) {
[2366] => if ($file != '.' &&
[2367] => $file != '..' &&
[2368] => ! in_array($file, $skip)) {
[2369] => //rrename("$src/$file", "$dst/$file");
[2370] => rename("$src/$file", "$dst/$file");
[2371] => }
[2372] => }
[2373] => } else {
[2374] => if (file_exists($src)) {
[2375] => rename($src, $dst);
[2376] => }
[2377] => }
[2378] => }
[2379] =>
[2380] => /**
[2381] => * Downloads content from a URL using cURL or file_get_contents.
[2382] => *
[2383] => * @param string $url The URL to download.
[2384] => * @return string|null The downloaded content, or null on failure.
[2385] => */
[2386] => function download(string $url): ?string {
[2387] => $result = false;
[2388] =>
[2389] => $url = html_entity_decode($url);
[2390] =>
[2391] => if (function_exists('curl_init')) {
[2392] => $ch = curl_init($url);
[2393] =>
[2394] => if ($ch) {
[2395] => curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
[2396] => //curl_setopt($ch, CURLOPT_RESOLVE, $ips);
[2397] => curl_setopt($ch, CURLOPT_FAILONERROR, true);
[2398] => curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
[2399] => curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT'] ?? 'Vvveb ' . V_VERSION);
[2400] => $result = curl_exec($ch);
[2401] => $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
[2402] =>
[2403] => if (PHP_MAJOR_VERSION < 8) {
[2404] => curl_close($ch);
[2405] => }
[2406] =>
[2407] => if ($httpcode != 200) {
[2408] => return null;
[2409] => }
[2410] => }
[2411] => } else {
[2412] => if (ini_get('allow_url_fopen') == '1') {
[2413] => $context_options = [
[2414] => 'http' => [
[2415] => 'timeout' => 5,
[2416] => 'ignore_errors' => 1,
[2417] => ],
[2418] => ];
[2419] => $context = stream_context_create($context_options);
[2420] => $result = file_get_contents($url, 'r', $context);
[2421] => }
[2422] => }
[2423] =>
[2424] => return $result;
[2425] => }
[2426] =>
[2427] => /**
[2428] => * Validates URL and resolves hostname to prevent SSRF attacks.
[2429] => *
[2430] => * @param string $url The URL to validate.
[2431] => * @return string|array The cURL resolve entries, an empty string for invalid URLs, or an empty array for private/reserved IPs.
[2432] => */
[2433] => function validateUrl(string $url): array {
[2434] => $p = parse_url($url);
[2435] => $scheme = $p['scheme'] ?? 'https';
[2436] => $port = $p['port'] ?? ($scheme === 'http' ? '80' : '443');
[2437] =>
[2438] => if (! $p || ! in_array($scheme, ['http', 'https'], true) || ! in_array($port, ['80', '443'], true) || empty($p['host'])) {
[2439] => return [];
[2440] => }
[2441] =>
[2442] => if (($scheme === 'http' && $port !== '80') || ($scheme === 'https' && $port !== '443')) {
[2443] => return [];
[2444] => }
[2445] =>
[2446] => $ips = gethostbynamel($p['host']) ?: [];
[2447] =>
[2448] => $results = [];
[2449] => foreach ($ips as $ip) {
[2450] => $results[] = $p['host'] . ':' . $port . ':[' . $ip . ']';
[2451] =>
[2452] => if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
[2453] => return []; // private / reserved / loopback / link-local
[2454] => }
[2455] => }
[2456] =>
[2457] => return $results;
[2458] => }
[2459] =>
[2460] => /**
[2461] => * Fetches URL content with caching support.
[2462] => *
[2463] => * @param string $url The URL to fetch.
[2464] => * @param bool $cache Whether to cache the result (default true).
[2465] => * @param int $expire Cache expiration in seconds (default 604800).
[2466] => * @param int $timeout Request timeout in seconds (default 5).
[2467] => * @param bool $exception Whether to throw exceptions on failure (default true).
[2468] => * @return mixed The fetched content, or null on failure.
[2469] => */
[2470] => function getUrl(string $url, bool $cache = true, int $expire = 604800, int $timeout = 5, bool $exception = true): mixed {
[2471] => $cacheDriver = System\Cache :: getInstance();
[2472] => $cacheKey = md5($url);
[2473] => $result = false;
[2474] =>
[2475] =>
[2476] => if ($cache && ($result = $cacheDriver->get('url', $cacheKey))) {
[2477] => return $result;
[2478] => } else {
[2479] => $result = false;
[2480] => $url = html_entity_decode($url);
[2481] => //try with curl
[2482] => if (function_exists('curl_init')) {
[2483] => $ch = curl_init($url);
[2484] =>
[2485] => curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
[2486] => //curl_setopt($ch, CURLOPT_RESOLVE, $ips);
[2487] => curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
[2488] => curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
[2489] => curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT'] ?? 'Vvveb ' . V_VERSION);
[2490] =>
[2491] => if (DEBUG) {
[2492] => curl_setopt($ch, CURLOPT_VERBOSE, true);
[2493] => $streamVerboseHandle = fopen('php://temp', 'w+');
[2494] => curl_setopt($ch, CURLOPT_STDERR, $streamVerboseHandle);
[2495] => }
[2496] =>
[2497] => $result = curl_exec($ch);
[2498] =>
[2499] => if ($result) {
[2500] => $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
[2501] => if ($httpcode != 200) {
[2502] => return null;
[2503] => }
[2504] =>
[2505] => if ($cache) {
[2506] => $cacheDriver->set('url', $cacheKey, $result);
[2507] => }
[2508] =>
[2509] => return $result;
[2510] => } else {
[2511] => if ($exception) {
[2512] => $message = 'Curl error: ' . curl_errno($ch) . ' - ' . curl_error($ch);
[2513] =>
[2514] => if (DEBUG) {
[2515] => $message .= "\n" . print_r(curl_getinfo($ch), 1);
[2516] => rewind($streamVerboseHandle);
[2517] => $message .= "\n" . stream_get_contents($streamVerboseHandle);
[2518] => }
[2519] =>
[2520] => throw new \Exception($message);
[2521] => }
[2522] => }
[2523] =>
[2524] => if (PHP_MAJOR_VERSION < 8) {
[2525] => curl_close($ch);
[2526] => }
[2527] => } else {
[2528] => //try with file get contents
[2529] => if (ini_get('allow_url_fopen') == '1') {
[2530] => $context_options = [
[2531] => 'http' => [
[2532] => 'timeout' => $timeout,
[2533] => 'ignore_errors' => 1,
[2534] => ],
[2535] => ];
[2536] =>
[2537] => $context = stream_context_create($context_options);
[2538] =>
[2539] => if ($exception) {
[2540] => $result = file_get_contents($url, false, $context);
[2541] => } else {
[2542] => $result = @file_get_contents($url, false, $context);
[2543] => }
[2544] => }
[2545] =>
[2546] => if ($result) {
[2547] => if ($cache) {
[2548] => $cacheDriver->set('url', $cacheKey, $result);
[2549] => }
[2550] =>
[2551] => return $result;
[2552] => }
[2553] => }
[2554] => }
[2555] =>
[2556] => return $result;
[2557] => }
[2558] =>
[2559] => /**
[2560] => * Extracts a zip archive.
[2561] => *
[2562] => * @param string $file The path to the zip file to extract.
[2563] => * @return bool True on success, false on failure.
[2564] => */
[2565] => function unzip(string $file): bool {
[2566] => // get the absolute path to $file
[2567] => $path = pathinfo(realpath($file), PATHINFO_DIRNAME);
[2568] =>
[2569] => $zip = new \ZipArchive();
[2570] => $res = $zip->open($file);
[2571] =>
[2572] => if ($res === true) {
[2573] => $zip->extractTo($path);
[2574] => $zip->close();
[2575] =>
[2576] => return true;
[2577] => }
[2578] =>
[2579] => return false;
[2580] => }
[2581] =>
[2582] => /**
[2583] => * Converts an HTML string to plain text.
[2584] => *
[2585] => * @param string $html The HTML string to convert.
[2586] => * @return string The plain text result.
[2587] => */
[2588] => function htmlToText(string $html): string {
[2589] => $html = preg_replace('/\s+/', ' ', $html);
[2590] => $html = str_replace('
', "\n", $html);
[2591] => //add new line for block elements
[2592] => $html = preg_replace('/<(div|p|li|hr|pre|blockquote|section|h\d+)/', "\n<$1", $html);
[2593] =>
[2594] => return trim(strip_tags($html));
[2595] => }
[2596] =>
[2597] => /**
[2598] => * Wraps double-newline-separated text in paragraph tags.
[2599] => *
[2600] => * @param string $string The text to wrap.
[2601] => * @return string The text wrapped in tags.
[2602] => */
[2603] => function nl2p(string $string): string {
[2604] => $paragraphs = '';
[2605] =>
[2606] => foreach (explode("\n\n", $string) as $line) {
[2607] => if (trim($line)) {
[2608] => $paragraphs .= "
$line
";
[2609] => }
[2610] => }
[2611] =>
[2612] => return $paragraphs;
[2613] => }
[2614] =>
[2615] => /**
[2616] => * Returns Bootstrap CSS class for order status badge.
[2617] => *
[2618] => * @param int $order_status_id Order status ID (1-7)
[2619] => * @return string CSS class string for the badge
[2620] => */
[2621] => function orderStatusBadgeClass(int $order_status_id = 1): string {
[2622] => $classes = [
[2623] => 1 => 'bg-primary-subtle text-body', //pending
[2624] => 2 => 'bg-info-subtle text-body', //processing
[2625] => 3 => 'bg-primary', //processed
[2626] => 4 => 'bg-success', //complete
[2627] => 5 => 'bg-danger', //canceled
[2628] => 6 => 'bg-secondary', //archived
[2629] => 7 => 'bg-warning', //requires_action
[2630] => ];
[2631] =>
[2632] => return $classes[$order_status_id] ?? 'bg-warning text-dark';
[2633] => }
[2634] =>
[2635] => /**
[2636] => * Returns Bootstrap CSS class for payment status badge.
[2637] => *
[2638] => * @param int $payment_status_id Payment status ID (1-10)
[2639] => * @return string CSS class string for the badge
[2640] => */
[2641] => function paymentStatusBadgeClass(int $payment_status_id = 1): string {
[2642] => $classes = [
[2643] => 1 => 'bg-warning-subtle text-body', //not_paid
[2644] => 2 => 'bg-success-subtle text-body', //awaiting
[2645] => 3 => 'bg-danger-subtle text-body', //captured
[2646] => 4 => 'bg-success', //paid
[2647] => 5 => 'bg-danger', //canceled
[2648] => 6 => 'bg-danger', //refunded
[2649] => 7 => 'bg-danger', //partially_refunded
[2650] => 8 => 'bg-danger', //chargeback
[2651] => 9 => 'bg-danger', //requires_action
[2652] => 10 => 'bg-danger', //fraud
[2653] => ];
[2654] =>
[2655] => return $classes[$payment_status_id] ?? 'bg-warning text-dark';
[2656] => }
[2657] =>
[2658] => /**
[2659] => * Returns Bootstrap CSS class for shipping status badge.
[2660] => *
[2661] => * @param int $shipping_status_id Shipping status ID (1-10)
[2662] => * @return string CSS class string for the badge
[2663] => */
[2664] => function shippingStatusBadgeClass(int $shipping_status_id = 1): string {
[2665] => $classes = [
[2666] => 1 => 'bg-warning-subtle text-body', //not_fulfilled
[2667] => 2 => 'bg-success-subtle text-body', //fulfilled
[2668] => 3 => 'bg-success-subtle text-body', //partially_fulfilled
[2669] => 4 => 'bg-success', //shipped
[2670] => 5 => 'bg-success-subtle text-body', //partially_shipped
[2671] => 6 => 'bg-danger-subtle text-body', //returned
[2672] => 7 => 'bg-danger-subtle text-body', //partially_returned
[2673] => 8 => 'bg-success', //delivered
[2674] => 9 => 'bg-danger', //canceled
[2675] => 10 => 'bg-warning', //requires_action
[2676] => ];
[2677] =>
[2678] => return $classes[$shipping_status_id] ?? 'bg-warning text-dark';
[2679] => }
[2680] =>
[2681] => /**
[2682] => * Returns Bootstrap CSS class for comment status badge.
[2683] => *
[2684] => * @param int $status Comment status ID (0-3)
[2685] => * @return string CSS class string for the badge
[2686] => */
[2687] => function commentStatusBadgeClass(int $status = 0): string {
[2688] => $classes = [
[2689] => 0 => 'bg-primary-subtle text-body', //pending
[2690] => 1 => 'bg-success-subtle text-body', //approved
[2691] => 2 => 'bg-warning-subtle text-body', //spam
[2692] => 3 => 'bg-danger-subtle text-body', //trash
[2693] => ];
[2694] =>
[2695] => return $classes[$status] ?? 'bg-secondary-subtle text-dark';
[2696] => }
[2697] =>
[2698] => /**
[2699] => * Glob with brace expansion support (fallback for systems without GLOB_BRACE).
[2700] => *
[2701] => * @param string $path Directory path
[2702] => * @param array $glob Array of glob patterns
[2703] => * @param string $filename Optional filename suffix
[2704] => * @return array Array of matching file paths
[2705] => */
[2706] => function globBrace(string $path, array $glob, string $filename = ''): array {
[2707] => //$glob = ['*','*/*','*/*/*'];
[2708] => $files = [];
[2709] =>
[2710] => if (defined('GLOB_BRACE')) {
[2711] => $path .= '{' . implode(',', $glob) . '}' . $filename;
[2712] => $files = glob($path, GLOB_BRACE);
[2713] => } else {
[2714] => foreach ($glob as $pattern) {
[2715] => $files = array_merge($files, glob($path . DS . $pattern . $filename));
[2716] => }
[2717] => }
[2718] =>
[2719] => return $files;
[2720] => }
[2721] =>
[2722] => /**
[2723] => * Checks if the current request is over HTTPS.
[2724] => *
[2725] => * @return bool True if request is secure, false otherwise
[2726] => */
[2727] => function isSecure(): bool {
[2728] => return
[2729] => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
[2730] => || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
[2731] => || ($_SERVER['SERVER_PORT'] ?? 80) == 443;
[2732] => }
[2733] =>
[2734] => /**
[2735] => * Generates a string of random digits of specified length.
[2736] => *
[2737] => * @param int $length Number of digits to generate
[2738] => * @return string Random digit string
[2739] => */
[2740] => function randomDigits(int $length): string {
[2741] => $result = '';
[2742] =>
[2743] => for ($i = 0; $i < $length; $i++) {
[2744] => $result .= random_int(0, 9);
[2745] => }
[2746] =>
[2747] => return $result;
[2748] => }
[2749] =>
[2750] => /**
[2751] => * Formats invoice number by replacing template placeholders with data.
[2752] => *
[2753] => * @param string $format The format string containing {placeholders} like {year}, {month}, {rand-no-6}
[2754] => * @param array $data Array of replacement values
[2755] => *
[2756] => * @return string The formatted invoice string
[2757] => */
[2758] => function invoiceFormat(string $format, array $data): string {
[2759] => if (! $format) {
[2760] => return '';
[2761] => }
[2762] => $data += ['date_added' => time(), 'year' => date('Y'), 'year2' => date('y'), 'month_name' => date('M'), 'month' => date('m'), 'day' => date('d')];
[2763] =>
[2764] => return preg_replace_callback('/{(.+?)}/', function ($matches) use ($data) {
[2765] => $key = $matches[1];
[2766] =>
[2767] => if (isset($data[$key])) {
[2768] => return $data[$key];
[2769] => }
[2770] =>
[2771] => if (strpos($key, 'rand-no-') !== false) {
[2772] => $length = (int) str_replace('rand-no-', '', $key);
[2773] =>
[2774] => return randomDigits($length);
[2775] => }
[2776] =>
[2777] => if (strpos($key, 'rand-str-') !== false) {
[2778] => $length = (int) str_replace('rand-str-', '', $key);
[2779] =>
[2780] => return strtoupper(System\Functions\Str::random($length));
[2781] => }
[2782] =>
[2783] => return $matches[0];
[2784] => }, $format);
[2785] => }
[2786] =>
[2787] => /**
[2788] => * Removes XML version declaration to avoid issues with PHP short open tags.
[2789] => *
[2790] => * @param string|null $xmlString The XML string to process
[2791] => *
[2792] => * @return string The XML string without the version declaration
[2793] => */
[2794] => function xmlStripVersionDeclr(?string $xmlString): string {
[2795] => $xmlString = preg_replace('/<\?xml\s+version[^\?]+\?>\s*/', '', $xmlString ?? '');
[2796] => //xml stylesheet
[2797] => $xmlString = str_replace('', $xmlString);
[2798] =>
[2799] => return $xmlString;
[2800] => }
[2801] =>
[2802] => /**
[2803] => * Converts a PHP array to XML string using SimpleXMLElement.
[2804] => *
[2805] => * Supports attribute encoding via @@ prefix and type preservation markers (--array, --boolean, --int, --null, --object).
[2806] => *
[2807] => * @param array $array The array to convert
[2808] => * @param \SimpleXMLElement|false $xml Existing XML element to append to, or false to create new
[2809] => *
[2810] => * @return string The XML string representation
[2811] => */
[2812] => function array2xml(array $array, \SimpleXMLElement|false $xml = false): string {
[2813] => if ($xml === false) {
[2814] => $xml = new \SimpleXMLElement(' ');
[2815] => }
[2816] =>
[2817] => foreach ($array as $key => $value) {
[2818] => $attributes = [];
[2819] => $processAttr = function (&$key) use (&$attributes) {
[2820] => $i = 0;
[2821] => $start = 0;
[2822] =>
[2823] => if ($key) {
[2824] => while (($start = strpos($key, '@@', $start))) {
[2825] => $i++;
[2826] => $start += 2;
[2827] => $end = (int)strpos($key,' ', $start);
[2828] => $name = substr($key, $start, $end ? $end - $start : null);
[2829] =>
[2830] => if ($end == 0) {
[2831] => $start += strlen($name);
[2832] => } else {
[2833] => $start = (int)$end;
[2834] => }
[2835] =>
[2836] => if ($name) {
[2837] => $name = explode('=', $name);
[2838] => $attributes[$name[0]] = trim($name[1] ?? '', '\'"');
[2839] => }
[2840] => }
[2841] =>
[2842] => $key = strstr($key, ' @@', true) ?: $key;
[2843] => }
[2844] => };
[2845] =>
[2846] => $processAttr($key);
[2847] =>
[2848] => if (is_array($value) || is_object($value)) {
[2849] => $node = $xml->addChild($key);
[2850] =>
[2851] => if ($attributes) {
[2852] => foreach ($attributes as $key => $val) {
[2853] => $node->addAttribute($key, htmlspecialchars($val));
[2854] => }
[2855] => }
[2856] => array2xml($value, $node);
[2857] => } else {
[2858] => if (substr_compare($key, '--xmlattr', -9, 9) === 0) {
[2859] => $key = substr($key, 0, -9);
[2860] => $xml->addAttribute($key, htmlspecialchars($value));
[2861] => } else {
[2862] => $processAttr($value);
[2863] =>
[2864] => if ($value && is_string($value)) {
[2865] => $value = htmlspecialchars($value);
[2866] => }
[2867] => $node = $xml->addChild($key, $value);
[2868] =>
[2869] => if ($attributes) {
[2870] => foreach ($attributes as $key => $val) {
[2871] => $node->addAttribute($key, $val);
[2872] => }
[2873] => }
[2874] => }
[2875] => }
[2876] => }
[2877] =>
[2878] => $xmlString = $xml->asXML();
[2879] =>
[2880] => return xmlStripVersionDeclr($xmlString);
[2881] => }
[2882] =>
[2883] => /**
[2884] => * Removes single-line and multi-line comments from a JSON string.
[2885] => *
[2886] => * @param string $json The JSON string with comments
[2887] => *
[2888] => * @return string The JSON string without comments
[2889] => */
[2890] => function removeJsonComments(string $json): string {
[2891] => $json = preg_replace('@^\s*//.*([\r\n]|\s)*|^\s*/\*([\r\n]|.)*?\*/([\r\n]|\s)*@m', '', $json);
[2892] =>
[2893] => return $json;
[2894] => }
[2895] =>
[2896] => /**
[2897] => * Encodes invalid XML name characters to numeric entity format (_xNNN_).
[2898] => *
[2899] => * @param string $name The XML element or attribute name to encode
[2900] => *
[2901] => * @return string The encoded name with invalid characters replaced
[2902] => */
[2903] => function encodeXmlName(string $name): string {
[2904] => $len = strlen($name);
[2905] => $newName = '';
[2906] =>
[2907] => for ($i = 0; $i < $len; $i++) {
[2908] => $char = $name[$i];
[2909] => $code = ord($char);
[2910] =>
[2911] => // = @ _ - . A-Z a-z 0-9
[2912] => if (($code >= 48 && $code <= 57) || ($code >= 65 && $code <= 90) || ($code >= 97 && $code <= 122) || $code === 32 || $code === 34 || $code === 45 || $code === 46 || $code === 61 || $code === 64 || $code === 95) {
[2913] => $char = '_x' . sprintf('%03d', $code) . '_';
[2914] => }
[2915] =>
[2916] => $newName .= $char;
[2917] => }
[2918] =>
[2919] => return $newName;
[2920] => }
[2921] =>
[2922] => /**
[2923] => * Decodes encoded XML name characters back to their original form.
[2924] => *
[2925] => * @param string $name The encoded XML name to decode
[2926] => *
[2927] => * @return string The decoded name with _xNNN_ entities restored to characters
[2928] => */
[2929] => function decodeXmlName(string $name): string {
[2930] => return preg_replace_callback('/_x(\d+)_/',function ($matches) {
[2931] => return chr(ltrim($matches[1], '0'));
[2932] => }, $name);
[2933] => }
[2934] =>
[2935] => /**
[2936] => * Prepares an array for XML/JSON conversion by encoding keys and preserving PHP types.
[2937] => *
[2938] => * Encodes attribute markers (@, @@), preserves empty arrays/objects, booleans, integers, and nulls
[2939] => * using type suffix markers (--array, --boolean, --int, --null, --object).
[2940] => *
[2941] => * @param array $array The array to prepare
[2942] => *
[2943] => * @return array The prepared array with encoded keys
[2944] => */
[2945] => function prepareJson(array $array): array { // <==
[2946] => if (! is_array($array)) {
[2947] => //return;
[2948] => }
[2949] => $helper = [];
[2950] =>
[2951] => foreach ($array as $key => $value) {
[2952] => if (is_numeric($key)) {
[2953] => $key = 'n--' . $key;
[2954] => }
[2955] =>
[2956] => if ($key[0] == '@') {
[2957] => if ($key[1] == '@') {
[2958] => $key = substr($key, 2) . '--xmlattr';
[2959] => } else {
[2960] => $key = substr($key, 1) . '--attr';
[2961] => }
[2962] => }
[2963] =>
[2964] => if (strpos($key, '@')) {
[2965] => }
[2966] =>
[2967] => //keep empty arrays as array type
[2968] => if (is_object($value) && ! $value) {
[2969] => $key .= '--object';
[2970] => } else {
[2971] => if (is_array($value) && ! $value) {
[2972] => $key .= '--array';
[2973] => }
[2974] => }
[2975] =>
[2976] => //keep boolean type
[2977] => if (is_bool($value)) {
[2978] => $key .= '--boolean';
[2979] => }
[2980] =>
[2981] => //keep int type
[2982] => if (is_int($value)) {
[2983] => $key .= '--int';
[2984] => }
[2985] =>
[2986] => //keep null type
[2987] => if ($value === null) {
[2988] => $key .= '--null';
[2989] => }
[2990] =>
[2991] => $key = encodeXmlName(trim($key));
[2992] =>
[2993] => $helper[$key] = is_array($value) ? prepareJson($value) : $value;
[2994] => }
[2995] =>
[2996] => return $helper;
[2997] => }
[2998] =>
[2999] => /**
[3000] => * Reverses prepareJson transformations to restore original array structure.
[3001] => *
[3002] => * Decodes type suffix markers, restores attribute prefixes, and converts numeric string keys back to integers.
[3003] => *
[3004] => * @param mixed $array The prepared array to reconstruct (passed by reference)
[3005] => * @param bool $removeAttrs Whether to remove @attributes keys entirely
[3006] => *
[3007] => * @return array|null The reconstructed array, or null if input is not iterable
[3008] => */
[3009] => function reconstructJson(mixed &$array, bool $removeAttrs = false): mixed {
[3010] => if (! is_iterable($array)) {
[3011] => return null;
[3012] => }
[3013] => $helper = [];
[3014] => $array = (array)$array;
[3015] =>
[3016] => foreach ($array as $key => $value) {
[3017] => if ($removeAttrs && $key == '@attributes') {
[3018] => continue;
[3019] => }
[3020] =>
[3021] => $key = decodeXmlName($key);
[3022] =>
[3023] => if (substr_compare($key, '--array', -7, 7) === 0) {
[3024] => $key = substr($key, 0, -7);
[3025] => $value = (array)$value;
[3026] => } else {
[3027] => if ($value === null) {
[3028] => $value = '';
[3029] => }
[3030] => }
[3031] =>
[3032] => if (substr_compare($key, '--boolean', -9, 9) === 0) {
[3033] => $key = substr($key, 0, -9);
[3034] => $value = (bool)$value;
[3035] => }
[3036] =>
[3037] => if (substr_compare($key, '--null', -6, 6) === 0) {
[3038] => $key = substr($key, 0, -6);
[3039] => $value = null;
[3040] => }
[3041] =>
[3042] => if (substr_compare($key, '--int', -5, 5) === 0) {
[3043] => $key = substr($key, 0, -5);
[3044] => $value = (int)$value;
[3045] => }
[3046] =>
[3047] => if (substr_compare($key, '--object', -8, 8) === 0) {
[3048] => $key = substr($key, 0, -8);
[3049] => $value = (object)$value;
[3050] => }
[3051] =>
[3052] => if (substr_compare($key, 'n--', 0, 3) === 0) {
[3053] => $newkey = substr($key, 3);
[3054] =>
[3055] => if (is_numeric($newkey)) {
[3056] => $key = intval($newkey);
[3057] => }
[3058] => }
[3059] =>
[3060] => if (substr_compare($key, '--xmlattr', -9, 9) === 0) {
[3061] => $key = '@@' . substr($key, 0, -9);
[3062] => }
[3063] =>
[3064] => if (substr_compare($key, '--attr', -6, 6) === 0) {
[3065] => $key = '@' . substr($key, 0, -6);
[3066] => }
[3067] =>
[3068] => $helper[$key] = (is_iterable($value)) ? reconstructJson($value, $removeAttrs) : $value;
[3069] => }
[3070] =>
[3071] => return $helper;
[3072] => }
[3073] =>
[3074] => /**
[3075] => * Returns a human-readable error message for a PHP file upload error code.
[3076] => *
[3077] => * @param int $errorCode One of the UPLOAD_ERR_* constants
[3078] => *
[3079] => * @return string The translated error message
[3080] => */
[3081] => function fileUploadErrMessage(int $errorCode): string {
[3082] => return match ($errorCode) {
[3083] => //UPLOAD_ERR_OK => __('Upload ok'),
[3084] => UPLOAD_ERR_NO_FILE => __('No file sent'),
[3085] => UPLOAD_ERR_PARTIAL => __('The uploaded file was only partially uploaded'),
[3086] => UPLOAD_ERR_NO_TMP_DIR => __('Missing a temporary folder'),
[3087] => UPLOAD_ERR_CANT_WRITE => __('Failed to write file to disk'),
[3088] => UPLOAD_ERR_EXTENSION => __('A PHP extension stopped the file upload'),
[3089] => UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => __('Exceeded filesize limit'),
[3090] => default => __('Unknown errors'),
[3091] => };
[3092] => }
)
)