Viewing File: /usr/local/cpanel/base/frontend/jupiter/ls_web_cache_manager/core/Lsc/UserWPInstallStorage.php
<?php
/** *********************************************
* LiteSpeed Web Cache Management Plugin for cPanel
*
* @author Michael Alegre
* @copyright 2018-2025 LiteSpeed Technologies, Inc.
* *******************************************
*/
namespace LsUserPanel\Lsc;
use LsUserPanel\CPanelWrapper;
use LsUserPanel\Ls_WebCacheMgr_Util as Util;
use LsUserPanel\Lsc\Context\UserContext;
use LsUserPanel\PluginSettings;
use LsUserPanel\QuicCloudApiUtil;
/**
* Map to data file.
*/
class UserWPInstallStorage
{
/**
* @since 2.2
* @var string
*/
const CMD_QUICCLOUD_CLEAR_CACHE = 'quicCloudClearCache';
/**
* @since 2.1
* @var string
*/
const CMD_QUICCLOUD_UPLOAD_SSL_CERT = 'quicCloudUploadSslCert';
/**
* @var string
*/
const DATA_VERSION = '1.0';
/**
* @var int
*/
const ERR_NOT_EXIST = 1;
/**
* @var int
*/
const ERR_CORRUPTED = 2;
/**
* @var int
*/
const ERR_VERSION_HIGH = 3;
/**
* @var int
*/
const ERR_VERSION_LOW = 4;
/**
* @var string
*/
private $dataFile;
/**
* @var null|UserWPInstall[] Key is WordPress installation path.
*/
private $wpInstalls = null;
/**
* @var int
*/
private $error;
/**
* @var UserWPInstall[]
*/
private $workingQueue = array();
/**
*
* @param string $dataFile
*
* @throws UserLSCMException Thrown indirectly by $this->init() call.
*/
public function __construct( $dataFile )
{
$this->dataFile = $dataFile;
$this->error = $this->init();
}
/**
*
* @return int
*
* @throws UserLSCMException Thrown indirectly by $this->checkDataFileVer()
* call.
*/
private function init()
{
if ( !file_exists($this->dataFile) ) {
return self::ERR_NOT_EXIST;
}
$content = file_get_contents($this->dataFile);
if ( ($data = json_decode($content, true)) === null ) {
/**
* Data file may be in old serialized format. Try unserializing.
*/
$data = unserialize($content);
}
if ( $data === false || !is_array($data) || !isset($data['__VER__']) ) {
return self::ERR_CORRUPTED;
}
if ( ($err = $this->checkDataFileVer($data['__VER__'])) ) {
return $err;
}
unset($data['__VER__']);
$this->wpInstalls = array();
foreach ( $data as $path => $idata ) {
$i = new UserWPInstall($path);
$i->initData($idata);
$this->wpInstalls[$path] = $i;
}
return 0;
}
/**
*
* @return int
*/
public function getError()
{
return $this->error;
}
/**
*
* @param bool $nonFatalOnly
*
* @return int
*/
public function getCount( $nonFatalOnly = false )
{
$count = 0;
if ( $this->wpInstalls != null ) {
if ( !$nonFatalOnly ) {
return count($this->wpInstalls);
}
foreach ( $this->wpInstalls as $install ) {
if ( !$install->hasFatalError() ) {
$count++;
}
}
}
return $count;
}
/**
*
* @return null|UserWPInstall[]
*/
public function getWPInstalls()
{
return $this->wpInstalls;
}
/**
* Get all known WPInstall paths.
*
* @return string[]
*/
public function getPaths()
{
if ( $this->wpInstalls == null ) {
return array();
}
return array_keys($this->wpInstalls);
}
/**
*
* @param string $path
*
* @return UserWPInstall|null
*/
public function getWPInstall( $path )
{
if ( ($realPath = realpath($path)) !== false ) {
$index = $realPath;
}
else {
$index = $path;
}
if ( isset($this->wpInstalls[$index]) ) {
return $this->wpInstalls[$index];
}
return null;
}
/**
*
* @param UserWPInstall $wpInstall
*/
public function addWPInstall( UserWPInstall $wpInstall )
{
$this->wpInstalls[$wpInstall->getPath()] = $wpInstall;
}
/**
*
* @throws UserLSCMException Thrown indirectly by $this->log() call.
*/
public function syncToDisk()
{
$data = array( '__VER__' => self::DATA_VERSION );
if ( !empty($this->wpInstalls) ) {
foreach ( $this->wpInstalls as $path => $install ) {
if ( !$install->shouldRemove() ) {
$data[$path] = $install->getData();
}
}
ksort($data);
}
file_put_contents($this->dataFile, json_encode($data), LOCK_EX);
chmod($this->dataFile, 0600);
$this->log("Data file saved $this->dataFile", UserLogger::L_DEBUG);
}
/**
* Updates data file to the latest format if possible/needed.
*
* @param string $dataFileVer
*
* @return int
*
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by $this->upgradeDataFile()
* call.
*/
private function checkDataFileVer( $dataFileVer )
{
$res = Util::betterVersionCompare($dataFileVer, self::DATA_VERSION);
if ( $res == 1 ) {
UserLogger::logMsg(
'Data file version is higher than expected and cannot be used.',
UserLogger::L_INFO
);
return self::ERR_VERSION_HIGH;
}
if ( $res == -1 && !$this->upgradeDataFile($dataFileVer) ) {
return self::ERR_VERSION_LOW;
}
return 0;
}
/**
*
* @param string $dataFileVersion
*
* @return bool
*
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by UserUtil::createBackup()
* call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
*/
private function upgradeDataFile( $dataFileVersion )
{
UserLogger::logMsg(
'Old data file version detected. Attempting to update...',
UserLogger::L_INFO
);
/**
* Currently no versions are upgradeable to 1.5
*/
$updatableVersions = array();
if ( !in_array($dataFileVersion, $updatableVersions)
|| !UserUtil::createBackup($this->dataFile) ) {
UserLogger::logMsg(
'Data file could not be updated to version '
. self::DATA_VERSION,
UserLogger::L_ERROR
);
return false;
}
/**
* Upgrade funcs will be called here.
*/
return true;
}
/**
*
* @param string $action
* @param string $path
* @param string[] $extraArgs
*
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->hasValidPath() call.
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->addUserFlagFile() call.
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->hasValidPath() call.
* @throws UserLSCMException Thrown indirectly by
* $this->quicCloudUploadSslCert() call.
* @throws UserLSCMException Thrown indirectly by
* $this->quicCloudTryToClearCache() call.
* @throws UserLSCMException Thrown indirectly by UserUserCommand::issue()
* call.
*/
private function doWPInstallAction( $action, $path, array $extraArgs )
{
if ( ($wpInstall = $this->getWPInstall($path)) == null ) {
$wpInstall = new UserWPInstall($path);
$this->addWPInstall($wpInstall);
}
switch ($action) {
case 'flag':
if ( !$wpInstall->hasValidPath() ) {
return;
}
if ( $wpInstall->addUserFlagFile() ) {
$wpInstall->setCmdStatusAndMsg(
UserUserCommand::EXIT_SUCC,
_('Flag file set')
);
}
else {
$wpInstall->setCmdStatusAndMsg(
UserUserCommand::EXIT_FAIL,
_('Could not create flag file')
);
}
$this->workingQueue[] = $wpInstall;
return;
case 'unflag':
if ( !$wpInstall->hasValidPath() ) {
return;
}
$wpInstall->removeFlagFile();
$wpInstall->setCmdStatusAndMsg(
UserUserCommand::EXIT_SUCC,
_('Flag file unset')
);
$this->workingQueue[] = $wpInstall;
return;
case UserUserCommand::CMD_DIRECT_ENABLE:
case UserUserCommand::CMD_DISABLE:
if ( $wpInstall->hasFatalError() ) {
$wpInstall->setCmdStatusAndMsg(
UserUserCommand::EXIT_FAIL,
_(
'Install skipped due to Error status. Please '
. 'Refresh Status before trying again.'
)
);
$this->workingQueue[] = $wpInstall;
return;
}
break;
case UserWPInstallStorage::CMD_QUICCLOUD_UPLOAD_SSL_CERT:
$this->quicCloudUploadSslCert($wpInstall);
return;
case UserWPInstallStorage::CMD_QUICCLOUD_CLEAR_CACHE:
$this->quicCloudTryToClearCache($wpInstall);
return;
//no default
}
if ( UserUserCommand::issue($action, $wpInstall, $extraArgs) ) {
$this->workingQueue[] = $wpInstall;
}
}
/**
*
* @param string $action
* @param string[] $list
* @param string[] $extraArgs
*
* @throws UserLSCMException Thrown indirectly by $this->log() call.
* @throws UserLSCMException Thrown indirectly by $this->scan() call.
* @throws UserLSCMException Thrown indirectly by
* $this->doWPInstallAction() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg()
* call.
* @throws UserLSCMException Thrown indirectly by $this->syncToDisk() call.
*/
public function doAction( $action, array $list, array $extraArgs = array() )
{
$this->log(
"doAction $action for " . count($list) . " items",
UserLogger::L_VERBOSE
);
$newWpInstalls = array();
foreach ( $list as $path ) {
if ( $action == 'scan' ) {
$this->scan($path, $newWpInstalls, true);
}
else {
$this->doWPInstallAction($action, $path, $extraArgs);
}
}
if ( $action == 'scan' ) {
if ( $this->wpInstalls !== null ) {
/**
* Add an error message for any remaining $this->wpInstalls[]
* installations not re-discovered during scan.
*/
foreach ( $this->wpInstalls as $key => $wpInstall ) {
UserLogger::logMsg(
"$key - Installation could not be found during Scan "
. 'and has been removed from the Cache Manager '
. 'list.',
UserLogger::L_NOTICE
);
UserLogger::addUiMsg(
"$key - "
. _(
'Installation could not be found during Scan '
. 'and has been removed from the '
. 'Cache Manager list.'
),
UserLogger::UI_ERR
);
}
}
$this->wpInstalls = $newWpInstalls;
/**
* Explicitly clear any data file errors after scanning as initial
* data file load and scan operation happen in the same
* process/page load.
*/
$this->error = 0;
}
$this->syncToDisk();
}
/**
*
* @param string $docroot
* @param UserWPInstall[] $newWpInstalls
* @param bool $forceRefresh
*
* @return void
*
* @throws UserLSCMException Thrown indirectly by
* UserContext::getScanDepth() call.
* @throws UserLSCMException Thrown indirectly by $this->log() call.
* @throws UserLSCMException Thrown indirectly by $this->log() call.
* @throws UserLSCMException Thrown indirectly by
* $newWpInstalls[$wp_path]->refreshStatus() call.
*/
private function scan(
$docroot,
array &$newWpInstalls,
$forceRefresh = false )
{
$cpanel = CPanelWrapper::getCpanelObj();
$result = $cpanel->uapi(
'lsws',
'getScanDirs',
array(
'docroot' => $docroot,
'depth' => UserContext::getScanDepth()
)
);
$installationsFound = preg_match_all(
"|$docroot(.*)(?=/wp-admin)|",
$result['cpanelresult']['result']['data']['scanData'],
$matches
);
/**
* Example:
* /home/user/public_html/wordpress/wp-admin
* /home/user/public_html/blog/wp-admin
* /home/user/public_html/wp/wp-admin
*/
if ( !$installationsFound ) {
return;
}
foreach ( $matches[1] as $path ) {
$wp_path = $docroot . $path;
$refresh = $forceRefresh;
if ( !isset($this->wpInstalls[$wp_path]) ) {
$newWpInstalls[$wp_path] = new UserWPInstall($wp_path);
$refresh = true;
$this->log(
"New Installation Found: $wp_path",
UserLogger::L_INFO
);
}
else {
$newWpInstalls[$wp_path] = $this->wpInstalls[$wp_path];
unset($this->wpInstalls[$wp_path]);
$this->log(
"Installation already found: $wp_path",
UserLogger::L_DEBUG
);
}
if ( $refresh ) {
$newWpInstalls[$wp_path]->refreshStatus();
$this->workingQueue[] = $newWpInstalls[$wp_path];
}
}
}
/**
*
* @since 2.2
*
* @param UserWPInstall $wpInstall
*
* @throws UserLSCMException Thrown indirectly by $this->log() call.
*/
private function quicCloudTryToClearCache( UserWPInstall $wpInstall )
{
$siteUrl = $wpInstall->getData(UserWPInstall::FLD_SITEURL);
if ( ! preg_match('#^https?://#', $siteUrl) ) {
$siteUrl = "http://$siteUrl";
}
$path = $wpInstall->getPath();
$purgeFileName = Util::createRandomizedQcPurgeFile($path);
$this->log(
"Clear all QUIC.cloud cache call for site URL $siteUrl returned: "
. var_export(
UserUtil::makeUrlRequest("$siteUrl/$purgeFileName", 'GET'),
true
),
UserLogger::L_DEBUG
);
unlink("$path/$purgeFileName");
}
/**
*
* @since 2.1
*
* @param UserWPInstall $wpInstall
*
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->refreshStatus() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
* @throws UserLSCMException Thrown indirectly by
* PluginSettings::getSetting() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
* @throws UserLSCMException Thrown indirectly by
* UserUserCommand::getValueFromWordPress() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::addUiMsg() call.
*/
private function quicCloudUploadSslCert( UserWPInstall $wpInstall )
{
if ( ! $wpInstall->isLscwpEnabled() ) {
$wpInstall->refreshStatus();
if ( ! $wpInstall->isLscwpEnabled() ) {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - "
. sprintf(
_(
'%1$s for %2$s must be installed and enabled '
. 'before attempting to upload SSL '
. 'certificate to %3$s.'
),
'LiteSpeed Cache Plugin',
'WordPress',
'QUIC.cloud'
),
UserLogger::UI_ERR
);
return;
}
}
$allowEcCertGen = (
PluginSettings::getSetting(PluginSettings::FLD_GENERATE_EC_CERTS)
==
PluginSettings::SETTING_ON_PLUS_AUTO
);
$domain = $wpInstall->getData(UserWPInstall::FLD_SERVERNAME);
$sslData = Util::getDomainSslData($domain, $allowEcCertGen);
$certFingerprint = $sslData['retCert'];
$key = $sslData['retKey'];
if ( $certFingerprint == '' || $key == '' ) {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - "
. _(
'Unable to get certificate/private key information for '
. 'this domain.'
),
UserLogger::UI_ERR
);
return;
}
$apiKey = UserUserCommand::getValueFromWordPress(
UserUserCommand::CMD_GET_QUICCLOUD_API_KEY,
$wpInstall
);
if ( $apiKey == '' ) {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - "
. sprintf(
_(
'Unable to retrieve %1$s API key. Please visit '
. '%2$s in the %3$s Dashboard and confirm that '
. 'the API key has already been generated.'
),
'QUIC.cloud',
'"LiteSPeed Cache -> General"',
'WordPress'
),
UserLogger::UI_ERR
);
return;
}
$siteUrl = $wpInstall->getData(UserWPInstall::FLD_SITEURL);
if ( ! preg_match('#^https?://#', $siteUrl) ) {
$siteUrl = "http://$siteUrl";
}
try {
$qcDomainList = QuicCloudApiUtil::callInfo($apiKey, $siteUrl);
}
catch ( UserLSCMException $e ) {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - {$e->getMessage()}",
UserLogger::UI_ERR
);
return;
}
$certFullyCoversSite = empty(
array_diff(
$qcDomainList,
Util::getCertificateAltNames($certFingerprint)
)
);
if ( !$certFullyCoversSite ) {
UserLogger::addUiMsg(
sprintf(
_(
'Detected SSL certificate does not contain all %s '
. 'configured domain and alias entries for this '
. 'site.'
),
'QUIC.cloud'
),
UserLogger::UI_ERR
);
return;
}
try {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - "
. QuicCloudApiUtil::uploadCert(
$apiKey,
$domain,
$siteUrl,
$certFingerprint,
$key
),
UserLogger::UI_SUCC
);
}
catch ( UserLSCMException $e ) {
UserLogger::addUiMsg(
"{$wpInstall->getPath()} - {$e->getMessage()}",
UserLogger::UI_ERR
);
return;
}
}
/**
* Get all WPInstall command messages as a key=>value array.
*
* @return string[][]
*/
public function getAllCmdMsgs()
{
$succ = $fail = $err = array();
foreach ( $this->workingQueue as $wpInstall ) {
$cmdStatus = $wpInstall->getCmdStatus();
switch (true) {
case $cmdStatus & UserUserCommand::EXIT_SUCC:
$msgType = &$succ;
break;
case $cmdStatus & UserUserCommand::EXIT_FAIL:
$msgType = &$fail;
break;
case $cmdStatus & UserUserCommand::EXIT_ERROR:
$msgType = &$err;
break;
default:
continue 2;
}
if ( ($msg = $wpInstall->getCmdMsg()) ) {
$msgType[] = "{$wpInstall->getPath()} - $msg";
}
}
return array( 'succ' => $succ, 'fail' => $fail, 'err' => $err );
}
/**
*
* @param string $msg
* @param int $level
*
* @throws UserLSCMException Thrown indirectly by
* UserLogger::logMsg() call.
*/
protected function log( $msg, $level )
{
UserLogger::logMsg("WPInstallStorage - $msg", $level);
}
}
Back to Directory
File Manager