Nathanwoodburn.github.io/libraries/plugins/phpMussel/vault/frontend_functions.php
2021-08-30 15:06:36 +10:00

1008 lines
42 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
};