<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/*EXTRA FUNCTIONS: sqlsrv\_.+*/

// See sup_sqlserver tutorial for documentation on using SQL Server.

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    core_database_drivers
 */

require_code('database/shared/sqlserver');

// See tut_sqlserver tutorial for documentation on using SQL Server.

/**
 * Database driver class.
 *
 * @package core_database_drivers
 */
class Database_Static_sqlserver extends Database_super_sqlserver
{
    protected $cache_db = [];

    private $query_timeout = 3000;

    /**
     * Get a database connection. This function shouldn't be used by you, as a connection to the database is established automatically.
     *
     * @param  boolean $persistent Whether to create a persistent connection
     * @param  string $db_name The database name
     * @param  string $db_host The database host (the server)
     * @param  string $db_user The database connection username
     * @param  string $db_password The database connection password
     * @param  boolean $fail_ok Whether to on error echo an error and return with a null, rather than giving a critical error
     * @return ?mixed A database connection (null: failed)
     */
    public function get_connection(bool $persistent, string $db_name, string $db_host, string $db_user, string $db_password, bool $fail_ok = false)
    {
        // Potential caching
        if (isset($this->cache_db[$db_name][$db_host])) {
            return $this->cache_db[$db_name][$db_host];
        }

        if (!function_exists('sqlsrv_connect')) {
            $error = 'The sqlserver PHP extension not installed (anymore?). You need to contact the system administrator of this server.';
            if ($fail_ok) {
                echo ((running_script('install')) && (get_param_string('type', '') == 'ajax_db_details')) ? strip_html($error) : $error;
                return null;
            }
            critical_error('PASSON', $error);
        }

        if ($db_host == '127.0.0.1' || $db_host == 'localhost') {
            $db_host = '(local)';
        }
        $connection = @sqlsrv_connect($db_host, ($db_user == '') ? ['Database' => $db_name] : ['UID' => $db_user, 'PWD' => $db_password, 'Database' => $db_name, 'CharacterSet' => 'UTF-8']);
        if ($connection === false) {
            $err = serialize(sqlsrv_errors());
            $error = 'Could not connect to database-server (' . $err . ', ' . cms_error_get_last() . ')';
            if ($fail_ok) {
                echo ((running_script('install')) && (get_param_string('type', '') == 'ajax_db_details')) ? strip_html($error) : $error;
                return null;
            }
            critical_error('PASSON', $error);
        }

        sqlsrv_query($connection, 'SET TEXTSIZE 20000000');

        $this->cache_db[$db_name][$db_host] = $connection;
        return $connection;
    }

    /**
     * This function is a very basic query executor. It shouldn't usually be used by you, as there are abstracted versions available.
     *
     * @param  string $query The complete SQL query
     * @param  mixed $connection The DB connection
     * @param  ?integer $max The maximum number of rows to affect (null: no limit)
     * @param  integer $start The start row to affect
     * @param  boolean $fail_ok Whether to output an error on failure
     * @param  boolean $get_insert_id Whether to get the autoincrement ID created for an insert query
     * @param  boolean $save_as_volatile Whether we are saving as a 'volatile' file extension
     * @return ?mixed The results (null: no results), or the insert ID
     */
    public function query(string $query, $connection, ?int $max = null, int $start = 0, bool $fail_ok = false, bool $get_insert_id = false, bool $save_as_volatile = false)
    {
        if ($max === 0) {
            return [];
        }
        $this->apply_sql_limit_clause($query, $max, $start);

        $this->rewrite_to_unicode_syntax($query);

        $results = @sqlsrv_query($connection, $query, [], ['Scrollable' => 'static', 'QueryTimeout' => $this->query_timeout]);
        if (($results === false) && (cms_strtoupper_ascii(substr(ltrim($query), 0, 12)) == 'INSERT INTO ') && ((strpos($query, '(id, ') !== false) || (strpos($query, '(_id, ') !== false))) {
            // FUDGE: Horrible, but we need to switch the active identity column somehow
            $pos = strpos($query, '(');
            $table_name = substr($query, 12, $pos - 13);
            if ((!multi_lang_content()) || (substr($table_name, -strlen('translate')) != 'translate')) {
                @sqlsrv_query($connection, 'SET IDENTITY_INSERT ' . $table_name . ' ON');
            }
        }
        if ($start != 0) {
            sqlsrv_fetch($results, SQLSRV_SCROLL_ABSOLUTE, $start - 1);
        }
        if ((($results === false) || ((cms_strtoupper_ascii(substr(ltrim($query), 0, 7)) == 'SELECT ') || (cms_strtoupper_ascii(substr(ltrim($query), 0, 8)) == '(SELECT ')) && ($results === true)) && (!$fail_ok)) {
            $err = serialize(sqlsrv_errors());
            if ((!running_script('upgrader')) && ((!get_mass_import_mode()) || (current_fatalistic() > 0))) {
                if ((!function_exists('do_lang')) || (do_lang('QUERY_FAILED', null, null, null, null, false) === null)) {
                    $this->failed_query_exit(htmlentities('Query failed: ' . $query . ' : ' . $err));
                }

                $this->failed_query_exit(do_lang_tempcode('QUERY_FAILED', escape_html($query), ($err)));
            } else {
                $this->failed_query_echo(htmlentities('Database query failed: ' . $query . ' [') . ($err) . htmlentities(']'));
                return null;
            }
        }

        if (((cms_strtoupper_ascii(substr(ltrim($query), 0, 7)) == 'SELECT ') || (cms_strtoupper_ascii(substr(ltrim($query), 0, 8)) == '(SELECT ')) && ($results !== false) && ($results !== true)) {
            return $this->get_query_rows($results, $query, $start);
        }

        if ($get_insert_id) {
            if (cms_strtoupper_ascii(substr(ltrim($query), 0, 7)) == 'UPDATE ') {
                return null;
            }

            $pos = strpos($query, '(');
            $table_name = substr($query, 12, $pos - 13);

            $res2 = sqlsrv_query($connection, 'SELECT MAX(IDENTITYCOL) AS v FROM ' . $table_name);
            $ar2 = sqlsrv_fetch_array($res2, SQLSRV_FETCH_ASSOC);
            return $ar2['v'];
        }

        return null;
    }

    /**
     * Get the rows returned from a SELECT query.
     *
     * @param  resource $results The query result pointer
     * @param  string $query The complete SQL query (useful for debugging)
     * @param  integer $start Where to start reading from
     * @return array A list of row maps
     */
    protected function get_query_rows($results, string $query, int $start) : array
    {
        $out = [];

        while (($row = sqlsrv_fetch_array($results, SQLSRV_FETCH_ASSOC)) !== null) {
            $out[] = $row;
        }

        sqlsrv_free_stmt($results);
        return $out;
    }

    /**
     * Close the database connections. We don't really need to close them (will close at exit), just disassociate so we can refresh them.
     */
    public function close_connections()
    {
        $this->cache_db = [];
    }
}
