|
Server : LiteSpeed System : Linux barito.iixcp.rumahweb.net 5.14.0-611.49.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Apr 21 16:39:08 EDT 2026 x86_64 User : elvh3918 ( 1528) PHP Version : 8.2.31 Disable Function : mail Directory : /opt/cloudlinux/venv/lib64/python3.11/site-packages/clwpos/php/ |
from __future__ import absolute_import
import dataclasses
import os
import re
import subprocess
from pathlib import Path
from clwpos.cl_wpos_exceptions import PhpBrokenException
from clwpos.scoped_cache import cached_in_scope
from clwpos.utils import run_in_cagefs_if_needed
@dataclasses.dataclass
class PHP:
# e.g. 'alt-php67', must be unique
identifier: str
# e.g. '5.6'
version: str
# e.g. '/opt/alt/php51/usr/lib64/php/modules/'
modules_dir: str
# e.g. '/opt/alt/php80/usr/bin/php'
bin: str
# e.g. '/opt/alt/php51/etc/php.ini'
ini: str
# e.g. '/opt/alt/php51/'
dir: str
def __hash__(self):
return hash(self.identifier)
def __eq__(self, other: 'PHP'):
return self.identifier == other.identifier
def __gt__(self, other):
return self.identifier > other.identifier
@cached_in_scope
def is_extension_installed(self, extension: str):
"""
Quick check that given extension is installed as
so file in default extensions directory
"""
is_present = bool(list(Path(self.modules_dir).glob(f"**/{extension}.so")))
return is_present
@cached_in_scope
def is_extension_loaded(self, extension: str):
"""
Complex check that given extension is loaded as
part of the ini configuration.
Be careful with this method because it actually runs
php process and may be heavy.
"""
if not self.is_extension_installed(extension):
return False
php_bin_path = self.bin
# run_in_cagefs_if_needed sets capture_output/text internally; subprocess.run does not.
# Pass those flags only on the root-subprocess.run path to avoid duplicate-kwarg TypeError.
if os.geteuid() == 0:
exec_func = subprocess.run
exec_kwargs = dict(capture_output=True, text=True)
else:
exec_func = run_in_cagefs_if_needed
exec_kwargs = {}
result = exec_func(
[php_bin_path, '-m'],
env={},
**exec_kwargs,
)
is_loaded = extension in result.stdout.split()
return is_loaded
def apply_php_selector(self) -> 'PHP':
"""
PHP selector can replace path with symlink.
It's a reason why we need normalization.
"""
# there is no need to execute binary
# when we see that it is not a link
# TODO: uncomment and check how it works
# if not os.path.islink(self.bin):
# return self
command = [self.bin, '-r', 'echo php_ini_loaded_file();']
result = run_in_cagefs_if_needed(command, env={})
# example:
# /opt/cpanel/ea-php74/root/etc/php.ini'
if result.stderr and not result.stdout:
raise PhpBrokenException(str(self.bin), result.stderr)
# check for alt version and replace if found
alt_pattern = re.compile(r"alt.*php[^/]*/")
captured_version = alt_pattern.search(result.stdout)
if captured_version:
php_name = captured_version[0].strip("/").replace("/", "-")
# we don't want to be dependent on
# whether panel supports alt-php or not
from clwpos.php.alt_php import create_generic_php
return create_generic_php(php_name)
return self