| Current Path : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/ |
| Current File : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/bfExtensions.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
*/
/*
* If we have not already included bfEncrypt then this must be a direct call, and
* so we need to decrypt the incoming request.
*/
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\Component\Installer\Administrator\Model\UpdateModel;
if (! class_exists('bfEncrypt')) {
require 'bfEncrypt.php';
}
/*
* Ok so this is stupid, but we are dealing with XML Parsing on crappy servers on sites with 100+ extensions installed - gulp!
* 5 Mins! GULP - Most well configured servers will probably not honour this, but in our live tests on crappy servers this seems to work!
*/
@set_time_limit(60 * 5);
/**
* 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.
*/
final class bfExtensions
{
private $db;
private $_dataObj;
/**
* We pass the command to run as a simple integer in our encrypted request this is mainly to speed up the decryption
* process, plus its a single digit(or 2) rather than a huge string to remember :-).
*/
private $_methods = [
1 => 'getExtensions',
2 => 'installExtensionFromUrl',
];
/**
* @param stdClass $dataObj
*/
public function __construct($dataObj = null)
{
// init Joomla
if (! defined('BF_JOOMLA_INIT_DONE')) {
require_once 'bfInitJoomla.php';
}
// Joomlfish Falang crapiness, used in Joomla 3, yet to see in Joomla 4 thankfully!
// Factory::$database = null;
// Set the request vars
$this->_dataObj = $dataObj;
}
/**
* I'm the controller - I run methods based on the request integer.
*/
public function run()
{
if (property_exists($this->_dataObj, 'c')) {
$c = (int) $this->_dataObj->c;
if (array_key_exists($c, $this->_methods)) {
// call the right method
$this->{$this->_methods[$c]} ();
} else {
// Die if an unknown function
bfEncrypt::reply('error', 'No Such method #err1 - ' . $c);
}
} else {
// Die if an unknown function
bfEncrypt::reply('error', 'No Such method #err2');
}
}
/**
* Install an extension from a provided URL.
*
* Parts of this code are from Joomla CMS, Modified under license.
*
* @license GNU General Public License version 2 or later; see https://github.com/joomla/joomla-cms/blob/staging/LICENSE.txt
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
*/
public function installExtensionFromUrl()
{
// Manipulate the base Uri in the Joomla Stack to provide compatibility with some 3pd extensions like ACL Manager!
try {
$uri = Uri::getInstance();
$reflection = new \ReflectionClass($uri);
$baseProperty = $reflection->getProperty('base');
if (PHP_VERSION_ID < 80100) {
$baseProperty->setAccessible(true);
}
$base = $baseProperty->getValue(null);
$base['prefix'] = $uri->toString(['scheme', 'host']);
$base['path'] = '/';
$baseProperty->setValue(null, $base);
} catch (ReflectionException $e) {
}
// Fake a user so that we can pass com_installer core.admin ACL checks.
$user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
$user->id = PHP_INT_MAX;
Factory::getApplication()->getSession()->set('user', $user);
Factory::getApplication()->set('root_user', $user->id);
// Get an installer instance.
$installer = Installer::getInstance();
/*
* Unfortunately, we need to load the installer and system plugins so that extensions like 4SEO and others with
* libraries can access their own code. This builds a bigger stack which we did not really want to do.
*/
PluginHelper::importPlugin('installer');
PluginHelper::importPlugin('system');
Factory::getApplication()->triggerEvent('onAfterInitialise');
// Get Global Config
$config = Factory::getApplication()->getConfig();
// Get the URL of the package to install.
$url = $this->_dataObj->url;
// Download the package at the URL given.
$p_file = InstallerHelper::downloadPackage($url);
// Was the package downloaded?
if ($p_file) {
// Unpack the downloaded package file.
$package = InstallerHelper::unpack($config->get('tmp_path') . '/' . $p_file, true);
// Install the package.
if (! $installer->install($package['dir'])) {
// There was an error installing the package.
$msg = 'There was an error installing the package';
$result = false;
$msgType = 'error';
} else {
// Package installed successfully.
$msg = 'Package installed successfully';
$result = true;
$msgType = 'message';
}
// Cleanup the install files.
if (! is_file($package['packagefile'])) {
$package['packagefile'] = $config->get('tmp_path') . '/' . $package['packagefile'];
}
// Cleanup the package file
InstallerHelper::cleanupInstall($package['packagefile'], $package['extractdir']);
} else {
$result = false;
$msgType = 'error';
$msg = 'Could not download package from URL: ' . $url;
}
$res = [
'result' => $result,
'msgType' => $msgType,
'message' => $msg,
'extension_message' => $installer->get('extension_message'),
'redirect_url' => $installer->get('redirect_url'),
];
bfEncrypt::reply($res['result'], $res);
}
/**
* Get a JSON formatted list of installed extensions Needs to be public so we can call it from the audit.
*
* @return string
*/
public function getExtensions()
{
// connect/get the Joomla db object
$this->db = Factory::getContainer()->get('DatabaseDriver');
$this->db->setQuery('SELECT e.extension_id, e.name, e.type, e.element, e.enabled, e.folder,
(
SELECT title
FROM #__menu AS m
WHERE m.component_id = e.extension_id
AND parent_id = 1
ORDER BY ID ASC LIMIT 1
)
AS title
FROM #__extensions AS e
WHERE protected = 0');
$installedExtensions = $this->db->loadObjectList();
// ok if we have none maybe we are Joomla < 1.5.26
if (! $installedExtensions) {
// Yooo hoo I'm on a crap old, out of date, probably hackable Joomla version!
$one5 = true;
// Get the extensions - used to be called components
$this->db->setQuery(
'SELECT "component" as "type", name, `option` as "element", enabled FROM #__components WHERE iscore != 1 and parent = 0'
);
$components = $this->db->loadObjectList();
// Get the plugins
$this->db->setQuery('SELECT "plugin" as "type", name, element, folder, published as enabled FROM #__plugins WHERE iscore != 1');
$plugins = $this->db->loadObjectList();
// get the modules
$this->db->setQuery(
'SELECT "module" as "type", module, module as name, client_id, published as enabled FROM #__modules WHERE iscore != 1'
);
$modules = $this->db->loadObjectList();
/**
* Get the templates - I n Joomla 1.5.x the templates are not in the db unless published so we need to read
* the folders from the /templates folders Note in Joomla 1.5.x there was no such think as admin templates.
*/
$folders = array_merge(scandir(JPATH_BASE . '/templates'), scandir(JPATH_ADMINISTRATOR . '/templates'));
$templates = [];
foreach ($folders as $templateFolder) {
$f = JPATH_BASE . '/templates/' . trim($templateFolder);
$a = JPATH_ADMINISTRATOR . '/templates/' . trim($templateFolder);
// We dont want index.html etc...
if (! is_dir($f) && ! is_dir($a) || ('.' == $templateFolder || '..' == $templateFolder)) {
continue;
}
if (is_dir($a)) {
$client_id = 1;
} else {
$client_id = 0;
}
// make it look like we want like Joomla 2.5+ would
$template = [
'type' => 'template',
'template' => $templateFolder,
'client_id' => $client_id,
'enabled' => 1,
];
// Convert to an obj
$templates[] = json_decode(json_encode($template));
}
// Merge all the "extensions" we have found all over the place
$installedExtensions = array_merge($components, $plugins, $modules, $templates);
}
$lang = Factory::getApplication()->getLanguage();
// Load all the language strings up front incase any strings are shared
foreach ($installedExtensions as $k => $ext) {
$lang->load(strtolower($ext->element ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->element ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($ext->name ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->name ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($ext->title ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->title ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($ext->element ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->element ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($ext->name ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->name ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($ext->title ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($ext->title ?? ''), JPATH_SITE, 'en-GB', true);
$element = str_replace('_TITLE', '', strtoupper($ext->element ?? ''));
$name = str_replace('_TITLE', '', strtoupper($ext->name ?? ''));
$title = str_replace('_TITLE', '', strtoupper($ext->title ?? ''));
$lang->load(strtolower($element ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($element ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($name ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($name ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($title ?? '') . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($title ?? '') . '.sys', JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($element ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($element ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($name ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($name ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($title ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load(strtolower($title ?? ''), JPATH_SITE, 'en-GB', true);
// templates
$lang->load('tpl_' . strtolower($name ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load('tpl_' . strtolower($name ?? ''), JPATH_SITE, 'en-GB', true);
// Joomla 1.5.x modules
$lang->load('mod_' . strtolower($name ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load('mod_' . strtolower($name ?? ''), JPATH_SITE, 'en-GB', true);
// tut tut Akeeba - bad naming!
$lang->load(strtolower('PLG_SYSTEM_SRP'), JPATH_ADMINISTRATOR, 'en-GB', true); // should be plg_srp
$lang->load(strtolower('PLG_SYSTEM_ONECLICKACTION'), JPATH_SITE, 'en-GB', true); // should be plg_oneclickaction
$lang->load(strtolower('PLG_SYSTEM_ONECLICKACTION'), JPATH_ADMINISTRATOR, 'en-GB', true); // should be plg_oneclickaction
// Joomla 1.5 plugins
if ('plugin' == $ext->type) {
$plg = 'plg_' . $ext->folder . '_' . $ext->element;
$lang->load(strtolower($plg ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($plg ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
}
if ('template' == $ext->type) {
$plg = 'tpl_' . $ext->name;
$lang->load(strtolower($plg ?? ''), JPATH_SITE, 'en-GB', true);
$lang->load(strtolower($plg ?? ''), JPATH_ADMINISTRATOR, 'en-GB', true);
}
}
// ok now we have the extensions - get the xml for further offline crunching
foreach ($installedExtensions as $k => $ext) {
// remove not supported types :-(
if ('file' == $ext->type || 'package' == $ext->type) {
unset($installedExtensions[$k]);
continue;
}
$ext->xmlFile = $this->findManifest($ext);
try {
if (false !== $ext->xmlFile) {
$parts = explode('/', $ext->xmlFile);
array_pop($parts);
$ext->path = implode('/', $parts);
bfLog::log('Loading XML file = ' . str_replace(JPATH_BASE, '', $ext->xmlFile));
$xml = trim(file_get_contents($ext->xmlFile));
$myXML = new SimpleXMLElement($xml);
if (property_exists($myXML, 'description')) {
$ext->desc = $myXML->description;
}
$ext->xmlFileContents = base64_encode(gzcompress($xml));
$ext->xmlFileCreated = filemtime($ext->xmlFile);
} else {
$ext->MANIFESTERROR = true;
}
} catch (Exception $e) {
bfLog::log('EXCEPTION = ' . $ext->xmlFile . ' ' . $e->getMessage());
exit('Could not process XML file at: ' . str_replace(JPATH_BASE, '', $ext->xmlFile));
}
$ext->name = Text::_($ext->name);
$ext->title = Text::_($ext->title);
if (property_exists($ext, 'desc')) {
$ext->desc = base64_encode(gzcompress(Text::_($ext->desc)));
} else {
$ext->desc = '';
}
// remove base paths - we dont want to leak data :)
if (property_exists($ext, 'path')) {
$ext->xmlFile = $this->removeBase($ext->xmlFile);
} else {
$ext->xmlFile = '';
}
if (property_exists($ext, 'path')) {
$ext->path = $this->removeBase($ext->path);
} else {
$ext->path = '';
}
// Sort so its pretty - not that anyone sees, but debugging is easier
$ext = (array) $ext;
ksort($ext);
// push to the result
$installedExtensions[$k] = $ext;
}
return json_encode($installedExtensions);
}
/**
* Find the XML file to parse.
*
* @param stdClass $ext
*
* @return bool
*/
private function findManifest($ext)
{
$prefixes = ['com_',
'ext_',
'plg_content_',
'plg_system_',
'plg_user_',
'plg_authentication_',
'plg_authentication_',
'plg_captcha_',
'plg_content_',
'plg_editors_',
'plg_editors-xtd_',
'plg_extension_',
'plg_finder_',
'plg_quickicon_',
'plg_search_',
'plg_system_',
'plg_twofactorauth_',
'plg_user_',
'plg_',
];
if (property_exists($ext, 'element')) {
$shortName = str_replace($prefixes, '', strtolower($ext->element));
} else {
$shortName = str_replace($prefixes, '', strtolower($ext->option));
}
$try = [];
$last = [];
// Let the UGLY code begin
switch ($ext->type) {
case 'component':
$try[] = JPATH_ADMINISTRATOR . '/components/' . $ext->element . '/' . $shortName . '.xml';
$last[] = JPATH_ADMINISTRATOR . '/components/' . $ext->element . '/';
break;
case 'module':
$try[] = JPATH_ADMINISTRATOR . '/modules/' . @$ext->element . '/' . $shortName . '.xml';
$try[] = JPATH_BASE . '/modules/' . @$ext->element . '/' . $shortName . '.xml';
$try[] = JPATH_ADMINISTRATOR . '/modules/' . @$ext->module . '/' . @$ext->module . '.xml';
$try[] = JPATH_BASE . '/modules/' . @$ext->module . '/' . @$ext->module . '.xml';
$last[] = JPATH_ADMINISTRATOR . '/modules/' . @$ext->module . '/';
$last[] = JPATH_BASE . '/modules/' . @$ext->module . '/';
break;
case 'template':
$try[] = JPATH_ADMINISTRATOR . '/templates/' . @$ext->element . '/templateDetails.xml';
$try[] = JPATH_BASE . '/templates/' . @$ext->element . '/templateDetails.xml';
if (property_exists($ext, 'template')) {
$try[] = JPATH_ADMINISTRATOR . '/templates/' . @$ext->template . '/templateDetails.xml';
$try[] = JPATH_BASE . '/templates/' . @$ext->template . '/templateDetails.xml';
}
if (property_exists($ext, 'name')) {
$try[] = JPATH_ADMINISTRATOR . '/templates/' . @$ext->name . '/templateDetails.xml';
$try[] = JPATH_BASE . '/templates/' . @$ext->name . '/templateDetails.xml';
}
break;
case 'language':
$try[] = JPATH_ADMINISTRATOR . '/language/' . $ext->element . '/' . $ext->element . '.xml';
$try[] = JPATH_BASE . '/language/' . $ext->element . '/' . $ext->element . '.xml';
break;
case 'plugin':
$try[] = JPATH_ADMINISTRATOR . '/plugins/' . $ext->element . '/' . $shortName . '.xml';
$try[] = JPATH_BASE . '/plugins/' . $ext->folder . '/' . $ext->element . '/' . $shortName . '.xml';
$try[] = JPATH_BASE . '/plugins/' . $ext->element . '/' . $shortName . '.xml';
$try[] = JPATH_BASE . '/plugins/' . $ext->folder . '/' . $shortName . '.xml';
$try[] = JPATH_BASE . '/plugins/' . @$ext->option . '/' . $shortName . '.xml';
$last[] = JPATH_ADMINISTRATOR . '/plugins/' . $ext->element . '/';
$last[] = JPATH_BASE . '/plugins/' . $ext->folder . '/' . $ext->element . '/';
$last[] = JPATH_BASE . '/plugins/' . $ext->element . '/';
$last[] = JPATH_BASE . '/plugins/' . $ext->folder . '/';
$last[] = JPATH_BASE . '/plugins/' . @$ext->option . '/';
break;
}
if (count($try)) {
foreach ($try as $tryThisFile) {
if (file_exists($tryThisFile)) {
return $tryThisFile;
}
}
}
// argh! still no xml file! - ok lets get tough!
foreach ($last as $tryThisFolder) {
$foldersAndFiles = @scandir($tryThisFolder);
if ($foldersAndFiles) {
foreach ($foldersAndFiles as $f) {
if (preg_match('/\.xml/', $f)) {
$fileContents = file_get_contents($tryThisFolder . '/' . $f);
if (preg_match('/(\<install|\<extension )/', $fileContents)) {
return realpath($tryThisFolder . '/' . $f);
}
}
}
}
// look for ANY xml files in this folder
// If you find an xml file - look inside it to see if its a manifest/install
}
return false;
}
/**
* Remove the JPATH_BASE from a file with path to prevent leaking absolute paths.
*
* @param string $path The full path to a file
*
* @return string The absolute path to the file
*/
private function removeBase($path)
{
return str_replace(JPATH_BASE, '', $path);
}
/**
* Updates an extension.
*
* @return bool
*/
public function doUpdate($extensionId)
{
// Fake a user so that we can pass com_installer core.admin ACL checks.
Factory::getApplication()->set('root_user', Factory::getApplication()->getSession()->get('user')->id = PHP_INT_MAX);
/*
* Unfortunately, we need to load the installer and system plugins so that extensions like 4SEO and others with
* libraries can access their own code. This builds a bigger stack which we did not really want to do.
*/
PluginHelper::importPlugin('installer');
PluginHelper::importPlugin('system');
Factory::getApplication()->triggerEvent('onAfterInitialise');
/** @var UpdateModel $model */
$model = Factory::getApplication()
->bootComponent('com_installer')
->getMVCFactory()
->createModel('Update', 'Administrator', [
'ignore_request' => true,
]);
$model->update([$extensionId]);
$cache = Factory::getContainer()
->get(CacheControllerFactoryInterface::class)
->createCacheController('callback', [
'defaultgroup' => 'mod_menu',
]);
$cache->clean();
$cache = Factory::getContainer()
->get(CacheControllerFactoryInterface::class)
->createCacheController('callback', [
'defaultgroup' => '_system',
]);
$cache->clean();
return $model->getState('result');
}
}