https://t.me/AnonymousX5
Server : Apache
System : Linux cvar2.toservers.com 3.10.0-962.3.2.lve1.5.73.el7.x86_64 #1 SMP Wed Aug 24 21:31:23 UTC 2022 x86_64
User : njnconst ( 1116)
PHP Version : 8.4.18
Disable Function : NONE
Directory :  /proc/self/root/opt/alt-old/python37/lib/python3.7/site-packages/clwpos/user/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/self/root/opt/alt-old/python37/lib/python3.7/site-packages/clwpos/user/wpos_user.py
# -*- coding: utf-8 -*-

# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

# wpos_user.py - work code for clwpos-user utility

from __future__ import absolute_import

import argparse
import json
import os
from enum import Enum

from typing import Dict, List, Tuple

from pkg_resources import parse_version
from dataclasses import dataclass, asdict, field

from clcommon.clpwd import drop_user_privileges

import cpanel
from clwpos.cl_wpos_exceptions import WposError
from clwpos.parse import ArgumentParser, CustomFormatter
from clwpos.optimization_modules import OBJECT_CACHE_MODULE, get_allowed_modules, ALL_OPTIMIZATION_MODULES
from clwpos.user.config import UserConfig
from clwpos.user.redis_lib import RedisLibUser
from clwpos import gettext as _

from clwpos.utils import (
    USER_WPOS_DIR,
    check_license_decorator,
    print_data,
    catch_error,
    error_and_exit,
    daemon_communicate,
    home_dir,
    user_name,
    user_uid,
    check_domain,
    is_run_under_user,
    is_conflict_modules_installed,
    supported_php_handlers,
    _run_clwpos_as_user_in_cagefs,
    is_redis_configuration_running
)

from clwpos.logsetup import setup_logging, USER_LOGFILE_PATH, init_wpos_sentry_safely
from clwpos import constants

from clwpos.user.website_check import post_site_check, RollbackException
from cpanel import wordpress, WordpressError
from clcommon.lib.cledition import is_cl_solo_edition

logger = setup_logging(__name__)

parser = ArgumentParser(
    "/usr/bin/clwpos-user",
    "Utility for control CL WPOS under user",
    formatter_class=CustomFormatter
)
if not is_run_under_user():
    parser.add_argument('--user', default=None, required=True)


class MaxCacheMemory(int):
    """
    Class to validate format and values of cache memory setted by user.
    """
    def __new__(cls, *args, **kwargs):
        try:
            instance = super(MaxCacheMemory, cls).__new__(cls, *args, **kwargs)
        except ValueError:
            raise argparse.ArgumentTypeError("invalid value type, must be integer")

        min_memory = constants.MINIMUM_MAX_CACHE_MEMORY
        max_memory = constants.MAXIMUM_MAX_CACHE_MEMORY

        if not min_memory <= instance <= max_memory:
            raise argparse.ArgumentTypeError(
                f"value must be in range: [{min_memory}, {max_memory}]")

        return instance


@dataclass
class Issue:
    """
    Generic class for keeping compatibility/misconfiguration issues
    """
    header: str
    description: str
    fix_tip: str

    context: Dict[str, str] = field(default_factory=dict)

    @property
    def dict_repr(self):
        return asdict(self)


@dataclass
class CompatibilityIssue(Issue):
    """
    For compatibility issues
    """
    type: str = 'incompatibility'


@dataclass
class MisconfigurationIssue(Issue):
    """
    For misconfiguration issues
    """
    type: str = 'misconfiguration'


class PluginStatus(Enum):
    UNINSTALLED = 'uninstalled'
    ACTIVE = 'active'
    INACTIVE = 'inactive'


def get_wp_plugin_status(wordpress_abs_path, plugin_name) -> PluginStatus:
    """
    Get information about WordPress plugin current status.

    :param wordpress_abs_path:
        absolute path to wordpress installation
    :param plugin_name:
        name of plugin as it listed in plugins directory
    :return:
        PluginStatus
    """
    plugins_data = wordpress(wordpress_abs_path, 'plugin', 'list', '--name=%s' % plugin_name, '--json')
    if isinstance(plugins_data, WordpressError):
        raise WposError(message=plugins_data.message, context=plugins_data.context)

    try:
        response = json.loads(plugins_data)
    except (ValueError, TypeError, json.JSONDecodeError) as e:
        raise WposError(
            message=_('Malformed plugins information received from wp-cli. '
                      'Raw response is %(response)s.'),
            context={'response': plugins_data},
            details=str(e)
        )

    # in case of missing plugin wp-cli returns empty dict
    if not response:
        return PluginStatus.UNINSTALLED

    # in any other case we get list of one element with parameters
    return PluginStatus(response[0]['status'])


def get_status_from_daemon(service):
    command_get_service_status_dict = {"command": f"get-{service}-status"}
    try:
        daemon_result = daemon_communicate(command_get_service_status_dict)
    except WposError:
        return False
    return daemon_result.get('status')


def redis_is_running() -> bool:
    return get_status_from_daemon('redis')


def litespeed_is_running() -> bool:
    return get_status_from_daemon('litespeed')


class CloudlinuxWposUser(object):
    """
    Class for run clwpos-user utility commands
    """
    COMMAND_RELOAD_DICT = {"command": "reload"}

    def __init__(self):
        self._is_json = False
        self._opts = None
        self._wp_config_suffix = "wp-config.php"
        self._is_redis_running = None
        self._is_litespeed_running = None
        self._supported_handlers = None
        self._supported_php_versions = None
        self._php_versions_with_redis_not_loaded = None
        init_wpos_sentry_safely(logger)

    @property
    def redis_socket_path(self):
        return os.path.join(home_dir(), USER_WPOS_DIR, 'redis.sock')

    @property
    def supported_handlers(self):
        if self._supported_handlers is None:
            self._supported_handlers = supported_php_handlers()
        return self._supported_handlers

    @property
    def is_redis_running(self):
        if self._is_redis_running is None:
            self._is_redis_running = redis_is_running()
        return self._is_redis_running

    @property
    def is_litespeed_running(self):
        if self._is_litespeed_running is None:
            self._is_litespeed_running = litespeed_is_running()
        return self._is_litespeed_running

    @property
    def supported_php_versions(self):
        """
        List of php version installed on the system with redis-extension enabled
        and version >= minimum supported version
        :return:
        """
        if self._supported_php_versions is None:
            php_with_redis = cpanel.get_cached_php_versions_with_redis_loaded()
            self._supported_php_versions = [php for php in php_with_redis
                                            if self.get_digits_from_version(php) >= constants.MINIMUM_SUPPORTED_PHP]
        return self._supported_php_versions

    @property
    def php_versions_with_redis_present(self):
        """
        List of php version installed on the system with redis-extension enabled
        and version >= minimum supported version
        :return:
        """
        if self._php_versions_with_redis_not_loaded is None:
            php_with_redis = cpanel.get_cached_php_versions_with_redis_present()
            self._php_versions_with_redis_not_loaded = [php for php in php_with_redis
                                                        if self.get_digits_from_version(php)
                                                        >= constants.MINIMUM_SUPPORTED_PHP]
        return self._php_versions_with_redis_not_loaded

    @staticmethod
    def get_digits_from_version(version: str) -> int:
        """

        :param version: version of php "ea-php56", "alt-php74"
        :return: digits extracted from version
        """
        version = version.replace("ea-php", "")
        version = version.replace("alt-php", "")
        return int(version)

    @catch_error
    def run(self, argv):
        """
        Run command action
        :param argv: sys.argv[1:]
        :return: clwpos-user utility retcode
        """
        self._parse_args(argv)

        if not is_run_under_user():
            _run_clwpos_as_user_in_cagefs(self._opts.user)
        else:
            _run_clwpos_as_user_in_cagefs()

        if not is_run_under_user() and is_cl_solo_edition():
            drop_user_privileges(self._opts.user, effective_or_real=False, set_env=True)

        result = getattr(self, self._opts.command.replace("-", "_"))()
        print_data(self._is_json, result)

    def _parse_args(self, argv):
        """
        Parse command line arguments
        :param argv: sys.argv[1:]
        """
        self._opts = parser.parse_args(argv)
        self._is_json = True

    def _check_monitoring_daemon_and_exit(self):
        """
        Ensures monitoring socket is present, otherwise - exit
        """
        if not os.path.exists(constants.WPOS_DAEMON_SOCKET_FILE):
            error_and_exit(
                self._is_json,
                {
                    "result": _("Unable to find monitoring daemon socket %(daemon_file)s. Please, contact your "
                                "system administrator to ensure service %(service_name)s is currently running"),
                    "context": {"daemon_file": constants.WPOS_DAEMON_SOCKET_FILE,
                                "service_name": constants.MONIROTING_SERVICE},
                }
            )

    def _check_redis_configuration_process_and_exit(self):
        """
        Ensures redis configuration processes are not in progress.
        """
        if is_redis_configuration_running():
            error_and_exit(
                self._is_json,
                {
                    "result": _("Configuration of PHP redis extension is in progress. "
                                "Please wait until the end of this process to use WP Optimization Suite."),
                }
            )

    @staticmethod
    def _get_php_versions_handlers_pair_for_docroot(domains_per_docroot: List, php_data: List) -> Tuple:
        """
        Returns pair (all_php_versions, all_php_handlers) for domains in
        docroot
        """
        versions, handlers = set(), set()
        for item in php_data:
            domain = item['vhost']
            if domain not in domains_per_docroot:
                continue
            versions.add(item['version'])
            if item['php_fpm']:
                handlers.add('php-fpm')
            else:
                handlers.add(cpanel._get_php_handler(domain))
        return versions, handlers

    def collect_general_issues(self):
        """
        Collects general (not depending on worpress/docroot setup)
        server misconfigurations/incompatibilities with WPOS
        """
        issues = []
        if self.is_litespeed_running:
            issues.append(
                CompatibilityIssue(
                    header=_('Litespeed is unsupported'),
                    description=_('Litespeed webserver is running on your server, '
                                  'it is not currently supported by WPOS.'),
                    fix_tip=_('Please ask your system administrator to switch webserver to Apache '
                              'using the following instruction: %(switch_lsws_link)s '
                              'or keep watching our blog: %(blog_link)s '
                              'for news about LiteSpeed support.'),
                    context=dict(
                        switch_lsws_link='https://www.interserver.net/tips/kb/switch-apache-litespeed/',
                        blog_link='https://blog.cloudlinux.com/'
                    )
                ).dict_repr)
        return issues

    def collect_docroot_issues(self, doc_root_info: Dict, php_info: List):
        """
        Collects incompatibilities related to docroot (non-supported handler, etc)
        """
        supported_handlers = self.supported_handlers
        issues = []
        incompatible_php_modules = {}

        domains_per_docroot = doc_root_info['domains']
        versions, handlers = self._get_php_versions_handlers_pair_for_docroot(domains_per_docroot, php_info)
        if len(versions) > 1:
            issues.append(
                CompatibilityIssue(
                    header=_('Different PHP versions for domains'),
                    description=_('Those domains: %(domains)s are in same docroot, '
                                  'but using different PHP version'),
                    fix_tip=_('Set or ask your system administrator to set same PHP version on those domains'),
                    context={
                        'domains': ', '.join(domains_per_docroot)
                    }
                ).dict_repr
            )
        if len(handlers) > 1:
            issues.append(
                CompatibilityIssue(
                    header=_('Different PHP handlers for domains'),
                    description=_('Those domains: %(domains)s are in same docroot, '
                                  'but using different PHP handlers'),
                    fix_tip=_('Set or ask your system administrator to set same PHP handler on those domains'),
                    context={
                        'domains': ', '.join(domains_per_docroot)
                    }
                ).dict_repr
            )
        if doc_root_info["php"]["handler"] not in supported_handlers:
            issues.append(
                CompatibilityIssue(
                    header=_('Unsupported PHP handler'),
                    description=_('Website uses unsupported PHP handler. Currently supported '
                                  'handler(s): %(supported_handlers)s.'),
                    fix_tip=_('Please, set or ask your system administrator to set one of the '
                              'supported PHP handlers for the domain: %(supported_handlers)s. '
                              'Or keep watching our blog: %(blog_url)s for supported handlers list updates.'),
                    context={
                        'supported_handlers': ", ".join(supported_handlers),
                        'blog_url': 'https://blog.cloudlinux.com/'
                    }
                ).dict_repr
            )
        incompatible_module = 'snuffleupagus'
        php_version = doc_root_info['php']['version']
        if incompatible_php_modules.get(php_version) == incompatible_module or \
                is_conflict_modules_installed(php_version, incompatible_module):
            incompatible_php_modules[php_version] = incompatible_module
            issues.append(
                CompatibilityIssue(
                    header=_('Unsupported PHP module is loaded'),
                    description=_('Incompatible PHP module "%(incompatible_module)s" is currently used.'),
                    fix_tip=_('Please, disable or remove "%(incompatible_module)s" PHP extension.'),
                    context=dict(incompatible_module=incompatible_module)
                ).dict_repr)
        if php_version not in self.supported_php_versions:
            if self.supported_php_versions:
                fix_tip = _('Please, set or ask your system administrator to set one of the '
                            'supported PHP version: %(compatible_versions)s for the domain.')
            else:
                fix_tip = _('Please, ask your system administrator to setup at least one of the '
                            'recommended PHP version in accordance with docs (%(docs_url)s).')
            issues.append(
                CompatibilityIssue(
                    header=_('Incompatible PHP version'),
                    description=_('Incompatible PHP version %(php_version)s is currently used.'),
                    fix_tip=fix_tip,
                    context=dict(php_version=php_version, compatible_versions=', '.join(self.supported_php_versions),
                                 docs_url=constants.CL_DOC_USER_PLUGIN)
                ).dict_repr)
        if php_version in self.php_versions_with_redis_present:
            if self.supported_php_versions:
                fix_tip = _('Please, set or ask your system administrator to set one of the '
                            'supported PHP version: %(compatible_versions)s for the domain.')
            else:
                fix_tip = _('Please, ask your system administrator to setup at least one of the '
                            'recommended PHP version in accordance with docs (%(docs_url)s).')
            issues.append(
                CompatibilityIssue(
                    header=_('Redis extension is not loaded'),
                    description=_('Redis PHP extension is required for optimization module, but not loaded for '
                                  'selected PHP version: %(php_version)s.'),
                    fix_tip=fix_tip,
                    context=dict(php_version=php_version, compatible_versions=', '.join(self.supported_php_versions),
                                 docs_url=constants.CL_DOC_USER_PLUGIN)
                ).dict_repr
            )

        return issues

    @staticmethod
    def _is_our_roc_plugin(wp_path: str) -> bool:
        """
        Checks that WP's WP_REDIS_PATH is defined and contains our path
        """
        wp_redis_path = cpanel.wp_get_constant(wp_path, 'WP_REDIS_PATH')
        if wp_redis_path and "/.clwpos/redis.sock" in wp_redis_path:
            return True
        return False

    def check_installed_roc_plugin(self, wp_path: str) -> bool:
        """
        Checks that ROC plugin was installed by us, or not exists at all
        Returns False if the plugin was found but our config modifications was not
        """
        if os.path.exists(os.path.join(wp_path, 'wp-content', 'plugins', 'redis-cache')):
            if not self._is_our_roc_plugin(wp_path):
                return False
        return True

    def collect_wordpress_issues(self, wordpress: Dict, docroot: str, check_module_compatibility: bool):
        """
        Collects incompatibilities related to wordpress setup (conflicting plugin enabled, etc)
        """
        issues = []
        minimum_supported_wp_version = '3.7'
        if parse_version(wordpress["version"]) < parse_version(minimum_supported_wp_version):
            issues.append(
                CompatibilityIssue(
                    header=_('Unsupported WordPress version'),
                    description=_('Optimization module is incompatible with '
                                  'WordPress version %(wp_version)s being used.'),
                    fix_tip=_('The minimal supported WordPress version is %(minimum_supported_wp_version)s. '
                              'Upgrade your WordPress installation or reinstall it from the scratch.'),
                    context=dict(
                        minimum_supported_wp_version=minimum_supported_wp_version,
                        wp_version=wordpress["version"]
                    )
                ).dict_repr)

        if check_module_compatibility:
            if wordpress["object_cache"] != "redis-cache":
                issue = self._get_wp_plugin_compatibility_issues(docroot, wordpress)
                if issue:
                    issues.append(issue)

            if not self.is_redis_running:
                issues.append(
                    MisconfigurationIssue(
                        header=_('Redis is not running'),
                        description=_('Object cache module is enabled, but redis process is not running.'),
                        fix_tip=_('Please refresh page, redis will start automatically in 5 minutes. '
                                  'If the issue persists - contact your system administrator and report this issue')
                    ).dict_repr
                )
            try:
                cpanel.diagnose_redis_connection_constants(docroot, wordpress['path'])
            except WposError as e:
                issues.append(
                    MisconfigurationIssue(
                        header=_('Missed redis constants in site config'),
                        description=_('WordPress config does not have needed constants '
                                      'for redis connection establishment.\n'
                                      'Details: %(reason)s'),
                        fix_tip=_('Please, try to disable and enable plugin again. '
                                  'If issue persists - please, contact CloudLinux support.'),
                        context=dict(
                            reason=e.message % e.context
                        )
                    ).dict_repr
                )
        detected_wp_plugin = wordpress.get('object_cache')
        if detected_wp_plugin == "Unknown":
            issues.append(
                CompatibilityIssue(
                    header=_('Conflicting object cache plugin enabled'),
                    description=_('Unknown custom object cache plugin is already enabled'),
                    fix_tip=_('Disable WordPress plugin that is being used for the object caching '
                              'using the WordPress settings.')
                ).dict_repr)
        elif detected_wp_plugin == "w3-total-cache":
            issues.append(
                CompatibilityIssue(
                    header=_('Object Cache module of W3 Total Cache plugin is incompatible'),
                    description=_('WordPress website already has Object Cache feature enabled '
                                  'with caching backend configured by the the W3 Total Cache plugin.'),
                    fix_tip=_('Deactivate Object Cache in W3 Total Cache plugin settings.'),
                    context=dict()
                ).dict_repr)
        elif detected_wp_plugin not in (None, "redis-cache"):
            issues.append(
                CompatibilityIssue(
                    header=_('Conflicting object cache plugin enabled'),
                    description=_('The "%(detected_wp_plugin)s" plugin conflicts with the '
                                  '"Redis Object Cache" that is required by optimization module.'),
                    fix_tip=_('Deactivate Object Cache in plugin settings or '
                              'completely turn off conflicting plugin using WordPress administration page'),
                    context=dict(detected_wp_plugin=detected_wp_plugin)
                ).dict_repr)
        if not self.check_installed_roc_plugin(os.path.join(docroot, wordpress['path'])):
            issues.append(
                CompatibilityIssue(
                    header=_('Another Redis Object Cache plugin is installed'),
                    description=_('Non CloudLinux Redis Object Cache is installed for the website'),
                    fix_tip=_('Uninstall Redis Object Cache plugin using WordPress administration page')
                ).dict_repr)

        try:
            multisite = cpanel.is_multisite(os.path.join(docroot, wordpress["path"]))
            if multisite:
                issues.append(
                    CompatibilityIssue(
                        header=_('WordPress Multisite mode is enabled'),
                        description=_('WordPress uses the Multisite mode which is currently not supported.'),
                        fix_tip=_('Install or configure WordPress in the single-site mode.')
                    ).dict_repr)
        except WposError as e:
            issues.append(
                CompatibilityIssue(
                    header=_('Unexpected WordPress error'),
                    description=_('Unable to detect if the WordPress installation has the Multisite mode enabled '
                                  'mode due to unexpected error. '
                                  '\n\n'
                                  'Technical details:\n%(error_message)s.\n'
                                  '\nMost likely WordPress installation is not working properly.'),
                    fix_tip=_('If this is only one issue, please check that your website is working properly – '
                              'try to run the specified command to find any obvious '
                              'errors in the WordPress configuration. '
                              'Otherwise, try to fix other issues first - it may help to resolve this issue as well.'),
                    context=dict(
                        error_message=e.message % e.context
                    )
                ).dict_repr)
        return issues

    def _get_wp_plugin_compatibility_issues(self, docroot, wordpress):
        """
        Get issue that relates to currently installed redis-cache
        plugin or None if everything is ok
        """
        try:
            plugin_status = get_wp_plugin_status(
                wordpress_abs_path=os.path.join(docroot, wordpress["path"]),
                plugin_name='redis-cache')
        except WposError as e:
            return CompatibilityIssue(
                header=_('Unexpected WordPress error'),
                description=_(
                    'Unable to detect the WordPress plugins '
                    'due to unexpected error. '
                    '\n\n'
                    'Technical details:\n%(error_message)s.\n'
                    '\nMost likely WordPress installation is not working properly.'
                ),
                fix_tip=_(
                    'Check that your website is working properly – '
                    'try to run the specified command to find any obvious '
                    'errors in the WordPress configuration. '
                    'Otherwise, try to fix other issues first - '
                    'it may help to resolve this issue as well.'
                ),
                context=dict(
                    error_message=e.message % e.context
                )
            ).dict_repr

        if plugin_status == PluginStatus.INACTIVE:
            return MisconfigurationIssue(
                header=_('"Redis Object Cache" plugin is turned off'),
                description=_('Object cache module is enabled, but the '
                              '"Redis Object Cache" plugin is turned off. Caching does not work'),
                fix_tip=_('Activate the Redis Object Cache plugin in the Wordpress admin page and '
                          'enable Object Cache Drop-in in the Redis Object Cache plugin settings. '
                          'As alternative, just turn the caching module off and on again.')
            ).dict_repr
        elif plugin_status == PluginStatus.ACTIVE:
            return MisconfigurationIssue(
                header=_('The Object Cache Drop-in not installed'),
                description=_('The Object Cache Drop-In is not activated. Caching does not work'),
                fix_tip=_('Activate the Object Cache using the Redis Object Cache plugin '
                          'settings page. '
                          'As alternative, just turn the caching module off and on again.')
            ).dict_repr
        elif plugin_status == PluginStatus.UNINSTALLED:
            return MisconfigurationIssue(
                header=_('"Redis Object Cache" plugin is not installed'),
                description=_('The "Redis Object Cache" WordPress plugin is not installed. '
                              'Caching does not work'),
                fix_tip=_('Turn the caching module off and on again. '
                          'Contact your administrator if the issue persists.')
            ).dict_repr
        else:
            raise WposError(_('Unexpected plugin status: %(status)s'), context=dict(status=plugin_status))

    @parser.argument("--module", help="modules list separated by comma",
                     choices=ALL_OPTIMIZATION_MODULES, type=str, default=OBJECT_CACHE_MODULE)
    @parser.command(help="Output WPOS user config (with Redis status and Redis current memory)")
    def get(self):
        user_info = cpanel.get_user_info()
        php_info = cpanel.php_info()

        general_issues = self.collect_general_issues()

        for docroot, doc_root_info in user_info.items():
            if doc_root_info["php"]["fpm"]:
                doc_root_info["php"]["handler"] = "php-fpm"
            docroot_issues = self.collect_docroot_issues(doc_root_info, php_info)

            new_wps = []
            for wp in doc_root_info["wps"]:

                enabled = self._is_enabled(doc_root_info["domains"][0], wp["path"], OBJECT_CACHE_MODULE)
                wordpress_issues = self.collect_wordpress_issues(wp, docroot, check_module_compatibility=enabled)

                redis_cache_module = {
                    "enabled": enabled
                }
                issues = [*general_issues, *docroot_issues, *wordpress_issues]
                if issues:
                    redis_cache_module.update({
                        'issues': issues
                    })

                new_wps.append(
                    {
                        "path": wp["path"],
                        "version": wp["version"],
                        "modules": {"object_cache": redis_cache_module},
                    }
                )
            doc_root_info["wps"] = new_wps

            php = doc_root_info.pop("php")
            doc_root_info["php_version"] = php["version"]
            doc_root_info["php_handler"] = php["handler"]

        return {
            "docroots": list(user_info.values()),
            "allowed_modules": get_allowed_modules(user_uid()),
            "used_memory": RedisLibUser(self.redis_socket_path).get_redis_used_memory(),
            "max_cache_memory": UserConfig(user_name()).get_config().get(
                "max_cache_memory", UserConfig.DEFAULT_MAX_CACHE_MEMORY
            )
        }

    def get_current_issues_by_docroot(self, wordpress_path: str, docroot: str, is_module_enabled: bool):
        """
        Obtains issues for special docroot and wordpress
        """
        wordpress_issues = []
        general_issues = self.collect_general_issues()
        php_info = cpanel.php_info()
        user_info_by_docroot = cpanel.get_user_info()[docroot]
        if user_info_by_docroot["php"]["fpm"]:
            user_info_by_docroot["php"]["handler"] = "php-fpm"
        docroot_issues = self.collect_docroot_issues(user_info_by_docroot, php_info)
        for wp in user_info_by_docroot["wps"]:
            if wp['path'] != wordpress_path:
                continue
            wordpress_issues = self.collect_wordpress_issues(wp, docroot,
                                                             check_module_compatibility=is_module_enabled)
        return [*general_issues, *docroot_issues, *wordpress_issues]

    def _is_enabled(self, domain: str, wp_path: str, module: str) -> bool:
        uc = UserConfig(user_name())
        return uc.is_module_enabled(domain, wp_path, module) and module in get_allowed_modules(user_uid())

    @parser.argument('--max_cache_memory', help='Maximum cache memory to use in MB',
                     type=MaxCacheMemory, default=constants.DEFAULT_MAX_CACHE_MEMORY)
    @parser.command(help='Set parameters of global modules in WPOS user config')
    @check_license_decorator
    def set(self):
        uc = UserConfig(user_name())
        # TODO: this method must probably UPDATE config and not REPLACE it completely
        params = {"max_cache_memory": f"{self._opts.max_cache_memory}mb"}
        uc.set_params(params)
        daemon_communicate(self.COMMAND_RELOAD_DICT)
        return params

    @parser.argument("--module", help="modules list separated by comma", type=str, default="object_cache")
    @parser.argument('--wp-path', help='Path to user\'s wordpress', type=str, default='')
    @parser.argument('--domain', help='User\'s wordpress domain', type=str, required=True)
    @parser.command(help='Disables and uninstalls module on wordpress')
    def disable(self):
        username, doc_root = check_domain(self._opts.domain)
        wp_path = self._opts.wp_path.lstrip("/")
        uc = UserConfig(user_name())
        errors = []

        if not uc.is_module_enabled(self._opts.domain, wp_path):
            return {"warning": _('Module %(module)s is already disabled on the domain %(domain)s. Nothing to be done.'),
                    "context": {"domain": self._opts.domain, "module": self._opts.module}}

        self._check_monitoring_daemon_and_exit()

        last_error = cpanel.disable_without_config_affecting(
            cpanel.DomainName(self._opts.domain), self._opts.wp_path)

        if last_error:
            logger.error(last_error.message % last_error.context)
            errors.append(last_error)

        try:
            uc.disable_module(self._opts.domain, self._opts.wp_path)
        except Exception as e:
            logger.exception("unable to disable module in config")
            errors.append(e)

        sites_count_with_enabled_module = uc.get_enabled_sites_count_by_module(self._opts.module)

        try:
            if sites_count_with_enabled_module == 0:
                daemon_communicate(self.COMMAND_RELOAD_DICT)
        except WposError as e:
            s_details = e.details % e.context
            logger.exception("CLWPOS daemon error: '%s'; details: '%s'", e.message, s_details)
            errors.append(e)
        except Exception as e:
            logger.exception("unable to reload cache backend")
            errors.append(e)

        enabled = self._is_enabled_and_exist_on_system(self._opts.domain, self._opts.wp_path, self._opts.module)

        try:
            last_error = errors.pop(-1)
        except IndexError:
            last_error = None

        if enabled:
            if last_error:
                raise WposError(
                    message=_("Optimization module disabling failed because one or more steps reported error. "
                              "Caching is still active, but may work unstable. Try disabling it again. "
                              "Contact your system administrator if this issue persists. "
                              "Detailed information you can find in log file '%(log_path)s'"),
                    details=last_error.message,
                    context={
                        'log_path': USER_LOGFILE_PATH.format(homedir=home_dir()),
                        **getattr(last_error, 'context', {})
                    }
                )
            else:
                error_and_exit(
                    self._is_json,
                    {"result": _("WordPress caching module is still enabled, but no errors happened. "
                                 "Try again and contact your system administrator if this issue persists.")},
                )
        else:
            if last_error:
                return {
                    "warning": _("Optimization module disabled, but one or more steps reported error. "
                                 "Detailed information you can find in log file '%(log_path)s'"),
                    "context": {
                        'log_path': USER_LOGFILE_PATH.format(homedir=home_dir())
                    }
                }
            else:
                current_module_status = self._is_enabled(self._opts.domain, wp_path, OBJECT_CACHE_MODULE)
                issues = self.get_current_issues_by_docroot(wp_path, doc_root, current_module_status)
                module_data = {
                    'enabled': current_module_status
                }
                if issues:
                    module_data.update({
                        'issues': issues
                    })
                return {
                    'module': module_data,
                    "used_memory": RedisLibUser(self.redis_socket_path).get_redis_used_memory()
                }

    def _is_enabled_and_exist_on_system(self, domain, wp_path, module):
        for doc_root in self.get()["docroots"]:
            if domain in doc_root["domains"]:
                for wp in doc_root["wps"]:
                    if wp["path"] == wp_path:
                        return wp["modules"][module]["enabled"]

        return False

    @parser.argument('--module', type=str, help='List of WPOS modules separated by commas'
                                                ' on which to perform an action',
                     choices=ALL_OPTIMIZATION_MODULES, default=OBJECT_CACHE_MODULE)
    @parser.argument('--action', type=str, help='Action to perform on WPOS modules', required=True)
    @parser.command(help='Perform action on WPOS modules')
    def do_action(self):
        """
        Perform action on selected WPOS modules.
        """
        action = self._opts.action
        module = self._opts.module
        # dictionary where key - action, value - function that provides that action
        action_to_func = {"purge": self.redis_purge}
        if action not in action_to_func:
            error_and_exit(
                self._is_json,
                {
                    "result": f'Invalid action "{action}", currently only "purge" action is supported'
                },
            )

        return action_to_func[action](module)  # perform action on module

    def redis_purge(self, *args):
        """
        Clean entire redis cache for user.
        """
        return RedisLibUser(self.redis_socket_path).purge_redis()

    @parser.argument(
        "--ignore-errors",
        help="ignore site check results after plugin install and enable",
        action="store_true",
    )
    @parser.argument("--wp-path", help="path to user's wordpresses", type=str, default="")
    @parser.argument("--domain", help="User's wordpress domain", type=str, required=True)
    @parser.argument("--module", help="Modules to enable", type=str, required=False,
                     choices=ALL_OPTIMIZATION_MODULES, default=OBJECT_CACHE_MODULE)
    @parser.command(help="Installs and enables module on wordpress")
    @check_license_decorator
    def enable(self):
        """
        Enable object_cache for user with compliance with end-user spec.
        :return:
        """
        domain = self._opts.domain
        wp_path = self._opts.wp_path.lstrip("/")
        module = self._opts.module
        ignore_errors = self._opts.ignore_errors

        username, doc_root = check_domain(self._opts.domain)

        abs_wp_path = os.path.join(doc_root, wp_path)
        uc = UserConfig(username)

        if self._is_enabled(domain, wp_path, OBJECT_CACHE_MODULE):
            return {"warning": _("The %(module)s module is already enabled on "
                                 "the domain %(domain)s. Nothing to be done."),
                    "context": {"domain": domain, "module": module}}

        self._check_redis_configuration_process_and_exit()
        self._check_monitoring_daemon_and_exit()

        compatibilities = self.get()

        # check that enabling of module is allowed by admin
        if module not in compatibilities["allowed_modules"]:
            error_and_exit(
                self._is_json,
                {
                    "result": _("Usage of the %(module)s module is prohibited by admin."),
                    "context": {"module": module},
                }
            )

        # check that user's wordpress fits to requirements
        ok, requirements_failed_info = cpanel.requirements_check(
            compatibilities, wp_path, domain, OBJECT_CACHE_MODULE)
        if not ok:
            error_and_exit(self._is_json, requirements_failed_info)

        # check user's site before installation
        if not ignore_errors:
            self._website_check(abs_wp_path, domain, uc, wp_path)

        # try enable module with wp-cli without adding info into user's wpos config
        ok, enable_failed_info = cpanel.enable_without_config_affecting(
            cpanel.DomainName(domain),
            wp_path,
            module=module,
        )
        if not ok:
            raise WposError(**enable_failed_info)

        uc.enable_module(domain, wp_path)
        sites_count_with_enabled_module = uc.get_enabled_sites_count_by_module(module)
        try:
            # reload redis only if this is the 1st site
            if sites_count_with_enabled_module == 1:
                cpanel.reload_redis()
        except Exception:
            uc.disable_module(domain, wp_path)
            cpanel.rollback_object_cache(abs_wp_path)
            raise

        # check user's site after installation and enabling
        if not ignore_errors:
            self._website_check(abs_wp_path, domain, uc, wp_path, rollback=True)

        current_module_status = self._is_enabled(domain, wp_path, OBJECT_CACHE_MODULE)
        issues = self.get_current_issues_by_docroot(wp_path, doc_root, current_module_status)
        module_data = {
            'enabled': current_module_status
        }
        if issues:
            module_data.update({
                'issues': issues
            })
        return {
            'module': module_data,
            'used_memory': RedisLibUser(self.redis_socket_path).get_redis_used_memory()
        }

    def _website_check(self, abs_wp_path, domain, uc, wp_path, rollback=False):
        """
        Performs website availability checks and raises
        errors in case of problems

        :param abs_wp_path:
            absolute path to wordpress installation
        :param domain:
            domain that wordpress installation belongs to
        :param uc:
            user config instance
        :param wp_path:
            relative path to wordpress installation
        :param rollback:
            whether to roll redis and plugin changes back
        :return:
        """

        try:
            post_site_check(domain, wp_path, abs_wp_path)
        except WposError as e:
            if rollback:
                uc.disable_module(domain, wp_path)
                cpanel.reload_redis()
                cpanel.rollback_object_cache(abs_wp_path)

            if isinstance(e, RollbackException):
                raise

            error_and_exit(
                is_json=True,
                message={
                    "context": {},
                    "result": _("Unexpected error occurred during plugin installation for WordPress. "
                                "Try again and contact your system administrator if the issue persists."),
                    "details": str(e)
                },
            )

https://t.me/AnonymousX5 - 2025