Viewing File: /usr/local/cpanel/base/frontend/jupiter/ls_web_cache_manager/core/Lsc/UserUserCommand.php
<?php
/** *********************************************
* LiteSpeed Web Cache Management Plugin for cPanel
*
* @author Michael Alegre
* @copyright (c) 2018-2025 LiteSpeed Technologies, Inc.
* *******************************************
*/
namespace LsUserPanel\Lsc;
use LsUserPanel\CPanelWrapper;
use LsUserPanel\Lsc\Context\UserContext;
use LsUserPanel\Lsc\Panel\UserControlPanel;
use LsUserPanel\PluginSettings;
/**
* Running as user - suexec
*/
class UserUserCommand
{
/**
* @var int
*/
const EXIT_ERROR = 1;
/**
* @var int
*/
const EXIT_SUCC = 2;
/**
* @var int
*/
const EXIT_FAIL = 4;
/**
* @var int
*/
const EXIT_INCR_SUCC = 8;
/**
* @var int
*/
const EXIT_INCR_FAIL = 16;
/**
* @var string
*/
const RETURN_CODE_TIMEOUT = 124;
/**
* @var string
*/
const CMD_STATUS = 'status';
/**
* @var string
*/
const CMD_DIRECT_ENABLE = 'direct_enable';
/**
* @var string
*/
const CMD_DISABLE = 'disable';
/**
* Sub-command.
*
* @var string
*/
const CMD_UPDATE_TRANSLATION = 'update_translation';
/**
* Sub-command.
*
* @var string
*/
const CMD_REMOVE_LSCWP_PLUGIN_FILES = 'remove_lscwp_plugin_files';
/**
* @since 2.1
* @var string
*/
const CMD_GET_QUICCLOUD_API_KEY = 'getQuicCloudApiKey';
private function __construct(){}
/**
* Handles logging unexpected error output (or not if too long) and returns
* a crafted message to be displayed instead.
*
* @param UserWPInstall $wpInstall WordPress Installation object.
* @param string $err Complied error message.
* @param int $lines Number of $output lines read into the
* error msg.
*
* @return string Message to be displayed instead.
*
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
*/
private static function handleUnexpectedError(
UserWPInstall $wpInstall,
$err,
$lines )
{
$msg = 'Unexpected Error Encountered!';
$path = $wpInstall->getPath();
/**
* $lines > 500 are likely some custom code triggering a page render.
* Throw out the actual message in this case.
*/
if ( $lines < 500 ) {
$commonErrs = array(
UserWPInstall::ST_ERR_EXECMD_DB =>
_('Error establishing a database connection.')
);
$match = false;
foreach ( $commonErrs as $statusBit => $commonErr ) {
if ( strpos($err, $commonErr) !== false ) {
$wpInstall->unsetStatusBit(UserWPInstall::ST_ERR_EXECMD);
$wpInstall->setStatusBit($statusBit);
$msg .= " $commonErr";
$match = true;
break;
}
}
if ( !$match ) {
UserLogger::logMsg("$path - $err", UserLogger::L_ERROR);
return "$msg "
. sprintf(
_('See %s for more information.'),
'ls_webcachemgr.log'
);
}
}
UserLogger::logMsg("$path - $msg", UserLogger::L_ERROR);
return $msg;
}
/**
* Parse out locale and plugin version information from escalated issue()
* result output GET_TRANSLATION or BAD_TRANSLATION line.
*
* @since 2.1.4
*
* @param string $line
*
* @return string[]
*/
private static function parseTranslationLocaleAndPluginVer( $line )
{
list($locale, $pluginVer) = explode(' ', $line);
return array( 'locale' => $locale, 'pluginVer' => $pluginVer );
}
/**
*
* @since 2.1
*
* @param UserWPInstall $wpInstall
* @param string $output
*
* @throws UserLSCMException Thrown indirectly by
* UserLscwpPlugin::retrieveTranslation() call.
* @throws UserLSCMException Thrown indirectly by self::subCommandIssue()
* call.
* @throws UserLSCMException Thrown indirectly by
* UserLscwpPlugin::removeTranslationZip() call.
*/
private static function handleGetTranslationOutput(
UserWPInstall $wpInstall,
$output )
{
$translationInfo = self::parseTranslationLocaleAndPluginVer($output);
$translationRetrieved = UserLscwpPlugin::retrieveTranslation(
$translationInfo['locale'],
$translationInfo['pluginVer']
);
if ( $translationRetrieved ) {
$subOutput =
self::subCommandIssue(self::CMD_UPDATE_TRANSLATION, $wpInstall);
foreach ( $subOutput as $subLine ) {
if ( preg_match('/BAD_TRANSLATION=(.+)/', $subLine, $m) ) {
$badTranslationInfo =
self::parseTranslationLocaleAndPluginVer($m[1]);
UserLscwpPlugin::removeTranslationZip(
$badTranslationInfo['locale'],
$badTranslationInfo['pluginVer']
);
}
}
}
}
/**
*
* @since 2.1
*
* @param UserWPInstall $wpInstall
* @param string $line
* @param int $retStatus
* @param string $err
*
* @return bool
*
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->populateDataFromUrl() call.
* @throws UserLSCMException Thrown indirectly by
* self::handleGetTranslationOutput() call.
*/
private static function handleResultOutput(
UserWPInstall $wpInstall,
$line,
&$retStatus,
&$err )
{
if ( preg_match('/SITEURL=(.+)/', $line, $m) ) {
if ( !$wpInstall->populateDataFromUrl($m[1]) ) {
/**
* Matching docroot could not be found, ignore other
* output.
* setCmdStatusAndMsg() etc. already handled in
* populateDataFromUrl().
*/
return false;
}
}
elseif ( preg_match('/STATUS=(.+)/', $line, $m) ) {
$retStatus |= (int)$m[1];
}
elseif ( preg_match('/GET_TRANSLATION=(.+)/', $line, $m) ) {
self::handleGetTranslationOutput($wpInstall, $m[1]);
}
else {
$err .= "Unexpected result line $line\n";
}
return true;
}
/**
*
* @since 2.1
*
* @param UserWPInstall $wpInstall
*
* @throws UserLSCMException Thrown indirectly by self::subCommandIssue()
* call.
*/
private static function removeLeftoverLscwpFiles(
UserWPInstall $wpInstall )
{
self::subCommandIssue(self::CMD_REMOVE_LSCWP_PLUGIN_FILES, $wpInstall);
$wpInstall->removeNewLscwpFlagFile();
}
/**
*
* @param string $action
* @param UserWPInstall $wpInstall
* @param array $extraArgs
*
* @return string
*
* @throws UserLSCMException Thrown when retrieved $lswsHome value is
* empty.
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->getPhpBinary() call.
* @throws UserLSCMException Thrown indirectly by
* PluginSettings::getSetting() call.
*/
private static function getIssueCmd(
$action,
UserWPInstall $wpInstall,
array $extraArgs = array() )
{
$serverName = $wpInstall->getData(UserWPInstall::FLD_SERVERNAME);
if ( $serverName === null ) {
$serverName = $docRoot = 'x';
}
else {
$docRoot = $wpInstall->getData(UserWPInstall::FLD_DOCROOT);
if ( $docRoot === null ) {
$docRoot = 'x';
}
}
$lswsHome =
PluginSettings::getSetting(PluginSettings::FLD_LSWS_HOME_DIR);
if ( empty($lswsHome) ) {
throw new UserLSCMException(
'LSWS_HOME_DIR value not set when attempting to execute '
. "LSWS_HOME_DIR required command '$action'."
);
}
$path = $wpInstall->getPath();
$modifier = implode(' ', $extraArgs);
return "cd $path/wp-admin && timeout "
. UserControlPanel::PHP_TIMEOUT
. " {$wpInstall->getPhpBinary()} "
. "$lswsHome/add-ons/webcachemgr/src/UserCommand.php $action "
. "$path $docRoot $serverName "
. UserContext::getOption()->getInvokerName()
. (($modifier !== '') ? " $modifier" : '');
}
/**
*
* @since 2.1
*
* @param string $action
* @param UserWPInstall $wpInstall
* @param string[] $extraArgs
*
* @return null|string
*
* @throws UserLSCMException Thrown indirectly by
* self::preIssueValidation() call.
* @throws UserLSCMException Thrown indirectly by self::getIssueCmd() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
*/
public static function getValueFromWordPress(
$action,
UserWPInstall $wpInstall,
array $extraArgs = array() )
{
if ( !self::preIssueValidation($action, $wpInstall, $extraArgs) ) {
return null;
}
$cmd = self::getIssueCmd($action, $wpInstall, $extraArgs);
$cpanel = CPanelWrapper::getCpanelObj();
$result = $cpanel->uapi('lsws', 'execIssueCmd', array( 'cmd' => $cmd ));
$return_var = $result['cpanelresult']['result']['data']['retVar'];
$resOutput = $result['cpanelresult']['result']['data']['output'];
$output = (!empty($resOutput)) ? explode("\n", $resOutput) : array();
UserLogger::debug(
"getValueFromWordPress command $action=$return_var $wpInstall\n$cmd"
);
UserLogger::debug('output = ' . var_export($output, true));
$debug = $upgrade = $err = '';
$curr = &$err;
$ret = null;
foreach ( $output as $line ) {
/**
* If this line is not present in output, did not return normally.
* This line will appear after any [UPGRADE] output.
*/
if ( strpos($line, 'LS UserCommand Output Start') !== false ) {
continue;
}
elseif ( strpos($line, '[RESULT]') !== false ) {
if ( preg_match('/API_KEY=(.*)/', $line, $m) ) {
$ret = $m[1];
}
else {
$err .= "Unexpected result line $line\n";
}
}
elseif ( ($pos = strpos($line, '[DEBUG]')) !== false ) {
$debug .= substr($line, $pos + 7) . "\n";
$curr = &$debug;
}
elseif ( strpos($line, '[UPGRADE]') !== false ) {
//Ignore this output
$curr = &$upgrade;
}
else {
$curr .= "$line\n";
}
}
$path = $wpInstall->getPath();
if ( $debug ) {
UserLogger::logMsg("$path - $debug", UserLogger::L_DEBUG);
}
if ( $err ) {
UserLogger::logMsg("$path - $err", UserLogger::L_ERROR);
}
return $ret;
}
/**
*
* @since 2.1.4
*
* @param string $subAction
* @param UserWPInstall $wpInstall
*
* @return string[]
*
* @throws UserLSCMException Thrown indirectly by self::getIssueCmd() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
*/
private static function subCommandIssue(
$subAction,
UserWPInstall $wpInstall)
{
$subCmd = self::getIssueCmd($subAction, $wpInstall);
$result = CPanelWrapper::getCpanelObj()->uapi(
'lsws',
'execIssueCmd',
array( 'cmd' => $subCmd )
);
$data = $result['cpanelresult']['result']['data'];
$subResOutput = $data['output'];
if ( !empty($subResOutput) ) {
$subOutput = explode("\n", $subResOutput);
}
else {
$subOutput = array();
}
UserLogger::debug(
"Issue sub command $subAction={$data['retVar']} $wpInstall\n$subCmd"
);
UserLogger::debug('sub output = ' . var_export($subOutput, true));
return $subOutput;
}
/**
*
* @param string $action
* @param UserWPInstall $wpInstall
* @param string[] $extraArgs
*
* @return bool
*
* @throws UserLSCMException Thrown indirectly by
* self::preIssueValidation() call.
* @throws UserLSCMException Thrown indirectly by self::getIssueCmd() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::debug() call.
* @throws UserLSCMException Thrown indirectly by
* self::removeLeftoverLscwpFiles() call.
* @throws UserLSCMException Thrown indirectly by
* self::handleResultOutput() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->addUserFlagFile() call.
* @throws UserLSCMException Thrown indirectly by UserLogger::logMsg()
* call.
* @throws UserLSCMException Thrown indirectly by
* self::handleUnexpectedError() call.
*/
public static function issue(
$action,
UserWPInstall $wpInstall,
array $extraArgs = array() )
{
if ( !self::preIssueValidation($action, $wpInstall, $extraArgs) ) {
return false;
}
$cmd = self::getIssueCmd($action, $wpInstall, $extraArgs);
$cpanel = CPanelWrapper::getCpanelObj();
$result = $cpanel->uapi('lsws', 'execIssueCmd', array( 'cmd' => $cmd ));
$data = $result['cpanelresult']['result']['data'];
$return_var = $data['retVar'];
$resOutput = $data['output'];
$output = (!empty($resOutput)) ? explode("\n", $resOutput) : array();
UserLogger::debug("Issue command $action=$return_var $wpInstall\n$cmd");
UserLogger::debug('output = ' . var_export($output, true));
if ( $wpInstall->hasNewLscwpFlagFile() ) {
self::removeLeftoverLscwpFiles($wpInstall);
}
$errorStatus = $retStatus = $cmdStatus = 0;
switch ($return_var) {
case UserUserCommand::RETURN_CODE_TIMEOUT:
$errorStatus |= UserWPInstall::ST_ERR_TIMEOUT;
break;
case UserUserCommand::EXIT_ERROR:
case 255:
$errorStatus |= UserWPInstall::ST_ERR_EXECMD;
break;
//no default
}
$path = $wpInstall->getPath();
$isExpectedOutput = false;
$unexpectedLines = 0;
$succ = $upgrade = $err = $msg = $logMsg = '';
$logLvl = -1;
$curr = &$err;
foreach ( $output as $line ) {
/**
* If this line is not present in output, did not return normally.
* This line will appear after any [UPGRADE] output.
*/
if ( strpos($line, 'LS UserCommand Output Start') !== false ) {
$isExpectedOutput = true;
}
elseif ( strpos($line, '[RESULT]') !== false ) {
$resultOutputHandled = self::handleResultOutput(
$wpInstall,
$line,
$retStatus,
$err
);
if ( !$resultOutputHandled ) {
/**
* Problem handling RESULT output, ignore other output.
*/
return false;
}
}
elseif ( ($pos = strpos($line, '[SUCCESS]')) !== false ) {
$succ .= substr($line, $pos + 9) . "\n";
$curr = &$succ;
}
elseif ( ($pos = strpos($line, '[ERROR]')) !== false ) {
$err .= substr($line, $pos + 7) . "\n";
$curr = &$err;
}
elseif ( strpos($line, '[LOG]') !== false ) {
if ( $logMsg != '' ) {
UserLogger::logMsg(trim($logMsg), $logLvl);
$logMsg = '';
}
if ( preg_match('/\[(\d+)] (.+)/', $line, $m) ) {
$logLvl = $m[1];
$logMsg = "$path - $m[2]\n";
}
$curr = &$logMsg;
}
elseif ( strpos($line, '[UPGRADE]') !== false ) {
/**
* Ignore this output
*/
$curr = &$upgrade;
}
else {
if ( !$isExpectedOutput ) {
$line = htmlentities($line);
$unexpectedLines++;
}
$curr .= "$line\n";
}
}
if ( $logMsg != '' ) {
UserLogger::logMsg(trim($logMsg), $logLvl);
}
if ( !$isExpectedOutput && !$errorStatus ) {
$errorStatus |= UserWPInstall::ST_ERR_EXECMD;
}
if ( $errorStatus ) {
$wpInstall->addUserFlagFile();
$errorStatus |= UserWPInstall::ST_FLAGGED;
$cmdStatus |= UserUserCommand::EXIT_INCR_FAIL;
}
$newStatus = ($errorStatus | $retStatus);
if ( $newStatus != 0 ) {
$wpInstall->setStatus($newStatus);
}
if ( $succ ) {
$cmdStatus |= UserUserCommand::EXIT_SUCC;
$msg = $succ;
}
if ( $err ) {
if ( $return_var == UserUserCommand::EXIT_FAIL ) {
$cmdStatus |= UserUserCommand::EXIT_FAIL;
}
else {
$cmdStatus |= UserUserCommand::EXIT_ERROR;
}
if ( $isExpectedOutput ) {
$msg = $err;
UserLogger::logMsg("$path - $err", UserLogger::L_ERROR);
}
else {
$msg = self::handleUnexpectedError(
$wpInstall,
$err,
$unexpectedLines
);
}
}
$wpInstall->setCmdStatusAndMsg($cmdStatus, $msg);
return true;
}
/**
*
* @param string $action
* @param UserWPInstall $wpInstall
* @param string[] $extraArgs Not used at the moment.
*
* @return bool
*
* @throws UserLSCMException Thrown when provided $action value is
* unexpected.
* @throws UserLSCMException Thrown indirectly by
* $wpInstall->hasValidPath() call.
*/
private static function preIssueValidation(
$action,
UserWPInstall $wpInstall,
array $extraArgs )
{
if ( !self::isSupportedCmd($action) ) {
throw new UserLSCMException(
"Illegal action $action.",
UserLSCMException::E_PROGRAM
);
}
if ( !$wpInstall->hasValidPath() ) {
return false;
}
return true;
}
/**
*
* @param string $action
*
* @return bool
*/
private static function isSupportedCmd( $action )
{
$supported = array(
self::CMD_DIRECT_ENABLE,
self::CMD_DISABLE,
self::CMD_GET_QUICCLOUD_API_KEY,
self::CMD_STATUS
);
return in_array($action, $supported);
}
}
Back to Directory
File Manager