Viewing File: /usr/local/cpanel/3rdparty/wpinstaller/src/WpInstaller/CpanelApi.php

<?php
namespace WpInstaller;

use \WpInstaller\Database\ApiInterface;

class CpanelApi implements \WpInstaller\Database\ApiInterface {
    private $username;
    private $interface;
    public $fake_cpanel_api;

    function __construct() {
        $this->fake_cpanel_api = (
            array_key_exists('FAKE_CPANEL_API', $_ENV)
            && $_ENV['FAKE_CPANEL_API']
        );

        $get_type = function () {
            if ($this->fake_cpanel_api) {
                return 'fake';
            }

            $live_api_available = (
                array_key_exists('CPANEL_PHPCONNECT_SOCKET', $_ENV)
                && $_ENV['CPANEL_PHPCONNECT_SOCKET']
            );

            if ($live_api_available) {
                return 'liveapi';
            }

            return 'cli';
        };

        $this->interface = [
            'liveapi' => function () { return ( new LiveAPI()          ); },
            'cli'     => function () { return ( new Cli()              ); },
            'fake'    => function () { return ( new DockerEnvFakeAPI() ); },
        ][ $get_type() ]();
    }

    public function create_database(string $database_name) {
        return $this->call(
            'Mysql', 'create_database',
            ['name' => $database_name]
        );
    }

    public function delete_database(string $database_name) {
        return $this->call(
            'Mysql', 'delete_database',
            ['name' => $database_name]
        );
    }

    public function create_user(string $username, string $password) {
        return $this->call(
            'Mysql', 'create_user',
            [
                'name'     => $username,
                'password' => $password,
            ]
        );
    }

    public function delete_user(string $username) {
        return $this->call(
            'Mysql', 'delete_user',
            ['name' => $username]
        );
    }

    public function set_user_privs(string $username, string $database_name, string $privileges = 'ALL') {
        return $this->call(
            'Mysql', 'set_privileges_on_database',
            [
                'user'       => $username,
                'database'   => $database_name,
                'privileges' => $privileges,
            ]
        );
    }

    public function get_server_host() {
        $resp = $this->call('Mysql', 'get_server_information', []);
        return $resp->data->host;
    }

    private function call($module, $function, $args) {
        $resp = ($this->interface)($module, $function, $args);
        $resp = json_decode($resp);

        if ( property_exists($resp, 'module') && property_exists($resp, 'result') ) {
            // nested cPanel Uapi output from cli
            $resp = $resp->result;
        }

        if ($resp->status !== 1) {
            $details = print_r($resp, true);
            throw new \Exception("Error calling cPanelAPI: $details");
        }

        return $resp;
    }
}

/*
    Generates a CLI command that looks something like:
        uapi Mysql get_privileges_on_database user=wpinstal_WPAG database=wpinstal_WPAG
    and executes it returning the resulting output.
*/
class Cli {
    function __invoke(string $module, string $method, $arguments=[]) {
        $mod  = escapeshellarg($module);
        $meth = escapeshellarg($method);

        $command = ['uapi', $mod, $meth];

        foreach ($arguments as $key => $value) {
            $k = escapeshellarg($key);
            $v = escapeshellarg($value);

            $command[] = "{$k}={$v}";
        }

        $command[] = '--output=json';

        $command = implode(' ', $command);

        return shell_exec($command);
    }
}

// https://documentation.cpanel.net/display/DD/Guide+to+the+LiveAPI+System+-+PHP+Class
class LiveAPI {
    const CPANEL_PHP_PATH = '/usr/local/cpanel/php/cpanel.php';
    private $cpanel;

    function __construct() {
        require_once CPANEL_PHP_PATH;
        $this->cpanel = new CPANEL();
    }

    function __invoke(string $module, string $method, $arguments=[]) {
        return $this->cpanel()->uapi($module, $func, $args);
    }
}

/* DockerEnvFakeAPI
    A fake api that allows for making the necessariy calls using raw sql on a mysql connection.
    This is meant to be used primarily for testing in the docker development environment.
*/
class DockerEnvFakeAPI {
    const DOCKER_TEST_HOST = 'wp-installer_mysql';
    const DOCKER_TEST_USER = 'root';
    const DOCKER_TEST_PW = 'super-duper-Secret-42';
    const SEC_TO_WAIT_FOR_DBSERVER = 15;

    function __construct() {
        $wait_for = self::SEC_TO_WAIT_FOR_DBSERVER;
        $waited = 0;
        while($waited <= $wait_for) {
            sleep(1);
            try {
                // Test database server credentials
                $this->mysqli = mysqli_connect(
                    self::DOCKER_TEST_HOST,
                    'root',
                    'super-duper-Secret-42'
                );
            } catch (\Exception $e) {
                $remaining = $wait_for - $waited;
                echo "Waiting $remaining more seconds for db server". PHP_EOL;
            }

            if(!!$this->mysqli) {
                break;
            }
            $waited++;
        }

        if(mysqli_connect_error()) {
            throw new \Exception ("Failed to connect to Mysql:" . mysqli_connect_error());
        }
    }

    function __invoke(string $module, string $method, $arguments=[]) {
        $prefix = strtolower($module). '_';

        $method_name = "{$prefix}{$method}";

        if(!method_exists($this, $method_name)) {
            throw new \Exception("$module $method not implemented in ".get_class($this)." $method_name");
        }

        $response = [
            'messages' => null,
            'errors'   => null,
            'data'     => null,
            'status'   => 1,
        ];

        try {
            $response['data'] = $this->$method_name($arguments);
        }
        catch (Exception $e) {
            $response['errors'] = $e;
            $response['status'] = 0;
        }

        return json_encode($response);
    }

    private function mysql_create_user($args) {
        $this->require_args(['name', 'password'], $args);
        # Not binding params because that's not allowed in "CREATE USER" statements.
        # We're okay with this because this is a FAKE used for testing only.
        $this->query("CREATE USER '{$args['name']}'@'%' IDENTIFIED BY '{$args['password']}'");
    }

    private function mysql_create_database($args) {
        $this->require_args(['name'], $args);
        $this->query("CREATE DATABASE {$args['name']}");
    }

    private function mysql_set_privileges_on_database($args) {
        $this->require_args(['user', 'database', 'privileges'], $args);
        $this->query( "GRANT {$args['privileges']} ON {$args['database']}.* TO '{$args['user']}'@'%'");
        $this->query('FLUSH PRIVILEGES');
    }

    private function mysql_delete_user($args) {
        $this->require_args(['name'], $args);
        $this->query("DROP USER {$args['name']}");
    }

    private function mysql_delete_database($args) {
        $this->require_args(['name'], $args);
        $this->query("DROP DATABASE {$args['name']}");
    }

    private function mysql_get_server_information($args) {
        return [ 'host' => self::DOCKER_TEST_HOST ];
    }

    private function require_args($required, $given) {
        foreach ($required as $e_arg) {
            if (!array_key_exists($e_arg, $given)) {
                throw new \Exception("Missing required argument $e_arg");
            }
        }
    }

    private function query($query, $bind_types = '', $params = []) {
        $statement = $this->mysqli->prepare($query);

        if (!$statement) {
            throw new \Exception("Failed to prepare query: {$this->mysqli->error}");
        }

        if (!empty($bind_types) && !$statement->bind_param($bind_types, $params)) {
            throw new \Exception("Failed to bind params: {$statement->error}");
        }
        if (!$statement->execute()) {
            throw new \Exception("Failed to execute query: {$statement->error}");
        }
    }

    function __destruct() {
        $this->mysqli->close();
    }
}
?>
Back to Directory File Manager