Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

include/database/SqlsrvManager.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
/*********************************************************************************
42
43
* Description: This file handles the Data base functionality for the application.
44
* It acts as the DB abstraction layer for the application. It depends on helper classes
45
* which generate the necessary SQL. This sql is then passed to PEAR DB classes.
46
* The helper class is chosen in DBManagerFactory, which is driven by 'db_type' in 'dbconfig' under config.php.
47
*
48
* All the functions in this class will work with any bean which implements the meta interface.
49
* The passed bean is passed to helper class which uses these functions to generate correct sql.
50
*
51
* The meta interface has the following functions:
52
* getTableName()	        	Returns table name of the object.
53
* getFieldDefinitions()	    	Returns a collection of field definitions in order.
54
* getFieldDefintion(name)		Return field definition for the field.
55
* getFieldValue(name)	    	Returns the value of the field identified by name.
56
*                           	If the field is not set, the function will return boolean FALSE.
57
* getPrimaryFieldDefinition()	Returns the field definition for primary key
58
*
59
* The field definition is an array with the following keys:
60
*
61
* name 		This represents name of the field. This is a required field.
62
* type 		This represents type of the field. This is a required field and valid values are:
63
*      		int
64
*      		long
65
*      		varchar
66
*      		text
67
*      		date
68
*      		datetime
69
*      		double
70
*      		float
71
*      		uint
72
*      		ulong
73
*      		time
74
*      		short
75
*      		enum
76
* length	This is used only when the type is varchar and denotes the length of the string.
77
*  			The max value is 255.
78
* enumvals  This is a list of valid values for an enum separated by "|".
79
*			It is used only if the type is ?enum?;
80
* required	This field dictates whether it is a required value.
81
*			The default value is ?FALSE?.
82
* isPrimary	This field identifies the primary key of the table.
83
*			If none of the fields have this flag set to ?TRUE?,
84
*			the first field definition is assume to be the primary key.
85
*			Default value for this field is ?FALSE?.
86
* default	This field sets the default value for the field definition.
87
*
88
*
89
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
90
* All Rights Reserved.
91
* Contributor(s): ______________________________________..
92
********************************************************************************/
93
94
include_once('include/database/MssqlManager.php');
95
96
/**
97
 * SQL Server (sqlsrv) manager
98
 */
99
class SqlsrvManager extends MssqlManager
100
{
101
    public $dbName = 'SQL Server';
102
    public $variant = 'sqlsrv';
103
    public $priority = 10;
104
    public $label = 'LBL_MSSQL_SQLSRV';
105
106
    protected $capabilities = array(
107
        "affected_rows" => true,
108
        'fulltext' => true,
109
        'limit_subquery' => true,
110
        'create_user' => true,
111
        "create_db" => true,
112
    );
113
114
    protected $type_map = array(
115
            'int'      => 'int',
116
            'double'   => 'float',
117
            'float'    => 'float',
118
            'uint'     => 'int',
119
            'ulong'    => 'int',
120
            'long'     => 'bigint',
121
            'short'    => 'smallint',
122
            'varchar'  => 'nvarchar',
123
            'text'     => 'nvarchar(max)',
124
            'longtext' => 'nvarchar(max)',
125
            'date'     => 'datetime',
126
            'enum'     => 'nvarchar',
127
            'relate'   => 'nvarchar',
128
            'multienum'=> 'nvarchar(max)',
129
            'html'     => 'nvarchar(max)',
130
            'longhtml' => 'nvarchar(max)',
131
            'datetime' => 'datetime',
132
            'datetimecombo' => 'datetime',
133
            'time'     => 'datetime',
134
            'bool'     => 'bit',
135
            'tinyint'  => 'tinyint',
136
            'char'     => 'char',
137
            'blob'     => 'nvarchar(max)',
138
            'longblob' => 'nvarchar(max)',
139
            'currency' => 'decimal(26,6)',
140
            'decimal'  => 'decimal',
141
            'decimal2' => 'decimal',
142
            'id'       => 'varchar(36)',
143
            'url'      => 'nvarchar',
144
            'encrypt'  => 'nvarchar',
145
            'file'     => 'nvarchar',
146
	        'decimal_tpl' => 'decimal(%d, %d)',
147
    );
148
149
	/**
150
     * @see DBManager::connect()
151
     */
152
    public function connect(array $configOptions = null, $dieOnError = false)
153
    {
154
        global $sugar_config;
155
156
        if (is_null($configOptions))
157
            $configOptions = $sugar_config['dbconfig'];
158
159
        //set the connections parameters
160
        $connect_param = '';
161
        $configOptions['db_host_instance'] = trim($configOptions['db_host_instance']);
162
        if (empty($configOptions['db_host_instance']))
163
            $connect_param = $configOptions['db_host_name'];
164
        else
165
            $connect_param = $configOptions['db_host_name']."\\".$configOptions['db_host_instance'];
166
167
        /*
168
         * Don't try to specifically use a persistent connection
169
         * since the driver will handle that for us
170
         */
171
        $options = array(
172
                    "UID" => $configOptions['db_user_name'],
173
                    "PWD" => $configOptions['db_password'],
174
                    "CharacterSet" => "UTF-8",
175
                    "ReturnDatesAsStrings" => true,
176
                    "MultipleActiveResultSets" => true,
177
                    );
178
        if(!empty($configOptions['db_name'])) {
179
            $options["Database"] = $configOptions['db_name'];
180
        }
181
        $this->database = sqlsrv_connect($connect_param, $options);
182
        if(empty($this->database)) {
183
            $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].".");
184
            if($dieOnError) {
185
                    if(isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
186
                        sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
187
                    } else {
188
                        sugar_die("Could not connect to the database. Please refer to suitecrm.log for details.");
189
                    }
190
            } else {
191
                return false;
192
            }
193
        }
194
195
        if($this->checkError('Could Not Connect:', $dieOnError))
196
            $GLOBALS['log']->info("connected to db");
197
198
        sqlsrv_query($this->database, 'SET DATEFORMAT mdy');
199
200
        $this->connectOptions = $configOptions;
201
202
        $GLOBALS['log']->info("Connect:".$this->database);
203
        return true;
204
    }
205
206
	/**
207
     * @see DBManager::query()
208
	 */
209
	public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false)
210
    {
211
        if(is_array($sql)) {
212
            return $this->queryArray($sql, $dieOnError, $msg, $suppress);
213
        }
214
        $sql = $this->_appendN($sql);
215
216
        $this->countQuery($sql);
0 ignored issues
show
The call to SqlsrvManager::countQuery() has too many arguments starting with $sql.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
217
        $GLOBALS['log']->info('Query:' . $sql);
218
        $this->checkConnection();
219
        $this->query_time = microtime(true);
0 ignored issues
show
Documentation Bug introduced by
The property $query_time was declared of type integer, but microtime(true) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
220
221
        $result = $suppress?@sqlsrv_query($this->database, $sql):sqlsrv_query($this->database, $sql);
222
223
        $this->query_time = microtime(true) - $this->query_time;
0 ignored issues
show
Documentation Bug introduced by
The property $query_time was declared of type integer, but microtime(true) - $this->query_time is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
224
        $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
225
226
227
        $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
228
229
        //suppress non error messages
230
        sqlsrv_configure('WarningsReturnAsErrors',false);
231
232
        return $result;
233
    }
234
235
	/**
236
     * @see DBManager::getFieldsArray()
237
     */
238
	public function getFieldsArray($result, $make_lower_case = false)
239
	{
240
        $field_array = array();
241
242
        if ( !$result ) {
243
        	return false;
244
        }
245
246
        foreach ( sqlsrv_field_metadata($result) as $fieldMetadata ) {
247
            $key = $fieldMetadata['Name'];
248
            if($make_lower_case==true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
249
                $key = strtolower($key);
250
251
            $field_array[] = $key;
252
        }
253
254
        return $field_array;
255
	}
256
257
	/**
258
	 * @see DBManager::fetchRow()
259
	 */
260
	public function fetchRow($result)
261
	{
262
		if (empty($result))	return false;
263
264
	    $row = sqlsrv_fetch_array($result,SQLSRV_FETCH_ASSOC);
265
        if (empty($row)) {
266
            return false;
267
        }
268
269
        foreach($row as $key => $column) {
270
            // MSSQL returns a space " " when a varchar column is empty ("") and not null.
271
            // We need to strip empty spaces
272
            // notice we only strip if one space is returned.  we do not want to strip
273
            // strings with intentional spaces (" foo ")
274
            if (!empty($column) && $column == " ") {
275
                $row[$key] = '';
276
            }
277
        }
278
279
        return $row;
280
	}
281
282
    /**
283
     * @see DBManager::convert()
284
     */
285
    public function convert($string, $type, array $additional_parameters = array())
286
    {
287
        if ( $type == 'datetime')
288
        // see http://msdn.microsoft.com/en-us/library/ms187928.aspx for details
289
            return "CONVERT(datetime,$string,120)";
290
        else
291
            return parent::convert($string, $type, $additional_parameters);
292
    }
293
294
	/**
295
     * Compares two vardefs. Overriding 39098  due to bug: 39098 . IN 6.0 we changed the id columns to dbType = 'id'
296
     * for example emails_beans.  In 554 the field email_id was nvarchar but in 6.0 since it id dbType = 'id' we would want to alter
297
     * it to varchar. This code will prevent it.
298
     *
299
     * @param  array  $fielddef1
300
     * @param  array  $fielddef2
301
     * @return bool   true if they match, false if they don't
302
     */
303
    public function compareVarDefs($fielddef1,$fielddef2, $ignoreName = false)
304
    {
305
        if((isset($fielddef2['dbType']) && $fielddef2['dbType'] == 'id') || preg_match('/(_id$|^id$)/', $fielddef2['name'])){
306
            if(isset($fielddef1['type']) && isset($fielddef2['type'])){
307
                $fielddef2['type'] = $fielddef1['type'];
308
            }
309
        }
310
        return parent::compareVarDefs($fielddef1, $fielddef2);
311
    }
312
313
    /**
314
     * Disconnects from the database
315
     *
316
     * Also handles any cleanup needed
317
     */
318
    public function disconnect()
319
    {
320
    	$GLOBALS['log']->debug('Calling Mssql::disconnect()');
321
        if(!empty($this->database)){
322
            $this->freeResult();
323
            sqlsrv_close($this->database);
324
            $this->database = null;
325
        }
326
    }
327
328
    /**
329
     * @see DBManager::freeDbResult()
330
     */
331
    protected function freeDbResult($dbResult)
332
    {
333
        if(!empty($dbResult))
334
            sqlsrv_free_stmt($dbResult);
335
    }
336
337
338
	/**
339
	 * Detect if no clustered index has been created for a table; if none created then just pick the first index and make it that
340
	 *
341
	 * @see MssqlHelper::indexSQL()
342
     */
343
    public function getConstraintSql($indices, $table)
344
    {
345
        if ( $this->doesTableHaveAClusteredIndexDefined($table) ) {
346
            return parent::getConstraintSql($indices, $table);
347
        }
348
349
        // check to see if one of the passed in indices is a primary one; if so we can bail as well
350
        foreach ( $indices as $index ) {
351
            if ( $index['type'] == 'primary' ) {
352
                return parent::getConstraintSql($indices, $table);
353
            }
354
        }
355
356
        // Change the first index listed to be a clustered one instead ( so we have at least one for the table )
357
        if ( isset($indices[0]) ) {
358
            $indices[0]['type'] = 'clustered';
359
        }
360
361
        return parent::getConstraintSql($indices, $table);
362
    }
363
364
    /**
365
     * @see DBManager::get_columns()
366
     */
367
    public function get_columns($tablename)
368
    {
369
        //find all unique indexes and primary keys.
370
        $result = $this->query("sp_columns_90 $tablename");
371
372
        $columns = array();
373
        while (($row=$this->fetchByAssoc($result)) !=null) {
374
            $column_name = strtolower($row['COLUMN_NAME']);
375
            $columns[$column_name]['name']=$column_name;
376
            $columns[$column_name]['type']=strtolower($row['TYPE_NAME']);
377
            if ( $row['TYPE_NAME'] == 'decimal' ) {
378
                $columns[$column_name]['len']=strtolower($row['PRECISION']);
379
                $columns[$column_name]['len'].=','.strtolower($row['SCALE']);
380
            }
381
			elseif ( in_array($row['TYPE_NAME'],array('nchar','nvarchar')) ) {
382
				$columns[$column_name]['len']=strtolower($row['PRECISION']);
383
				if ( $row['TYPE_NAME'] == 'nvarchar' && $row['PRECISION'] == '0' ) {
384
				    $columns[$column_name]['len']='max';
385
				}
386
			}
387
            elseif ( !in_array($row['TYPE_NAME'],array('datetime','text')) ) {
388
                $columns[$column_name]['len']=strtolower($row['LENGTH']);
389
            }
390
            if ( stristr($row['TYPE_NAME'],'identity') ) {
391
                $columns[$column_name]['auto_increment'] = '1';
392
                $columns[$column_name]['type']=str_replace(' identity','',strtolower($row['TYPE_NAME']));
393
            }
394
395
            if (!empty($row['IS_NULLABLE']) && $row['IS_NULLABLE'] == 'NO' && (empty($row['KEY']) || !stristr($row['KEY'],'PRI')))
396
                $columns[strtolower($row['COLUMN_NAME'])]['required'] = 'true';
397
398
            $column_def = 1;
399
            if ( strtolower($tablename) == 'relationships' ) {
400
                $column_def = $this->getOne("select cdefault from syscolumns where id = object_id('relationships') and name = '$column_name'");
401
            }
402
            if ( $column_def != 0 && ($row['COLUMN_DEF'] != null)) {	// NOTE Not using !empty as an empty string may be a viable default value.
403
                $matches = array();
404
                $row['COLUMN_DEF'] = html_entity_decode($row['COLUMN_DEF'],ENT_QUOTES);
405
                if ( preg_match('/\([\(|\'](.*)[\)|\']\)/i',$row['COLUMN_DEF'],$matches) )
406
                    $columns[$column_name]['default'] = $matches[1];
407
                elseif ( preg_match('/\(N\'(.*)\'\)/i',$row['COLUMN_DEF'],$matches) )
408
                    $columns[$column_name]['default'] = $matches[1];
409
                else
410
                    $columns[$column_name]['default'] = $row['COLUMN_DEF'];
411
            }
412
        }
413
        return $columns;
414
    }
415
416
    /**
417
     * protected function to return true if the given tablename has any clustered indexes defined.
418
     *
419
     * @param  string $tableName
420
     * @return bool
421
     */
422
    protected function doesTableHaveAClusteredIndexDefined($tableName)
423
    {
424
        $query = <<<EOSQL
425
SELECT IST.TABLE_NAME
426
    FROM INFORMATION_SCHEMA.TABLES IST
427
    WHERE objectProperty(object_id(IST.TABLE_NAME), 'IsUserTable') = 1
428
        AND objectProperty(object_id(IST.TABLE_NAME), 'TableHasClustIndex') = 1
429
        AND IST.TABLE_NAME = '{$tableName}'
430
EOSQL;
431
432
        $result = $this->getOne($query);
433
        if ( !$result ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
434
            return false;
435
        }
436
437
        return true;
438
    }
439
440
    /**
441
     * protected function to return true if the given tablename has any fulltext indexes defined.
442
     *
443
     * @param  string $tableName
444
     * @return bool
445
     */
446
    protected function doesTableHaveAFulltextIndexDefined($tableName)
447
    {
448
        $query = <<<EOSQL
449
SELECT 1
450
    FROM sys.fulltext_indexes i
451
        JOIN sys.objects o ON i.object_id = o.object_id
452
    WHERE o.name = '{$tableName}'
453
EOSQL;
454
455
        $result = $this->getOne($query);
456
        if ( !$result ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
457
            return false;
458
        }
459
460
        return true;
461
    }
462
463
    /**
464
     * Override method to add support for detecting and dropping fulltext indices.
465
     *
466
     * @see DBManager::changeColumnSQL()
467
     * @see MssqlHelper::changeColumnSQL()
468
     */
469
    protected function changeColumnSQL($tablename,$fieldDefs, $action, $ignoreRequired = false)
470
    {
471
        $sql = '';
472
        if ( $action == 'drop' && $this->doesTableHaveAFulltextIndexDefined($tablename) ) {
473
            $sql .= "DROP FULLTEXT INDEX ON {$tablename}";
474
        }
475
476
        $sql .= parent::changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired);
477
478
        return $sql;
479
    }
480
481
    /**
482
     * Truncate table
483
     * @param  $name
484
     * @return string
485
     */
486
    public function truncateTableSQL($name)
487
    {
488
        return "TRUNCATE TABLE $name";
489
    }
490
491
	/**
492
	 * (non-PHPdoc)
493
	 * @see DBManager::lastDbError()
494
	 */
495
    public function lastDbError()
496
    {
497
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
498
        if(empty($errors)) return false;
499
        global $app_strings;
500
        if (empty($app_strings)
501
		    or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])
502
			or !isset($app_strings['ERR_MSSQL_WARNING']) ) {
503
        //ignore the message from sql-server if $app_strings array is empty. This will happen
504
        //only if connection if made before languge is set.
505
		    return false;
506
        }
507
        $messages = array();
508
        foreach($errors as $error) {
509
            $sqlmsg = $error['message'];
510
            $sqlpos = strpos($sqlmsg, 'Changed database context to');
511
            $sqlpos2 = strpos($sqlmsg, 'Warning:');
512
            $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
513
            if ( $sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false ) {
514
                continue;
515
            }
516
            $sqlpos = strpos($sqlmsg, $app_strings['ERR_MSSQL_DB_CONTEXT']);
517
            $sqlpos2 = strpos($sqlmsg, $app_strings['ERR_MSSQL_WARNING']);
518
    		if ( $sqlpos !== false || $sqlpos2 !== false) {
519
                    continue;
520
            }
521
            $messages[] = $sqlmsg;
522
        }
523
524
        if(!empty($messages)) {
525
            return join("\n", $messages);
526
        }
527
        return false;
528
    }
529
530
    /**
531
     * (non-PHPdoc)
532
     * @see DBManager::getDbInfo()
533
     * @return array
534
     */
535
    public function getDbInfo()
536
    {
537
        $info = array_merge(sqlsrv_client_info(), sqlsrv_server_info());
538
        return $info;
539
    }
540
541
    /**
542
     * Execute data manipulation statement, then roll it back
543
     * @param  $type
544
     * @param  $table
545
     * @param  $query
546
     * @return string
547
     */
548
    protected function verifyGenericQueryRollback($type, $table, $query)
549
    {
550
        $this->log->debug("verifying $type statement");
551
        if(!sqlsrv_begin_transaction($this->database)) {
552
            return "Failed to create transaction";
553
        }
554
        $this->query($query, false);
555
        $error = $this->lastError();
556
        sqlsrv_rollback($this->database);
557
        return $error;
558
    }
559
560
    /**
561
     * Tests an INSERT INTO query
562
     * @param string table The table name to get DDL
563
     * @param string query The query to test.
564
     * @return string Non-empty if error found
565
     */
566
    public function verifyInsertInto($table, $query)
567
    {
568
        return $this->verifyGenericQueryRollback("INSERT", $table, $query);
569
    }
570
571
    /**
572
     * Tests an UPDATE query
573
     * @param string table The table name to get DDL
574
     * @param string query The query to test.
575
     * @return string Non-empty if error found
576
     */
577
    public function verifyUpdate($table, $query)
578
    {
579
        return $this->verifyGenericQueryRollback("UPDATE", $table, $query);
580
    }
581
582
    /**
583
     * Tests an DELETE FROM query
584
     * @param string table The table name to get DDL
585
     * @param string query The query to test.
586
     * @return string Non-empty if error found
587
     */
588
    public function verifyDeleteFrom($table, $query)
589
    {
590
        return $this->verifyGenericQueryRollback("DELETE", $table, $query);
591
    }
592
593
    /**
594
     * Select database
595
     * @param string $dbname
596
     */
597
    protected function selectDb($dbname)
598
    {
599
        return $this->query("USE ".$this->quoted($dbname));
600
    }
601
602
    /**
603
     * Check if this driver can be used
604
     * @return bool
605
     */
606
    public function valid()
607
    {
608
        return function_exists("sqlsrv_connect");
609
    }
610
}
611