$Data) { if (!isset($Data['Version']) && !isset($Data['Files']) && !isset($ModelArr[$Element])) { if ($Validate) { return false; } $Base = preg_replace("~\n" . $Element . ":?(\n [^\n]*)*\n~i", "\n", $Base); } } return $Validate ? true : $Base; }; /** * Can be used to delete some files via the front-end. * * @param string $File The file to delete. * @return bool Success or failure. */ $phpMussel['Delete'] = function ($File) use (&$phpMussel) { if (!empty($File) && file_exists($phpMussel['Vault'] . $File) && $phpMussel['Traverse']($File)) { if (!unlink($phpMussel['Vault'] . $File)) { return false; } $phpMussel['DeleteDirectory']($File); return true; } return false; }; /** * Can be used to delete some empty directories via the front-end. * * @param string $Dir The directory to delete. */ $phpMussel['DeleteDirectory'] = function ($Dir) use (&$phpMussel) { while (strrpos($Dir, '/') !== false || strrpos($Dir, "\\") !== false) { $Separator = (strrpos($Dir, '/') !== false) ? '/' : "\\"; $Dir = substr($Dir, 0, strrpos($Dir, $Separator)); if (is_dir($phpMussel['Vault'] . $Dir) && $phpMussel['FileManager-IsDirEmpty']($phpMussel['Vault'] . $Dir)) { rmdir($phpMussel['Vault'] . $Dir); } else { break; } } }; /** * Adds integer values; Returns zero if the sum total is negative or if any * contained values aren't integers, and otherwise, returns the sum total. */ $phpMussel['ZeroMin'] = function () { $Sum = 0; foreach (func_get_args() as $Value) { $IntValue = (int)$Value; if ($IntValue !== $Value) { return 0; } $Sum += $IntValue; } return $Sum < 0 ? 0 : $Sum; }; /** Format filesize information. */ $phpMussel['FormatFilesize'] = function (&$Filesize) use (&$phpMussel) { $Scale = ['field_size_bytes', 'field_size_KB', 'field_size_MB', 'field_size_GB', 'field_size_TB']; $Iterate = 0; $Filesize = (int)$Filesize; while ($Filesize > 1024) { $Filesize = $Filesize / 1024; $Iterate++; if ($Iterate > 4) { break; } } $Filesize = $phpMussel['Number_L10N']($Filesize, ($Iterate === 0) ? 0 : 2) . ' ' . $phpMussel['Plural']($Filesize, $phpMussel['lang'][$Scale[$Iterate]]); }; /** * Remove an entry from the front-end cache data. * * @param string $Source Variable containing cache file data. * @param bool $Rebuild Flag indicating to rebuild cache file. * @param string $Entry Name of the cache entry to be deleted. */ $phpMussel['FECacheRemove'] = function (&$Source, &$Rebuild, $Entry) { $Entry64 = base64_encode($Entry); while (($EntryPos = strpos($Source, "\n" . $Entry64 . ',')) !== false) { $EoL = strpos($Source, "\n", $EntryPos + 1); if ($EoL !== false) { $Line = substr($Source, $EntryPos, $EoL - $EntryPos); $Source = str_replace($Line, '', $Source); $Rebuild = true; } } }; /** * Add an entry to the front-end cache data. * * @param string $Source Variable containing cache file data. * @param bool $Rebuild Flag indicating to rebuild cache file. * @param string $Entry Name of the cache entry to be added. * @param string $Data Cache entry data (what should be cached). * @param int $Expires When should the cache entry expire (be deleted). */ $phpMussel['FECacheAdd'] = function (&$Source, &$Rebuild, $Entry, $Data, $Expires) use (&$phpMussel) { $phpMussel['FECacheRemove']($Source, $Rebuild, $Entry); $Expires = (int)$Expires; $NewLine = base64_encode($Entry) . ',' . base64_encode($Data) . ',' . $Expires . "\n"; $Source .= $NewLine; $Rebuild = true; }; /** * Get an entry from the front-end cache data. * * @param string $Source Variable containing cache file data. * @param bool $Rebuild Flag indicating to rebuild cache file. * @param string $Entry Name of the cache entry to get. * return string|bool Returned cache entry data (or false on failure). */ $phpMussel['FECacheGet'] = function ($Source, $Entry) { $Entry = base64_encode($Entry); $EntryPos = strpos($Source, "\n" . $Entry . ','); if ($EntryPos !== false) { $EoL = strpos($Source, "\n", $EntryPos + 1); if ($EoL !== false) { $Line = substr($Source, $EntryPos, $EoL - $EntryPos); $Entry = explode(',', $Line); if (!empty($Entry[1])) { return base64_decode($Entry[1]); } } } return false; }; /** * Compare two different versions of phpMussel, or two different versions of a * component for phpMussel, to see which is newer (mostly used by the updater). * * @param string $A The 1st version string. * @param string $B The 2nd version string. * return bool True if the 2nd version is newer than the 1st version, and false * otherwise (i.e., if they're the same, or if the 1st version is newer). */ $phpMussel['VersionCompare'] = function ($A, $B) { $Normalise = function (&$Ver) { $Ver = preg_match('~^v?([0-9]+)$~i', $Ver, $Matches) ?: preg_match('~^v?([0-9]+)\.([0-9]+)$~i', $Ver, $Matches) ?: preg_match('~^v?([0-9]+)\.([0-9]+)\.([0-9]+)(RC[0-9]{1,2}|-[0-9a-z_+\\/]+)?$~i', $Ver, $Matches) ?: preg_match('~^([0-9]{1,4})[.-]([0-9]{1,2})[.-]([0-9]{1,4})(RC[0-9]{1,2}|[.+-][0-9a-z_+\\/]+)?$~i', $Ver, $Matches) ?: preg_match('~^([a-z]+)-([0-9a-z]+)-([0-9a-z]+)$~i', $Ver, $Matches); $Ver = [ 'Major' => isset($Matches[1]) ? $Matches[1] : 0, 'Minor' => isset($Matches[2]) ? $Matches[2] : 0, 'Patch' => isset($Matches[3]) ? $Matches[3] : 0, 'Build' => isset($Matches[4]) ? substr($Matches[4], 1) : 0 ]; $Ver = array_map(function ($Var) { $VarInt = (int)$Var; $VarLen = strlen($Var); if ($Var == $VarInt && strlen($VarInt) === $VarLen && $VarLen > 1) { return $VarInt; } return strtolower($Var); }, $Ver); }; $Normalise($A); $Normalise($B); return ( $B['Major'] > $A['Major'] || ( $B['Major'] === $A['Major'] && $B['Minor'] > $A['Minor'] ) || ( $B['Major'] === $A['Major'] && $B['Minor'] === $A['Minor'] && $B['Patch'] > $A['Patch'] ) || ( $B['Major'] === $A['Major'] && $B['Minor'] === $A['Minor'] && $B['Patch'] === $A['Patch'] && !empty($A['Build']) && ( empty($B['Build']) || $B['Build'] > $A['Build'] ) ) ); }; /** * Remove sub-arrays from an array. * * @param array $Arr An array. * return array An array. */ $phpMussel['ArrayFlatten'] = function ($Arr) { return array_filter($Arr, function () { return (!is_array(func_get_args()[0])); }); }; /** Isolate a L10N array down to a single relevant L10N string. */ $phpMussel['IsolateL10N'] = function (&$Arr, $Lang) { if (isset($Arr[$Lang])) { $Arr = $Arr[$Lang]; } elseif (isset($Arr['en'])) { $Arr = $Arr['en']; } else { $Key = key($Arr); $Arr = $Arr[$Key]; } }; /** * Append one or two values to a string, depending on whether that string is * empty prior to calling the closure (allows cleaner code in some areas). * * @param string $String The string to work with. * @param string $Delimit Appended first, if the string is not empty. * @param string $Append Appended second, and always (empty or otherwise). */ $phpMussel['AppendToString'] = function (&$String, $Delimit = '', $Append = '') { if (!empty($String)) { $String .= $Delimit; } $String .= $Append; }; /** * Used by the file manager to generate a list of the files contained in a * working directory (normally, the vault). * * @param string $Base The path to the working directory. * @return array A list of the files contained in the working directory. */ $phpMussel['FileManager-RecursiveList'] = function ($Base) use (&$phpMussel) { $Arr = []; $Key = -1; $Offset = strlen($Base); $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($Base), RecursiveIteratorIterator::SELF_FIRST); foreach ($List as $Item => $List) { $Key++; $ThisName = substr($Item, $Offset); if (preg_match('~^(?:/\.\.|./\.|\.{3})$~', str_replace("\\", '/', substr($Item, -3)))) { continue; } $Arr[$Key] = ['Filename' => $ThisName]; if (is_dir($Item)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Directory'] = true; $Arr[$Key]['Filesize'] = 0; $Arr[$Key]['Filetype'] = $phpMussel['lang']['field_filetype_directory']; $Arr[$Key]['Icon'] = 'icon=directory'; } elseif (is_file($Item)) { $Arr[$Key]['CanEdit'] = true; $Arr[$Key]['Directory'] = false; $Arr[$Key]['Filesize'] = filesize($Item); if (isset($phpMussel['FE']['TotalSize'])) { $phpMussel['FE']['TotalSize'] += $Arr[$Key]['Filesize']; } if (isset($phpMussel['Components']['Components'])) { $Component = $phpMussel['lang']['field_filetype_unknown']; $ThisNameFixed = str_replace("\\", '/', $ThisName); if (isset($phpMussel['Components']['Files'][$ThisNameFixed])) { if (!empty($phpMussel['Components']['Names'][$phpMussel['Components']['Files'][$ThisNameFixed]])) { $Component = $phpMussel['Components']['Names'][$phpMussel['Components']['Files'][$ThisNameFixed]]; } else { $Component = $phpMussel['Components']['Files'][$ThisNameFixed]; } if ($Component === 'phpMussel') { $Component .= ' (' . $phpMussel['lang']['field_component'] . ')'; } } elseif (substr($ThisNameFixed, -10) === 'config.ini') { $Component = $phpMussel['lang']['link_config']; } else { $LastFour = strtolower(substr($ThisNameFixed, -4)); if ( $LastFour === '.tmp' || $ThisNameFixed === 'index.dat' || $ThisNameFixed === 'fe_assets/frontend.dat' || substr($ThisNameFixed, -9) === '.rollback' ) { $Component = $phpMussel['lang']['label_fmgr_cache_data']; } elseif ($LastFour === '.log' || $LastFour === '.txt') { $Component = $phpMussel['lang']['link_logs']; } elseif ($LastFour === '.qfu') { $Component = $phpMussel['lang']['label_quarantined']; } elseif (preg_match('/^\.(?:dat|inc|ya?ml)$/i', $LastFour)) { $Component = $phpMussel['lang']['label_fmgr_updates_metadata']; } } if (!isset($phpMussel['Components']['Components'][$Component])) { $phpMussel['Components']['Components'][$Component] = 0; } $phpMussel['Components']['Components'][$Component] += $Arr[$Key]['Filesize']; } if (($ExtDel = strrpos($Item, '.')) !== false) { $Ext = strtoupper(substr($Item, $ExtDel + 1)); if (!$Ext) { $Arr[$Key]['Filetype'] = $phpMussel['lang']['field_filetype_unknown']; $Arr[$Key]['Icon'] = 'icon=unknown'; $phpMussel['FormatFilesize']($Arr[$Key]['Filesize']); continue; } $Arr[$Key]['Filetype'] = $phpMussel['ParseVars'](['EXT' => $Ext], $phpMussel['lang']['field_filetype_info']); if ($Ext === 'ICO') { $Arr[$Key]['Icon'] = 'file=' . urlencode($Prepend . $Item); $phpMussel['FormatFilesize']($Arr[$Key]['Filesize']); continue; } if (preg_match( '/^(?:.?[BGL]Z.?|7Z|A(CE|LZ|P[KP]|R[CJ]?)?|B([AH]|Z2?)|CAB|DMG|' . 'I(CE|SO)|L(HA|Z[HOWX]?)|P(AK|AQ.?|CK|EA)|RZ|S(7Z|EA|EN|FX|IT.?|QX)|' . 'X(P3|Z)|YZ1|Z(IP.?|Z)?|(J|M|PH|R|SH|T|X)AR)$/' , $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=archive'; } elseif (preg_match('/^[SDX]?HT[AM]L?$/', $Ext)) { $Arr[$Key]['Icon'] = 'icon=html'; } elseif (preg_match('/^(?:CSV|JSON|NEON|SQL|YAML)$/', $Ext)) { $Arr[$Key]['Icon'] = 'icon=ods'; } elseif (preg_match('/^(?:PDF|XDP)$/', $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=pdf'; } elseif (preg_match('/^DOC[XT]?$/', $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=doc'; } elseif (preg_match('/^XLS[XT]?$/', $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=xls'; } elseif (preg_match('/^(?:CSS|JS|OD[BFGPST]|P(HP|PT))$/', $Ext)) { $Arr[$Key]['Icon'] = 'icon=' . strtolower($Ext); if (!preg_match('/^(?:CSS|JS|PHP)$/', $Ext)) { $Arr[$Key]['CanEdit'] = false; } } elseif (preg_match('/^(?:FLASH|SWF)$/', $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=swf'; } elseif (preg_match( '/^(?:BM[2P]|C(D5|GM)|D(IB|W[FG]|XF)|ECW|FITS|GIF|IMG|J(F?IF?|P[2S]|PE?G?2?|XR)|P(BM|CX|DD|GM|IC|N[GMS]|PM|S[DP])|S(ID|V[AG])|TGA|W(BMP?|EBP|MP)|X(CF|BMP))$/' , $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=image'; } elseif (preg_match( '/^(?:H?264|3GP(P2)?|A(M[CV]|VI)|BIK|D(IVX|V5?)|F([4L][CV]|MV)|GIFV|HLV|' . 'M(4V|OV|P4|PE?G[4V]?|KV|VR)|OGM|V(IDEO|OB)|W(EBM|M[FV]3?)|X(WMV|VID))$/' , $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=video'; } elseif (preg_match( '/^(?:3GA|A(AC|IFF?|SF|U)|CDA|FLAC?|M(P?4A|IDI|KA|P[A23])|OGG|PCM|' . 'R(AM?|M[AX])|SWA|W(AVE?|MA))$/' , $Ext)) { $Arr[$Key]['CanEdit'] = false; $Arr[$Key]['Icon'] = 'icon=audio'; } elseif (preg_match('/^(?:MD|NFO|RTF|TXT)$/', $Ext)) { $Arr[$Key]['Icon'] = 'icon=text'; } } else { $Arr[$Key]['Filetype'] = $phpMussel['lang']['field_filetype_unknown']; } } if (empty($Arr[$Key]['Icon'])) { $Arr[$Key]['Icon'] = 'icon=unknown'; } if ($Arr[$Key]['Filesize']) { $phpMussel['FormatFilesize']($Arr[$Key]['Filesize']); } else { $Arr[$Key]['Filesize'] = ''; } } return $Arr; }; /** * Used by the file manager and the updates pages to fetch the components list. * * @param string $Base The path to the working directory. * @param array $Arr The array to use for rendering components file YAML data. */ $phpMussel['FetchComponentsLists'] = function ($Base, &$Arr) use (&$phpMussel) { $Files = new DirectoryIterator($Base); foreach ($Files as $ThisFile) { if (!empty($ThisFile) && preg_match('/\.(?:dat|inc|ya?ml)$/i', $ThisFile)) { $Data = $phpMussel['ReadFile']($Base . $ThisFile); if (substr($Data, 0, 4) === "---\n" && ($EoYAML = strpos($Data, "\n\n")) !== false) { $phpMussel['YAML'](substr($Data, 4, $EoYAML - 4), $Arr); } } } }; /** * Checks paths for directory traversal and ensures that they only contain * expected characters. * * @param string $Path The path to check. * @return bool False when directory traversals and/or unexpected characters * are detected, and true otherwise. */ $phpMussel['FileManager-PathSecurityCheck'] = function ($Path) { $Path = str_replace("\\", '/', $Path); if ( preg_match('~(?://|[^!0-9A-Za-z\._-]$)~', $Path) || preg_match('~^(?:/\.\.|./\.|\.{3})$~', str_replace("\\", '/', substr($Path, -3))) ) { return false; } $Path = preg_split('@/@', $Path, -1, PREG_SPLIT_NO_EMPTY); $Valid = true; array_walk($Path, function($Segment) use (&$Valid) { if (empty($Segment) || preg_match('/(?:[\x00-\x1f\x7f]+|^\.+$)/i', $Segment)) { $Valid = false; } }); return $Valid; }; /** * Checks whether the specified directory is empty. * * @param string $Directory The directory to check. * @return bool True if empty; False if not empty. */ $phpMussel['FileManager-IsDirEmpty'] = function ($Directory) { return !((new \FilesystemIterator($Directory))->valid()); }; /** * Used by the logs viewer to generate a list of the logfiles contained in a * working directory (normally, the vault). * * @param string $Base The path to the working directory. * @return array A list of the logfiles contained in the working directory. */ $phpMussel['Logs-RecursiveList'] = function ($Base) use (&$phpMussel) { $Arr = []; $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($Base), RecursiveIteratorIterator::SELF_FIRST); foreach ($List as $Item => $List) { if ( preg_match('~^(?:/\.\.|./\.|\.{3})$~', str_replace("\\", '/', substr($Item, -3))) || !preg_match('~(?:logfile|\.(txt|log)$)~i', $Item) || !is_file($Item) || !is_readable($Item) || is_dir($Item) ) { continue; } $ThisName = substr($Item, strlen($Base)); $Arr[$ThisName] = ['Filename' => $ThisName, 'Filesize' => filesize($Item)]; $phpMussel['FormatFilesize']($Arr[$ThisName]['Filesize']); } return $Arr; }; /** * Checks whether a component is in use (front-end closure). * * @param array $Files The list of files to be checked. * @return bool Returns true (in use) or false (not in use). */ $phpMussel['IsInUse'] = function ($Files) use (&$phpMussel) { foreach ($Files as $File) { if ( substr($File, 0, 11) === 'signatures/' && strpos(',' . $phpMussel['Config']['signatures']['Active'] . ',', ',' . substr($File, 11) . ',') !== false ) { return true; } } return false; }; /** Fetch remote data (front-end updates page). */ $phpMussel['FetchRemote'] = function () use (&$phpMussel) { $phpMussel['Components']['ThisComponent']['RemoteData'] = $phpMussel['FECacheGet']( $phpMussel['FE']['Cache'], $phpMussel['Components']['ThisComponent']['Remote'] ); if (!$phpMussel['Components']['ThisComponent']['RemoteData']) { $phpMussel['Components']['ThisComponent']['RemoteData'] = $phpMussel['Request']($phpMussel['Components']['ThisComponent']['Remote']); if ( strtolower(substr($phpMussel['Components']['ThisComponent']['Remote'], -2)) === 'gz' && substr($phpMussel['Components']['ThisComponent']['RemoteData'], 0, 2) === "\x1f\x8b" ) { $phpMussel['Components']['ThisComponent']['RemoteData'] = gzdecode($phpMussel['Components']['ThisComponent']['RemoteData']); } if (empty($phpMussel['Components']['ThisComponent']['RemoteData'])) { $phpMussel['Components']['ThisComponent']['RemoteData'] = '-'; } $phpMussel['FECacheAdd']( $phpMussel['FE']['Cache'], $phpMussel['FE']['Rebuild'], $phpMussel['Components']['ThisComponent']['Remote'], $phpMussel['Components']['ThisComponent']['RemoteData'], $phpMussel['Time'] + 3600 ); } }; /** Prepares component extended description (front-end updates page). */ $phpMussel['PrepareExtendedDescription'] = function (&$Arr, $Key = '') use (&$phpMussel) { $Key = 'Extended Description: ' . $Key; if (isset($phpMussel['lang'][$Key])) { $Arr['Extended Description'] = $phpMussel['lang'][$Key]; } elseif (empty($Arr['Extended Description'])) { $Arr['Extended Description'] = ''; } if (is_array($Arr['Extended Description'])) { $phpMussel['IsolateL10N']($Arr['Extended Description'], $phpMussel['Config']['general']['lang']); } }; /** Prepares component name (front-end updates page). */ $phpMussel['PrepareName'] = function (&$Arr, $Key = '') use (&$phpMussel) { $Key = 'Name: ' . $Key; if (isset($phpMussel['lang'][$Key])) { $Arr['Name'] = $phpMussel['lang'][$Key]; } elseif (empty($Arr['Name'])) { $Arr['Name'] = ''; } if (is_array($Arr['Name'])) { $phpMussel['IsolateL10N']($Arr['Name'], $phpMussel['Config']['general']['lang']); } }; /** Duplication avoidance (front-end updates page). */ $phpMussel['ComponentFunctionUpdatePrep'] = function () use (&$phpMussel) { if (!empty($phpMussel['Components']['Meta'][$_POST['ID']]['Files'])) { $phpMussel['Arrayify']($phpMussel['Components']['Meta'][$_POST['ID']]['Files']); $phpMussel['Arrayify']($phpMussel['Components']['Meta'][$_POST['ID']]['Files']['To']); $phpMussel['Components']['Meta'][$_POST['ID']]['Files']['InUse'] = $phpMussel['IsInUse']($phpMussel['Components']['Meta'][$_POST['ID']]['Files']['To']); } }; /** * Filter the available language options provided by the configuration page on * the basis of the availability of the corresponding language files. * * @param string $ChoiceKey Language code. * @return bool Valid/Invalid. */ $phpMussel['FilterLang'] = function ($ChoiceKey) use (&$phpMussel) { $Path = $phpMussel['Vault'] . 'lang/lang.' . $ChoiceKey; return (file_exists($Path . '.php') && file_exists($Path . '.fe.php')); }; /** * Filter the available hash algorithms provided by the configuration page on * the basis of their availability. * * @param string $ChoiceKey Hash algorithm. * @return bool Valid/Invalid. */ $phpMussel['FilterAlgo'] = function ($ChoiceKey) use (&$phpMussel) { return ($ChoiceKey === 'PASSWORD_ARGON2I') ? !$phpMussel['VersionCompare'](PHP_VERSION, '7.2.0RC1') : true; }; /** * Filter the available theme options provided by the configuration page on * the basis of their availability. * * @param string $ChoiceKey Theme ID. * @return bool Valid/Invalid. */ $phpMussel['FilterTheme'] = function ($ChoiceKey) use (&$phpMussel) { if ($ChoiceKey === 'default') { return true; } $Path = $phpMussel['Vault'] . 'fe_assets/' . $ChoiceKey . '/'; return (file_exists($Path . 'frontend.css') || file_exists($phpMussel['Vault'] . 'template_' . $ChoiceKey . '.html')); }; /** * Get the appropriate path for a specified asset as per the defined theme. * * @param string $Asset The asset filename. * @param bool $CanFail Is failure acceptable? (Default: False) * @return string The asset path. */ $phpMussel['GetAssetPath'] = function ($Asset, $CanFail = false) use (&$phpMussel) { if ( $phpMussel['Config']['template_data']['theme'] !== 'default' && file_exists($phpMussel['Vault'] . 'fe_assets/' . $phpMussel['Config']['template_data']['theme'] . '/' . $Asset) ) { return $phpMussel['Vault'] . 'fe_assets/' . $phpMussel['Config']['template_data']['theme'] . '/' . $Asset; } if (file_exists($phpMussel['Vault'] . 'fe_assets/' . $Asset)) { return $phpMussel['Vault'] . 'fe_assets/' . $Asset; } if ($CanFail) { return ''; } throw new \Exception('Asset not found'); }; /** * Determines whether to display warnings about the PHP version used (based * upon what we know at the time that the package was last updated; information * herein is likely to become stale very quickly when not updated frequently). * * References: * - secure.php.net/releases/ * - secure.php.net/supported-versions.php * - cvedetails.com/vendor/74/PHP.html * - maikuolan.github.io/Compatibility-Charts/ * - maikuolan.github.io/Vulnerability-Charts/php.html * * @param string $Version The PHP version used (defaults to PHP_VERSION). * return int Warning level. */ $phpMussel['VersionWarning'] = function ($Version = PHP_VERSION) use (&$phpMussel) { $Date = date('Y.n.j', $phpMussel['Time']); $Level = 0; if (!empty($phpMussel['ForceVersionWarning']) || $phpMussel['VersionCompare']($Version, '5.6.31') || ( !$phpMussel['VersionCompare']($Version, '7.0.0') && $phpMussel['VersionCompare']($Version, '7.0.17') ) || ( !$phpMussel['VersionCompare']($Version, '7.1.0') && $phpMussel['VersionCompare']($Version, '7.1.3') )) { $Level += 2; } if ($phpMussel['VersionCompare']($Version, '7.0.0') || ( !$phpMussel['VersionCompare']($Date, '2017.12.3') && $phpMussel['VersionCompare']($Version, '7.1.0') ) || ( !$phpMussel['VersionCompare']($Date, '2018.12.1') && $phpMussel['VersionCompare']($Version, '7.2.0') )) { $Level += 1; } $phpMussel['ForceVersionWarning'] = false; return $Level; }; /** * Executes a list of closures or commands when specific conditions are met. * * @param array|string $Closures The list of closures or commands to execute. */ $phpMussel['FE_Executor'] = function ($Closures) use (&$phpMussel) { $phpMussel['Arrayify']($Closures); foreach ($Closures as $Closure) { if (isset($phpMussel[$Closure]) && is_object($phpMussel[$Closure])) { $phpMussel[$Closure](); } elseif (($Pos = strpos($Closure, ' ')) !== false) { $Params = substr($Closure, $Pos + 1); $Closure = substr($Closure, 0, $Pos); if (isset($phpMussel[$Closure]) && is_object($phpMussel[$Closure])) { $phpMussel[$Closure]($Params); } } } }; /** * Localises a number according to configuration specification. * * @param int $Number The number to localise. * @param int $Decimals Decimal places (optional). */ $phpMussel['Number_L10N'] = function ($Number, $Decimals = 0) use (&$phpMussel) { $Number = (real)$Number; $Sets = [ 'NoSep-1' => ['.', '', 3, false, 0], 'NoSep-2' => [',', '', 3, false, 0], 'Latin-1' => ['.', ',', 3, false, 0], 'Latin-2' => ['.', ' ', 3, false, 0], 'Latin-3' => [',', '.', 3, false, 0], 'Latin-4' => [',', ' ', 3, false, 0], 'Latin-5' => ['·', ',', 3, false, 0], 'China-1' => ['.', ',', 4, false, 0], 'India-1' => ['.', ',', 2, false, -1], 'India-2' => ['.', ',', 2, ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], -1], 'Bengali-1' => ['.', ',', 2, ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], -1], 'Arabic-1' => ['٫', '', 3, ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], 0], 'Arabic-2' => ['٫', '٬', 3, ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], 0], 'Thai-1' => ['.', ',', 3, ['๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'], 0] ]; $Set = empty($Sets[$phpMussel['Config']['general']['numbers']]) ? 'Latin-1' : $Sets[$phpMussel['Config']['general']['numbers']]; $DecPos = strpos($Number, '.') ?: strlen($Number); if ($Decimals && $Set[0]) { $Fraction = substr($Number, $DecPos + 1, $Decimals); $Fraction .= str_repeat('0', $Decimals - strlen($Fraction)); } for ($Formatted = '', $ThouPos = $Set[4], $Pos = 1; $Pos <= $DecPos; $Pos++) { if ($ThouPos >= $Set[2]) { $ThouPos = 1; $Formatted = $Set[1] . $Formatted; } else { $ThouPos++; } $NegPos = $DecPos - $Pos; $ThisChar = substr($Number, $NegPos, 1); $Formatted = empty($Set[3][$ThisChar]) ? $ThisChar . $Formatted : $Set[3][$ThisChar] . $Formatted; } if ($Decimals && $Set[0]) { $Formatted .= $Set[0]; for ($FracLen = strlen($Fraction), $Pos = 0; $Pos < $FracLen; $Pos++) { $Formatted .= empty($Set[3][$Fraction[$Pos]]) ? $Fraction[$Pos] : $Set[3][$Fraction[$Pos]]; } } return $Formatted; }; /** * Generates JavaScript code for localising numbers according to configuration * specification. */ $phpMussel['Number_L10N_JS'] = function () use (&$phpMussel) { $Base = 'function l10nn(l10nd){%4$s};function nft(r){var x=r.indexOf(\'.\')!=-1?' . '\'%1$s\'+r.replace(/^.*\./gi,\'\'):\'\',n=r.replace(/\..*$/gi,\'\').rep' . 'lace(/[^0-9]/gi,\'\'),t=n.length;for(e=\'\',b=%5$d,i=1;i<=t;i++){b>%3$d' . '&&(b=1,e=\'%2$s\'+e);var e=l10nn(n.substring(t-i,t-(i-1)))+e;b++}var t=' . 'x.length;for(y=\'\',b=1,i=1;i<=t;i++){var y=l10nn(x.substring(t-i,t-(i-' . '1)))+y}return e+y}'; $Sets = [ 'NoSep-1' => ['.', '', 3, 'return l10nd', 1], 'NoSep-2' => [',', '', 3, 'return l10nd', 1], 'Latin-1' => ['.', ',', 3, 'return l10nd', 1], 'Latin-2' => ['.', ' ', 3, 'return l10nd', 1], 'Latin-3' => [',', '.', 3, 'return l10nd', 1], 'Latin-4' => [',', ' ', 3, 'return l10nd', 1], 'Latin-5' => ['·', ',', 3, 'return l10nd', 1], 'China-1' => ['.', ',', 4, 'return l10nd', 1], 'India-1' => ['.', ',', 2, 'return l10nd', 0], 'India-2' => ['.', ',', 2, 'var nls=[\'०\',\'१\',\'२\',\'३\',\'४\',\'५\',\'६\',\'७\',\'८\',\'९\'];return nls[l10nd]||l10nd', 0], 'Bengali-1' => ['.', ',', 2, 'var nls=[\'০\',\'১\',\'২\',\'৩\',\'৪\',\'৫\',\'৬\',\'৭\',\'৮\',\'৯\'];return nls[l10nd]||l10nd', 0], 'Arabic-1' => ['٫', '', 3, 'var nls=[\'٠\',\'١\',\'٢\',\'٣\',\'٤\',\'٥\',\'٦\',\'٧\',\'٨\',\'٩\'];return nls[l10nd]||l10nd', 1], 'Arabic-2' => ['٫', '٬', 3, 'var nls=[\'٠\',\'١\',\'٢\',\'٣\',\'٤\',\'٥\',\'٦\',\'٧\',\'٨\',\'٩\'];return nls[l10nd]||l10nd', 1], 'Thai-1' => ['.', ',', 3, 'var nls=[\'๐\',\'๑\',\'๒\',\'๓\',\'๔\',\'๕\',\'๖\',\'๗\',\'๘\',\'๙\'];return nls[l10nd]||l10nd', 1], ]; if (!empty($phpMussel['Config']['general']['numbers']) && isset($Sets[$phpMussel['Config']['general']['numbers']])) { $Set = $Sets[$phpMussel['Config']['general']['numbers']]; return sprintf($Base, $Set[0], $Set[1], $Set[2], $Set[3], $Set[4]); } return sprintf($Base, $Sets['Latin-1'][0], $Sets['Latin-1'][1], $Sets['Latin-1'][2], $Sets['Latin-1'][3], $Sets['Latin-1'][4]); }; /** * Switch control for front-end page filters. * * @param array $Switches Names of available switches. * @param string $Selector Switch selector variable. * @param bool $StateModified Determines whether the filter state has been modified. * @param string $Redirect Reconstructed path to redirect to when the state changes. * @param string $Options Recontructed filter controls. */ $phpMussel['FilterSwitch'] = function($Switches, $Selector, &$StateModified, &$Redirect, &$Options) use (&$phpMussel) { foreach ($Switches as $Switch) { $State = (!empty($Selector) && $Selector === $Switch); $phpMussel['FE'][$Switch] = empty($phpMussel['QueryVars'][$Switch]) ? false : ( ($phpMussel['QueryVars'][$Switch] === 'true' && !$State) || ($phpMussel['QueryVars'][$Switch] !== 'true' && $State) ); if ($State) { $StateModified = true; } if ($phpMussel['FE'][$Switch]) { $Redirect .= '&' . $Switch . '=true'; $LangItem = 'switch-' . $Switch . '-set-false'; } else { $Redirect .= '&' . $Switch . '=false'; $LangItem = 'switch-' . $Switch . '-set-true'; } $Label = isset($phpMussel['lang'][$LangItem]) ? $phpMussel['lang'][$LangItem] : $LangItem; $Options .= ''; } }; /** Quarantine file list generator (returns an array of quarantined files). */ $phpMussel['Quarantine-RecursiveList'] = function ($DeleteMode = false) use (&$phpMussel) { $Arr = []; $Key = -1; $Offset = strlen($phpMussel['qfuPath']); $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($phpMussel['qfuPath']), RecursiveIteratorIterator::SELF_FIRST); foreach ($List as $Item => $List) { /** Skips if not a quarantined file. */ if (!preg_match('~\.qfu$~i', $Item) || is_dir($Item) || !is_file($Item) || !is_readable($Item)) { continue; } /** Deletes all files in quarantine. */ if ($DeleteMode) { $DeleteMe = substr($Item, $Offset); $phpMussel['FE']['state_msg'] .= '' . $DeleteMe . ' ' . (unlink( $phpMussel['qfuPath'] . $DeleteMe ) ? $phpMussel['lang']['response_file_deleted'] : $phpMussel['lang']['response_delete_error']) . '
'; continue; } $Key++; $Arr[$Key] = [ 'QFU-Name' => substr($Item, $Offset), 'QFU-JS-ID' => substr($Item, $Offset, -4), 'QFU-Size' => filesize($Item) ]; $phpMussel['FormatFilesize']($Arr[$Key]['QFU-Size']); $Head = $phpMussel['ReadFile']($Item, 256); /** Upload date/time. */ $Arr[$Key]['Upload-Date'] = ( ($DatePos = strpos($Head, 'Time/Date Uploaded: ')) !== false ) ? $phpMussel['TimeFormat']( (int)substr($Head, $DatePos + 20, 16), $phpMussel['Config']['general']['timeFormat'] ) : $phpMussel['lang']['field_filetype_unknown']; /** Upload origin. */ $Arr[$Key]['Upload-Origin'] = ( ($OriginStartPos = strpos($Head, 'Uploaded From: ')) !== false && ($OriginEndPos = strpos($Head, ' ', $OriginStartPos + 15)) !== false ) ? substr($Head, $OriginStartPos + 15, $OriginEndPos - $OriginStartPos - 15) : $phpMussel['lang']['field_filetype_unknown']; /** If the phpMussel QFU (Quarantined File Upload) header isn't found, it probably isn't a quarantined file. */ if (($HeadPos = strpos($Head, "\xa1phpMussel\x21")) !== false && (substr($Head, $HeadPos + 31, 1) === "\x01")) { $Head = substr($Head, $HeadPos); $Arr[$Key]['Upload-MD5'] = bin2hex(substr($Head, 11, 16)); $Arr[$Key]['Upload-Size'] = $phpMussel['UnpackSafe']('l*', substr($Head, 27, 4)); $Arr[$Key]['Upload-Size'] = isset($Arr[$Key]['Upload-Size'][1]) ? (int)$Arr[$Key]['Upload-Size'][1] : 0; $phpMussel['FormatFilesize']($Arr[$Key]['Upload-Size']); } else { $Arr[$Key]['Upload-MD5'] = $phpMussel['lang']['field_filetype_unknown']; $Arr[$Key]['Upload-Size'] = $phpMussel['lang']['field_filetype_unknown']; } /** Appends Virus Total search URL for this hash onto the hash. */ if (strlen($Arr[$Key]['Upload-MD5']) === 32) { $Arr[$Key]['Upload-MD5'] = sprintf( '%1$s', $Arr[$Key]['Upload-MD5'] ); } } return $Arr; }; /** Restore a quarantined file (returns the restored file data or false on failure). */ $phpMussel['Quarantine-Restore'] = function ($File, $Key) use (&$phpMussel) { $phpMussel['RestoreStatus'] = 1; if (!$File || !$Key) { return false; } $Data = $phpMussel['ReadFile']($File); /** Fetch headers. */ if (($HeadPos = strpos($Data, "\xa1phpMussel\x21")) === false || (substr($Data, $HeadPos + 31, 1) !== "\x01")) { $phpMussel['RestoreStatus'] = 2; return false; } $Data = substr($Data, $HeadPos); $UploadMD5 = bin2hex(substr($Data, 11, 16)); $UploadSize = $phpMussel['UnpackSafe']('l*', substr($Data, 27, 4)); $UploadSize = isset($UploadSize[1]) ? (int)$UploadSize[1] : 0; $Data = substr($Data, 32); $DataLen = strlen($Data); if ($Key < 128) { $Key = $phpMussel['HexSafe'](hash('sha512', $Key) . hash('whirlpool', $Key)); } $KeyLen = strlen($Key); $Output = ''; $Cycle = 0; while ($Cycle < $DataLen) { for ($Inner = 0; $Inner < $KeyLen; $Inner++, $Cycle++) { if (strlen($Output) >= $UploadSize) { break 2; } $L = substr($Data, $Cycle, 1); $R = substr($Key, $Inner, 1); $Output .= ($L === false ? "\x00" : $L) ^ ($R === false ? "\x00" : $R); } } $Output = gzinflate($Output); if (empty($Output) || md5($Output) !== $UploadMD5) { $phpMussel['RestoreStatus'] = 3; return false; } $phpMussel['RestoreStatus'] = 0; return $Output; }; /** Duplication avoidance (front-end updates page). */ $phpMussel['AppendTests'] = function (&$Component) use (&$phpMussel) { $TestData = $phpMussel['FECacheGet']( $phpMussel['FE']['Cache'], $phpMussel['Components']['RemoteMeta'][$Component['ID']]['Tests'] ); if (!$TestData) { $TestData = $phpMussel['Request']( $phpMussel['Components']['RemoteMeta'][$Component['ID']]['Tests'] ) ?: '-'; $phpMussel['FECacheAdd']( $phpMussel['FE']['Cache'], $phpMussel['FE']['Rebuild'], $phpMussel['Components']['RemoteMeta'][$Component['ID']]['Tests'], $TestData, $phpMussel['Time'] + 1800 ); } if (substr($TestData, 0, 1) === '{' && substr($TestData, -1) === '}') { $TestData = json_decode($TestData, true, 5); } if (!empty($TestData['statuses']) && is_array($TestData['statuses'])) { $TestsTotal = 0; $TestsPassed = 0; $TestDetails = ''; foreach ($TestData['statuses'] as $ThisStatus) { $TestsTotal++; $StatusHead = ''; if ( !empty($ThisStatus['context']) && !empty($ThisStatus['target_url']) && !empty($ThisStatus['state']) ) { if ($ThisStatus['state'] === 'success') { if ($TestsPassed !== '?') { $TestsPassed++; } $StatusHead .= '✔️ '; } elseif ($ThisStatus['state'] === 'pending') { $TestsPassed = '?'; $StatusHead .= '❓ '; } else { $StatusHead .= '❌ '; } } $StatusHead .= ''; $phpMussel['AppendToString']($TestDetails, '
', $StatusHead . $ThisStatus['context'] . '
' ); } if ($TestsTotal === $TestsPassed) { $TestClr = 'txtGn'; } else { $TestClr = ($TestsPassed === '?' || $TestsPassed >= ($TestsTotal / 2)) ? 'txtOe' : 'txtRd'; } $TestsTotal = sprintf( '%2$s/%3$s ' . '' . '', $TestClr, ($TestsPassed === '?' ? '?' : $phpMussel['Number_L10N']($TestsPassed)), $phpMussel['Number_L10N']($TestsTotal), $Component['ID'], $TestDetails ); $phpMussel['AppendToString']($Component['StatusOptions'], '
', '
' . $phpMussel['lang']['label_tests'] . ' ' . $TestsTotal ); } }; /** Traversal detection. */ $phpMussel['Traverse'] = function ($Path) { return !preg_match('~(?:[\./]{2}|[\x01-\x1f\[-^`?*$])~i', str_replace("\\", '/', $Path)); }; /** Sort function used by the front-end updates page. */ $phpMussel['UpdatesSortFunc'] = function ($A, $B) { $CheckA = preg_match('/^l10n/i', $A); $CheckB = preg_match('/^l10n/i', $B); if (($CheckA && !$CheckB) || ($A === 'phpMussel' && $B !== 'phpMussel')) { return -1; } if (($CheckB && !$CheckA) || ($B === 'phpMussel' && $A !== 'phpMussel')) { return 1; } if ($A < $B) { return -1; } if ($A > $B) { return 1; } return 0; };