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/MssqlManager.php (39 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
/**
95
 * SQL Server (mssql) manager
96
 */
97
class MssqlManager extends DBManager
98
{
99
    /**
100
     * @see DBManager::$dbType
101
     */
102
    public $dbType = 'mssql';
103
    public $dbName = 'MsSQL';
104
    public $variant = 'mssql';
105
    public $label = 'LBL_MSSQL';
106
107
    protected $capabilities = array(
108
        "affected_rows" => true,
109
        "select_rows" => true,
110
        'fulltext' => true,
111
        'limit_subquery' => true,
112
        "fix:expandDatabase" => true, // Support expandDatabase fix
113
        "create_user" => true,
114
        "create_db" => true,
115
    );
116
117
    /**
118
     * Maximum length of identifiers
119
     */
120
    protected $maxNameLengths = array(
121
        'table' => 128,
122
        'column' => 128,
123
        'index' => 128,
124
        'alias' => 128
125
    );
126
127
    protected $type_map = array(
128
            'int'      => 'int',
129
            'double'   => 'float',
130
            'float'    => 'float',
131
            'uint'     => 'int',
132
            'ulong'    => 'int',
133
            'long'     => 'bigint',
134
            'short'    => 'smallint',
135
            'varchar'  => 'varchar',
136
            'text'     => 'text',
137
            'longtext' => 'text',
138
            'date'     => 'datetime',
139
            'enum'     => 'varchar',
140
            'relate'   => 'varchar',
141
            'multienum'=> 'text',
142
            'html'     => 'text',
143
			'longhtml' => 'text',
144
    		'datetime' => 'datetime',
145
            'datetimecombo' => 'datetime',
146
            'time'     => 'datetime',
147
            'bool'     => 'bit',
148
            'tinyint'  => 'tinyint',
149
            'char'     => 'char',
150
            'blob'     => 'image',
151
            'longblob' => 'image',
152
            'currency' => 'decimal(26,6)',
153
            'decimal'  => 'decimal',
154
            'decimal2' => 'decimal',
155
            'id'       => 'varchar(36)',
156
            'url'      => 'varchar',
157
            'encrypt'  => 'varchar',
158
            'file'     => 'varchar',
159
	        'decimal_tpl' => 'decimal(%d, %d)',
160
            );
161
162
    protected $connectOptions = null;
163
164
    /**
165
     * @see DBManager::connect()
166
     */
167
    public function connect(array $configOptions = null, $dieOnError = false)
168
    {
169
        global $sugar_config;
170
171
        if (is_null($configOptions))
172
            $configOptions = $sugar_config['dbconfig'];
173
174
        //SET DATEFORMAT to 'YYYY-MM-DD''
175
        ini_set('mssql.datetimeconvert', '0');
176
177
        //set the text size and textlimit to max number so that blob columns are not truncated
178
        ini_set('mssql.textlimit','2147483647');
179
        ini_set('mssql.textsize','2147483647');
180
        ini_set('mssql.charset','UTF-8');
181
182
        if(!empty($configOptions['db_host_instance'])) {
183
            $configOptions['db_host_instance'] = trim($configOptions['db_host_instance']);
184
        }
185
        //set the connections parameters
186
        if (empty($configOptions['db_host_instance'])) {
187
            $connect_param = $configOptions['db_host_name'];
188
        } else {
189
            $connect_param = $configOptions['db_host_name']."\\".$configOptions['db_host_instance'];
190
        }
191
192
        //create persistent connection
193
        if ($this->getOption('persistent')) {
194
            $this->database =@mssql_pconnect(
195
                $connect_param ,
196
                $configOptions['db_user_name'],
197
                $configOptions['db_password']
198
                );
199
        }
200
        //if no persistent connection created, then create regular connection
201
        if(!$this->database){
202
            $this->database = mssql_connect(
203
                    $connect_param ,
204
                    $configOptions['db_user_name'],
205
                    $configOptions['db_password']
206
                    );
207
            if(!$this->database){
208
                $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name'].
209
                    " as ".$configOptions['db_user_name'].".");
210
                if($dieOnError) {
211
                    sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
212
                } else {
213
                    return false;
214
                }
215
            }
216
            if($this->database && $this->getOption('persistent')){
217
                $_SESSION['administrator_error'] = "<B>Severe Performance Degradation: Persistent Database Connections "
218
                    . "not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false in your "
219
                    . "config.php file</B>";
220
            }
221
        }
222
        //make sure connection exists
223
        if(!$this->database) {
224
                if($dieOnError) {
225
                    sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
226
                } else {
227
                    return false;
228
                }
229
        }
230
231
        //select database
232
233
        //Adding sleep and retry for mssql connection. We have come across scenarios when
234
        //an error is thrown.' Unable to select database'. Following will try to connect to
235
        //mssql db maximum number of 5 times at the interval of .2 second. If can not connect
236
        //it will throw an Unable to select database message.
237
238
        if(!empty($configOptions['db_name']) && !@mssql_select_db($configOptions['db_name'], $this->database)){
239
			$connected = false;
240
			for($i=0;$i<5;$i++){
241
				usleep(200000);
242
				if(@mssql_select_db($configOptions['db_name'], $this->database)){
243
					$connected = true;
244
					break;
245
				}
246
			}
247
			if(!$connected){
248
			    $GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}");
249
                if($dieOnError) {
250
                    if(isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
251
                        sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
252
                    } else {
253
                        sugar_die("Could not connect to the database. Please refer to suitecrm.log for details.");
254
                    }
255
                } else {
256
                    return false;
257
                }
258
			}
259
         }
260
261
        if(!$this->checkError('Could Not Connect', $dieOnError))
262
            $GLOBALS['log']->info("connected to db");
263
264
        $this->connectOptions = $configOptions;
265
266
        $GLOBALS['log']->info("Connect:".$this->database);
267
        return true;
268
    }
269
270
	/**
271
     * @see DBManager::version()
272
     */
273
    public function version()
274
    {
275
        return $this->getOne("SELECT @@VERSION as version");
276
	}
277
278
	/**
279
     * @see DBManager::query()
280
	 */
281
	public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false)
282
    {
283
        if(is_array($sql)) {
284
            return $this->queryArray($sql, $dieOnError, $msg, $suppress);
285
        }
286
        // Flag if there are odd number of single quotes
287
        if ((substr_count($sql, "'") & 1))
288
            $GLOBALS['log']->error("SQL statement[" . $sql . "] has odd number of single quotes.");
289
290
		$sql = $this->_appendN($sql);
291
292
        $GLOBALS['log']->info('Query:' . $sql);
293
        $this->checkConnection();
294
        $this->countQuery($sql);
0 ignored issues
show
The call to MssqlManager::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...
295
        $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...
296
297
        // Bug 34892 - Clear out previous error message by checking the @@ERROR global variable
298
		@mssql_query("SELECT @@ERROR", $this->database);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
299
300
        $result = $suppress?@mssql_query($sql, $this->database):mssql_query($sql, $this->database);
301
302
        if (!$result) {
303
            // awu Bug 10657: ignoring mssql error message 'Changed database context to' - an intermittent
304
            // 				  and difficult to reproduce error. The message is only a warning, and does
305
            //				  not affect the functionality of the query
306
            $sqlmsg = mssql_get_last_message();
307
            $sqlpos = strpos($sqlmsg, 'Changed database context to');
308
			$sqlpos2 = strpos($sqlmsg, 'Warning:');
309
			$sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
310
311
			if ($sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false)		// if sqlmsg has 'Changed database context to', just log it
312
				$GLOBALS['log']->debug($sqlmsg . ": " . $sql );
313
			else {
314
				$GLOBALS['log']->fatal($sqlmsg . ": " . $sql );
315
				if($dieOnError)
316
					sugar_die('SQL Error : ' . $sqlmsg);
317
				else
318
					echo 'SQL Error : ' . $sqlmsg;
319
			}
320
        }
321
322
        $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...
323
        $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
324
325
326
        $this->checkError($msg.' Query Failed: ' . $sql, $dieOnError);
327
328
        return $result;
329
    }
330
331
    /**
332
     * This function take in the sql for a union query, the start and offset,
333
     * and wraps it around an "mssql friendly" limit query
334
     *
335
     * @param  string $sql
336
     * @param  int    $start record to start at
337
     * @param  int    $count number of records to retrieve
338
     * @return string SQL statement
339
     */
340
    private function handleUnionLimitQuery($sql, $start, $count)
341
    {
342
        //set the start to 0, no negs
343
        if ($start < 0)
344
            $start=0;
345
346
        $GLOBALS['log']->debug(print_r(func_get_args(),true));
347
348
        $this->lastsql = $sql;
349
350
        //change the casing to lower for easier string comparison, and trim whitespaces
351
        $sql = strtolower(trim($sql)) ;
352
353
        //set default sql
354
        $limitUnionSQL = $sql;
355
        $order_by_str = 'order by';
356
357
        //make array of order by's.  substring approach was proving too inconsistent
358
        $orderByArray = explode($order_by_str, $sql);
359
        $unionOrderBy = '';
360
        $rowNumOrderBy = '';
361
362
        //count the number of array elements
363
        $unionOrderByCount = count($orderByArray);
364
        $arr_count = 0;
365
366
        //process if there are elements
367
        if ($unionOrderByCount){
368
            //we really want the last order by, so reconstruct string
369
            //adding a 1 to count, as we dont wish to process the last element
370
            $unionsql = '';
371
            while ($unionOrderByCount>$arr_count+1) {
372
                $unionsql .= $orderByArray[$arr_count];
373
                $arr_count = $arr_count+1;
374
                //add an "order by" string back if we are coming into loop again
375
                //remember they were taken out when array was created
376
                if ($unionOrderByCount>$arr_count+1) {
377
                    $unionsql .= "order by";
378
                }
379
            }
380
            //grab the last order by element, set both order by's'
381
            $unionOrderBy = $orderByArray[$arr_count];
382
            $rowNumOrderBy = $unionOrderBy;
383
384
            //if last element contains a "select", then this is part of the union query,
385
            //and there is no order by to use
386
            if (strpos($unionOrderBy, "select")) {
387
                $unionsql = $sql;
388
                //with no guidance on what to use for required order by in rownumber function,
389
                //resort to using name column.
390
                $rowNumOrderBy = 'id';
391
                $unionOrderBy = "";
392
            }
393
        }
394
        else {
395
            //there are no order by elements, so just pass back string
396
            $unionsql = $sql;
397
            //with no guidance on what to use for required order by in rownumber function,
398
            //resort to using name column.
399
            $rowNumOrderBy = 'id';
400
            $unionOrderBy = '';
401
        }
402
        //Unions need the column name being sorted on to match across all queries in Union statement
403
        //so we do not want to strip the alias like in other queries.  Just add the "order by" string and
404
        //pass column name as is
405
        if ($unionOrderBy != '') {
406
            $unionOrderBy = ' order by ' . $unionOrderBy;
407
        }
408
409
        //Bug 56560, use top query in conjunction with rownumber() function
410
        //to create limit query when paging is needed. Otherwise,
411
        //it shows duplicates when paging on activities subpanel.
412
        //If not for paging, no need to use rownumber() function
413
        if ($count == 1 && $start == 0)
414
        {
415
            $limitUnionSQL = "SELECT TOP $count * FROM (" .$unionsql .") as top_count ".$unionOrderBy;
416
        }
417
        else
418
        {
419
            $limitUnionSQL = "SELECT TOP $count * FROM( select ROW_NUMBER() OVER ( order by "
420
            .$rowNumOrderBy.") AS row_number, * FROM ("
421
            .$unionsql .") As numbered) "
422
            . "As top_count_limit WHERE row_number > $start "
423
            .$unionOrderBy;
424
        }
425
426
        return $limitUnionSQL;
427
    }
428
429
	/**
430
	 * FIXME: verify and thoroughly test this code, these regexps look fishy
431
     * @see DBManager::limitQuery()
432
     */
433
    public function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true)
434
    {
435
        $start = (int)$start;
436
        $count = (int)$count;
437
        $newSQL = $sql;
438
        $distinctSQLARRAY = array();
439
        if (strpos($sql, "UNION") && !preg_match("/(')(UNION).?(')/i", $sql))
440
            $newSQL = $this->handleUnionLimitQuery($sql,$start,$count);
441
        else {
442
            if ($start < 0)
443
                $start = 0;
444
            $GLOBALS['log']->debug(print_r(func_get_args(),true));
445
            $this->lastsql = $sql;
446
            $matches = array();
447
            preg_match('/^(.*SELECT\b)(.*?\bFROM\b.*\bWHERE\b)(.*)$/isU',$sql, $matches);
448
            if (!empty($matches[3])) {
449
                if ($start == 0) {
450
                    $match_two = strtolower($matches[2]);
451
                    if (!strpos($match_two, "distinct")> 0 && strpos($match_two, "distinct") !==0) {
452
                        $orderByMatch = array();
453
                        preg_match('/^(.*)(\bORDER BY\b)(.*)$/is',$matches[3], $orderByMatch);
454
                        if (!empty($orderByMatch[3])) {
455
                            $selectPart = array();
456
                            preg_match('/^(.*)(\bFROM\b.*)$/isU', $matches[2], $selectPart);
457
                            $newSQL = "SELECT TOP $count * FROM
458
                                (
459
                                    " . $matches[1] . $selectPart[1] . ", ROW_NUMBER()
460
                                    OVER (ORDER BY " . $this->returnOrderBy($sql, $orderByMatch[3]) . ") AS row_number
461
                                    " . $selectPart[2] . $orderByMatch[1]. "
462
                                ) AS a
463
                                WHERE row_number > $start";
464
                        }
465
                        else {
466
                            $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
467
                        }
468
                    }
469
                    else {
470
                        $distinct_o = strpos($match_two, "distinct");
471
                        $up_to_distinct_str = substr($match_two, 0, $distinct_o);
472
                        //check to see if the distinct is within a function, if so, then proceed as normal
473
                        if (strpos($up_to_distinct_str,"(")) {
474
                            //proceed as normal
475
                            $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
476
                        }
477
                        else {
478
                            //if distinct is not within a function, then parse
479
                            //string contains distinct clause, "TOP needs to come after Distinct"
480
                            //get position of distinct
481
                            $match_zero = strtolower($matches[0]);
482
                            $distinct_pos = strpos($match_zero , "distinct");
483
                            //get position of where
484
                            $where_pos = strpos($match_zero, "where");
485
                            //parse through string
486
                            $beg = substr($matches[0], 0, $distinct_pos+9 );
487
                            $mid = substr($matches[0], strlen($beg), ($where_pos+5) - (strlen($beg)));
488
                            $end = substr($matches[0], strlen($beg) + strlen($mid) );
489
                            //repopulate matches array
490
                            $matches[1] = $beg; $matches[2] = $mid; $matches[3] = $end;
491
492
                            $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
493
                        }
494
                    }
495
                } else {
496
                    $orderByMatch = array();
497
                    preg_match('/^(.*)(\bORDER BY\b)(.*)$/is',$matches[3], $orderByMatch);
498
499
                    //if there is a distinct clause, parse sql string as we will have to insert the rownumber
500
                    //for paging, AFTER the distinct clause
501
                    $grpByStr = '';
502
                    $hasDistinct = strpos(strtolower($matches[0]), "distinct");
503
504
                    require_once('include/php-sql-parser.php');
505
                    $parser = new PHPSQLParser();
506
                    $sqlArray = $parser->parse($sql);
507
508
                    if ($hasDistinct) {
509
                        $matches_sql = strtolower($matches[0]);
510
                        //remove reference to distinct and select keywords, as we will use a group by instead
511
                        //we need to use group by because we are introducing rownumber column which would make every row unique
512
513
                        //take out the select and distinct from string so we can reuse in group by
514
                        $dist_str = 'distinct';
515
                        preg_match('/\b' . $dist_str . '\b/simU', $matches_sql, $matchesPartSQL, PREG_OFFSET_CAPTURE);
516
                        $matches_sql = trim(substr($matches_sql,$matchesPartSQL[0][1] + strlen($dist_str)));
517
                        //get the position of where and from for further processing
518
                        preg_match('/\bfrom\b/simU', $matches_sql, $matchesPartSQL, PREG_OFFSET_CAPTURE);
519
                        $from_pos = $matchesPartSQL[0][1];
520
                        preg_match('/\where\b/simU', $matches_sql, $matchesPartSQL, PREG_OFFSET_CAPTURE);
521
                        $where_pos = $matchesPartSQL[0][1];
522
                        //split the sql into a string before and after the from clause
523
                        //we will use the columns being selected to construct the group by clause
524
                        if ($from_pos>0 ) {
525
                            $distinctSQLARRAY[0] = substr($matches_sql, 0, $from_pos);
526
                            $distinctSQLARRAY[1] = substr($matches_sql, $from_pos);
527
                            //get position of order by (if it exists) so we can strip it from the string
528
                            $ob_pos = strpos($distinctSQLARRAY[1], "order by");
529
                            if ($ob_pos) {
530
                                $distinctSQLARRAY[1] = substr($distinctSQLARRAY[1],0,$ob_pos);
531
                            }
532
533
                            // strip off last closing parentheses from the where clause
534
                            $distinctSQLARRAY[1] = preg_replace('/\)\s$/',' ',$distinctSQLARRAY[1]);
535
                        }
536
537
                        $grpByStr = array();
538
                        foreach ($sqlArray['SELECT'] as $record) {
539
                            if ($record['expr_type'] == 'const') {
540
                                continue;
541
                            }
542
                            $grpByStr[] = trim($record['base_expr']);
543
                        }
544
                        $grpByStr = implode(', ', $grpByStr);
545
                    }
546
547
                    if (!empty($orderByMatch[3])) {
548
                        //if there is a distinct clause, form query with rownumber after distinct
549
                        if ($hasDistinct) {
550
                            $newSQL = "SELECT TOP $count * FROM
551
                                        (
552
                                            SELECT ROW_NUMBER()
553
                                                OVER (ORDER BY " . preg_replace('/^' . $dist_str . '\s+/', '', $this->returnOrderBy($sql, $orderByMatch[3])) . ") AS row_number,
554
                                                count(*) counter, " . $distinctSQLARRAY[0] . "
555
                                                " . $distinctSQLARRAY[1] . "
556
                                                group by " . $grpByStr . "
557
                                        ) AS a
558
                                        WHERE row_number > $start";
559
                        }
560
                        else {
561
                        $newSQL = "SELECT TOP $count * FROM
562
                                    (
563
                                        " . $matches[1] . " ROW_NUMBER()
564
                                        OVER (ORDER BY " . $this->returnOrderBy($sql, $orderByMatch[3]) . ") AS row_number,
565
                                        " . $matches[2] . $orderByMatch[1]. "
566
                                    ) AS a
567
                                    WHERE row_number > $start";
568
                        }
569
                    }else{
570
                        //if there is a distinct clause, form query with rownumber after distinct
571
                        if ($hasDistinct) {
572
                             $newSQL = "SELECT TOP $count * FROM
573
                                            (
574
                            SELECT ROW_NUMBER() OVER (ORDER BY ".$grpByStr.") AS row_number, count(*) counter, " . $distinctSQLARRAY[0] . "
575
                                                        " . $distinctSQLARRAY[1] . "
576
                                                    group by " . $grpByStr . "
577
                                            )
578
                                            AS a
579
                                            WHERE row_number > $start";
580
                        }
581
                        else {
582
                             $newSQL = "SELECT TOP $count * FROM
583
                                           (
584
                                  " . $matches[1] . " ROW_NUMBER() OVER (ORDER BY " . $sqlArray['FROM'][0]['alias'] . ".id) AS row_number, " . $matches[2] . $matches[3]. "
585
                                           )
586
                                           AS a
587
                                           WHERE row_number > $start";
588
                        }
589
                    }
590
                }
591
            }
592
        }
593
594
        $GLOBALS['log']->debug('Limit Query: ' . $newSQL);
595
        if($execute) {
596
            $result =  $this->query($newSQL, $dieOnError, $msg);
597
            $this->dump_slow_queries($newSQL);
598
            return $result;
599
        } else {
600
            return $newSQL;
601
        }
602
    }
603
604
605
    /**
606
     * Searches for begginning and ending characters.  It places contents into
607
     * an array and replaces contents in original string.  This is used to account for use of
608
     * nested functions while aliasing column names
609
     *
610
     * @param  string $p_sql     SQL statement
611
     * @param  string $strip_beg Beginning character
612
     * @param  string $strip_end Ending character
613
     * @param  string $patt      Optional, pattern to
614
     */
615
    private function removePatternFromSQL($p_sql, $strip_beg, $strip_end, $patt = 'patt')
616
    {
617
        //strip all single quotes out
618
        $count = substr_count ( $p_sql, $strip_beg);
619
        $increment = 1;
620
        if ($strip_beg != $strip_end)
621
            $increment = 2;
622
623
        $i=0;
624
        $offset = 0;
625
        $strip_array = array();
626
        while ($i<$count && $offset<strlen($p_sql)) {
627
            if ($offset > strlen($p_sql))
628
            {
629
				break;
630
            }
631
632
            $beg_sin = strpos($p_sql, $strip_beg, $offset);
633
            if (!$beg_sin)
634
            {
635
                break;
636
            }
637
            $sec_sin = strpos($p_sql, $strip_end, $beg_sin+1);
638
            $strip_array[$patt.$i] = substr($p_sql, $beg_sin, $sec_sin - $beg_sin +1);
639
            if ($increment > 1) {
640
                //we are in here because beginning and end patterns are not identical, so search for nesting
641
                $exists = strpos($strip_array[$patt.$i], $strip_beg );
642
                if ($exists>=0) {
643
                    $nested_pos = (strrpos($strip_array[$patt.$i], $strip_beg ));
644
                    $strip_array[$patt.$i] = substr($p_sql,$nested_pos+$beg_sin,$sec_sin - ($nested_pos+$beg_sin)+1);
645
                    $p_sql = substr($p_sql, 0, $nested_pos+$beg_sin) . " ##". $patt.$i."## " . substr($p_sql, $sec_sin+1);
646
                    $i = $i + 1;
647
                    continue;
648
                }
649
            }
650
            $p_len = strlen("##". $patt.$i."##");
651
            $p_sql = substr($p_sql, 0, $beg_sin) . " ##". $patt.$i."## " . substr($p_sql, $sec_sin+1);
652
            //move the marker up
653
            $offset = ($sec_sin-($sec_sin-$beg_sin))+$p_len+1; // Adjusting the starting point of the marker
654
655
            $i = $i + 1;
656
        }
657
        $strip_array['sql_string'] = $p_sql;
658
659
        return $strip_array;
660
    }
661
662
    /**
663
     * adds a pattern
664
     *
665
     * @param  string $token
666
     * @param  array  $pattern_array
667
     * @return string
668
     */
669
	private function addPatternToSQL($token, array $pattern_array)
670
    {
671
        //strip all single quotes out
672
        $pattern_array = array_reverse($pattern_array);
673
674
        foreach ($pattern_array as $key => $replace) {
675
            $token = str_replace( " ##".$key."## ", $replace,$token);
676
        }
677
678
        return $token;
679
    }
680
681
    /**
682
     * gets an alias from the sql statement
683
     *
684
     * @param  string $sql
685
     * @param  string $alias
686
     * @return string
687
     */
688
	private function getAliasFromSQL($sql, $alias)
689
    {
690
        $matches = array();
691
        preg_match('/^(.*SELECT)(.*?FROM.*WHERE)(.*)$/isU',$sql, $matches);
692
        //parse all single and double  quotes out of array
693
        $sin_array = $this->removePatternFromSQL($matches[2], "'", "'","sin_");
694
        $new_sql = array_pop($sin_array);
695
        $dub_array = $this->removePatternFromSQL($new_sql, "\"", "\"","dub_");
696
        $new_sql = array_pop($dub_array);
697
698
        //search for parenthesis
699
        $paren_array = $this->removePatternFromSQL($new_sql, "(", ")", "par_");
700
        $new_sql = array_pop($paren_array);
701
702
        //all functions should be removed now, so split the array on commas
703
        $mstr_sql_array = explode(",", $new_sql);
704
        foreach($mstr_sql_array as $token ) {
705
            if (strpos($token, $alias)) {
706
                //found token, add back comments
707
                $token = $this->addPatternToSQL($token, $paren_array);
708
                $token = $this->addPatternToSQL($token, $dub_array);
709
                $token = $this->addPatternToSQL($token, $sin_array);
710
711
                //log and break out of this function
712
                return $token;
713
            }
714
        }
715
        return null;
716
    }
717
718
719
    /**
720
     * Finds the alias of the order by column, and then return the preceding column name
721
     *
722
     * @param  string $sql
723
     * @param  string $orderMatch
724
     * @return string
725
     */
726
    private function findColumnByAlias($sql, $orderMatch)
727
    {
728
        //change case to lowercase
729
        $sql = strtolower($sql);
730
        $patt = '/\s+'.trim($orderMatch).'\s*(,|from)/';
731
732
        //check for the alias, it should contain comma, may contain space, \n, or \t
733
        $matches = array();
734
        preg_match($patt, $sql, $matches, PREG_OFFSET_CAPTURE);
735
        $found_in_sql = isset($matches[0][1]) ? $matches[0][1] : false;
736
737
738
        //set default for found variable
739
        $found = $found_in_sql;
740
741
        //if still no match found, then we need to parse through the string
742
        if (!$found_in_sql){
0 ignored issues
show
Bug Best Practice introduced by
The expression $found_in_sql of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
743
            //get count of how many times the match exists in string
744
            $found_count = substr_count($sql, $orderMatch);
745
            $i = 0;
746
            $first_ = 0;
747
            $len = strlen($orderMatch);
748
            //loop through string as many times as there is a match
749
            while ($found_count > $i) {
750
                //get the first match
751
                $found_in_sql = strpos($sql, $orderMatch,$first_);
752
                //make sure there was a match
753
                if($found_in_sql){
754
                    //grab the next 2 individual characters
755
                    $str_plusone = substr($sql,$found_in_sql + $len,1);
756
                    $str_plustwo = substr($sql,$found_in_sql + $len+1,1);
757
                    //if one of those characters is a comma, then we have our alias
758
                    if ($str_plusone === "," || $str_plustwo === ","){
759
                        //keep track of this position
760
                        $found = $found_in_sql;
761
                    }
762
                }
763
                //set the offset and increase the iteration counter
764
                $first_ = $found_in_sql+$len;
765
                $i = $i+1;
766
            }
767
        }
768
        //return $found, defaults have been set, so if no match was found it will be a negative number
769
        return $found;
770
    }
771
772
773
    /**
774
     * Return the order by string to use in case the column has been aliased
775
     *
776
     * @param  string $sql
777
     * @param  string $orig_order_match
778
     * @return string
779
     */
780
    private function returnOrderBy($sql, $orig_order_match)
781
    {
782
        $sql = strtolower($sql);
783
        $orig_order_match = trim($orig_order_match);
784
        if (strpos($orig_order_match, ".") != 0)
785
            //this has a tablename defined, pass in the order match
786
            return $orig_order_match;
787
788
        // If there is no ordering direction (ASC/DESC), use ASC by default
789
        if (strpos($orig_order_match, " ") === false) {
790
        	$orig_order_match .= " ASC";
791
        }
792
793
        //grab first space in order by
794
        $firstSpace = strpos($orig_order_match, " ");
795
796
        //split order by into column name and ascending/descending
797
        $orderMatch = " " . strtolower(substr($orig_order_match, 0, $firstSpace));
798
        $asc_desc = trim(substr($orig_order_match,$firstSpace));
799
800
        //look for column name as an alias in sql string
801
        $found_in_sql = $this->findColumnByAlias($sql, $orderMatch);
802
803
        if (!$found_in_sql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $found_in_sql of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
804
            //check if this column needs the tablename prefixed to it
805
            $orderMatch = ".".trim($orderMatch);
806
            $colMatchPos = strpos($sql, $orderMatch);
807
            if ($colMatchPos !== false) {
808
                //grab sub string up to column name
809
                $containsColStr = substr($sql,0, $colMatchPos);
810
                //get position of first space, so we can grab table name
811
                $lastSpacePos = strrpos($containsColStr, " ");
812
                //use positions of column name, space before name, and length of column to find the correct column name
813
                $col_name = substr($sql, $lastSpacePos, $colMatchPos-$lastSpacePos+strlen($orderMatch));
814
				//bug 25485. When sorting by a custom field in Account List and then pressing NEXT >, system gives an error
815
				$containsCommaPos = strpos($col_name, ",");
816
				if($containsCommaPos !== false) {
817
					$col_name = substr($col_name, $containsCommaPos+1);
818
				}
819
                //add the "asc/desc" order back
820
                $col_name = $col_name. " ". $asc_desc;
821
822
                //return column name
823
                return $col_name;
824
            }
825
            //break out of here, log this
826
            $GLOBALS['log']->debug("No match was found for order by, pass string back untouched as: $orig_order_match");
827
            return $orig_order_match;
828
        }
829
        else {
830
            //if found, then parse and return
831
            //grab string up to the aliased column
832
            $GLOBALS['log']->debug("order by found, process sql string");
833
834
            $psql = (trim($this->getAliasFromSQL($sql, $orderMatch )));
835
            if (empty($psql))
836
                $psql = trim(substr($sql, 0, $found_in_sql));
837
838
            //grab the last comma before the alias
839
            preg_match('/\s+' . trim($orderMatch). '/', $psql, $match, PREG_OFFSET_CAPTURE);
840
            $comma_pos = $match[0][1];
841
            //substring between the comma and the alias to find the joined_table alias and column name
842
            $col_name = substr($psql,0, $comma_pos);
843
844
            //make sure the string does not have an end parenthesis
845
            //and is not part of a function (i.e. "ISNULL(leads.last_name,'') as name"  )
846
            //this is especially true for unified search from home screen
847
848
            $alias_beg_pos = 0;
849
            if(strpos($psql, " as "))
850
                $alias_beg_pos = strpos($psql, " as ");
851
852
            // Bug # 44923 - This breaks the query and does not properly filter isnull
853
            // as there are other functions such as ltrim and rtrim.
854
            /* else if (strncasecmp($psql, 'isnull', 6) != 0)
855
                $alias_beg_pos = strpos($psql, " "); */
856
857
            if ($alias_beg_pos > 0) {
858
                $col_name = substr($psql,0, $alias_beg_pos );
859
            }
860
            //add the "asc/desc" order back
861
            $col_name = $col_name. " ". $asc_desc;
862
863
            //pass in new order by
864
            $GLOBALS['log']->debug("order by being returned is " . $col_name);
865
            return $col_name;
866
        }
867
    }
868
869
    /**
870
     * Take in a string of the module and retrieve the correspondent table name
871
     *
872
     * @param  string $module_str module name
873
     * @param  string $sql        SQL statement
874
     * @return string table name
875
     */
876
    private function getTableNameFromModuleName($module_str, $sql)
877
    {
878
879
        global $beanList, $beanFiles;
880
        $GLOBALS['log']->debug("Module being processed is " . $module_str);
881
        //get the right module files
882
        //the module string exists in bean list, then process bean for correct table name
883
        //note that we exempt the reports module from this, as queries from reporting module should be parsed for
884
        //correct table name.
885
        if (($module_str != 'Reports' && $module_str != 'SavedReport') && isset($beanList[$module_str])  &&  isset($beanFiles[$beanList[$module_str]])){
886
            //if the class is not already loaded, then load files
887
            if (!class_exists($beanList[$module_str]))
888
                require_once($beanFiles[$beanList[$module_str]]);
889
890
            //instantiate new bean
891
            $module_bean = new $beanList[$module_str]();
892
            //get table name from bean
893
            $tbl_name = $module_bean->table_name;
894
            //make sure table name is not just a blank space, or empty
895
            $tbl_name = trim($tbl_name);
896
897
            if(empty($tbl_name)){
898
                $GLOBALS['log']->debug("Could not find table name for module $module_str. ");
899
                $tbl_name = $module_str;
900
            }
901
        }
902
        else {
903
            //since the module does NOT exist in beanlist, then we have to parse the string
904
            //and grab the table name from the passed in sql
905
            $GLOBALS['log']->debug("Could not find table name from module in request, retrieve from passed in sql");
906
            $tbl_name = $module_str;
907
            $sql = strtolower($sql);
908
909
            // Bug #45625 : Getting Multi-part identifier (reports.id) could not be bound error when navigating to next page in reprots in mssql
910
            // there is cases when sql string is multiline string and it we cannot find " from " string in it
911
            $sql = str_replace(array("\n", "\r"), " ", $sql);
912
913
            //look for the location of the "from" in sql string
914
            $fromLoc = strpos($sql," from " );
915
            if ($fromLoc>0){
916
                //found from, substring from the " FROM " string in sql to end
917
                $tableEnd = substr($sql, $fromLoc+6);
918
                //We know that tablename will be next parameter after from, so
919
                //grab the next space after table name.
920
                // MFH BUG #14009: Also check to see if there are any carriage returns before the next space so that we don't grab any arbitrary joins or other tables.
921
                $carriage_ret = strpos($tableEnd,"\n");
922
                $next_space = strpos($tableEnd," " );
923
                if ($carriage_ret < $next_space)
924
                    $next_space = $carriage_ret;
925
                if ($next_space > 0) {
926
                    $tbl_name= substr($tableEnd,0, $next_space);
927
                    if(empty($tbl_name)){
928
                        $GLOBALS['log']->debug("Could not find table name sql either, return $module_str. ");
929
                        $tbl_name = $module_str;
930
                    }
931
                }
932
933
                //grab the table, to see if it is aliased
934
                $aliasTableEnd = trim(substr($tableEnd, $next_space));
935
                $alias_space = strpos ($aliasTableEnd, " " );
936
                if ($alias_space > 0){
937
                    $alias_tbl_name= substr($aliasTableEnd,0, $alias_space);
938
                    strtolower($alias_tbl_name);
939
                    if(empty($alias_tbl_name)
0 ignored issues
show
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
940
                        || $alias_tbl_name == "where"
941
                        || $alias_tbl_name == "inner"
942
                        || $alias_tbl_name == "left"
943
                        || $alias_tbl_name == "join"
944
                        || $alias_tbl_name == "outer"
945
                        || $alias_tbl_name == "right") {
946
                        //not aliased, do nothing
947
                    }
948
                    elseif ($alias_tbl_name == "as") {
949
                            //the next word is the table name
950
                            $aliasTableEnd = trim(substr($aliasTableEnd, $alias_space));
951
                            $alias_space = strpos ($aliasTableEnd, " " );
952
                            if ($alias_space > 0) {
953
                                $alias_tbl_name= trim(substr($aliasTableEnd,0, $alias_space));
954
                                if (!empty($alias_tbl_name))
955
                                    $tbl_name = $alias_tbl_name;
956
                            }
957
                    }
958
                    else {
959
                        //this is table alias
960
                        $tbl_name = $alias_tbl_name;
961
                    }
962
                }
963
            }
964
        }
965
        //return table name
966
        $GLOBALS['log']->debug("Table name for module $module_str is: ".$tbl_name);
967
        return $tbl_name;
968
    }
969
970
971
	/**
972
     * @see DBManager::getFieldsArray()
973
     */
974
	public function getFieldsArray($result, $make_lower_case = false)
975
	{
976
		$field_array = array();
977
978
		if(! isset($result) || empty($result))
979
            return 0;
980
981
        $i = 0;
982
        while ($i < mssql_num_fields($result)) {
983
            $meta = mssql_fetch_field($result, $i);
984
            if (!$meta)
985
                return 0;
986
            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...
987
                $meta->name = strtolower($meta->name);
988
989
            $field_array[] = $meta->name;
990
991
            $i++;
992
        }
993
994
        return $field_array;
995
	}
996
997
    /**
998
     * @see DBManager::getAffectedRowCount()
999
     */
1000
	public function getAffectedRowCount($result)
1001
    {
1002
        return $this->getOne("SELECT @@ROWCOUNT");
1003
    }
1004
1005
	/**
1006
	 * @see DBManager::fetchRow()
1007
	 */
1008
	public function fetchRow($result)
1009
	{
1010
		if (empty($result))	return false;
1011
1012
        $row = mssql_fetch_assoc($result);
1013
        //MSSQL returns a space " " when a varchar column is empty ("") and not null.
1014
        //We need to iterate through the returned row array and strip empty spaces
1015
        if(!empty($row)){
1016
            foreach($row as $key => $column) {
1017
               //notice we only strip if one space is returned.  we do not want to strip
1018
               //strings with intentional spaces (" foo ")
1019
               if (!empty($column) && $column ==" ") {
1020
                   $row[$key] = '';
1021
               }
1022
            }
1023
        }
1024
        return $row;
1025
	}
1026
1027
    /**
1028
     * @see DBManager::quote()
1029
     */
1030
    public function quote($string)
1031
    {
1032
        if(is_array($string)) {
1033
            return $this->arrayQuote($string);
1034
        }
1035
        return str_replace("'","''", $this->quoteInternal($string));
1036
    }
1037
1038
    /**
1039
     * @see DBManager::quoteIdentifier()
1040
     */
1041
    public function quoteIdentifier($string)
1042
    {
1043
        return '['.$string.']';
1044
    }
1045
1046
    /**
1047
     * @see DBManager::tableExists()
1048
     */
1049
    public function tableExists($tableName)
1050
    {
1051
        $GLOBALS['log']->info("tableExists: $tableName");
1052
1053
        $this->checkConnection();
1054
        $result = $this->getOne(
1055
            "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME=".$this->quoted($tableName));
1056
1057
        return !empty($result);
1058
    }
1059
1060
    /**
1061
     * Get tables like expression
1062
     * @param $like string
1063
     * @return array
1064
     */
1065
    public function tablesLike($like)
1066
    {
1067
        if ($this->getDatabase()) {
1068
            $tables = array();
1069
            $r = $this->query('SELECT TABLE_NAME tn FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE=\'BASE TABLE\' AND TABLE_NAME LIKE '.$this->quoted($like));
1070
            if (!empty($r)) {
1071
                while ($a = $this->fetchByAssoc($r)) {
1072
                    $row = array_values($a);
1073
					$tables[]=$row[0];
1074
                }
1075
                return $tables;
1076
            }
1077
        }
1078
        return false;
1079
    }
1080
1081
    /**
1082
     * @see DBManager::getTablesArray()
1083
     */
1084
    public function getTablesArray()
1085
    {
1086
        $GLOBALS['log']->debug('MSSQL fetching table list');
1087
1088
        if($this->getDatabase()) {
1089
            $tables = array();
1090
            $r = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES');
1091
            if (is_resource($r)) {
1092
                while ($a = $this->fetchByAssoc($r))
1093
                    $tables[] = $a['TABLE_NAME'];
1094
1095
                return $tables;
1096
            }
1097
        }
1098
1099
        return false; // no database available
1100
    }
1101
1102
1103
    /**
1104
     * This call is meant to be used during install, when Full Text Search is enabled
1105
     * Indexing would always occur after a fresh sql server install, so this code creates
1106
     * a catalog and table with full text index.
1107
     */
1108
    public function full_text_indexing_setup()
1109
    {
1110
        $GLOBALS['log']->debug('MSSQL about to wakeup FTS');
1111
1112
        if($this->getDatabase()) {
1113
                //create wakeup catalog
1114
                $FTSqry[] = "if not exists(  select * from sys.fulltext_catalogs where name ='wakeup_catalog' )
1115
                CREATE FULLTEXT CATALOG wakeup_catalog
1116
                ";
1117
1118
                //drop wakeup table if it exists
1119
                $FTSqry[] = "IF EXISTS(SELECT 'fts_wakeup' FROM sysobjects WHERE name = 'fts_wakeup' AND xtype='U')
1120
                    DROP TABLE fts_wakeup
1121
                ";
1122
                //create wakeup table
1123
                $FTSqry[] = "CREATE TABLE fts_wakeup(
1124
                    id varchar(36) NOT NULL CONSTRAINT pk_fts_wakeup_id PRIMARY KEY CLUSTERED (id ASC ),
1125
                    body text NULL,
1126
                    kb_index int IDENTITY(1,1) NOT NULL CONSTRAINT wakeup_fts_unique_idx UNIQUE NONCLUSTERED
1127
                )
1128
                ";
1129
                //create full text index
1130
                 $FTSqry[] = "CREATE FULLTEXT INDEX ON fts_wakeup
1131
                (
1132
                    body
1133
                    Language 0X0
1134
                )
1135
                KEY INDEX wakeup_fts_unique_idx ON wakeup_catalog
1136
                WITH CHANGE_TRACKING AUTO
1137
                ";
1138
1139
                //insert dummy data
1140
                $FTSqry[] = "INSERT INTO fts_wakeup (id ,body)
1141
                VALUES ('".create_guid()."', 'SugarCRM Rocks' )";
1142
1143
1144
                //create queries to stop and restart indexing
1145
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup STOP POPULATION';
1146
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup DISABLE';
1147
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup ENABLE';
1148
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup SET CHANGE_TRACKING MANUAL';
1149
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup START FULL POPULATION';
1150
                $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup SET CHANGE_TRACKING AUTO';
1151
1152
                foreach($FTSqry as $q){
1153
                    sleep(3);
1154
                    $this->query($q);
1155
                }
1156
                $this->create_default_full_text_catalog();
1157
        }
1158
1159
        return false; // no database available
1160
    }
1161
1162
    protected $date_formats = array(
1163
        '%Y-%m-%d' => 10,
1164
        '%Y-%m' => 7,
1165
        '%Y' => 4,
1166
    );
1167
1168
    /**
1169
     * @see DBManager::convert()
1170
     */
1171
    public function convert($string, $type, array $additional_parameters = array())
1172
    {
1173
        // convert the parameters array into a comma delimited string
1174
        if (!empty($additional_parameters)) {
1175
            $additional_parameters_string = ','.implode(',',$additional_parameters);
1176
        } else {
1177
            $additional_parameters_string = '';
1178
        }
1179
        $all_parameters = $additional_parameters;
1180
        if(is_array($string)) {
1181
            $all_parameters = array_merge($string, $all_parameters);
1182
        } elseif (!is_null($string)) {
1183
            array_unshift($all_parameters, $string);
1184
        }
1185
1186
        switch (strtolower($type)) {
1187
            case 'today':
1188
                return "GETDATE()";
1189
            case 'left':
1190
                return "LEFT($string$additional_parameters_string)";
1191
            case 'date_format':
0 ignored issues
show
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1192
                if(!empty($additional_parameters[0]) && $additional_parameters[0][0] == "'") {
1193
                    $additional_parameters[0] = trim($additional_parameters[0], "'");
1194
                }
1195
                if(!empty($additional_parameters) && isset($this->date_formats[$additional_parameters[0]])) {
1196
                    $len = $this->date_formats[$additional_parameters[0]];
1197
                    return "LEFT(CONVERT(varchar($len),". $string . ",120),$len)";
1198
                } else {
1199
                   return "LEFT(CONVERT(varchar(10),". $string . ",120),10)";
1200
                }
1201
            case 'ifnull':
1202
                if(empty($additional_parameters_string)) {
1203
                    $additional_parameters_string = ",''";
1204
                }
1205
                return "ISNULL($string$additional_parameters_string)";
1206
            case 'concat':
1207
                return implode("+",$all_parameters);
1208
            case 'text2char':
1209
                return "CAST($string AS varchar(8000))";
1210
            case 'quarter':
1211
                return "DATENAME(quarter, $string)";
1212
            case "length":
1213
                return "LEN($string)";
1214
            case 'month':
1215
                return "MONTH($string)";
1216
            case 'add_date':
1217
                return "DATEADD({$additional_parameters[1]},{$additional_parameters[0]},$string)";
1218
            case 'add_time':
1219
                return "DATEADD(hh, {$additional_parameters[0]}, DATEADD(mi, {$additional_parameters[1]}, $string))";
1220
            case 'add_tz_offset' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1221
                $getUserUTCOffset = $GLOBALS['timedate']->getUserUTCOffset();
1222
                $operation = $getUserUTCOffset < 0 ? '-' : '+';
1223
                return 'DATEADD(minute, ' . $operation . abs($getUserUTCOffset) . ', ' . $string. ')';
1224
            case 'avg':
1225
                return "avg($string)";
1226
        }
1227
1228
        return "$string";
1229
    }
1230
1231
    /**
1232
     * @see DBManager::fromConvert()
1233
     */
1234
    public function fromConvert($string, $type)
1235
    {
1236
        switch($type) {
1237
            case 'datetimecombo':
1238
            case 'datetime': return substr($string, 0,19);
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1239
            case 'date': return substr($string, 0, 10);
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1240
            case 'time': return substr($string, 11);
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1241
		}
1242
		return $string;
1243
    }
1244
1245
    /**
1246
     * @see DBManager::createTableSQLParams()
1247
     */
1248
	public function createTableSQLParams($tablename, $fieldDefs, $indices)
1249
    {
1250
        if (empty($tablename) || empty($fieldDefs))
1251
            return '';
1252
1253
        $columns = $this->columnSQLRep($fieldDefs, false, $tablename);
1254
        if (empty($columns))
1255
            return '';
1256
1257
        return "CREATE TABLE $tablename ($columns)";
1258
    }
1259
1260
    /**
1261
     * Does this type represent text (i.e., non-varchar) value?
1262
     * @param string $type
1263
     */
1264
    public function isTextType($type)
1265
    {
1266
        $type = strtolower($type);
1267
        if(!isset($this->type_map[$type])) return false;
1268
        return in_array($this->type_map[$type], array('ntext','text','image', 'nvarchar(max)'));
1269
    }
1270
1271
    /**
1272
     * Return representation of an empty value depending on type
1273
     * @param string $type
1274
     */
1275
    public function emptyValue($type)
1276
    {
1277
        $ctype = $this->getColumnType($type);
1278
        if($ctype == "datetime") {
1279
            return $this->convert($this->quoted("1970-01-01 00:00:00"), "datetime");
1280
        }
1281
        if($ctype == "date") {
1282
            return $this->convert($this->quoted("1970-01-01"), "datetime");
1283
        }
1284
        if($ctype == "time") {
1285
            return $this->convert($this->quoted("00:00:00"), "time");
1286
        }
1287
        return parent::emptyValue($type);
1288
    }
1289
1290
    public function renameColumnSQL($tablename, $column, $newname)
1291
    {
1292
        return "SP_RENAME '$tablename.$column', '$newname', 'COLUMN'";
1293
    }
1294
1295
    /**
1296
     * Returns the SQL Alter table statment
1297
     *
1298
     * MSSQL has a quirky T-SQL alter table syntax. Pay special attention to the
1299
     * modify operation
1300
     * @param string $action
1301
     * @param array  $def
1302
     * @param bool   $ignorRequired
0 ignored issues
show
There is no parameter named $ignorRequired. Did you maybe mean $ignoreRequired?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1303
     * @param string $tablename
1304
     */
1305
    protected function alterSQLRep($action, array $def, $ignoreRequired, $tablename)
1306
    {
1307
        switch($action){
1308
        case 'add':
1309
             $f_def=$this->oneColumnSQLRep($def, $ignoreRequired,$tablename,false);
1310
            return "ADD " . $f_def;
1311
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1312
        case 'drop':
1313
            return "DROP COLUMN " . $def['name'];
1314
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1315
        case 'modify':
1316
            //You cannot specify a default value for a column for MSSQL
1317
            $f_def  = $this->oneColumnSQLRep($def, $ignoreRequired,$tablename, true);
1318
            $f_stmt = "ALTER COLUMN ".$f_def['name'].' '.$f_def['colType'].' '.
1319
                        $f_def['required'].' '.$f_def['auto_increment']."\n";
1320
            if (!empty( $f_def['default']))
1321
                $f_stmt .= " ALTER TABLE " . $tablename .  " ADD  ". $f_def['default'] . " FOR " . $def['name'];
1322
            return $f_stmt;
1323
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1324
        default:
1325
            return '';
1326
    	}
1327
    }
1328
1329
    /**
1330
     * @see DBManager::changeColumnSQL()
1331
     *
1332
     * MSSQL uses a different syntax than MySQL for table altering that is
1333
     * not quite as simplistic to implement...
1334
     */
1335
    protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false)
1336
    {
1337
        $sql=$sql2='';
1338
        $constraints = $this->get_field_default_constraint_name($tablename);
1339
        $columns = array();
1340
        if ($this->isFieldArray($fieldDefs)) {
1341
            foreach ($fieldDefs as $def)
1342
      		{
1343
          		//if the column is being modified drop the default value
1344
          		//constraint if it exists. alterSQLRep will add the constraint back
1345
          		if (!empty($constraints[$def['name']])) {
1346
          			$sql.=" ALTER TABLE " . $tablename . " DROP CONSTRAINT " . $constraints[$def['name']];
1347
          		}
1348
          		//check to see if we need to drop related indexes before the alter
1349
          		$indices = $this->get_indices($tablename);
1350
                foreach ( $indices as $index ) {
1351
                    if ( in_array($def['name'],$index['fields']) ) {
1352
                        $sql  .= ' ' . $this->add_drop_constraint($tablename,$index,true).' ';
1353
                        $sql2 .= ' ' . $this->add_drop_constraint($tablename,$index,false).' ';
1354
                    }
1355
                }
1356
1357
          		$columns[] = $this->alterSQLRep($action, $def, $ignoreRequired,$tablename);
1358
      		}
1359
        }
1360
        else {
1361
            //if the column is being modified drop the default value
1362
      		//constraint if it exists. alterSQLRep will add the constraint back
1363
      		if (!empty($constraints[$fieldDefs['name']])) {
1364
      			$sql.=" ALTER TABLE " . $tablename . " DROP CONSTRAINT " . $constraints[$fieldDefs['name']];
1365
      		}
1366
      		//check to see if we need to drop related indexes before the alter
1367
            $indices = $this->get_indices($tablename);
1368
            foreach ( $indices as $index ) {
1369
                if ( in_array($fieldDefs['name'],$index['fields']) ) {
1370
                    $sql  .= ' ' . $this->add_drop_constraint($tablename,$index,true).' ';
1371
                    $sql2 .= ' ' . $this->add_drop_constraint($tablename,$index,false).' ';
1372
                }
1373
            }
1374
1375
1376
          	$columns[] = $this->alterSQLRep($action, $fieldDefs, $ignoreRequired,$tablename);
1377
        }
1378
1379
        $columns = implode(", ", $columns);
1380
        $sql .= " ALTER TABLE $tablename $columns " . $sql2;
1381
1382
        return $sql;
1383
    }
1384
1385
    protected function setAutoIncrement($table, $field_name)
1386
    {
1387
		return "identity(1,1)";
1388
	}
1389
1390
    /**
1391
     * @see DBManager::setAutoIncrementStart()
1392
     */
1393
    public function setAutoIncrementStart($table, $field_name, $start_value)
1394
    {
1395
        if($start_value > 1)
1396
            $start_value -= 1;
1397
		$this->query("DBCC CHECKIDENT ('$table', RESEED, $start_value) WITH NO_INFOMSGS");
1398
        return true;
1399
    }
1400
1401
	/**
1402
     * @see DBManager::getAutoIncrement()
1403
     */
1404
    public function getAutoIncrement($table, $field_name)
1405
    {
1406
		$result = $this->getOne("select IDENT_CURRENT('$table') + IDENT_INCR ( '$table' ) as 'Auto_increment'");
1407
        return $result;
1408
    }
1409
1410
    /**
1411
     * @see DBManager::get_indices()
1412
     */
1413
    public function get_indices($tableName)
1414
    {
1415
        //find all unique indexes and primary keys.
1416
        $query = <<<EOSQL
1417
SELECT sys.tables.object_id, sys.tables.name as table_name, sys.columns.name as column_name,
1418
                sys.indexes.name as index_name, sys.indexes.is_unique, sys.indexes.is_primary_key
1419
            FROM sys.tables, sys.indexes, sys.index_columns, sys.columns
1420
            WHERE (sys.tables.object_id = sys.indexes.object_id
1421
                    AND sys.tables.object_id = sys.index_columns.object_id
1422
                    AND sys.tables.object_id = sys.columns.object_id
1423
                    AND sys.indexes.index_id = sys.index_columns.index_id
1424
                    AND sys.index_columns.column_id = sys.columns.column_id)
1425
                AND sys.tables.name = '$tableName'
1426
EOSQL;
1427
        $result = $this->query($query);
1428
1429
        $indices = array();
1430
        while (($row=$this->fetchByAssoc($result)) != null) {
1431
            $index_type = 'index';
1432
            if ($row['is_primary_key'] == '1')
1433
                $index_type = 'primary';
1434
            elseif ($row['is_unique'] == 1 )
1435
                $index_type = 'unique';
1436
            $name = strtolower($row['index_name']);
1437
            $indices[$name]['name']     = $name;
1438
            $indices[$name]['type']     = $index_type;
1439
            $indices[$name]['fields'][] = strtolower($row['column_name']);
1440
        }
1441
        return $indices;
1442
    }
1443
1444
    /**
1445
     * @see DBManager::get_columns()
1446
     */
1447
    public function get_columns($tablename)
1448
    {
1449
        //find all unique indexes and primary keys.
1450
        $result = $this->query("sp_columns $tablename");
1451
1452
        $columns = array();
1453
        while (($row=$this->fetchByAssoc($result)) !=null) {
1454
            $column_name = strtolower($row['COLUMN_NAME']);
1455
            $columns[$column_name]['name']=$column_name;
1456
            $columns[$column_name]['type']=strtolower($row['TYPE_NAME']);
1457
            if ( $row['TYPE_NAME'] == 'decimal' ) {
1458
                $columns[$column_name]['len']=strtolower($row['PRECISION']);
1459
                $columns[$column_name]['len'].=','.strtolower($row['SCALE']);
1460
            }
1461
			elseif ( in_array($row['TYPE_NAME'],array('nchar','nvarchar')) )
1462
				$columns[$column_name]['len']=strtolower($row['PRECISION']);
1463
            elseif ( !in_array($row['TYPE_NAME'],array('datetime','text')) )
1464
                $columns[$column_name]['len']=strtolower($row['LENGTH']);
1465
            if ( stristr($row['TYPE_NAME'],'identity') ) {
1466
                $columns[$column_name]['auto_increment'] = '1';
1467
                $columns[$column_name]['type']=str_replace(' identity','',strtolower($row['TYPE_NAME']));
1468
            }
1469
1470
            if (!empty($row['IS_NULLABLE']) && $row['IS_NULLABLE'] == 'NO' && (empty($row['KEY']) || !stristr($row['KEY'],'PRI')))
1471
                $columns[strtolower($row['COLUMN_NAME'])]['required'] = 'true';
1472
1473
            $column_def = 1;
1474
            if ( strtolower($tablename) == 'relationships' ) {
1475
                $column_def = $this->getOne("select cdefault from syscolumns where id = object_id('relationships') and name = '$column_name'");
1476
            }
1477
            if ( $column_def != 0 && ($row['COLUMN_DEF'] != null)) {	// NOTE Not using !empty as an empty string may be a viable default value.
1478
                $matches = array();
1479
                $row['COLUMN_DEF'] = html_entity_decode($row['COLUMN_DEF'],ENT_QUOTES);
1480
                if ( preg_match('/\([\(|\'](.*)[\)|\']\)/i',$row['COLUMN_DEF'],$matches) )
1481
                    $columns[$column_name]['default'] = $matches[1];
1482
                elseif ( preg_match('/\(N\'(.*)\'\)/i',$row['COLUMN_DEF'],$matches) )
1483
                    $columns[$column_name]['default'] = $matches[1];
1484
                else
1485
                    $columns[$column_name]['default'] = $row['COLUMN_DEF'];
1486
            }
1487
        }
1488
        return $columns;
1489
    }
1490
1491
1492
    /**
1493
     * Get FTS catalog name for current DB
1494
     */
1495
    protected function ftsCatalogName()
1496
    {
1497
        if(isset($this->connectOptions['db_name'])) {
1498
            return $this->connectOptions['db_name']."_fts_catalog";
1499
        }
1500
        return 'sugar_fts_catalog';
1501
    }
1502
1503
    /**
1504
     * @see DBManager::add_drop_constraint()
1505
     */
1506
    public function add_drop_constraint($table, $definition, $drop = false)
1507
    {
1508
        $type         = $definition['type'];
1509
        $fields       = is_array($definition['fields'])?implode(',',$definition['fields']):$definition['fields'];
1510
        $name         = $definition['name'];
1511
        $sql          = '';
1512
1513
        switch ($type){
1514
        // generic indices
1515
        case 'index':
1516
        case 'alternate_key':
1517
            if ($drop)
1518
                $sql = "DROP INDEX {$name} ON {$table}";
1519
            else
1520
                $sql = "CREATE INDEX {$name} ON {$table} ({$fields})";
1521
            break;
1522
        case 'clustered':
1523
            if ($drop)
1524
                $sql = "DROP INDEX {$name} ON {$table}";
1525
            else
1526
                $sql = "CREATE CLUSTERED INDEX $name ON $table ($fields)";
1527
            break;
1528
            // constraints as indices
1529
        case 'unique':
1530
            if ($drop)
1531
                $sql = "ALTER TABLE {$table} DROP CONSTRAINT $name";
1532
            else
1533
                $sql = "ALTER TABLE {$table} ADD CONSTRAINT {$name} UNIQUE ({$fields})";
1534
            break;
1535
        case 'primary':
1536
            if ($drop)
1537
                $sql = "ALTER TABLE {$table} DROP CONSTRAINT {$name}";
1538
            else
1539
                $sql = "ALTER TABLE {$table} ADD CONSTRAINT {$name} PRIMARY KEY ({$fields})";
1540
            break;
1541
        case 'foreign':
1542
            if ($drop)
1543
                $sql = "ALTER TABLE {$table} DROP FOREIGN KEY ({$fields})";
1544
            else
1545
                $sql = "ALTER TABLE {$table} ADD CONSTRAINT {$name}  FOREIGN KEY ({$fields}) REFERENCES {$definition['foreignTable']}({$definition['foreignFields']})";
1546
            break;
1547
        case 'fulltext':
1548
            if ($this->full_text_indexing_enabled() && $drop) {
1549
                $sql = "DROP FULLTEXT INDEX ON {$table}";
1550
            } elseif ($this->full_text_indexing_enabled()) {
1551
                $catalog_name=$this->ftsCatalogName();
1552
                if ( isset($definition['catalog_name']) && $definition['catalog_name'] != 'default')
1553
                    $catalog_name = $definition['catalog_name'];
1554
1555
                $language = "Language 1033";
1556
                if (isset($definition['language']) && !empty($definition['language']))
1557
                    $language = "Language " . $definition['language'];
1558
1559
                $key_index = $definition['key_index'];
1560
1561
                $change_tracking = "auto";
1562
                if (isset($definition['change_tracking']) && !empty($definition['change_tracking']))
1563
                    $change_tracking = $definition['change_tracking'];
1564
1565
                $sql = " CREATE FULLTEXT INDEX ON $table ($fields $language) KEY INDEX $key_index ON $catalog_name WITH CHANGE_TRACKING $change_tracking" ;
1566
            }
1567
            break;
1568
        }
1569
        return $sql;
1570
    }
1571
1572
    /**
1573
     * Returns true if Full Text Search is installed
1574
     *
1575
     * @return bool
1576
     */
1577
    public function full_text_indexing_installed()
1578
    {
1579
        $ftsChckRes = $this->getOne("SELECT FULLTEXTSERVICEPROPERTY('IsFulltextInstalled') as fts");
1580
        return !empty($ftsChckRes);
1581
    }
1582
1583
    /**
1584
     * @see DBManager::full_text_indexing_enabled()
1585
     */
1586
    protected function full_text_indexing_enabled($dbname = null)
1587
    {
1588
        // check to see if we already have install setting in session
1589
    	if(!isset($_SESSION['IsFulltextInstalled']))
1590
            $_SESSION['IsFulltextInstalled'] = $this->full_text_indexing_installed();
1591
1592
        // check to see if FTS Indexing service is installed
1593
        if(empty($_SESSION['IsFulltextInstalled']))
1594
            return false;
1595
1596
        // grab the dbname if it was not passed through
1597
		if (empty($dbname)) {
1598
			global $sugar_config;
1599
			$dbname = $sugar_config['dbconfig']['db_name'];
1600
		}
1601
        //we already know that Indexing service is installed, now check
1602
        //to see if it is enabled
1603
		$res = $this->getOne("SELECT DATABASEPROPERTY('$dbname', 'IsFulltextEnabled') ftext");
1604
        return !empty($res);
1605
	}
1606
1607
    /**
1608
     * Creates default full text catalog
1609
     */
1610
	protected function create_default_full_text_catalog()
1611
    {
1612
		if ($this->full_text_indexing_enabled()) {
1613
		    $catalog = $this->ftsCatalogName();
1614
            $GLOBALS['log']->debug("Creating the default catalog for full-text indexing, $catalog");
1615
1616
            //drop catalog if exists.
1617
			$ret = $this->query("
1618
                if not exists(
1619
                    select *
1620
                        from sys.fulltext_catalogs
1621
                        where name ='$catalog'
1622
                        )
1623
                CREATE FULLTEXT CATALOG $catalog");
1624
1625
			if (empty($ret)) {
1626
				$GLOBALS['log']->error("Error creating default full-text catalog, $catalog");
1627
			}
1628
		}
1629
	}
1630
1631
    /**
1632
     * Function returns name of the constraint automatically generated by sql-server.
1633
     * We request this for default, primary key, required
1634
     *
1635
     * @param  string $table
1636
     * @param  string $column
1637
     * @return string
1638
     */
1639
	private function get_field_default_constraint_name($table, $column = null)
1640
    {
1641
        static $results = array();
1642
1643
        if ( empty($column) && isset($results[$table]) )
1644
            return $results[$table];
1645
1646
        $query = <<<EOQ
1647
select s.name, o.name, c.name dtrt, d.name ctrt
1648
    from sys.default_constraints as d
1649
        join sys.objects as o
1650
            on o.object_id = d.parent_object_id
1651
        join sys.columns as c
1652
            on c.object_id = o.object_id and c.column_id = d.parent_column_id
1653
        join sys.schemas as s
1654
            on s.schema_id = o.schema_id
1655
    where o.name = '$table'
1656
EOQ;
1657
        if ( !empty($column) )
1658
            $query .= " and c.name = '$column'";
1659
        $res = $this->query($query);
1660
        if ( !empty($column) ) {
1661
            $row = $this->fetchByAssoc($res);
1662
            if (!empty($row))
1663
                return $row['ctrt'];
1664
        }
1665
        else {
1666
            $returnResult = array();
1667
            while ( $row = $this->fetchByAssoc($res) )
1668
                $returnResult[$row['dtrt']] = $row['ctrt'];
1669
            $results[$table] = $returnResult;
1670
            return $returnResult;
1671
        }
1672
1673
        return null;
1674
	}
1675
1676
    /**
1677
     * @see DBManager::massageFieldDef()
1678
     */
1679
    public function massageFieldDef(&$fieldDef, $tablename)
1680
    {
1681
        parent::massageFieldDef($fieldDef,$tablename);
1682
1683
        if ($fieldDef['type'] == 'int')
1684
            $fieldDef['len'] = '4';
1685
1686
        if(empty($fieldDef['len']))
1687
        {
1688
            switch($fieldDef['type']) {
1689
                case 'bit'      :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1690
                case 'bool'     : $fieldDef['len'] = '1'; break;
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1691
                case 'smallint' : $fieldDef['len'] = '2'; break;
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1692
                case 'float'    : $fieldDef['len'] = '8'; break;
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1693
                case 'varchar'  :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1694
                case 'nvarchar' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1695
                                  $fieldDef['len'] = $this->isTextType($fieldDef['dbType']) ? 'max' : '255';
1696
                                  break;
1697
                case 'image'    : $fieldDef['len'] = '2147483647'; break;
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1698
                case 'ntext'    : $fieldDef['len'] = '2147483646'; break;   // Note: this is from legacy code, don't know if this is correct
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1699
            }
1700
        }
1701
        if($fieldDef['type'] == 'decimal'
1702
           && empty($fieldDef['precision'])
1703
           && !strpos($fieldDef['len'], ','))
1704
        {
1705
             $fieldDef['len'] .= ',0'; // Adding 0 precision if it is not specified
1706
        }
1707
1708
        if(empty($fieldDef['default'])
1709
            && in_array($fieldDef['type'],array('bit','bool')))
1710
        {
1711
            $fieldDef['default'] = '0';
1712
        }
1713
		if (isset($fieldDef['required']) && $fieldDef['required'] && !isset($fieldDef['default']) )
1714
			$fieldDef['default'] = '';
1715
//        if ($fieldDef['type'] == 'bit' && empty($fieldDef['len']) )
1716
//            $fieldDef['len'] = '1';
1717
//		if ($fieldDef['type'] == 'bool' && empty($fieldDef['len']) )
1718
//            $fieldDef['len'] = '1';
1719
//        if ($fieldDef['type'] == 'float' && empty($fieldDef['len']) )
1720
//            $fieldDef['len'] = '8';
1721
//        if ($fieldDef['type'] == 'varchar' && empty($fieldDef['len']) )
1722
//            $fieldDef['len'] = '255';
1723
//		if ($fieldDef['type'] == 'nvarchar' && empty($fieldDef['len']) )
1724
//            $fieldDef['len'] = '255';
1725
//        if ($fieldDef['type'] == 'image' && empty($fieldDef['len']) )
1726
//            $fieldDef['len'] = '2147483647';
1727
//        if ($fieldDef['type'] == 'ntext' && empty($fieldDef['len']) )
1728
//            $fieldDef['len'] = '2147483646';
1729
//        if ($fieldDef['type'] == 'smallint' && empty($fieldDef['len']) )
1730
//            $fieldDef['len'] = '2';
1731
//        if ($fieldDef['type'] == 'bit' && empty($fieldDef['default']) )
1732
//            $fieldDef['default'] = '0';
1733
//		if ($fieldDef['type'] == 'bool' && empty($fieldDef['default']) )
1734
//            $fieldDef['default'] = '0';
1735
1736
    }
1737
1738
    /**
1739
     * @see DBManager::oneColumnSQLRep()
1740
     */
1741
    protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
1742
    {
1743
    	//Bug 25814
1744
		if(isset($fieldDef['name'])){
1745
		    $colType = $this->getFieldType($fieldDef);
1746
        	if(stristr($this->getFieldType($fieldDef), 'decimal') && isset($fieldDef['len'])){
1747
				$fieldDef['len'] = min($fieldDef['len'],38);
1748
			}
1749
		    //bug: 39690 float(8) is interpreted as real and this generates a diff when doing repair
1750
			if(stristr($colType, 'float') && isset($fieldDef['len']) && $fieldDef['len'] == 8){
1751
				unset($fieldDef['len']);
1752
			}
1753
		}
1754
1755
		// always return as array for post-processing
1756
		$ref = parent::oneColumnSQLRep($fieldDef, $ignoreRequired, $table, true);
1757
1758
		// Bug 24307 - Don't add precision for float fields.
1759
		if ( stristr($ref['colType'],'float') )
1760
			$ref['colType'] = preg_replace('/(,\d+)/','',$ref['colType']);
1761
1762
        if ( $return_as_array )
1763
            return $ref;
1764
        else
1765
            return "{$ref['name']} {$ref['colType']} {$ref['default']} {$ref['required']} {$ref['auto_increment']}";
1766
	}
1767
1768
    /**
1769
     * Saves changes to module's audit table
1770
     *
1771
     * @param object $bean    Sugarbean instance
1772
     * @param array  $changes changes
1773
     */
1774
    public function save_audit_records(SugarBean $bean, $changes)
1775
	{
1776
		//Bug 25078 fixed by Martin Hu: sqlserver haven't 'date' type, trim extra "00:00:00"
1777
		if($changes['data_type'] == 'date'){
1778
			$changes['before'] = str_replace(' 00:00:00','',$changes['before']);
1779
		}
1780
		parent::save_audit_records($bean,$changes);
1781
	}
1782
1783
    /**
1784
     * Disconnects from the database
1785
     *
1786
     * Also handles any cleanup needed
1787
     */
1788
    public function disconnect()
1789
    {
1790
    	$GLOBALS['log']->debug('Calling Mssql::disconnect()');
1791
        if(!empty($this->database)){
1792
            $this->freeResult();
1793
            mssql_close($this->database);
1794
            $this->database = null;
1795
        }
1796
    }
1797
1798
    /**
1799
     * @see DBManager::freeDbResult()
1800
     */
1801
    protected function freeDbResult($dbResult)
1802
    {
1803
        if(!empty($dbResult))
1804
            mssql_free_result($dbResult);
1805
    }
1806
1807
	/**
1808
	 * (non-PHPdoc)
1809
	 * @see DBManager::lastDbError()
1810
	 */
1811
    public function lastDbError()
1812
    {
1813
        $sqlmsg = mssql_get_last_message();
1814
        if(empty($sqlmsg)) return false;
1815
        global $app_strings;
1816
        if (empty($app_strings)
1817
		    or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])
1818
			or !isset($app_strings['ERR_MSSQL_WARNING']) ) {
1819
        //ignore the message from sql-server if $app_strings array is empty. This will happen
1820
        //only if connection if made before language is set.
1821
		    return false;
1822
        }
1823
1824
        $sqlpos = strpos($sqlmsg, 'Changed database context to');
1825
        $sqlpos2 = strpos($sqlmsg, 'Warning:');
1826
        $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
1827
        if ( $sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false ) {
1828
            return false;
1829
        } else {
1830
        	global $app_strings;
1831
            //ERR_MSSQL_DB_CONTEXT: localized version of 'Changed database context to' message
1832
            if (empty($app_strings) or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])) {
1833
                //ignore the message from sql-server if $app_strings array is empty. This will happen
1834
                //only if connection if made before languge is set.
1835
                $GLOBALS['log']->debug("Ignoring this database message: " . $sqlmsg);
1836
                return false;
1837
            }
1838
            else {
1839
                $sqlpos = strpos($sqlmsg, $app_strings['ERR_MSSQL_DB_CONTEXT']);
1840
                if ( $sqlpos !== false )
1841
                    return false;
1842
            }
1843
        }
1844
1845
        if ( strlen($sqlmsg) > 2 ) {
1846
        	return "SQL Server error: " . $sqlmsg;
1847
        }
1848
1849
        return false;
1850
    }
1851
1852
    /**
1853
     * (non-PHPdoc)
1854
     * @see DBManager::getDbInfo()
1855
     */
1856
    public function getDbInfo()
1857
    {
1858
        return array("version" => $this->version());
1859
    }
1860
1861
    /**
1862
     * (non-PHPdoc)
1863
     * @see DBManager::validateQuery()
1864
     */
1865
    public function validateQuery($query)
1866
    {
1867
        if(!$this->isSelect($query)) {
1868
            return false;
1869
        }
1870
        $this->query("SET SHOWPLAN_TEXT ON");
1871
        $res = $this->getOne($query);
1872
        $this->query("SET SHOWPLAN_TEXT OFF");
1873
        return !empty($res);
1874
    }
1875
1876
    /**
1877
     * This is a utility function to prepend the "N" character in front of SQL values that are
1878
     * surrounded by single quotes.
1879
     *
1880
     * @param  $sql string SQL statement
1881
     * @return string SQL statement with single quote values prepended with "N" character for nvarchar columns
1882
     */
1883
    protected function _appendN($sql)
1884
    {
1885
        // If there are no single quotes, don't bother, will just assume there is no character data
1886
        if (strpos($sql, "'") === false)
1887
            return $sql;
1888
1889
        // Flag if there are odd number of single quotes, just continue without trying to append N
1890
        if ((substr_count($sql, "'") & 1)) {
1891
            $GLOBALS['log']->error("SQL statement[" . $sql . "] has odd number of single quotes.");
1892
            return $sql;
1893
        }
1894
1895
        //The only location of three subsequent ' will be at the beginning or end of a value.
1896
        $sql = preg_replace('/(?<!\')(\'{3})(?!\')/', "'<@#@#@PAIR@#@#@>", $sql);
1897
1898
        // Remove any remaining '' and do not parse... replace later (hopefully we don't even have any)
1899
        $pairs        = array();
1900
        $regexp       = '/(\'{2})/';
1901
        $pair_matches = array();
1902
        preg_match_all($regexp, $sql, $pair_matches);
1903
        if ($pair_matches) {
1904
            foreach (array_unique($pair_matches[0]) as $key=>$value) {
1905
                $pairs['<@PAIR-'.$key.'@>'] = $value;
1906
            }
1907
            if (!empty($pairs)) {
1908
                $sql = str_replace($pairs, array_keys($pairs), $sql);
1909
            }
1910
        }
1911
1912
        $regexp  = "/(N?'.+?')/is";
1913
        $matches = array();
1914
        preg_match_all($regexp, $sql, $matches);
1915
        $replace = array();
1916
        if (!empty($matches)) {
1917
            foreach ($matches[0] as $value) {
1918
                // We are assuming that all nvarchar columns are no more than 200 characters in length
1919
                // One problem we face is the image column type in reports which cannot accept nvarchar data
1920
                if (!empty($value) && !is_numeric(trim(str_replace(array("'", ","), "", $value))) && !preg_match('/^\'[\,]\'$/', $value)) {
1921
                    $replace[$value] = 'N' . trim($value, "N");
1922
                }
1923
            }
1924
        }
1925
1926
        if (!empty($replace))
1927
            $sql = str_replace(array_keys($replace), $replace, $sql);
1928
1929
        if (!empty($pairs))
1930
            $sql = str_replace(array_keys($pairs), $pairs, $sql);
1931
1932
        if(strpos($sql, "<@#@#@PAIR@#@#@>"))
1933
            $sql = str_replace(array('<@#@#@PAIR@#@#@>'), array("''"), $sql);
1934
1935
        return $sql;
1936
    }
1937
1938
    /**
1939
     * Quote SQL Server search term
1940
     * @param string $term
1941
     * @return string
1942
     */
1943
    protected function quoteTerm($term)
1944
    {
1945
        $term = str_replace("%", "*", $term); // Mssql wildcard is *
1946
        return '"'.str_replace('"', '', $term).'"';
1947
    }
1948
1949
    /**
1950
     * Generate fulltext query from set of terms
1951
     * @param string $fields Field to search against
0 ignored issues
show
There is no parameter named $fields. Did you maybe mean $field?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1952
     * @param array $terms Search terms that may be or not be in the result
1953
     * @param array $must_terms Search terms that have to be in the result
1954
     * @param array $exclude_terms Search terms that have to be not in the result
1955
     */
1956
    public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array())
1957
    {
1958
        $condition = $or_condition = array();
1959
        foreach($must_terms as $term) {
1960
            $condition[] = $this->quoteTerm($term);
1961
        }
1962
1963
        foreach($terms as $term) {
1964
            $or_condition[] = $this->quoteTerm($term);
1965
        }
1966
1967
        if(!empty($or_condition)) {
1968
            $condition[] = "(".join(" | ", $or_condition).")";
1969
        }
1970
1971
        foreach($exclude_terms as $term) {
1972
            $condition[] = " NOT ".$this->quoteTerm($term);
1973
        }
1974
        $condition = $this->quoted(join(" AND ",$condition));
1975
        return "CONTAINS($field, $condition)";
1976
    }
1977
1978
    /**
1979
     * Check if certain database exists
1980
     * @param string $dbname
1981
     */
1982
    public function dbExists($dbname)
1983
    {
1984
        $db = $this->getOne("SELECT name FROM master..sysdatabases WHERE name = N".$this->quoted($dbname));
1985
        return !empty($db);
1986
    }
1987
1988
    /**
1989
     * Select database
1990
     * @param string $dbname
1991
     */
1992
    protected function selectDb($dbname)
1993
    {
1994
        return mssql_select_db($dbname);
1995
    }
1996
1997
    /**
1998
     * Check if certain DB user exists
1999
     * @param string $username
2000
     */
2001
    public function userExists($username)
2002
    {
2003
        $this->selectDb("master");
2004
        $user = $this->getOne("select count(*) from sys.sql_logins where name =".$this->quoted($username));
2005
        // FIXME: go back to the original DB
2006
        return !empty($user);
2007
    }
2008
2009
    /**
2010
     * Create DB user
2011
     * @param string $database_name
2012
     * @param string $host_name
2013
     * @param string $user
2014
     * @param string $password
2015
     */
2016
    public function createDbUser($database_name, $host_name, $user, $password)
2017
    {
2018
        $qpassword = $this->quote($password);
2019
        $this->selectDb($database_name);
2020
        $this->query("CREATE LOGIN $user WITH PASSWORD = '$qpassword'", true);
2021
        $this->query("CREATE USER $user FOR LOGIN $user", true);
2022
        $this->query("EXEC sp_addRoleMember 'db_ddladmin ', '$user'", true);
2023
        $this->query("EXEC sp_addRoleMember 'db_datareader','$user'", true);
2024
        $this->query("EXEC sp_addRoleMember 'db_datawriter','$user'", true);
2025
    }
2026
2027
    /**
2028
     * Create a database
2029
     * @param string $dbname
2030
     */
2031
    public function createDatabase($dbname)
2032
    {
2033
        return $this->query("CREATE DATABASE $dbname", true);
2034
    }
2035
2036
    /**
2037
     * Drop a database
2038
     * @param string $dbname
2039
     */
2040
    public function dropDatabase($dbname)
2041
    {
2042
        return $this->query("DROP DATABASE $dbname", true);
2043
    }
2044
2045
    /**
2046
     * Check if this driver can be used
2047
     * @return bool
2048
     */
2049
    public function valid()
2050
    {
2051
        return function_exists("mssql_connect");
2052
    }
2053
2054
    /**
2055
     * Check if this DB name is valid
2056
     *
2057
     * @param string $name
2058
     * @return bool
2059
     */
2060
    public function isDatabaseNameValid($name)
2061
    {
2062
        // No funny chars, does not begin with number
2063
        return preg_match('/^[0-9#@]+|[\"\'\*\/\\?\:\\<\>\-\ \&\!\(\)\[\]\{\}\;\,\.\`\~\|\\\\]+/', $name)==0;
2064
    }
2065
2066
    public function installConfig()
2067
    {
2068
        return array(
2069
        	'LBL_DBCONFIG_MSG3' =>  array(
2070
                "setup_db_database_name" => array("label" => 'LBL_DBCONF_DB_NAME', "required" => true),
2071
            ),
2072
            'LBL_DBCONFIG_MSG2' =>  array(
2073
                "setup_db_host_name" => array("label" => 'LBL_DBCONF_HOST_NAME', "required" => true),
2074
                "setup_db_host_instance" => array("label" => 'LBL_DBCONF_HOST_INSTANCE'),
2075
            ),
2076
            'LBL_DBCONF_TITLE_USER_INFO' => array(),
2077
            'LBL_DBCONFIG_B_MSG1' => array(
2078
                "setup_db_admin_user_name" => array("label" => 'LBL_DBCONF_DB_ADMIN_USER', "required" => true),
2079
                "setup_db_admin_password" => array("label" => 'LBL_DBCONF_DB_ADMIN_PASSWORD', "type" => "password"),
2080
            )
2081
        );
2082
    }
2083
2084
    /**
2085
     * Returns a DB specific FROM clause which can be used to select against functions.
2086
     * Note that depending on the database that this may also be an empty string.
2087
     * @return string
2088
     */
2089
    public function getFromDummyTable()
2090
    {
2091
        return '';
2092
    }
2093
2094
    /**
2095
     * Returns a DB specific piece of SQL which will generate GUID (UUID)
2096
     * This string can be used in dynamic SQL to do multiple inserts with a single query.
2097
     * I.e. generate a unique Sugar id in a sub select of an insert statement.
2098
     * @return string
2099
     */
2100
2101
	public function getGuidSQL()
2102
    {
2103
      	return 'NEWID()';
2104
    }
2105
}
2106