| Current Path : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/ |
| Current File : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/bfSnapshot.php |
<?php
/*
* @package bfNetwork
* @copyright Copyright (C) 2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026 Blue Flame Digital Solutions Ltd. All rights reserved.
* @license GNU General Public License version 3 or later
*
* @see https://mySites.guru/
* @see https://www.phil-taylor.com/
*
* @author Phil Taylor / Blue Flame Digital Solutions Limited.
*
* bfNetwork is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* bfNetwork is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package. If not, see http://www.gnu.org/licenses/
*
* If you have any questions regarding this code, please contact phil@phil-taylor.com
*/
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Schema\ChangeSet;
use Joomla\CMS\Version;
use Joomla\Component\Installer\Administrator\Model\DatabaseModel;
require 'bfEncrypt.php';
/*
* If we have got here then we have already passed through decrypting
* the encrypted header and so we are sure we are now secure and no one
* else cannot run the code below.
*/
// require all we need to access Joomla API
if (! defined('BF_JOOMLA_INIT_DONE')) {
require_once 'bfInitJoomla.php';
}
require_once 'bfActivitylog.php';
final class bfSnapshot
{
public $_data;
private $db;
private $version;
private $container;
private $config;
private $privacyRequestsTableExists = null;
public function __construct()
{
$this->cleanOurCrap();
// Ask Joomla to report config through its API
$this->container = Factory::getContainer();
$this->config = $this->container->get('config');
// Connect to the database
$this->initDb();
$this->logSnapshot();
$session_save_path = ini_get('session.save_path');
$this->_data = [
'version' => $this->getJoomlaVersion(),
'connectorversion' => file_get_contents('./VERSION'),
'php_version' => \PHP_VERSION,
'php_disabled_functions' => ini_get('disable_functions'),
'display_errors' => ini_get('display_errors'),
'register_globals' => (int) ini_get('register_globals'),
'safe_mode' => (int) ini_get('safe_mode'),
'file_uploads' => (int) ini_get('file_uploads'),
'magic_quotes_gpc' => (int) ini_get('magic_quotes_gpc'),
'magic_quotes_runtime' => (int) ini_get('magic_quotes_runtime'),
'session_autostart' => (int) ini_get('session_autostart'),
'gc_probability' => (int) ini_get('session.gc_probability'),
'mysql_version' => $this->initDb(),
'session_save_path' => @ini_get('session.save_path') ? ini_get('session.save_path') : '/tmp',
'is_windows_host' => (int) ('WIN' == substr(\PHP_OS, 0, 3)) ? 1 : 0,
'session_save_path_writable' => (int) @is_writable($session_save_path),
'db_prefix' => $this->config->get('dbprefix', ''),
'dbs_visible' => $this->getVisibleDbsCount(),
'db_user_is_root' => (int) ('root' == $this->config->get('user', '') ? 1 : 0),
'db_bak_tables' => (int) $this->hasBakTables(),
'memory_limit' => ini_get('memory_limit'),
'has_installation_folders' => (int) $this->hasInstallationFolders(),
'site_debug_enabled' => (int) $this->config->get('debug') ? 1 : 0,
'has_ftp_configured' => (int) $this->checkFTPLayer(),
'numberofsuperadmins' => $this->getNumberOfSuperAdmins(),
'adminusernames' => $this->getAdminUserNameCount(),
'neverloggedinusers' => $this->getNeverLoggedInUsersCount(),
'inactiveusers' => $this->getInactiveUsersCount(),
'unactivatedusers' => $this->getUnactivatedUsersCount(),
'blockedusers' => $this->getBlockedUsersCount(),
'hasjce' => $this->hasExtensionWithNameInstalled('com_jce'),
'hasakeebabackup' => $this->hasExtensionWithNameInstalled('com_akeebabackup'),
'hasadmintools' => $this->hasAdmintoolsInstalled(),
'admintoolsipblocked' => $this->isAdmintoolsIpBlocked(),
'site_offline' => $this->config->get('offline', ''),
'cache_enabled' => $this->config->get('caching', ''),
'sef_enabled' => $this->config->get('sef', ''),
'tmplogfolderswritable' => (int) $this->hastmplogfolderswritable(),
'extensionupdatesavailable' => null,
// Now called in separate job was $this->hasUpdatesAvailable(),
'defaulttemplateused' => (int) $this->hasUsedDefaultTemplate(),
'tpequalsone' => $this->hastpequalsone(),
'configsymlinked' => (is_link(JPATH_BASE . '/configuration.php') ? 1 : 0),
'kickstartseen' => (int) $this->kickstartexists(),
'fpaseen' => (int) $this->fpaexists(),
'userregistrationenabled' => (int) ComponentHelper::getParams('com_users')->get('allowUserRegistration'),
'has_root_htaccess' => (int) (file_exists(JPATH_BASE . '/.htaccess') ? 1 : 0),
'adminhtaccess' => (int) (file_exists(JPATH_BASE . '/administrator/.htaccess') ? 1 : 0),
'gzipenabled' => (int) $this->config->get('gzip', ''),
'gcerrorreportingnone' => (int) $this->getErrorReportingLevel(),
'livesitevarset' => strlen($this->config->get('live_site', '')) > 1 ? 1 : 0,
'cookiedomainpath' => ($this->config->get('cookie_path') || $this->config->get('cookie_domain')) ? 1 : 0,
'sessionlifetime' => (int) $this->config->get('lifetime'),
'akeebabackupscount' => (int) $this->getNumberOfAkeebaBackups(),
'md5passwords' => (int) $this->hasmd5passwords(),
'tmplogfoldersdefaultpaths' => (int) $this->hastmplogfoldersdefaultpaths(),
'max_allowed_packet' => (int) $this->getMaxAllowedPacket(),
'jceversion' => $this->checkJCEVersion(),
'jcerogueprofiles' => $this->countJceRogueProfiles(),
'jcemaliciousfiles' => $this->countJceMaliciousFiles(),
'rogueadmins' => $this->countRogueAdmins(),
'fluff' => (int) $this->checkfluff(),
'db_schema' => $this->checkdbschema(),
'robots_blocks_media' => (int) $this->checkRobotsBlocksMedia(),
'server_hostname' => function_exists('gethostname') ? gethostname() : php_uname('n'),
'akeeba_dir_problems' => 0,
// @deprecated
'diskspace' => $this->getDiskSpace(),
'hacked' => $this->checkIf100percentHackedOrNot(),
'new_usertype' => $this->getNewUserType(),
'non2faadmins' => (int) $this->getNon2FaAdmins(),
'users_hacked' => $this->checkJoomlaUserHelperHack2016(),
'sessiongcpublished' => $this->getSessionGCStatus(),
'twofactorenabled' => $this->getTwoFactorPluginsEnabled(),
'adminfilterfixed' => $this->getAdminFilterFixed(),
'userfilterfixed' => $this->getUserFilterFixed(),
'plaintextpasswordsfixed' => $this->getPlaintextpasswordsFixed(),
'uploadsettingsfixed' => (int) $this->getUploadsettingsfixed(),
'captchaenabled' => (int) $this->getCaptchaDetails(),
'useractionlogenabled' => (int) $this->getUseractionlogenabled(),
'plgprivacyconsentenabled' => (int) $this->getPrivacyConsentPluginEnabled(),
'useractionlogiplogenabled' => (int) $this->getActionLogsIPLoggingEnabled(),
'systemlogrotationenabled' => (int) $this->getSystemLogRotationEnabled(),
'hasprivacypolicy' => (int) $this->hasprivacypolicy(),
'privacypendingremove' => (int) $this->getPrivacypendingremove(),
'privacycompletedexport' => (int) $this->getPrivacycompletedexport(),
'privacypendingexport' => (int) $this->getPrivacypendingexport(),
'privacycompletedremove' => (int) $this->getPrivacycompletedremove(),
'privacyoverdue' => (int) $this->getPrivacyoverdue(),
'privacyconfirmedremove' => (int) $this->getPrivacyconfirmedremove(),
'privacyconfirmedexport' => (int) $this->getPrivacyconfirmedexport(),
'enablepurge30days' => (int) $this->getPurge30Days(),
'extensionsjson' => $this->getExtensions(),
'hasrootuser' => $this->config->get('root_user') ? 1 : 0,
'debuglanguage' => $this->config->get('debug_lang') ? 1 : 0,
'apilogenabled' => (int) ComponentHelper::getParams('com_actionlogs')->get('loggable_api'),
'postinstallmsgcount' => count($this->getPostInstallMessages()),
'sendcopytosubmitter' => (int) ComponentHelper::getParams('com_contact')->get('show_email_copy', 0),
'fileinfo' => \extension_loaded('fileinfo'),
'guidedtour' => (int) $this->getGuidedToursEnabled(),
'thumbnails' => (int) $this->getThumbnailsEnabled(),
'updatesource' => (string) ComponentHelper::getParams('com_joomlaupdate')->get('updatesource'),
'activetheme' => (string) $this->getThemeName(),
'updateemails' => (int) $this->getUpdateEmailsState(),
'lockedtasks' => (int) $this->getLockedTasksCount(),
'logeverything' => (int) $this->config->get('log_everything', '0'),
'logdeprecated' => (int) $this->config->get('log_deprecated', '0'),
];
}
public function getUpdateEmailsState()
{
$sql = 'select count(*) from #__extensions where type = "plugin" and element = "updatenotification" and enabled = 1';
$this->db->setQuery($sql);
return $this->db->LoadResult();
}
public function getLockedTasksCount()
{
// #__scheduler_tasks exists in Joomla 4.1.0+
if (version_compare(JVERSION, '4.1.0', '<')) {
return 0;
}
$this->db->setQuery('SELECT COUNT(*) FROM #__scheduler_tasks WHERE locked IS NOT NULL');
return (int) $this->db->loadResult();
}
public function getThemeName()
{
try {
$sql = 'select template from #__template_styles where client_id = 0 and home = 1 LIMIT 1';
$this->db->setQuery($sql);
$row = $this->db->LoadObject();
$data = [];
$data['Name']=$row->template;
return json_encode($data);
} catch (\Exception $exception) {
}
return '';
}
public function getThumbnailsEnabled()
{
$this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_filesystem_local'");
$params = $this->db->LoadResult();
if ('{}' == $params || null == $params || '' == $params) {
return;
}
$params = json_decode($params, JSON_OBJECT_AS_ARRAY);
if (! array_key_exists('directories', $params)) {
return;
}
$thumbsCount = 0;
foreach ($params['directories'] as $directory) {
if (! array_key_exists('thumbs', $directory)) {
continue;
}
if ($directory['thumbs'] == 1) {
$thumbsCount++;
}
}
return (int) $thumbsCount === \count($params['directories']);
}
/**
* Get the post install messages from a Joomla 3+ site.
*/
public function getPostInstallMessages()
{
require JPATH_BASE . '/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php';
require JPATH_BASE . '/administrator/components/com_postinstall/src/Model/MessagesModel.php';
$model = Factory::getApplication()
->bootComponent('com_postinstall')
->getMVCFactory()
->createModel('Messages', 'Administrator', [
'ignore_request' => true,
]);
$messages = $model->getItems();
foreach ($messages as $k => $item) {
$item->title_key = Text::_($item->title_key);
$item->description_key = Text::_($item->description_key);
}
return $messages;
}
private function logSnapshot()
{
bfActivitylog::getInstance()->log(
'bfNetwork',
'',
'Snapshot Taken',
'onSnapshotTaken',
'',
null,
null,
'',
bfEvents::onSnapshotTaken,
'onSnapshotTaken',
bfEvents::onSnapshotTaken
);
}
public function getExtensions()
{
require 'bfExtensions.php';
$ext = new bfExtensions();
return $ext->getExtensions();
}
/**
* Get the number of days to delete logs after from the System - User Actions Log.
*
* @return int
*/
public function getPurge30Days()
{
$this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$params = $this->db->LoadResult();
if ('{}' == $params || $params === null) {
return;
}
$params = json_decode($params);
if (! $params) {
return;
}
return $params->logDeletePeriod;
}
/**
* Does the #__privacy_requests table exist?
*
* The privacy suite shipped in Joomla 3.9 but the table can still be missing
* (incomplete upgrade, dropped table, partial schema), and the raw counts below
* then throw a 1146 "table doesn't exist" that kills the whole snapshot. Cached
* so the seven privacy getters share one cheap lookup.
*/
private function privacyRequestsTableExists()
{
if ($this->privacyRequestsTableExists !== null) {
return $this->privacyRequestsTableExists;
}
try {
$this->db->setQuery('SHOW TABLES LIKE ' . $this->db->quote($this->db->getPrefix() . 'privacy_requests'));
$this->privacyRequestsTableExists = (bool) $this->db->LoadResult();
} catch (\Exception $exception) {
$this->privacyRequestsTableExists = false;
}
return $this->privacyRequestsTableExists;
}
public function getPrivacypendingremove()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 0 and request_type = 'remove'");
return $this->db->LoadResult();
}
public function getPrivacyconfirmedremove()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 1 and request_type = 'remove'");
return $this->db->LoadResult();
}
public function getPrivacyconfirmedexport()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 1 and request_type = 'export'");
return $this->db->LoadResult();
}
public function getPrivacycompletedexport()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 2 and request_type = 'export'");
return $this->db->LoadResult();
}
public function getPrivacypendingexport()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 0 and request_type = 'export'");
return $this->db->LoadResult();
}
public function getPrivacycompletedremove()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
$this->db->setQuery("select count(*) from #__privacy_requests where status = 2 and request_type = 'remove'");
return $this->db->LoadResult();
}
/**
* Get the overdue requests.
*
* @return bool
*/
public function getPrivacyoverdue()
{
if (! $this->privacyRequestsTableExists()) {
return false;
}
// Load the parameters.
$params = ComponentHelper::getComponent('com_privacy')->getParams();
$notify = (int) $params->get('notify', 14);
$now = Factory::getDate()->toSql();
$period = '-' . $notify;
$query = $this->db->getQuery(true)
->select('COUNT(*)');
$query->from($this->db->quoteName('#__privacy_requests'));
$query->where($this->db->quoteName('status') . ' = 1 ');
$query->where($query->dateAdd($this->db->quote($now), $period, 'DAY') . ' > ' . $this->db->quoteName('requested_at'));
$this->db->setQuery($query);
return $this->db->LoadResult();
}
/**
* Joomla 3.9.0+ Check for system log rotation plugin.
*/
public function getSystemLogRotationEnabled()
{
$this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_logrotation' and enabled = 1");
return $this->db->LoadResult();
}
public function getGuidedToursEnabled()
{
$this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_guidedtours' and enabled = 1");
return $this->db->LoadResult();
}
/**
* Joomla 3.9.0+ Check for action log ip logging enabled.
*/
public function hasprivacypolicy()
{
$this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_system_privacyconsent'");
$res = $this->db->LoadResult();
if (! $res) {
return 0;
}
$params = json_decode($res);
if ($params && property_exists($params, 'privacy_article')) {
return $params->privacy_article > 0 ? 1 : 0;
}
return 0;
}
/**
* Joomla 3.9.0+ Check for action log ip logging enabled.
*/
public function getActionLogsIPLoggingEnabled()
{
$this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'com_actionlogs'");
$params = json_decode($this->db->LoadResult());
if ($params && property_exists($params, 'ip_logging')) {
return $params->ip_logging;
}
return 0;
}
/**
* Joomla 3.9.0+ Check for action log ip logging enabled.
*/
public function getUseractionlogenabled()
{
$this->db->setQuery(
"SELECT count(*) FROM `#__extensions` WHERE (`name` = 'PLG_ACTIONLOG_JOOMLA' or `name` = 'PLG_SYSTEM_ACTIONLOGS') and enabled = 1"
);
return 2 == $this->db->LoadResult() ? 1 : 0;
}
/**
* Joomla 3.9.0+ Check for plg_privacy_actionlogs enabled.
*/
public function getPrivacyConsentPluginEnabled()
{
$this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_privacyconsent' and enabled = 1");
return $this->db->LoadResult();
}
/**
* Checks if the FTP Layer is in anyway configured.
*
* @return bool
*/
private function checkFTPLayer()
{
$ftp_pass = $this->config->get('ftp_pass', '');
$ftp_user = $this->config->get('ftp_user', '');
$ftp_enable = $this->config->get('ftp_enable', '');
$ftp_host = $this->config->get('ftp_host', '');
$ftp_root = $this->config->get('ftp_root', '');
if ($ftp_pass || $ftp_user || '1' == $ftp_enable || $ftp_host || $ftp_root) {
return true;
}
return false;
}
/**
* Clean up old mysites.guru files and features.
*/
private function cleanOurCrap()
{
// cleanup old files
$oldFiles = [
'upgrade.zip',
'./bfViewLog.php',
'./bfDev.php',
'./bfMysql.php',
'./j25_30_bfnetwork.xml', // dont get confused with the one in the folder above this.
'./install.bfnetwork.php',
'./bfnetwork.xml',
'./bfJson.php',
'./tmp/log.tmp',
'./tmp/tmp.ob',
];
foreach ($oldFiles as $file) {
if (file_exists($file)) {
@unlink($file);
}
}
// cleanup
if (file_exists('../j25_30_bfnetwork.xml')) {
@copy('../j25_30_bfnetwork.xml', '../bfnetwork.xml');
@unlink('../j25_30_bfnetwork.xml');
}
$fileContent = file_get_contents('../bfnetwork.php');
if (! preg_match('/bfPlugin/', $fileContent)) {
$fileContent = str_replace([
"\n\n",
'// For more details please contact Phil Taylor <phil@phil-taylor.com>',
'// This is NOT a Joomla Extension or Plugin and is NOT designed for consumption within Joomla - yet :)', ], '', $fileContent);
$fileContent = $fileContent . "
/**
* All our code is in the sub folder, as that is what is auto-upgraded
* and fully maintained by the automated processes at mysites.guru
*/
require 'bfnetwork/bfPlugin.php';";
file_put_contents('../bfnetwork.php', $fileContent);
}
bfActivitylog::getInstance();
}
/**
* Init the Joomla db connection.
*/
private function initDb()
{
try {
$this->db = $this->container->get('DatabaseDriver');
if (! $conn = $this->db->getConnection()) {
$this->db->connect();
$conn = $this->db->getConnection();
}
if (! $conn) {
return '';
}
if (get_class($conn) === \PDO::class) {
$dbVerString = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
} else {
$dbVerString = $conn->server_info;
}
} catch (\Exception $exception) {
$dbVerString = '';
}
return $dbVerString;
}
private function getJoomlaVersion()
{
$VERSION = new Version();
// Store in our object for switching configs
$this->version = $VERSION->getShortVersion();
return $VERSION->getShortVersion();
}
/**
* How many databases can I see?
*
* We need to reconnect again to the db so we are ot going through the Joomla DB Layer because it just crashes too
* far up the stack for us to catch the exception
*
* @return int
*/
private function getVisibleDbsCount()
{
if (! function_exists('mysqli_connect')) {
return 1;
}
$count = 0;
try {
// Create correct commands based on how old and crap the server is!
switch ($this->config->get('dbtype')) {
default:
case \mysqli::class:
$port = '3306';
// Handle localhost:3306 type hosts
$parts = explode(':', $this->config->get('host'));
if (\count($parts) > 1) {
$port = $parts[1];
} else {
$port = null;
}
$link = mysqli_connect($parts[0], $this->config->get('user'), $this->config->get('password'), null, $port);
if (! $link) {
return 0;
}
$res = mysqli_query(
$link,
'SHOW DATABASES where `Database` NOT IN ("test","performance_schema", "information_schema", "mysql")'
);
if (! $res) {
return 0;
}
$count = $res->num_rows;
// tidy up
mysqli_close($link);
break;
}
// return number seen
return $count;
} catch (Exception $e) {
return $count;
}
}
/**
* Do we have any backup tables.
*
* @return string
*/
private function hasBakTables()
{
$this->db->setQuery("SHOW TABLES WHERE `Tables_in_{$this->config->get('db', '')}` like 'bak_%'");
return $this->db->loadResult() ? true : false;
}
/**
* See if we have any installation folders.
*
* @return string "TRUE|FALSE" if we do
*/
private function hasInstallationFolders()
{
$folders = $this->getFolders(JPATH_BASE);
foreach ($folders as $folder) {
if (preg_match(
'/installation|installation.old|docs\/installation|install|installation.bak|installation.old|installation.backup|installation.delete/i',
$folder,
$matches
)) {
return true;
}
}
return false;
}
/**
* Function taken from Akeeba filesystem.php.
*
* Akeeba Engine The modular PHP5 site backup engine
*
* @copyright Copyright (c)2009 Nicholas K. Dionysopoulos
* @license GNU GPL version 3 or, at your option, any later version
*
* @version Id: scanner.php 158 2010-06-10 08:46:49Z nikosdion
*/
private function getFolders($folder)
{
// Initialize variables
$arr = [];
$false = false;
$folder = trim($folder);
if (! is_dir($folder) && ! is_dir($folder . \DIRECTORY_SEPARATOR) || is_link($folder . \DIRECTORY_SEPARATOR) || is_link(
$folder
) || ! $folder) {
return $false;
}
if (@file_exists($folder . \DIRECTORY_SEPARATOR . '.myjoomla.ignore.folder')) {
return [];
}
$handle = @opendir($folder);
if (false === $handle) {
$handle = @opendir($folder . \DIRECTORY_SEPARATOR);
}
// If directory is not accessible, just return FALSE
if (false === $handle) {
return $false;
}
while ((false !== ($file = @readdir($handle)))) {
if (('.' != $file) && ('..' != $file) && (null != trim($file))) {
$ds = ('' == $folder) || (\DIRECTORY_SEPARATOR == $folder) || (\DIRECTORY_SEPARATOR == @substr(
$folder,
-1
)) || (\DIRECTORY_SEPARATOR == @substr($folder, -1)) ? '' : \DIRECTORY_SEPARATOR;
$dir = trim($folder . $ds . $file);
$isDir = @is_dir($dir);
if ($isDir) {
$arr[] = $this->cleanupFileFolderName(str_replace(JPATH_BASE, '', $folder . \DIRECTORY_SEPARATOR . $file));
}
}
}
@closedir($handle);
return $arr;
}
/**
* Clean up a string, a path name.
*
* @param string $str
*
* @return string
*/
private function cleanupFileFolderName($str)
{
$str = str_replace('////', '/', $str);
$str = str_replace('///', '/', $str);
$str = str_replace('//', '/', $str);
$str = str_replace('\\/', '/', $str);
$str = str_replace('\\t', '/t', $str);
$str = str_replace("\/", '/', $str);
return addslashes($str);
}
/**
* The the number of super admins.
*
* @return int The number of super admins
* @todo remove hard coded 8 and look for the correct group_id if people have messed with ACL
*/
private function getNumberOfSuperAdmins()
{
$this->db->setQuery('SELECT count(*) FROM #__user_usergroup_map WHERE group_id = 8');
return (int) $this->db->LoadResult();
}
/**
* Report if any users have a username of 'admin'.
*
* @return int
*/
private function getAdminUserNameCount()
{
$this->db->setQuery('SELECT COUNT(*) FROM #__users WHERE username = "admin"');
return (int) $this->db->LoadResult();
}
private function getNeverLoggedInUsersCount()
{
$this->db->setQuery('SELECT COUNT(*) FROM #__users WHERE lastvisitDate IS NULL');
return (int) $this->db->LoadResult();
}
private function getInactiveUsersCount()
{
// Users inactive for 180+ days OR never logged in
$this->db->setQuery(
'SELECT COUNT(*) FROM #__users
WHERE lastvisitDate IS NULL
OR lastvisitDate < DATE_SUB(NOW(), INTERVAL 180 DAY)'
);
return (int) $this->db->LoadResult();
}
/**
* Get count of users with pending activation (registered but not yet confirmed).
* In Joomla, unactivated users have a non-empty activation column and are not blocked.
*/
private function getUnactivatedUsersCount()
{
$this->db->setQuery(
"SELECT COUNT(*) FROM #__users
WHERE activation != ''
AND activation IS NOT NULL
AND block = 0"
);
return (int) $this->db->loadResult();
}
/**
* Get count of blocked/disabled user accounts.
* In Joomla, blocked users have block = 1 in the users table.
*/
private function getBlockedUsersCount()
{
$this->db->setQuery('SELECT COUNT(*) FROM #__users WHERE block = 1');
return (int) $this->db->loadResult();
}
/**
* See if we have extension installed.
*/
private function hasExtensionWithNameInstalled($name)
{
$count = 0;
$folders = $this->getFolders(JPATH_BASE . '/administrator/components/');
foreach ($folders as $folder) {
if (preg_match('/' . $name . '/i', $folder, $matches)) {
++$count;
}
}
return $count;
}
private function hastmplogfolderswritable()
{
return is_writable($this->config->get('tmp_path')) && is_writable($this->config->get('log_path'));
}
/**
* @return bool
*/
private function hasUsedDefaultTemplate()
{
$core_templates = [
'atomic',
'beez_20',
'beez_5',
'beez3',
'ja_purity',
'protostar',
'rhuk_milkyway',
'rhuk_milkyway_2',
'cassiopeia',
];
$this->db->setQuery('SELECT template FROM #__template_styles WHERE client_id=0 AND home=1');
return (bool) in_array($this->db->loadResult(), $core_templates);
}
private function hastpequalsone()
{
if (1 == ComponentHelper::getParams('com_templates')
->get('template_positions_display')
) {
// allowed - which is bad
$tpequalsone = 1;
} else {
// not allowed - which is good
$tpequalsone = 0;
}
return $tpequalsone;
}
private function kickstartexists()
{
$files = scandir(JPATH_BASE);
foreach ($files as $file) {
if (preg_match('/.*kickstart.*\.php/i', $file)) {
return true;
}
}
return false;
}
private function fpaexists()
{
$files = scandir(JPATH_BASE);
foreach ($files as $file) {
if (preg_match('/.*fpa.*\.php/i', $file)) {
return true;
}
}
return false;
}
private function getErrorReportingLevel()
{
$er = $this->config->get('error_reporting');
if (! is_int($er)) {
switch ($er) {
case 'none':
$er = 0;
break;
case 'simple':
$er = 7;
break;
case 'maximum':
$er = 2047;
break;
case 'development':
$er = -1;
break;
default:
$er = $er; // yeah yeah I know!
break;
}
}
return $er;
}
private function getNumberOfAkeebaBackups()
{
$count = 0;
$folder = JPATH_BASE . '/administrator/components/com_akeeba/backup';
if (file_exists($folder)) {
$folderContents = scandir($folder);
foreach ($folderContents as $file) {
if (preg_match('/\.jpa$/i', $file)) {
++$count;
}
}
}
return $count;
}
private function hasmd5passwords()
{
$this->db->setQuery('SELECT count(*) FROM #__users WHERE CHAR_LENGTH(password) = 32');
return (int) $this->db->LoadResult();
}
private function hastmplogfoldersdefaultpaths()
{
$logPath = $this->config->get('log_path');
$tmpPath = $this->config->get('tmp_path');
$expectedLogPath1 = JPATH_BASE . '/logs';
$expectedLogPath2 = JPATH_BASE . '/administrator/logs'; // Introduced in Joomla 3.6.0
$expectedTmpPath = JPATH_BASE . '/tmp';
return (int) (($expectedLogPath1 == $logPath || $expectedLogPath2 == $logPath) && $expectedTmpPath == $tmpPath);
}
private function getMaxAllowedPacket()
{
$this->db->setQuery('SHOW VARIABLES LIKE "max_allowed_packet"');
$res = $this->db->loadObjectList();
return $res[0]->Value;
}
/**
* @return string
*/
private function checkJCEVersion()
{
$versionFile = JPATH_BASE . '/administrator/components/com_jce/jce.xml';
if (file_exists($versionFile)) {
$xml = file_get_contents($versionFile);
preg_match('/\<version\>(.*)\<\/version\>/', $xml, $matches);
if (count($matches)) {
return $matches[1];
} else {
return false;
}
} else {
return false;
}
}
private function countJceRogueProfiles()
{
require_once 'bfJce.php';
return (new bfJce($this->db))->countRogueProfiles();
}
private function countJceMaliciousFiles()
{
require_once 'bfJce.php';
return (new bfJce($this->db))->countMaliciousFiles();
}
private function countRogueAdmins()
{
require_once 'bfRogueAdmin.php';
return (new bfRogueAdmin($this->db))->countRogueAdmins();
}
private function admintoolsHelper()
{
require_once 'bfAdmintools.php';
return new bfAdmintools($this->db);
}
private function hasAdmintoolsInstalled(): int
{
return $this->admintoolsHelper()->isInstalled() ? 1 : 0;
}
/**
* Is the IP this snapshot request arrived from currently blocked by Admin Tools? Only checked
* when Admin Tools is installed; the same self-detected IP the unblock tool would clear.
*/
private function isAdmintoolsIpBlocked(): int
{
$helper = $this->admintoolsHelper();
if (! $helper->isInstalled()) {
return 0;
}
$ip = str_replace('::ffff:', '', (string) (@getenv('HTTP_X_FORWARDED_FOR') ?: @$_SERVER['REMOTE_ADDR']));
return ($ip !== '' && $helper->isIpBlocked($ip)) ? 1 : 0;
}
private function checkfluff()
{
$fluffFiles = [
'/.drone.yml',
'/robots.txt.dist',
'/web.config.txt',
'/joomla.xml',
'/build.xml',
'/LICENSE.txt',
'/README.txt',
'/htaccess.txt',
'/LICENSES.php',
'/configuration.php-dist',
'/CHANGELOG.php',
'/COPYRIGHT.php',
'/CREDITS.php',
'/INSTALL.php',
'/LICENSE.php',
'/CONTRIBUTING.md',
'/phpunit.xml.dist',
'/README.md',
'/.travis.yml',
'/travisci-phpunit.xml',
'/images/banners/osmbanner1.png',
'/images/banners/osmbanner2.png',
'/images/banners/shop-ad-books.jpg',
'/images/banners/shop-ad.jpg',
'/images/banners/white.png',
'/images/headers/blue-flower.jpg',
'/images/headers/maple.jpg',
'/images/headers/raindrops.jpg',
'/images/headers/walden-pond.jpg',
'/images/headers/windows.jpg',
'/images/joomla_black.gif',
'/images/joomla_black.png',
'/images/joomla_green.gif',
'/images/joomla_logo_black.jpg',
'/images/powered_by.png',
'/images/sampledata/fruitshop/apple.jpg',
'/images/sampledata/fruitshop/bananas_2.jpg',
'/images/sampledata/fruitshop/fruits.gif',
'/images/sampledata/fruitshop/tamarind.jpg',
'/images/sampledata/parks/animals/180px_koala_ag1.jpg',
'/images/sampledata/parks/animals/180px_wobbegong.jpg',
'/images/sampledata/parks/animals/200px_phyllopteryx_taeniolatus1.jpg',
'/images/sampledata/parks/animals/220px_spottedquoll_2005_seanmcclean.jpg',
'/images/sampledata/parks/animals/789px_spottedquoll_2005_seanmcclean.jpg',
'/images/sampledata/parks/animals/800px_koala_ag1.jpg',
'/images/sampledata/parks/animals/800px_phyllopteryx_taeniolatus1.jpg',
'/images/sampledata/parks/animals/800px_wobbegong.jpg',
'/images/sampledata/parks/banner_cradle.jpg',
'/images/sampledata/parks/landscape/120px_pinnacles_western_australia.jpg',
'/images/sampledata/parks/landscape/120px_rainforest_bluemountainsnsw.jpg',
'/images/sampledata/parks/landscape/180px_ormiston_pound.jpg',
'/images/sampledata/parks/landscape/250px_cradle_mountain_seen_from_barn_bluff.jpg',
'/images/sampledata/parks/landscape/727px_rainforest_bluemountainsnsw.jpg',
'/images/sampledata/parks/landscape/800px_cradle_mountain_seen_from_barn_bluff.jpg',
'/images/sampledata/parks/landscape/800px_ormiston_pound.jpg',
'/images/sampledata/parks/landscape/800px_pinnacles_western_australia.jpg',
'/images/sampledata/parks/parks.gif',
];
$fluffCount = 0;
foreach ($fluffFiles as $file) {
$fileWithPath = JPATH_BASE . $file;
if (file_exists($fileWithPath)) {
++$fluffCount;
}
}
return (int) $fluffCount;
}
private function checkdbschema()
{
/** @var DatabaseModel $model */
$model = Factory::getApplication()
->bootComponent('com_installer')
->getMVCFactory()
->createModel('Database', 'Administrator', [
'ignore_request' => true,
]);
$schemaData = new stdClass();
// Force sensible defaults to stop Joomla, with PHP 8+, and Error Reporting Maximum - from showing Deprecated warnings
$model->setState('list.limit', 1000);
$model->setState('list.start', 0);
$model->setState('filter.search', '');
foreach ($model->getItems() as $item) {
if ('com_admin' !== $item['extension']->element) {
continue;
}
$schemaData->version_latest = $item['extension']->version;
$schemaData->version_current = JVERSION;
}
$changeSet = new ChangeSet($this->db, null);
$this->db->setQuery('select extension_id from #__extensions where name = "files_joomla"');
$filesJoomlaId = $this->db->loadResult();
$schemaData->latest = $changeSet->getSchema();
$schemaData->current = $model->getSchemaVersion($filesJoomlaId);
return json_encode($schemaData);
}
private function checkRobotsBlocksMedia()
{
$robots_blocks_media = 0;
if (file_exists(JPATH_BASE . '/robots.txt')) {
$robotsTxTContent = file_get_contents(JPATH_BASE . '/robots.txt');
if (preg_match('/Disallow:\s\/(templates|media)\//', $robotsTxTContent)) {
$robots_blocks_media = 1;
}
}
return $robots_blocks_media;
}
private function getDiskSpace()
{
if (! function_exists('disk_free_space') || ! function_exists('disk_total_space')) {
return json_encode([]);
}
$data = [
'free' => disk_free_space(JPATH_BASE),
'total' => disk_total_space(JPATH_BASE),
];
$data['used'] = $data['total'] - $data['free'];
$data['percentUsed'] = sprintf('%.2f', ($data['used'] / $data['total']) * 100);
$data['free'] = $this->formatSize($data['free']);
$data['total'] = $this->formatSize($data['total']);
$data['used'] = $this->formatSize($data['used']);
return json_encode($data);
}
private function formatSize($bytes)
{
$types = ['B', 'KB', 'MB', 'GB', 'TB'];
for ($i = 0; $bytes >= 1024 && $i < (count($types) - 1); $bytes /= 1024, $i++);
return round($bytes, 2) . ' ' . $types[$i];
}
/**
* Run some very specific checks to see if this site is hacked or not.
*/
private function checkIf100percentHackedOrNot()
{
// oh, not dont this yet :) doing it service site instead :)
}
private function getNewUserType()
{
$this->db->setQuery("SELECT params FROM #__extensions WHERE name ='com_users'");
$paramsJsonString = $this->db->loadResult();
preg_match('/new_usertype\":\"([0-9]*)\"/', $paramsJsonString, $matches);
return count($matches) ? $matches[1] : null;
}
private function getNon2FaAdmins()
{
if (version_compare($this->getJoomlaVersion(), '4.2.0') >= 0) {
$this->db->setQuery("
select * from #__users as u
left join #__user_usergroup_map as ugm on ugm.user_id = u.id
WHERE ugm.group_id IN
(
select id from #__usergroups where title = 'Super Users' or title = 'Super Utilisateur'
)
");
$superUsers = $this->db->loadObjectList();
$Non2FaAdmins = \count($superUsers);
foreach ($superUsers as $user) {
if (\count(\Joomla\Component\Users\Administrator\Helper\Mfa::getUserMfaRecords($user->id))) {
$Non2FaAdmins--;
}
}
return $Non2FaAdmins;
} else {
try {
$this->db->setQuery("select count(*) from #__users as u
left join #__user_usergroup_map as ugm on ugm.user_id = u.id
where (otpKey = \"\" or otpKey IS NULL)
and (ugm.group_id IN (select id from #__usergroups where (title = 'Super Users' or title = 'Super Utilisateur')))");
return $this->db->loadResult();
} catch (\Exception $e) {
return 0;
}
}
}
/**
* Check for joomla.user.helper.XXXXX usernames - hack seen in Q4 2016.
*/
private function checkJoomlaUserHelperHack2016()
{
$this->db->setQuery("select count(*) from #__users where username LIKE 'joomla.user.helper.%'");
return $this->db->loadResult();
}
/**
* Check the session gc plugin in Joomla 3.
*
* @return int
*/
public function getSessionGCStatus()
{
$res = 2;
// Session GC
$this->db->setQuery("select count(*) from #__extensions where name = 'plg_system_sessiongc'");
$hasSessionGcPlugin = $this->db->LoadResult();
if ($hasSessionGcPlugin) {
$this->db->setQuery("select enabled from #__extensions where name = 'plg_system_sessiongc'");
$res = $this->db->LoadResult();
}
return $res;
}
/**
* Check how many Two Factor Plugins are enabled.
*/
public function getTwoFactorPluginsEnabled()
{
// Session GC
$this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `folder` = 'twofactorauth' and enabled = 1");
return $this->db->LoadResult();
}
/**
* Load filters from com_config without using a helper.
*/
public function getAdminFilterFixed()
{
$this->db->setQuery("select params from #__extensions where element = 'com_config'");
$params = json_decode($this->db->LoadResult());
if ('NONE' == $params->filters->{7}->filter_type) {
return 0;
}
if ('BL' == $params->filters->{7}->filter_type) {
return 1;
} else {
return 2;
}
}
/**
* Load filters from com_config without using a helper.
*/
public function getUserFilterFixed()
{
$this->db->setQuery("select params from #__extensions where element = 'com_config'");
$params = json_decode($this->db->LoadResult());
if ('NH' != @$params->filters->{1}->filter_type) {
return 0;
}
if ('NH' != @$params->filters->{2}->filter_type) {
return 0;
}
if ('NH' != @$params->filters->{9}->filter_type) {
return 0;
}
return 1;
}
/**
* Load sendpassword from params from com_users without using a helper.
*/
public function getPlaintextpasswordsFixed()
{
$this->db->setQuery("select params from #__extensions where element = 'com_users'");
$params = json_decode($this->db->LoadResult());
return 1 - (int) ($params->sendpassword ?? 1);
}
/**
* Load Flash Upload Settings from params from com_media without using a helper.
*/
public function getUploadsettingsfixed()
{
$this->db->setQuery("select params from #__extensions where element = 'com_media'");
$res = $this->db->LoadResult();
if (! $res) {
return 0;
}
$params = json_decode($res);
if (! $params || ! property_exists($params, 'upload_extensions')) {
return 0;
}
if (
! preg_match('/swf/ism', $params->upload_extensions)
&&
! preg_match('/application\/x-shockwave-flash/ism', $params->upload_mime)
) {
return 1;
} else {
return 0;
}
}
/**
* Get the configuration of the google recaptcha plugin and global config.
*/
private function getCaptchaDetails()
{
$this->db->setQuery(
"SELECT count(*) FROM #__extensions WHERE (name ='plg_captcha_recaptcha' or name = 'plg_captcha_recaptcha_invisible') and enabled = 1"
);
return 0 != $this->db->loadResult() ? 1 : 0;
}
public function getData()
{
return $this->_data;
}
private function hasUpdatesAvailable()
{
set_time_limit(60);
ob_start();
require 'bfUpdates.php';
$upCheck = new bfUpdates($this->db);
$extensionupdatesavailable = $upCheck->getUpdates(true);
// remove any stray output
echo ' '; // must have something to clean else warning occurs
@ob_clean();
return $extensionupdatesavailable;
}
}
$data = new bfSnapshot();
bfEncrypt::reply(bfReply::SUCCESS, $data->getData());