Nathanwoodburn.github.io/libraries/plugins/phpMussel/vault/frontend_functions.php

1008 lines
42 KiB
PHP
Raw Normal View History

2021-08-30 15:06:36 +10:00
<?php
/**
* This file is a part of the phpMussel package.
* Homepage: https://phpmussel.github.io/
*
* PHPMUSSEL COPYRIGHT 2013 AND BEYOND BY THE PHPMUSSEL TEAM.
*
* Authors:
* @see PEOPLE.md
*
* License: GNU/GPLv2
* @see LICENSE.txt
*
* This file: Front-end functions file (last modified: 2017.10.27).
*/
/**
* Validates or ensures that two different sets of component metadata share the
* same base elements (or components). One set acts as a model for which base
* elements are expected, and if additional/superfluous entries are found in
* the other set (the base), they'll be removed. Installed components are
* ignored as to future-proof legacy support (just removes non-installed
* components).
*
* @param string $Base The base set (generally, the local copy).
* @param string $Model The model set (generally, the remote copy).
* @param bool $Validate Validate (true) or ensure congruency (false; default).
* @return string|bool If $Validate is true, returns true|false according to
* whether the sets are congruent. If $Validate is false, returns the
* corrected $Base set.
*/
$phpMussel['Congruency'] = function ($Base, $Model, $Validate = false) use (&$phpMussel) {
if (empty($Base) || empty($Model)) {
return $Validate ? false : '';
}
$BaseArr = $ModelArr = [];
$phpMussel['YAML']($Base, $BaseArr);
$phpMussel['YAML']($Model, $ModelArr);
foreach ($BaseArr as $Element => $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 .= '<option value="' . $Switch . '">' . $Label . '</option>';
}
};
/** 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'] .= '<code>' . $DeleteMe . '</code> ' . (unlink(
$phpMussel['qfuPath'] . $DeleteMe
) ? $phpMussel['lang']['response_file_deleted'] : $phpMussel['lang']['response_delete_error']) . '<br />';
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(
'<a href="https://www.virustotal.com/#/file/%1$s">%1$s</a>',
$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 .= '<span class="txtGn">✔️ ';
} elseif ($ThisStatus['state'] === 'pending') {
$TestsPassed = '?';
$StatusHead .= '<span class="txtOe">❓ ';
} else {
$StatusHead .= '<span class="txtRd">❌ ';
}
}
$StatusHead .= '<a href="' . $ThisStatus['target_url'] . '">';
$phpMussel['AppendToString']($TestDetails, '<br />',
$StatusHead . $ThisStatus['context'] . '</a></span>'
);
}
if ($TestsTotal === $TestsPassed) {
$TestClr = 'txtGn';
} else {
$TestClr = ($TestsPassed === '?' || $TestsPassed >= ($TestsTotal / 2)) ? 'txtOe' : 'txtRd';
}
$TestsTotal = sprintf(
'<span class="%1$s">%2$s/%3$s</span> <span id="%4$s-showtests">' .
'<input class="auto" type="button" onclick="javascript:showid(\'%4$s-tests\');hideid(\'%4$s-showtests\');showid(\'%4$s-hidetests\')" value="+" />' .
'</span><span id="%4$s-hidetests" style="display:none">' .
'<input class="auto" type="button" onclick="javascript:hideid(\'%4$s-tests\');showid(\'%4$s-showtests\');hideid(\'%4$s-hidetests\')" value="-" />' .
'</span><span id="%4$s-tests" style="display:none"><br />%5$s</span>',
$TestClr,
($TestsPassed === '?' ? '?' : $phpMussel['Number_L10N']($TestsPassed)),
$phpMussel['Number_L10N']($TestsTotal),
$Component['ID'],
$TestDetails
);
$phpMussel['AppendToString']($Component['StatusOptions'], '<hr />',
'<div class="s">' . $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;
};