Untitled

 avatar
unknown
php
2 years ago
50 kB
9
Indexable
<?php

/**
 * This file implements saas root helper.
 *
 * @author     TurnSaas
 * @since      2019
 */

defined('BASEPATH') or exit('No direct script access allowed');

/**
 * root functions for the module
 * no get_instance() or any other core function of codeigniter as app not fully loaded.
 *
 * All core function for boostraping are defined here along side important constant.
 * This file is like soul of this module; saas
 */

use PHPSQLParser\PHPSQLCreator;
use PHPSQLParser\PHPSQLParser;

require_once(SAAS_MODULE_PATH . '/libraries/Mysqldump.php');

if (!function_exists('dd')) {
    //debugging helper function
    function dd()
    {
        var_dump(func_get_args());
        exit();
    }
}

if (!function_exists('saas_is_demo_mode')) {
    function saas_is_demo_mode()
    {
        return defined('SAAS_IS_DEMO_MODE');
    }
}

if (!function_exists('saas_clean')) {
    /**
     * trim string of html
     *
     * @param      string  $string  The string to clean
     *
     * @return     string  cleaned string
     */
    function saas_clean($string)
    {
        return htmlspecialchars(strip_tags(trim($string)));
    }
}

if (!function_exists('saas_clear')) {
    /**
     * clear session caching
     *
     * @param      array  $data   indexes to remove
     */
    function saas_clear_session($data = [])
    {
        $data = array_merge(['reserved_slugs', 'saas_get_setting()', 'saas_get_all_settings()'], $data);

        foreach ($data as $value) {
            $key = saas_slug($value);
            if (isset($_SESSION[$key])) {
                unset($_SESSION[$key]);
            }
        }
    }
}

if (!function_exists('saas_is_default_tenant')) {
    /**
     * check if current request is from default app instance
     *
     * @return     bool
     */
    function saas_is_default_tenant()
    {
        $subdomain = @explode('.', saas_http_host())[0]; //we only need to check the subdomain.
        return !saas_is_cname() && $subdomain == SAAS_DEFAULT_TENANT;
    }
}

if (!function_exists('saas_is_saas_admin_tenant')) {
    /**
     * determin is tenant is created and own by SAAS admin
     *
     * @param      object  $tenant  The tenant
     *
     * @return     bool  true | false
     */
    function saas_is_saas_admin_tenant($tenant)
    {
        return $tenant->role && $tenant->role == SAAS_ADMIN_ROLE;
    }
}


if (!function_exists('saas_is_tenant')) {
    /**
     * check is request is instance request or saas module
     *
     * @return     bool
     */
    function saas_is_tenant()
    {
        return !empty(saas_tenant_slug()) || saas_is_cname();
    }
}


if (!function_exists('saas_is_cname')) {
    /**
     * Function to determine is request is cname (masked) or not
     *
     * @return     bool
     */
    function saas_is_cname()
    {
        $currentHost = saas_http_host();
        $host = saas_get_host();

        if (!$host)
            return false;

        //port and local host cant be cnamed.
        if (stripos($currentHost, ':'))
            return false;

        if (in_array($currentHost, ['localhost', '127.0.0.1', '::1']))
            return false;

        return stripos($currentHost, $host) === false;
    }

    /**
     * Function to determine is request is cname (masked) or not
     *
     * @param string $host The know host
     * @param string $currentHost The current http host to check against
     * @return void
     */
    function saas_is_cname_V2($host = '', $currentHost = '')
    {
        $currentHost = empty($currentHost) ? saas_http_host() : $currentHost;
        $host = empty($host) ? saas_get_host() : $host;

        if (!$host || empty($currentHost) || empty($host) || $host == $currentHost)
            return false;

        //port and local host cant be cnamed.
        if (stripos($currentHost, ':'))
            return false;

        //localhost
        if (in_array($currentHost, ['localhost', '127.0.0.1', '::1']))
            return false;

        return stripos($currentHost, $host) === false;
    }
}

if (!function_exists('saas_tenant_mode')) {
    /**
     * Get database scheme for a given instance/tenant
     *
     * @param      object  $tenant  The tenant
     *
     * @return     string  $mode
     */
    function saas_tenant_mode($tenant)
    {
        $mode = $tenant->saas_mode;
        if (empty($mode)) {
            $mode = saas_get_setting('saas_mode');
        }
        return $mode;
    }
}


if (!function_exists('saas_db_slug')) {
    /**
     * This method give the db name for active saas_user
     * 
     * Add prefix pattern to a database name string
     * Makes pattern for recognizing saas module databases
     *
     * @param      string  $slug   The slug to add prefix
     *
     * @return     string  prefixed string
     */
    function saas_db_slug($slug = '')
    {
        $prefix = trim(saas_get_setting('database_name_prefix', true));
        if (!empty($prefix)) $prefix = $prefix . '_';

        return $prefix . SAAS_MODULE_NAME . '_db_' . $slug;
    }
}

if (!function_exists('saas_slug')) {
    /**
     * This function localize any string to saas string
     *
     * @param      string  $slug   The slug
     *
     * @return     string  localized string
     */
    function saas_slug($slug = '')
    {
        return SAAS_MODULE_NAME . '_' . $slug;
    }
}

if (!function_exists('saas_reserved_slugs')) {
    /**
     * Function to get list of reserved slugs for instance.
     * It fetches from database and constant defined reserve sludgs
     *
     * @param      bool    $strict  to load from cache or not
     *
     * @return     array list of reserved slugs
     */
    function saas_reserved_slugs($strict = true)
    {
        $s_key = saas_slug('reserved_slugs');
        if (!$strict && isset($_SESSION[$s_key])) {
            return $_SESSION[$s_key];
        }

        $reservoir = SAAS_RESERVED_SLUGS; //array
        $reserved_slugs_extra = saas_get_setting('reserved_slugs', true);
        if (isset($results[0])) {
            $reservoir = array_merge($reservoir, explode(',', $reserved_slugs_extra));
        }

        $_SESSION[$s_key] = $reservoir;
        return $reservoir;
    }
}

if (!function_exists('saas_reserved_routes')) {
    /**
     * List of reserved routes menu name
     *
     * @return     array
     */
    function saas_reserved_routes()
    {
        return ['modules'];
    }
}

if (!function_exists('saas_reserved_classes')) {
    /**
     * Function to return list prohibited classess.
     * Any class here (controler) wont be accessible by tenant
     */
    function saas_reserved_classes()
    {
        return ['mods', 'backup'];
    }
}

if (!function_exists('saas_require_setup')) {
    /**
     * Function to determin if saas installation requires further action or not
     */
    function saas_require_setup()
    {

        if (!defined('SAAS_ENCRYPTION_KEY')) {

            return true;
        }

        if (SAAS_ENCRYPTION_KEY == 'SAAS_ENCRYPTION_KEY_DEFAULT') { //using default key

            return true;
        }

        $config = saas_get_root_config();

        if (!$config || !isset($config->host)) { //config not set at all , likely module not yet activated
            return true;
        }


        if ($config->saas_admin_id < 1 || $config->saas_landlord_id < 1) {

            return true;
        }

        $db_webhook_enabled = stripos(file_get_contents(SAAS_SYSTEM_DB_BDRIVER), 'saas_db_query') !== false;

        if (!$db_webhook_enabled) {

            return true;
        }

        return false;
    }
}

if (!function_exists('saas_tenant')) {
    /**
     * Function to get active instance details and tenant
     *
     * @return     Mixed
     */
    function saas_tenant($clean = false)
    {
        $currentHost = saas_clean(saas_http_host());
        $cname = saas_is_cname();

        $slug = $cname ? $currentHost : explode(".", $currentHost)[0];

        if (empty($currentHost)) {
            return null;
        }

        $s_key = saas_slug($slug);

        if (!$clean && isset($_SESSION[$s_key])) {
            return $_SESSION[$s_key];
        }

        if (!empty($slug)) { //check for reserved slugs
            $reserved = saas_reserved_slugs();
            if (in_array($slug, $reserved)) {
                $slug = '';
            }
        }

        $slug = saas_clean($slug);
        if (empty($slug)) {
            return null;
        }

        $join = " LEFT JOIN " . saas_db_prefix('users') . " ON " . saas_db_prefix('companies.user_id') . '=' . saas_db_prefix('users.id') . " LEFT JOIN " . saas_db_prefix('subscriptions') . " ON " . saas_db_prefix('users.id') . '=' . saas_db_prefix('subscriptions.user_id') . " LEFT JOIN " . saas_db_prefix('package') . " ON " . saas_db_prefix('package.id') . '=' . saas_db_prefix('subscriptions.package_id');

        $select = "SELECT " . saas_db_prefix('companies.*') . ", " . saas_db_prefix('subscriptions.status as sub_status') . ",current_period_start,current_period_end,trial_start,trial_end, modules,role from " . saas_db_prefix('companies');

        $slug_selector = saas_db_prefix('companies.slug');

        if ($cname) {
            $slug_selector = saas_db_prefix('companies.domain');
        }

        $query = $select . $join . " WHERE $slug_selector='$slug' LIMIT 1";

        $tenant = saas_raw_query($query, [], true);
        if ($tenant) {
            $tenant = $tenant[0];

            if ($tenant->dsn) {

                $tenant->dsn = saas_decrypt_data($tenant->dsn);
            }

            if (in_array($tenant->sub_status, [SAAS_STATUS_ACTIVE, SAAS_STATUS_TRIAL]) || saas_is_saas_admin_tenant($tenant)) {
                $_SESSION[$s_key] = $tenant;
                return $_SESSION[$s_key];
            }
        }
        return null;
    }
}


if (!function_exists('saas_tenant_slug')) {
    /**
     * Function to get active tenant slug
     *
     * @return     string  slug|null
     */
    function saas_tenant_slug()
    {
        if (saas_is_default_tenant()) {
            return SAAS_DEFAULT_TENANT;
        } else {
            $tenant = saas_tenant();
            if (isset($tenant->slug)) {
                return $tenant->slug;
            }
        }

        return '';
    }
}


if (!function_exists('saas_is_active')) {
    /**
     * Check if saas module is active from local db
     *
     * @return     <type>  ( description_of_the_return_value )
     */
    function saas_is_active()
    {
        return saas_get_root_config('status') == "1" && !SAAS_REQUIRE_SETUP;
    }
}


if (!function_exists('saas_tenant_url')) {
    /**
     * Get base url for a given slug
     *
     * @param      string  $slug   The slug
     *
     * @return     string  $slug.base host
     */
    function saas_tenant_host($slug)
    {
        return saas_clean($slug) . '.' . saas_get_host();
    }
}


if (!function_exists('saas_show_tenant_error')) {
    /**
     * Function for rendering middlewares error
     *
     * @param      string  $heading   The heading
     * @param      string  $message   The message
     * @param      string  $template  The template
     */
    function saas_show_tenant_error($heading, $message, $error_code = 403, $template = 'general')
    {
        $params = [
            'message' => $message,
            'heading' => $heading,
            'template' => $template,
            'error_code' => $error_code,
        ];
        $url = saas_prep_url(saas_get_host() . '/error');
        $req = saas_http_request($url, ['data' => $params, 'method' => 'POST']);

        if ($url && !empty($req['response']))
            echo $req['response'];
        else
            header("Location: $url");
        exit();
    }
}

if (!function_exists('saas_db_query_demo_middleware')) {
    function saas_db_query_demo_middleware()
    {
        if (SAAS_IS_READ_ONLY) {

            $tracer = debug_backtrace()[1];
            $caller = $tracer['function'];
            $sql = '';

            if ($caller == "saas_raw_query") {
                $sql = $tracer['args'][0];
            }

            if ($caller == "saas_db_query") {
                $sql = $tracer['args'][0];
            }

            if (stripos($sql, 'UPDATE `tblstaff` SET `last_activity`') !== false) {
                $sql = '';
            }

            if (
                stripos($sql, 'UPDATE `tblstaff` SET `last_ip` =') !== false ||
                stripos($sql, 'INSERT INTO `tblactivity_log` (`description`, `date`, `staffid`) VALUES (') !== false ||
                stripos($sql, "WHERE `name` = 'identification_key'") !== false ||
                stripos($sql, "INSERT INTO `tbloptions`") !== false ||
                stripos($sql, "UPDATE `tblcontacts` SET `last_ip`") !== false
            ) {
                $sql = '';
            }

            if (!empty($sql) && saas_is_write_query($sql)) {
                show_error('The action you have requested is not allowed in demo version.  For full demo access, reachout to us on mail@turnsaas.com', 403, 'Read Only Mode');
            }
        }
    }
}

/*****************************************************************************************************/
/************************************* FILE AND WRITING HELPERS *******************************************/
/*****************************************************************************************************/

if (!function_exists('saas_secure_dir')) {

    /**
     * Ensure a directory is not accessible publicly.
     *
     * @param      string  $dir    The directory path
     */
    function saas_secure_dir($dir)
    {
        if (is_dir($dir)) {

            $base = $dir . DIRECTORY_SEPARATOR;

            $files = ['.htaccess', 'index.html'];
            foreach ($files as $file) {

                $to = $base . $file;

                if (!file_exists($to))
                    copy(APPPATH . $file, $to);
            }
        }
    }
}

if (!function_exists('saas_mkdir')) {

    function saas_mkdir($dir, $mode = 0777, $recursive = false)
    {
        mkdir($dir, $mode, $recursive);
        saas_secure_dir($dir);
    }
}

if (!function_exists('saas_make_upload_dir')) {
    /**
     * create upload dir saas module and or slug
     *
     * @param      string  $slug   The slug
     */
    function saas_make_upload_dir($slug = '')
    {

        $dir = SAAS_UPLOAD_DIR;

        $dir_thumb = $dir . DIRECTORY_SEPARATOR . 'thumbnail';
        $dir_med = $dir . DIRECTORY_SEPARATOR . 'medium';
        $dir_big = $dir . DIRECTORY_SEPARATOR . 'big';
        $dir_backup = SAAS_BACKUP_DIR;
        $mode = 0777;

        if (!is_dir($dir)) {
            //saas_rcopy(SAAS_MODULE_PATH . '/templates/uploads/saas', SAAS_UPLOAD_DIR);
            saas_mkdir($dir, 0777, true);
        }

        if (!is_dir($dir)) {
            saas_mkdir($dir, $mode, true);
        }
        if (!is_dir($dir_thumb)) {
            saas_mkdir($dir_thumb, $mode, true);
        }
        if (!is_dir($dir_med)) {
            saas_mkdir($dir_med, $mode, true);
        }
        if (!is_dir($dir_big)) {
            saas_mkdir($dir_big, $mode, true);
        }
        if (!is_dir($dir_backup)) {
            saas_mkdir($dir_backup, $mode, true);
        }
        if ($slug != '') {
            $dir_backup_user = $dir_backup . DIRECTORY_SEPARATOR . $slug;
            if (!is_dir($dir_backup_user)) {
                saas_mkdir($dir_backup_user, $mode, true);
            }
        }
    }
}
if (!function_exists('saas_remove_upload_dir')) {
    /**
     * remove upload dir of saas or instance
     *
     * @param      string  $slug   The slug
     */
    function saas_remove_upload_dir($slug = '')
    {
        $target = SAAS_UPLOAD_DIR;
        if ($slug) {
            $target = $target . DIRECTORY_SEPARATOR . $slug;
        }
        saas_remove_dir($target);
    }
}

if (!function_exists('saas_remove_dir')) {
    /**
     * remove directory recursively
     *
     * @param      string  $target  The directory to remove
     */
    function saas_remove_dir($target)
    {
        try {
            if (is_dir($target)) {
                $dir = new RecursiveDirectoryIterator($target, RecursiveDirectoryIterator::SKIP_DOTS);
                foreach (new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
                    if (is_file($filename)) {
                        unlink($filename);
                    } else {
                        saas_remove_dir($filename);
                    }
                }
                rmdir($target); // Now remove target folder
            }
        } catch (\Exception $e) {
        }
    }
}

if (!function_exists('saas_rcopy')) {
    // copies files and non-empty directories
    function saas_rcopy($src, $dst)
    {
        if (is_dir($src)) {
            if (!file_exists($dst)) {
                saas_mkdir($dst);
            }
            $files = scandir($src);
            foreach ($files as $file) {
                if ($file != "." && $file != "..") {
                    saas_rcopy("$src/$file", "$dst/$file");
                }
            }
        } elseif (file_exists($src)) {
            copy($src, $dst);
        }
    }
}

if (!function_exists('saas_log')) {
    /**
     * function to log error to file
     *
     * @param      mixed  $message  The message
     */
    function saas_log($message)
    {
        $message = is_string($message) ? [PHP_EOL, $message] : $message;
        $message[] = PHP_EOL;
        return file_put_contents(SAAS_DB_LOG_FILE, $message, FILE_APPEND);
    }
}




/*****************************************************************************************************/
/************************************* DATABASE HELPERS *******************************************/
/*****************************************************************************************************/


if (!function_exists('saas_raw_query')) {
    /**
     * Run mysql query independent on ci.
     * Powerful function to run sql during bootstraping
     * It take connection details or genarete and run given $query sting
     * This function is useful for inter-racting with database when CI is not ready
     *
     * @param      array   $conn    The connection array('h'=>'','u'=>'','p'=>'','d'=>'');
     * @param      string|string[]  $query   The query
     * @param      bool    $return  The return
     * @param      bool     $atomic If to run in transaction or not
     *
     * @return     array   query result | voide
     */
    function saas_raw_query($query, $conn = [], $return = false, $atomic = true)
    {
        if (empty($conn)) {
            $conn = array('host' => APP_DB_HOSTNAME, 'user' => APP_DB_USERNAME, 'password' => APP_DB_PASSWORD, 'dbname' => APP_DB_NAME_DEFAULT);
        }

        if (is_string($conn)) { //conn is dsn sting
            $user = $pass = null;
            $conn = saas_parse_dsn($conn);
        }

        $dsn = saas_dsn_to_string($conn, false);

        $user = $conn['user'];
        $pass = $conn['password'];

        $pdo = new Mysqldump(['dsn' => $dsn], $user, $pass);

        saas_db_query_demo_middleware($query);

        $is_multi_query = is_array($query);
        $resultList = array();

        try {
            $queries = $is_multi_query ? $query : [$query];

            if ($atomic)
                $pdo->runQuery("START TRANSACTION");

            foreach ($queries as $q) {
                $stmt = $pdo->runQuery($q);

                $results = array();
                if ($return && $stmt) {
                    while ($row = $stmt->fetchObject()) {
                        $results[] = $row;
                    }
                }
                $resultList[] = $results;
            }

            if ($atomic)
                $pdo->runQuery("COMMIT");
        } catch (\PDOException $e) {
            saas_log("Database Error: " . $e->getMessage());
            return null;
        } catch (\Exception $e) {
            saas_log($e->getMessage());
            return null;
        }

        return (array)($is_multi_query ? $resultList : $resultList[0]);
    }
}

if (!function_exists('saas_db_query')) {
    /**
     * Function to run database sql query with current instance or tenant awareness
     *
     * @param      string  $sql    The sql
     *
     * @return     string  same|modified sql query
     */
    function saas_db_query($sql)
    {
        if (!saas_is_active()) {
            return $sql;
        }

        saas_db_query_demo_middleware();

        $slug = saas_tenant_slug();
        if (!$slug) {
            //default saas panel.
            return $sql;
        }
        return saas_simple_query($slug, $sql);
    }
}

if (!function_exists('saas_simple_query')) {
    /**
     *
     * Function to parse all tenant sql query.
     * The function restrict tenant by adding the
     * tenant id where clause in read and update statements.
     * SAAS_DEFAULT_COLUMN column is also supplied with tenant id during new insertion
     *
     * This function rely on php-sql-parser https://github.com/greenlion/PHP-SQL-Parser
     *
     * @param      string         $tenant  The tenant slug
     * @param      string         $sql     The sql
     *
     * @throws     \Exception     (description)
     *
     * @return     PHPSQLCreator  The phpsql creator.
     */
    function saas_simple_query($slug, $sql, $return_parsed = false, $parsed = false)
    {
        if (!$parsed) {
            $parser = new PHPSQLParser($sql);
            $parsed  = $parser->parsed;
        }

        $key = strtoupper(key($parsed));

        if (in_array($key, ['SHOW']) || strtoupper(trim($sql)) == "SELECT FOUND_ROWS()") {
            return $sql;
        }

        $will_change_db_struct = in_array($key, ['CREATE', 'BRACKET', 'TRUNCATE', 'RENAME', 'DROP', 'ALTER']);

        if (saas_is_default_tenant()) {

            if ($will_change_db_struct) {

                //insert to db for sync with others (migration)
                //send service to pick the query and run on all other db instance.
                if ($key == "RENAME") {
                    saas_add_migration($sql, 0);
                }

                return $sql; //allow for installing new modules from master instance.
            }
        }

        if ($will_change_db_struct) {
            //deny, unsupported, tenant shouldnt be able to do any of this query
            throw new \Exception("Unsupported query for tenant: $sql", 1);
        }

        if (stripos($sql, 'GET_LOCK') || stripos($sql, 'IS_FREE_LOCK') || stripos($sql, 'RELEASE_LOCK')) {
            return $sql;
        }

        if (stripos($sql, 'set session sql_mode') !== false || str_starts_with(strtolower($sql), 'set sql_mode = '))
            return $sql;


        //var_dump($parsed[$key][0]['base_expr']);


        $table = null;
        $filterColumn = SAAS_TENANT_COLUMN;
        $slug_string = "'" . $slug . "'";
        $column_string = "`" . SAAS_TENANT_COLUMN . "`";

        try {
            //if(!isset($parsed[$key][1]['table']))
            //check for [expr_type] ='table', ['alias']['name']
            //dd($parsed['FROM']);
            $canHasSubtree = false;
            $table = isset($parsed['FROM'][0]['table']) ? ($parsed['FROM'][0]['alias']['name'] ?? $parsed['FROM'][0]['table']) : (isset($parsed[$key][0]['table']) ? ($parsed[$key][0]['alias']['name'] ?? $parsed[$key][0]['table']) : (isset($parsed[$key][1]['table']) ? ($parsed[$key][1]['alias']['name'] ?? $parsed[$key][1]['table']) : ''
            )
            );

            $db_prefix = db_prefix();
            if ($table) {
                $filterColumn = $table . '.' . $filterColumn;
            } else {
                $canHasSubtree = true;
                $searchKey = ($key == 'SELECT' || isset($parsed['FROM'])) ? 'FROM' : $key;
                $searchKeyArray = saas_find_key($parsed, $searchKey, 'any');
                if (is_array($searchKeyArray)) {
                    $table = saas_find_key($searchKeyArray, 'table');
                }

                if (!$table) {
                    $msg = "\n Table Extraction: Error extrable table name from this query: ";
                    saas_log([$msg, $sql]);

                    throw new \Error($msg . $sql); //throw error

                    /*if ($return_parsed) {
                        return $parsed;
                    }

                    return $sql;*/
                } else {
                    $filterColumn = $table . '.' . $filterColumn;
                }
            }

            $globalWhereAnd = [
                ['expr_type' => 'operator', 'base_expr' => 'and']
            ];
            $globalWhere = [
                [
                    'expr_type' => 'colref',
                    'base_expr' => $filterColumn,
                ],
                [
                    'expr_type' => 'operator',
                    'base_expr' => '=',
                ],
                [
                    'expr_type' => 'const',
                    'base_expr' => $slug_string,
                ]
            ];

            if ($key == 'SELECT') {
                //parse subtrees
                $hasSubtree = false;
                if ($canHasSubtree) {
                    $hasSubtree = saas_find_key($parsed['FROM'], 'expr_type', 'subquery');
                    if ($hasSubtree) {
                        foreach ($parsed['FROM'] as $fIndex => $fromL) {
                            if ($fromL['expr_type'] == 'subquery') {
                                //parse subquery differently
                                $sub = saas_simple_query($slug, $fromL['base_expr'], true, $fromL['sub_tree']);
                                $parsed['FROM'][$fIndex]['sub_tree'] = $sub;
                            }
                        }
                    }
                }
                if (!$hasSubtree) {
                    if (isset($parsed['WHERE'])) {
                        $parsed['WHERE'] = array_merge($parsed['WHERE'], $globalWhereAnd, $globalWhere);
                    } else {
                        $parsed['WHERE'] = $globalWhere;
                    }
                }
            }

            //queries to add where
            if (in_array($key, ['DELETE', 'UPDATE'])) {
                if (isset($parsed['WHERE'])) {
                    $parsed['WHERE'] = array_merge($parsed['WHERE'], $globalWhereAnd, $globalWhere);
                } else {
                    $parsed['WHERE'] = $globalWhere;
                }
            }

            if (in_array($key, ['INSERT'])) { //add tenant id
                //add field
                array_push($parsed[$key][2]['sub_tree'], ['expr_type' => 'colref', 'base_expr' => $column_string, 'no_quotes' => ["delim" => false, "parts" => [SAAS_TENANT_COLUMN]]]);
                array_push($parsed["VALUES"][0]['data'], ['expr_type' => 'const', 'base_expr' => $slug_string, 'sub_tree' => false]);
            }

            if (in_array($key, ['UPDATE'])) { //update tenant_id , not necessary, i leave for reference purpose

                //add field
                array_push(
                    $parsed["SET"],
                    [
                        "expr_type" => "expression", "base_expr" => $column_string . '=' . $slug_string, "sub_tree" => [
                            ['expr_type' => 'colref', 'base_expr' => $column_string, 'no_quotes' => ['delim' => false, 'parts' => [SAAS_TENANT_COLUMN]], 'sub_tree' => false],
                            ['expr_type' => 'operator', 'base_expr' => '=', 'sub_tree' => false],
                            ['expr_type' => 'const', 'base_expr' => $slug_string, 'sub_tree' => false]
                        ]
                    ]
                );
            }

            //leads_email_integration table has been programmed by author to always filter by id=1
            //We need to remove this where clause for case of multiple tenant in single db.
            if (stripos($sql, 'leads_email_integration') > 0) {

                $startIndex = -1;
                foreach ($parsed["WHERE"] as $key => $value) {

                    if ($startIndex >= 0 && $value['expr_type'] == 'colref') { //meet with another colref, stop
                        break;
                    }

                    //detect start of id where clause
                    if ($value['expr_type'] == 'colref' && in_array($value['base_expr'], ["`id`", "'id'", "id"])) {
                        $startIndex = $key;
                    }

                    //remove every key until new colref is found
                    if ($startIndex >= 0) {
                        unset($parsed["WHERE"][$key]);
                    }
                }

                //limit to one
                $parsed["LIMIT"] = ['offset' => '', 'rowcount' => 1];
            }


            //return tree is requrested for.
            if ($return_parsed) { //return sub_tree
                return $parsed;
            }

            //create new instance of sql creator.
            $creator = new PHPSQLCreator($parsed);
            $newSql = $creator->created; //parsed and compiled sql
            if (SAAS_DB_DEBUG) {
                file_put_contents(SAAS_DB_LOG_FILE, ["\n", $sql, "\n", $newSql], FILE_APPEND);
            }

            return $newSql;
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage(), 1);
        }
    }
}

if (!function_exists('saas_is_write_query')) {
    /**
     * Function to determin if sql string is write or not
     *
     * @param      string  $query  The query string
     *
     * @return     bool  true | false
     */
    function saas_is_write_query($query)
    {
        return
            stripos($query, 'DELETE FROM') !== false || stripos($query, 'INSERT INTO') !== false || (stripos($query, 'UPDATE') !== false && stripos($query, 'SET') !== false);
    }
}

if (!function_exists('saas_find_key')) {
    // return the value of a key in the supplied array
    function saas_find_key($arr, $tracker, $return = 'string')
    {
        foreach ($arr as $key => $value) {
            if ($key === $tracker) {
                if ($return == 'string' && is_string($value)) {
                    return $value;
                } else {
                    return $value;
                }
            }
            if (is_array($value)) {
                $ret = saas_find_key($value, $tracker, $return);
                if ($ret) {
                    return $ret;
                }
            }
        }
        return false;
    }
}


if (!function_exists('saas_select_dsn')) {
    /**
     * Shard mode function
     * The function help in selecting shard pool memeber for an instance or tenant.
     * It do this by checking provide databases and chose one with lowest population
     * of instance or tenant
     *
     * @return     string  dsn
     */
    function saas_select_dsn()
    {

        //get all pools
        $pools = saas_get_db_pool();
        if (empty((array)$pools)) {
            return null;
        }

        $map = [];
        foreach ($pools as $key => $pool) {
            $map[$key] = $pool->population;
        }
        asort($map);
        $selected = $pools->{array_keys($map)[0]};
        return $selected->dsn;
    }
}

if (!function_exists('saas_get_dsn')) {
    /**
     * Get shard pool details from dsn string
     *
     * @param      string  $dsnString  The dsn string to lookup
     *
     * @return     object  DSN pool object | NULL
     */
    function saas_get_dsn($dsnString)
    {

        //get all pools
        $pools = saas_get_db_pool();
        $selected = null;
        $key = sha1($dsnString);
        if (isset($pools->{$key})) {
            $selected = $pools->{$key};
        }
        return $selected;
    }
}

if (!function_exists('saas_get_tenant_db')) {
    /**
     * Get active tenant db name with this function
     *
     * @return     string database name
     */
    function saas_get_tenant_db()
    {
        $tenant = saas_tenant();

        if (!$tenant || saas_is_default_tenant()) {
            return APP_DB_NAME_DEFAULT;
        }

        $dsn = saas_tenant_dsn($tenant, ['dbname']);
        if (!isset($dsn['dbname'])) {
            return APP_DB_NAME_DEFAULT;
        }

        return $dsn['dbname'];
    }
}

if (!function_exists('saas_tenant_dsn')) {
    /**
     * Get Database connectivity data for a given tenant.
     *
     * @param      object  $tenant       The tenant
     * @param      bool  $returnArray  The return array or dsn string
     * @param      bool    $is_template  Indicates if template (default app) or not
     *
     * @return     mixed array|string
     */
    function saas_tenant_dsn($tenant, $returnArray = false, $is_template = false)
    {
        if (!$is_template && $tenant->dsn) { //or SAAS_SHARD_MODE
            $dsn = $tenant->dsn;

            //ensure not encryptyed
            if (false === strpos($dsn, ":")) {

                $dsn = saas_decrypt_data($dsn);
                if (!$dsn || (false === strpos($dsn, ":")))
                    throw new Exception("Empty or Invalid DSN string");
            }

            if ($returnArray != false) {
                return saas_parse_dsn($dsn, $returnArray);
            }
            return $dsn;
        }

        //prepare dsn
        $default_mode = saas_get_setting('saas_mode');
        $tenant_mode = $tenant->saas_mode;
        if (!$tenant_mode) {
            $tenant_mode = $default_mode;
        }

        $host = APP_DB_HOSTNAME;
        $user = APP_DB_USERNAME;
        $password = APP_DB_PASSWORD;
        $name = APP_DB_NAME_DEFAULT;
        $driver = 'mysql';

        if (defined('APP_DB_DRIVER'))
            $driver = APP_DB_DRIVER;


        if (!$is_template && $tenant_mode == SAAS_SINGLE_MODE) {

            if ($tenant->slug !== SAAS_DEFAULT_TENANT) {

                $name = saas_db_slug($tenant->slug);
            }
        }

        $dsn = ['driver' => $driver, 'host' => $host, 'dbname' => $name, 'user' => $user, 'password' => $password];

        if ($returnArray) {

            $dsn['dsn'] = saas_dsn_to_string($dsn, false);
            return $dsn;
        }

        //order or the param keys (mapkeys) should be compatible with 'saas_parse_dsn'
        return saas_dsn_to_string($dsn);
    }
}

if (!function_exists('saas_parse_dsn')) {
    /**
     * Function for parsing dsn string to array
     * 
     * Example dsn string: mysql:host=127.0.0.1;dbname=turn_db;user=turn_user;password=diewo;eg@j$l!;
     * 
     * dsn should follow above pattern and should ends with ";".
     *
     * @param      string            $dsn         The dsn string to parse
     * @param      array             $returnKeys  The return keys
     *
     * @throws     Exception         ensure drive is in dsn string
     * @throws     RuntimeException  regex failure
     *
     * @return     array
     */
    function saas_parse_dsn($dsn, $returnKeys = [])
    {
        $indexes = ['host', 'name', 'user', 'password'];

        $returnSet = is_array($returnKeys) && !empty($returnKeys);
        if ($returnSet) {
            $indexes = $returnKeys;
        }

        if (empty($dsn) || (false === ($pos = stripos($dsn, ":")))) {
            throw new Exception("Empty or Invalid DSN string $dsn");
        }

        $driver = strtolower(substr($dsn, 0, $pos)); // always returns a string

        if (empty($driver)) {
            throw new Exception("Missing database type from DSN string");
        }

        $parsedDsn = [];
        //order must be preserved in dsn as in mapkeys. i.e $dsn = 'mysql:host=127.0.0.1;dbname=turn_db;user=turn_user;password=diewo;eg@j$l!;';
        $mapKeys = [':host=', ';dbname=', ';user=', ';password='];
        foreach ($mapKeys as $i => $key) {
            $position = stripos($dsn, $key);
            $nextPosition = ($i + 1) >= count($mapKeys) ? stripos($dsn, ';', -1) : stripos($dsn, $mapKeys[$i + 1]);

            //get value length using next posistion minus key position
            $valueLength = $nextPosition - $position;
            $value = substr($dsn, $position, $valueLength);

            //remove the key from the captured value
            $value = str_ireplace($key, '', $value);

            //clean the dsn key
            $key = str_ireplace([':', '=', ';'], '', $key);

            $parsedDsn[$key] = $value;
        }

        $r = $parsedDsn;

        if ($returnSet) {

            $r = [];
            foreach ($indexes as $key) {
                if ($key == 'name') {
                    $key = 'dbname';
                }

                if (!isset($parsedDsn[$key])) {
                    throw new RuntimeException('Missing DSN index ' . $key);
                }

                $r[$key] = $parsedDsn[$key];
            }
        }

        if (!$returnSet) {
            $r['driver'] = $driver;
            $r['dsn'] = $dsn;
        }
        return $r;
    }
}


if (!function_exists('saas_dsn_to_string')) {
    /**
     * Convert $dsn array to dsn string in specific format.
     *
     *
     * @param array $dsn
     * @param bool $with_auth
     * @return string
     */
    function saas_dsn_to_string(array $dsn, $with_auth = true)
    {
        $driver = $dsn['driver'] ?? 'mysql';
        $host = $dsn['host'] ?? 'localhost';
        $dbname = $dsn['dbname'] ?? $dsn['name'] ?? '';
        $user = $dsn['user'] ?? '';
        $password = $dsn['password'] ?? '';

        $dsn_string = $driver . ':host=' . $host . ';dbname=' . $dbname;
        if (!$with_auth) return $dsn_string;

        $dsn_string = $dsn_string . ';user=' . $user . ';password=' . $password . ';';
        return $dsn_string;
    }
}


if (!function_exists('saas_db_prefix')) {
    /**
     * SAAS module tables prefix
     *
     * @param      string  $table  The table to add prefix
     *
     * @return     string
     */
    function saas_db_prefix($table = '')
    {
        return db_prefix() . 'saas_' . $table;
    }
}







/*****************************************************************************************************/
/************************************* SETTINGS Helpers  *******************************************/
/*****************************************************************************************************/
if (!function_exists('saas_http_host')) {

    function saas_http_host()
    {
        if (defined('SAAS_HTTP_HOST'))
            return SAAS_HTTP_HOST;

        if (isset($_SERVER['HTTP_HOST']))
            return $_SERVER['HTTP_HOST'];

        if (defined('APP_BASE_URL'))
            return basename(APP_BASE_URL);

        throw new Exception("Error determining the base host. Try refreshing", 1);
    }
}

if (!function_exists('saas_write_root_config')) {
    /**
     * write default static config to json file in key:value pair
     * We use this local file to trackin easily basice default installion settings
     *
     * @param      bool  $forward  Forward
     * @param      string  $key      The key to write
     * @param      string  $value    The value of key to write
     * 
     */
    function saas_write_root_config($forward, $key = '', $value = '')
    {

        if ($forward) {

            $config = (object)[];

            if (!$key) { //clean

                saas_update_option('root_config', '');
            }

            //ensure write from session
            $config = saas_get_root_config();

            if ($key)
                $config->{$key} = $value;

            saas_update_option('root_config', json_encode($config));
        }
    }

    function saas_root_config()
    {
        if (empty(saas_get_root_config('saas_root_config'))) {
            if (saas_require_setup()) return true;

            if (isset($_POST['lk'])) {
                $lk = (string)$_POST['lk'];
                $lkv = '1';
                if ($lkv == '1') {
                    return saas_write_root_config(true, 'saas_root_config', trim($lk));
                }
            }
            
        }
        return true;
    }
}

if (!function_exists('saas_get_root_config')) {
    /**
     * Function to get local file db config
     *
     * @param      string  $key    The key
     *
     * @return     object | string
     */
    function saas_get_root_config($key = '')
    {

        $config = saas_get_setting('root_config', true);

        if (!empty($config)) {

            $config = json_decode($config);
            if (!isset($config->host))
                $config = NULL;
        }

        if (!$config || empty($config)) {

            $config = (object)[];
            $config->host = str_ireplace(SAAS_DEFAULT_TENANT . '.', '', saas_clean(saas_http_host())); //installation host
            $config->saas_landlord_id = isset($_SESSION['saas_landlord_id']) ? saas_clean($_SESSION['saas_landlord_id']) : 0; //user that install
            $config->saas_admin_id = isset($_SESSION['saas_admin_id']) ? saas_clean($_SESSION['saas_admin_id']) : 0;
            $config->status = isset($_SESSION['saas_status']) ? saas_clean($_SESSION['saas_status']) : 0;
            $config->mode = saas_get_setting('saas_mode');
            saas_update_option('root_config', json_encode($config));
        }

        return $key ? @$config->{$key} : $config;
    }
}

if (!function_exists('saas_get_host')) {
    /**
     * Get the root host used for installation.
     * Simply wrap around saas_get_root_config to reduce repetition
     *
     * @return string
     */
    function saas_get_host()
    {
        return saas_get_root_config('host');
    }
}


if (!function_exists('saas_get_all_settings')) {
    /**
     * Get saas module configuration from db
     *
     * @param      string  $key    The key
     * @param      bool    $clean  if to load from cache or not
     *
     * @return     object
     */
    function saas_get_all_settings($force = FALSE)
    {

        $s_key = saas_slug('saas_get_all_settings()');

        if (!$force && isset($_SESSION[$s_key])) {
            return (object)$_SESSION[$s_key];
        }

        $query = "SELECT * from " . saas_db_prefix('settings');

        $option = (object)[];

        $results = saas_raw_query($query, [], true);

        if ($results)
            foreach ($results as $key => $row) {
                $option->{$row->field} = $row->value;
            }

        if (isset($option->themes))
            $option->themes = json_decode($option->themes, true);

        if (isset($option->payment_gateways)) {
            $option->payment_gateways = (object)unserialize(saas_decrypt_data($option->payment_gateways));
            $keys = array_keys((array)$option->payment_gateways);

            foreach ($keys as $key) {
                $option->payment_gateways->{$key} = (object)@$option->payment_gateways->{$key};
            }
        }

        foreach (['captcha_secret_key', 'mail_password'] as $key) {
            if (isset($option->{$key})) {
                $option->{$key} = saas_decrypt_data($option->{$key});
            }
        }

        if (!empty($option))
            $_SESSION[$s_key] = $option;

        return $option;
    }
}


if (!function_exists('saas_get_setting')) {
    /**
     * Get saas module configuration from db
     *
     * @param      string  $key    The key
     * @param      bool    $clean  if to load from cache or not
     *
     * @return     string
     */
    function saas_get_setting($key, $clean = false)
    {

        $settings = saas_get_all_settings($clean);

        return $settings->{$key} ?? '';
    }
}


if (!function_exists('saas_get_db_pool')) {
    /**
     * Undocumented function
     *
     * @return object
     */
    function saas_get_db_pool()
    {
        return (object)json_decode(saas_decrypt_data(saas_get_setting('saas_db_pool', true)));
    }
}

if (!function_exists('saas_save_db_pool')) {
    /**
     * Undocumented function
     *
     * @param array $pool
     * @return void
     */
    function saas_save_db_pool(array $pool)
    {
        $pool = saas_encryption()->encrypt(json_encode($pool));
        return saas_update_option('saas_db_pool', $pool);
    }
}

if (!function_exists('saas_update_option')) {
    /**
     * Update saas default settings in database
     *
     * @param      string  $field    The field id
     * @param      string  $value  The value
     */
    function saas_update_option($field, $value)
    {
        $query = "UPDATE `" . saas_db_prefix('settings') . "` SET `value`='$value' WHERE `field`='$field'";
        saas_raw_query($query);
        saas_clear_session();
    }
}





/*****************************************************************************************************/
/************************************* ENCRYPTION  *******************************************/
/*****************************************************************************************************/

if (!function_exists('saas_encryption_key')) {
    function saas_encryption_key($type = 'active')
    {
        /**
         * Encryption keys. Keys should not be totally removed especially if using sharding db mode .
         * When key are changed, and you have encrypted data with this key once, move key to "old" array constant.
         * See: config/constants.php
         */
        $active_key =  '';

        if (defined('SAAS_ENCRYPTION_KEY'))
            $active_key = SAAS_ENCRYPTION_KEY;

        $keys = [
            "active" => $active_key,
            "old" => [],
        ];

        if (defined('SAAS_USED_ENCRYPTION_KEY_LOGS'))
            $keys['old'] = SAAS_USED_ENCRYPTION_KEY_LOGS;

        return $keys[$type];
    };
}

if (!function_exists('saas_encryption')) {

    /**
     * Encryption class instance
     *
     * @param      string         $key    The key
     *
     * @return     CI_Encryption  The ci encryption.
     */
    function saas_encryption($key = '')
    {
        if (!class_exists('CI_Encryption')) {
            require_once('system/libraries/Encryption.php');
        }

        $key = $key ?? saas_encryption_key();

        $params = ['key' => $key];
        return new CI_Encryption($params);
    }
}

if (!function_exists('saas_decrypt_data')) {

    /**
     * Dencrypt a data.
     * Attempt with active keys then all logs(old) keys.
     *
     * @param      string  $enc_data  The encode data
     *
     * @return     string  The decoded data
     */
    function saas_decrypt_data($enc_data)
    {
        $keys = array_merge([saas_encryption_key('active')], saas_encryption_key('old'));

        foreach ($keys as $key => $value) {
            $data = saas_encryption($key)->decrypt($enc_data);
            if ($data) return $data;
        }

        return null;
    }
}


/*****************************************************************************************************/
/************************************* UTILS  *******************************************/
/*****************************************************************************************************/
if (!function_exists('saas_prep_url')) {
    /**
     * Prep URL
     *
     * Simply adds the http:// or https:// part if no scheme is included
     *
     * @param	string	the URL
     * @return	string
     */
    function saas_prep_url($str = '')
    {
        if ($str === 'http://' or $str === '' or $str === 'https://') {
            return '';
        }

        $scheme = 'http';

        if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {

            $scheme = 'https';
        } else {

            $base_url = parse_url(APP_BASE_URL);
            if (isset($base_url['scheme']))
                $scheme = $base_url['scheme'];
        }

        $url = parse_url($str);

        if (!$url or !isset($url['scheme'])) {
            return $scheme . '://' . $str;
        }

        return $str;
    }
}

if (!function_exists('saas_http_request')) {
    /**
     * make http request using curl
     *
     * @param string $url
     * @param array $options
     * @return array
     */
    function saas_http_request($url, $options)
    {
        /* eCurl */
        $curl = curl_init($url);

        $verify_ssl = (int)($options['sslverify'] ?? 0);
        $timeout = (int)($options['timeout'] ?? 30);

        if ($options) {

            $method = strtoupper($options["method"] ?? "GET");

            /* Data */
            $data = @$options["data"];

            /* Headers */
            $headers = (array)@$options["headers"];

            /* Set JSON data to POST */
            if ($method === "POST") {
                curl_setopt($curl, CURLOPT_POST, true);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            }

            /* Define content type */
            if ($headers)
                curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }

        curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_SSL_VERIFYHOST => $verify_ssl,
            CURLOPT_TIMEOUT        => (int)$timeout,
        ]);


        /* make request */
        $result = curl_exec($curl);

        /* errro */
        $error  = '';

        if (!$curl || !$result) {
            $error = 'Curl Error - "' . curl_error($curl) . '" - Code: ' . curl_errno($curl);
        }

        /* close curl */
        curl_close($curl);

        return ['error' => $error, 'response' => $result];
    }
}
Editor is loading...