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/MysqlManager.php (8 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
 * MySQL manager implementation for mysql extension
96
 */
97
class MysqlManager extends DBManager
98
{
99
	/**
100
	 * @see DBManager::$dbType
101
	 */
102
	public $dbType = 'mysql';
103
	public $variant = 'mysql';
104
	public $dbName = 'MySQL';
105
	public $label = 'LBL_MYSQL';
106
107
	protected $maxNameLengths = array(
108
		'table' => 64,
109
		'column' => 64,
110
		'index' => 64,
111
		'alias' => 256
112
	);
113
114
	protected $type_map = array(
115
			'int'      => 'int',
116
			'double'   => 'double',
117
			'float'    => 'float',
118
			'uint'     => 'int unsigned',
119
			'ulong'    => 'bigint unsigned',
120
			'long'     => 'bigint',
121
			'short'    => 'smallint',
122
			'varchar'  => 'varchar',
123
			'text'     => 'text',
124
			'longtext' => 'longtext',
125
			'date'     => 'date',
126
			'enum'     => 'varchar',
127
			'relate'   => 'varchar',
128
			'multienum'=> 'text',
129
			'html'     => 'text',
130
			'longhtml' => 'longtext',
131
			'datetime' => 'datetime',
132
			'datetimecombo' => 'datetime',
133
			'time'     => 'time',
134
			'bool'     => 'bool',
135
			'tinyint'  => 'tinyint',
136
			'char'     => 'char',
137
			'blob'     => 'blob',
138
			'longblob' => 'longblob',
139
			'currency' => 'decimal(26,6)',
140
			'decimal'  => 'decimal',
141
			'decimal2' => 'decimal',
142
			'id'       => 'char(36)',
143
			'url'      => 'varchar',
144
			'encrypt'  => 'varchar',
145
			'file'     => 'varchar',
146
			'decimal_tpl' => 'decimal(%d, %d)',
147
148
	);
149
150
	protected $capabilities = array(
151
		"affected_rows" => true,
152
		"select_rows" => true,
153
		"inline_keys" => true,
154
		"create_user" => true,
155
		"fulltext" => true,
156
	    "collation" => true,
157
	    "create_db" => true,
158
	    "disable_keys" => true,
159
	);
160
161
	/**
162
	 * Parses and runs queries
163
	 *
164
	 * @param  string   $sql        SQL Statement to execute
165
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
166
	 * @param  string   $msg        Message to log if error occurs
167
	 * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
168
	 * @param  bool     $keepResult True if we want to push this result into the $lastResult array.
169
	 * @return resource result set
170
	 */
171
	public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false)
172
	{
173
		if(is_array($sql)) {
174
			return $this->queryArray($sql, $dieOnError, $msg, $suppress);
175
		}
176
177
		parent::countQuery($sql);
0 ignored issues
show
The call to DBManager::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...
178
		$GLOBALS['log']->info('Query:' . $sql);
179
		$this->checkConnection();
180
		$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...
181
		$this->lastsql = $sql;
182
		$result = $suppress?@mysql_query($sql, $this->database):mysql_query($sql, $this->database);
183
184
		$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...
185
		$GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
186
187
188
		if($keepResult)
189
			$this->lastResult = $result;
190
191
		$this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
192
		return $result;
193
	}
194
195
    /**
196
     * Returns the number of rows affected by the last query
197
     * @param $result
198
     * @return int
199
     */
200
	public function getAffectedRowCount($result)
201
	{
202
		return mysql_affected_rows($this->getDatabase());
203
	}
204
205
	/**
206
	 * Returns the number of rows returned by the result
207
	 *
208
	 * This function can't be reliably implemented on most DB, do not use it.
209
	 * @abstract
210
	 * @deprecated
211
	 * @param  resource $result
212
	 * @return int
213
	 */
214
	public function getRowCount($result)
215
	{
216
	    return mysql_num_rows($result);
217
	}
218
219
	/**
220
	 * Disconnects from the database
221
	 *
222
	 * Also handles any cleanup needed
223
	 */
224
	public function disconnect()
225
	{
226
		$GLOBALS['log']->debug('Calling MySQL::disconnect()');
227
		if(!empty($this->database)){
228
			$this->freeResult();
229
			mysql_close($this->database);
230
			$this->database = null;
231
		}
232
	}
233
234
	/**
235
	 * @see DBManager::freeDbResult()
236
	 */
237
	protected function freeDbResult($dbResult)
238
	{
239
		if(!empty($dbResult))
240
			mysql_free_result($dbResult);
241
	}
242
243
244
	/**
245
	 * @abstract
246
	 * Check if query has LIMIT clause
247
	 * Relevant for now only for Mysql
248
	 * @param string $sql
249
	 * @return bool
250
	 */
251 9
	protected function hasLimit($sql)
252
	{
253 9
	    return stripos($sql, " limit ") !== false;
254
	}
255
256
	/**
257
	 * @see DBManager::limitQuery()
258
	 */
259 172
	public function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true)
260
	{
261 172
        $start = (int)$start;
262 172
        $count = (int)$count;
263 172
	    if ($start < 0)
264
			$start = 0;
265 172
		$GLOBALS['log']->debug('Limit Query:' . $sql. ' Start: ' .$start . ' count: ' . $count);
266
267 172
	    $sql = "$sql LIMIT $start,$count";
268 172
		$this->lastsql = $sql;
269
270 172
		if(!empty($GLOBALS['sugar_config']['check_query'])){
271
			$this->checkQuery($sql);
272
		}
273 172
		if(!$execute) {
274
			return $sql;
275
		}
276
277 172
		return $this->query($sql, $dieOnError, $msg);
278
	}
279
280
281
	/**
282
	 * @see DBManager::checkQuery()
283
	 */
284
	protected function checkQuery($sql, $object_name = false)
285
	{
286
		$result   = $this->query('EXPLAIN ' . $sql);
287
		$badQuery = array();
288
		while ($row = $this->fetchByAssoc($result)) {
289
			if (empty($row['table']))
290
				continue;
291
			$badQuery[$row['table']] = '';
292
			if (strtoupper($row['type']) == 'ALL')
293
				$badQuery[$row['table']]  .=  ' Full Table Scan;';
294
			if (empty($row['key']))
295
				$badQuery[$row['table']] .= ' No Index Key Used;';
296
			if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using filesort') > 0)
297
				$badQuery[$row['table']] .= ' Using FileSort;';
298
			if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using temporary') > 0)
299
				$badQuery[$row['table']] .= ' Using Temporary Table;';
300
		}
301
302
		if ( empty($badQuery) )
303
			return true;
304
305
		foreach($badQuery as $table=>$data ){
306
			if(!empty($data)){
307
				$warning = ' Table:' . $table . ' Data:' . $data;
308
				if(!empty($GLOBALS['sugar_config']['check_query_log'])){
309
					$GLOBALS['log']->fatal($sql);
310
					$GLOBALS['log']->fatal('CHECK QUERY:' .$warning);
311
				}
312
				else{
313
					$GLOBALS['log']->warn('CHECK QUERY:' .$warning);
314
				}
315
			}
316
		}
317
318
		return false;
319
	}
320
321
	/**
322
	 * @see DBManager::get_columns()
323
	 */
324
	public function get_columns($tablename)
325
	{
326
		//find all unique indexes and primary keys.
327
		$result = $this->query("DESCRIBE $tablename");
328
329
		$columns = array();
330
		while (($row=$this->fetchByAssoc($result)) !=null) {
331
			$name = strtolower($row['Field']);
332
			$columns[$name]['name']=$name;
333
			$matches = array();
334
			preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)( unsigned)?/i', $row['Type'], $matches);
335
			$columns[$name]['type']=strtolower($matches[1][0]);
336
			if ( isset($matches[2][0]) && in_array(strtolower($matches[1][0]),array('varchar','char','varchar2','int','decimal','float')) )
337
				$columns[$name]['len']=strtolower($matches[2][0]);
338
			if ( stristr($row['Extra'],'auto_increment') )
339
				$columns[$name]['auto_increment'] = '1';
340
			if ($row['Null'] == 'NO' && !stristr($row['Key'],'PRI'))
341
				$columns[$name]['required'] = 'true';
342
			if (!empty($row['Default']) )
343
				$columns[$name]['default'] = $row['Default'];
344
		}
345
		return $columns;
346
	}
347
348
	/**
349
	 * @see DBManager::getFieldsArray()
350
	 */
351
	public function getFieldsArray($result, $make_lower_case=false)
352
	{
353
		$field_array = array();
354
355
		if(empty($result))
356
			return 0;
357
358
		$fields = mysql_num_fields($result);
359
		for ($i=0; $i < $fields; $i++) {
360
			$meta = mysql_fetch_field($result, $i);
361
			if (!$meta)
362
				return array();
363
364
			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...
365
				$meta->name = strtolower($meta->name);
366
367
			$field_array[] = $meta->name;
368
		}
369
370
		return $field_array;
371
	}
372
373
	/**
374
	 * @see DBManager::fetchRow()
375
	 */
376
	public function fetchRow($result)
377
	{
378
		if (empty($result))	return false;
379
380
		return mysql_fetch_assoc($result);
381
	}
382
383
	/**
384
	 * @see DBManager::getTablesArray()
385
	 */
386
	public function getTablesArray()
387
	{
388
		$this->log->debug('Fetching table list');
389
390
		if ($this->getDatabase()) {
391
			$tables = array();
392
			$r = $this->query('SHOW TABLES');
393
			if (!empty($r)) {
394
				while ($a = $this->fetchByAssoc($r)) {
395
					$row = array_values($a);
396
					$tables[]=$row[0];
397
				}
398
				return $tables;
399
			}
400
		}
401
402
		return false; // no database available
403
	}
404
405
	/**
406
	 * @see DBManager::version()
407
	 */
408
	public function version()
409
	{
410
		return $this->getOne("SELECT version() version");
411
	}
412
413
	/**
414
	 * @see DBManager::tableExists()
415
	 */
416
	public function tableExists($tableName)
417
	{
418
		$this->log->info("tableExists: $tableName");
419
420
		if ($this->getDatabase()) {
421
			$result = $this->query("SHOW TABLES LIKE ".$this->quoted($tableName));
422
			if(empty($result)) return false;
423
			$row = $this->fetchByAssoc($result);
424
			return !empty($row);
425
		}
426
427
		return false;
428
	}
429
430
	/**
431
	 * Get tables like expression
432
	 * @param $like string
433
	 * @return array
434
	 */
435
	public function tablesLike($like)
436
	{
437
		if ($this->getDatabase()) {
438
			$tables = array();
439
			$r = $this->query('SHOW TABLES LIKE '.$this->quoted($like));
440
			if (!empty($r)) {
441
				while ($a = $this->fetchByAssoc($r)) {
442
					$row = array_values($a);
443
					$tables[]=$row[0];
444
				}
445
				return $tables;
446
			}
447
		}
448
		return false;
449
	}
450
451
	/**
452
	 * @see DBManager::quote()
453
	 */
454
	public function quote($string)
455
	{
456
		if(is_array($string)) {
457
			return $this->arrayQuote($string);
458
		}
459
		return mysql_real_escape_string($this->quoteInternal($string), $this->getDatabase());
460
	}
461
462
    /**
463
     * @see DBManager::quoteIdentifier()
464
     */
465 25
    public function quoteIdentifier($string)
466
    {
467 25
        return '`'.$string.'`';
468
    }
469
470
	/**
471
	 * @see DBManager::connect()
472
	 */
473
	public function connect(array $configOptions = null, $dieOnError = false)
474
	{
475
		global $sugar_config;
476
477
		if(is_null($configOptions))
478
			$configOptions = $sugar_config['dbconfig'];
479
480
		if ($this->getOption('persistent')) {
481
			$this->database = @mysql_pconnect(
482
				$configOptions['db_host_name'],
483
				$configOptions['db_user_name'],
484
				$configOptions['db_password']
485
				);
486
		}
487
488
		if (!$this->database) {
489
			$this->database = mysql_connect(
490
					$configOptions['db_host_name'],
491
					$configOptions['db_user_name'],
492
					$configOptions['db_password']
493
					);
494
			if(empty($this->database)) {
495
				$GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].":".mysql_error());
496
				if($dieOnError) {
497
					if(isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
498
						sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
499
					} else {
500
						sugar_die("Could not connect to the database. Please refer to suitecrm.log for details.");
501
					}
502
				} else {
503
					return false;
504
				}
505
			}
506
			// Do not pass connection information because we have not connected yet
507
			if($this->database  && $this->getOption('persistent')){
508
				$_SESSION['administrator_error'] = "<b>Severe Performance Degradation: Persistent Database Connections "
509
					. "not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false "
510
					. "in your config.php file</b>";
511
			}
512
		}
513
		if(!empty($configOptions['db_name']) && !@mysql_select_db($configOptions['db_name'])) {
514
			$GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}: " . mysql_error($this->database));
515
			if($dieOnError) {
516
				sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
517
			} else {
518
				return false;
519
			}
520
		}
521
522
		// cn: using direct calls to prevent this from spamming the Logs
523
	    mysql_query("SET CHARACTER SET utf8", $this->database);
524
	    $names = "SET NAMES 'utf8'";
525
	    $collation = $this->getOption('collation');
526
	    if(!empty($collation)) {
527
	        $names .= " COLLATE '$collation'";
528
		}
529
	    mysql_query($names, $this->database);
530
531
		if(!$this->checkError('Could Not Connect:', $dieOnError))
532
			$GLOBALS['log']->info("connected to db");
533
		$this->connectOptions = $configOptions;
534
535
		$GLOBALS['log']->info("Connect:".$this->database);
536
		return true;
537
	}
538
539
	/**
540
	 * @see DBManager::repairTableParams()
541
	 *
542
	 * For MySQL, we can write the ALTER TABLE statement all in one line, which speeds things
543
	 * up quite a bit. So here, we'll parse the returned SQL into a single ALTER TABLE command.
544
	 */
545
	public function repairTableParams($tablename, $fielddefs, $indices, $execute = true, $engine = null)
546
	{
547
		$sql = parent::repairTableParams($tablename,$fielddefs,$indices,false,$engine);
548
549
		if ( $sql == '' )
550
			return '';
551
552
		if ( stristr($sql,'create table') )
553
		{
554
			if ($execute) {
555
				$msg = "Error creating table: ".$tablename. ":";
556
				$this->query($sql,true,$msg);
557
			}
558
			return $sql;
559
		}
560
561
		// first, parse out all the comments
562
		$match = array();
563
		preg_match_all('!/\*.*?\*/!is', $sql, $match);
564
		$commentBlocks = $match[0];
565
		$sql = preg_replace('!/\*.*?\*/!is','', $sql);
566
567
		// now, we should only have alter table statements
568
		// let's replace the 'alter table name' part with a comma
569
		$sql = preg_replace("!alter table $tablename!is",', ', $sql);
570
571
		// re-add it at the beginning
572
		$sql = substr_replace($sql,'',strpos($sql,','),1);
573
		$sql = str_replace(";","",$sql);
574
		$sql = str_replace("\n","",$sql);
575
		$sql = "ALTER TABLE $tablename $sql";
576
577
		if ( $execute )
578
			$this->query($sql,'Error with MySQL repair table');
579
580
		// and re-add the comments at the beginning
581
		$sql = implode("\n",$commentBlocks) . "\n". $sql . "\n";
582
583
		return $sql;
584
	}
585
586
	/**
587
	 * @see DBManager::convert()
588
	 */
589 111
	public function convert($string, $type, array $additional_parameters = array())
590
	{
591 111
		$all_parameters = $additional_parameters;
592 111
		if(is_array($string)) {
593
			$all_parameters = array_merge($string, $all_parameters);
594 111
		} elseif (!is_null($string)) {
595 110
			array_unshift($all_parameters, $string);
596
		}
597 111
		$all_strings = implode(',', $all_parameters);
598
599 111
		switch (strtolower($type)) {
600 111
			case 'today':
601 32
				return "CURDATE()";
602 104
			case 'left':
603 1
				return "LEFT($all_strings)";
604 103
			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...
605 1
				if(empty($additional_parameters)) {
606 1
					return "DATE_FORMAT($string,'%Y-%m-%d')";
607
				} else {
608
					$format = $additional_parameters[0];
609
					if($format[0] != "'") {
610
						$format = $this->quoted($format);
611
					}
612
					return "DATE_FORMAT($string,$format)";
613
				}
614 102
			case 'ifnull':
615 15
				if(empty($additional_parameters) && !strstr($all_strings, ",")) {
616
					$all_strings .= ",''";
617
				}
618 15
				return "IFNULL($all_strings)";
619 101
			case 'concat':
620 15
				return "CONCAT($all_strings)";
621 89
			case 'quarter':
622 1
					return "QUARTER($string)";
623 88
			case "length":
624 5
					return "LENGTH($string)";
625 86
			case 'month':
626 1
					return "MONTH($string)";
627 85
			case 'add_date':
628 1
					return "DATE_ADD($string, INTERVAL {$additional_parameters[0]} {$additional_parameters[1]})";
629 84
			case 'add_time':
630 1
					return "DATE_ADD($string, INTERVAL + CONCAT({$additional_parameters[0]}, ':', {$additional_parameters[1]}) HOUR_MINUTE)";
631 83
            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...
632 1
                $getUserUTCOffset = $GLOBALS['timedate']->getUserUTCOffset();
633 1
                $operation = $getUserUTCOffset < 0 ? '-' : '+';
634 1
                return $string . ' ' . $operation . ' INTERVAL ' . abs($getUserUTCOffset) . ' MINUTE';
635 82
            case 'avg':
636 1
                return "avg($string)";
637
		}
638
639 81
		return $string;
640
	}
641
642
	/**
643
	 * (non-PHPdoc)
644
	 * @see DBManager::fromConvert()
645
	 */
646 92
	public function fromConvert($string, $type)
647
	{
648 92
		return $string;
649
	}
650
651
	/**
652
	 * Returns the name of the engine to use or null if we are to use the default
653
	 *
654
	 * @param  object $bean SugarBean instance
655
	 * @return string
656
	 */
657
	protected function getEngine($bean)
658
	{
659
		global $dictionary;
660
		$engine = null;
661
		if (isset($dictionary[$bean->getObjectName()]['engine'])) {
662
			$engine = $dictionary[$bean->getObjectName()]['engine'];
663
		}
664
		return $engine;
665
	}
666
667
	/**
668
	 * Returns true if the engine given is enabled in the backend
669
	 *
670
	 * @param  string $engine
671
	 * @return bool
672
	 */
673
	protected function isEngineEnabled($engine)
674
	{
675
		if(!is_string($engine)) return false;
676
677
		$engine = strtoupper($engine);
678
679
		$r = $this->query("SHOW ENGINES");
680
681
		while ( $row = $this->fetchByAssoc($r) )
682
			if ( strtoupper($row['Engine']) == $engine )
683
				return ($row['Support']=='YES' || $row['Support']=='DEFAULT');
684
685
		return false;
686
	}
687
688
	/**
689
	 * @see DBManager::createTableSQL()
690
	 */
691
	public function createTableSQL(SugarBean $bean)
692
	{
693
		$tablename = $bean->getTableName();
694
		$fieldDefs = $bean->getFieldDefinitions();
695
		$indices   = $bean->getIndices();
696
		$engine    = $this->getEngine($bean);
697
		return $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
698
	}
699
700
	/**
701
	 * Generates sql for create table statement for a bean.
702
	 *
703
	 * @param  string $tablename
704
	 * @param  array  $fieldDefs
705
	 * @param  array  $indices
706
	 * @param  string $engine optional, MySQL engine to use
707
	 * @return string SQL Create Table statement
708
	*/
709
	public function createTableSQLParams($tablename, $fieldDefs, $indices, $engine = null)
710
	{
711
		if ( empty($engine) && isset($fieldDefs['engine']))
712
			$engine = $fieldDefs['engine'];
713
		if ( !$this->isEngineEnabled($engine) )
714
			$engine = '';
715
716
		$columns = $this->columnSQLRep($fieldDefs, false, $tablename);
717
		if (empty($columns))
718
			return false;
719
720
		$keys = $this->keysSQL($indices);
721
		if (!empty($keys))
722
			$keys = ",$keys";
723
724
		// cn: bug 9873 - module tables do not get created in utf8 with assoc collation
725
		$collation = $this->getOption('collation');
726
		if(empty($collation)) {
727
		    $collation = 'utf8_general_ci';
728
		}
729
		$sql = "CREATE TABLE $tablename ($columns $keys) CHARACTER SET utf8 COLLATE $collation";
730
731
		if (!empty($engine))
732
			$sql.= " ENGINE=$engine";
733
734
		return $sql;
735
	}
736
737
    /**
738
     * Does this type represent text (i.e., non-varchar) value?
739
     * @param string $type
740
     */
741 13
    public function isTextType($type)
742
    {
743 13
        $type = $this->getColumnType(strtolower($type));
744 13
        return in_array($type, array('blob','text','longblob', 'longtext'));
745
    }
746
747
	/**
748
	 * @see DBManager::oneColumnSQLRep()
749
	 */
750
	protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
751
	{
752
		// always return as array for post-processing
753
		$ref = parent::oneColumnSQLRep($fieldDef, $ignoreRequired, $table, true);
754
755
		if ( $ref['colType'] == 'int' && !empty($fieldDef['len']) ) {
756
			$ref['colType'] .= "(".$fieldDef['len'].")";
757
		}
758
759
		// bug 22338 - don't set a default value on text or blob fields
760
		if ( isset($ref['default']) &&
761
            in_array($ref['colBaseType'], array('text', 'blob', 'longtext', 'longblob')))
762
			    $ref['default'] = '';
763
764
		if ( $return_as_array )
765
			return $ref;
766
		else
767
			return "{$ref['name']} {$ref['colType']} {$ref['default']} {$ref['required']} {$ref['auto_increment']}";
768
	}
769
770
	/**
771
	 * @see DBManager::changeColumnSQL()
772
	 */
773
	protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false)
774
	{
775
		$columns = array();
776
		if ($this->isFieldArray($fieldDefs)){
777
			foreach ($fieldDefs as $def){
778
				if ($action == 'drop')
779
					$columns[] = $def['name'];
780
				else
781
					$columns[] = $this->oneColumnSQLRep($def, $ignoreRequired);
782
			}
783
		} else {
784
			if ($action == 'drop')
785
				$columns[] = $fieldDefs['name'];
786
		else
787
			$columns[] = $this->oneColumnSQLRep($fieldDefs);
788
		}
789
790
		return "ALTER TABLE $tablename $action COLUMN ".implode(",$action column ", $columns);
791
	}
792
793
	/**
794
	 * Generates SQL for key specification inside CREATE TABLE statement
795
	 *
796
	 * The passes array is an array of field definitions or a field definition
797
	 * itself. The keys generated will be either primary, foreign, unique, index
798
	 * or none at all depending on the setting of the "key" parameter of a field definition
799
	 *
800
	 * @param  array  $indices
801
	 * @param  bool   $alter_table
802
	 * @param  string $alter_action
803
	 * @return string SQL Statement
804
	 */
805
	protected function keysSQL($indices, $alter_table = false, $alter_action = '')
806
	{
807
	// check if the passed value is an array of fields.
808
	// if not, convert it into an array
809
	if (!$this->isFieldArray($indices))
810
		$indices[] = $indices;
811
812
	$columns = array();
813
	foreach ($indices as $index) {
814
		if(!empty($index['db']) && $index['db'] != $this->dbType)
815
			continue;
816
		if (isset($index['source']) && $index['source'] != 'db')
817
			continue;
818
819
		$type = $index['type'];
820
		$name = $index['name'];
821
822
		if (is_array($index['fields']))
823
			$fields = implode(", ", $index['fields']);
824
		else
825
			$fields = $index['fields'];
826
827
		switch ($type) {
828
		case 'unique':
829
			$columns[] = " UNIQUE $name ($fields)";
830
			break;
831
		case 'primary':
832
			$columns[] = " PRIMARY KEY ($fields)";
833
			break;
834
		case 'index':
835
		case 'foreign':
836
		case 'clustered':
837
		case 'alternate_key':
838
			/**
839
				* @todo here it is assumed that the primary key of the foreign
840
				* table will always be named 'id'. It must be noted though
841
				* that this can easily be fixed by referring to db dictionary
842
				* to find the correct primary field name
843
				*/
844
			if ( $alter_table )
845
				$columns[] = " INDEX $name ($fields)";
846
			else
847
				$columns[] = " KEY $name ($fields)";
848
			break;
849
		case 'fulltext':
850
			if ($this->full_text_indexing_installed())
851
				$columns[] = " FULLTEXT ($fields)";
852
			else
853
				$GLOBALS['log']->debug('MYISAM engine is not available/enabled, full-text indexes will be skipped. Skipping:',$name);
854
			break;
855
		}
856
	}
857
	$columns = implode(", $alter_action ", $columns);
858
	if(!empty($alter_action)){
859
		$columns = $alter_action . ' '. $columns;
860
	}
861
	return $columns;
862
	}
863
864
	/**
865
	 * @see DBManager::setAutoIncrement()
866
	 */
867
	protected function setAutoIncrement($table, $field_name)
868
	{
869
		return "auto_increment";
870
	}
871
872
	/**
873
	 * Sets the next auto-increment value of a column to a specific value.
874
	 *
875
	 * @param  string $table tablename
876
	 * @param  string $field_name
877
	 */
878
	public function setAutoIncrementStart($table, $field_name, $start_value)
879
	{
880
		$start_value = (int)$start_value;
881
		return $this->query( "ALTER TABLE $table AUTO_INCREMENT = $start_value;");
882
	}
883
884
	/**
885
	 * Returns the next value for an auto increment
886
	 *
887
	 * @param  string $table tablename
888
	 * @param  string $field_name
889
	 * @return string
890
	 */
891
	public function getAutoIncrement($table, $field_name)
892
	{
893
		$result = $this->query("SHOW TABLE STATUS LIKE '$table'");
894
		$row = $this->fetchByAssoc($result);
895
		if (!empty($row['Auto_increment']))
896
			return $row['Auto_increment'];
897
898
		return "";
899
	}
900
901
	/**
902
	 * @see DBManager::get_indices()
903
	 */
904
	public function get_indices($tablename)
905
	{
906
		//find all unique indexes and primary keys.
907
		$result = $this->query("SHOW INDEX FROM $tablename");
908
909
		$indices = array();
910
		while (($row=$this->fetchByAssoc($result)) !=null) {
911
			$index_type='index';
912
			if ($row['Key_name'] =='PRIMARY') {
913
				$index_type='primary';
914
			}
915
			elseif ( $row['Non_unique'] == '0' ) {
916
				$index_type='unique';
917
			}
918
			$name = strtolower($row['Key_name']);
919
			$indices[$name]['name']=$name;
920
			$indices[$name]['type']=$index_type;
921
			$indices[$name]['fields'][]=strtolower($row['Column_name']);
922
		}
923
		return $indices;
924
	}
925
926
	/**
927
	 * @see DBManager::add_drop_constraint()
928
	 */
929
	public function add_drop_constraint($table, $definition, $drop = false)
930
	{
931
		$type         = $definition['type'];
932
		$fields       = implode(',',$definition['fields']);
933
		$name         = $definition['name'];
934
		$sql          = '';
935
936
		switch ($type){
937
		// generic indices
938
		case 'index':
939
		case 'alternate_key':
940
		case 'clustered':
941
			if ($drop)
942
				$sql = "ALTER TABLE {$table} DROP INDEX {$name} ";
943
			else
944
				$sql = "ALTER TABLE {$table} ADD INDEX {$name} ({$fields})";
945
			break;
946
		// constraints as indices
947
		case 'unique':
948
			if ($drop)
949
				$sql = "ALTER TABLE {$table} DROP INDEX $name";
950
			else
951
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT UNIQUE {$name} ({$fields})";
952
			break;
953
		case 'primary':
954
			if ($drop)
955
				$sql = "ALTER TABLE {$table} DROP PRIMARY KEY";
956
			else
957
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT PRIMARY KEY ({$fields})";
958
			break;
959
		case 'foreign':
960
			if ($drop)
961
				$sql = "ALTER TABLE {$table} DROP FOREIGN KEY ({$fields})";
962
			else
963
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT FOREIGN KEY {$name} ({$fields}) REFERENCES {$definition['foreignTable']}({$definition['foreignField']})";
964
			break;
965
		}
966
		return $sql;
967
	}
968
969
	/**
970
	 * Runs a query and returns a single row
971
	 *
972
	 * @param  string   $sql        SQL Statement to execute
973
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
974
	 * @param  string   $msg        Message to log if error occurs
975
	 * @param  bool     $suppress   Message to log if error occurs
976
	 * @return array    single row from the query
977
	 */
978 3
	public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
979
	{
980 3
		if(stripos($sql, ' LIMIT ') === false) {
981
			// little optimization to just fetch one row
982 3
			$sql .= " LIMIT 0,1";
983
		}
984 3
		return parent::fetchOne($sql, $dieOnError, $msg, $suppress);
985
	}
986
987
	/**
988
	 * @see DBManager::full_text_indexing_installed()
989
	 */
990
	public function full_text_indexing_installed($dbname = null)
991
	{
992
		return $this->isEngineEnabled('MyISAM');
993
	}
994
995
	/**
996
	 * @see DBManager::massageFieldDef()
997
	 */
998
	public function massageFieldDef(&$fieldDef, $tablename)
999
	{
1000
		parent::massageFieldDef($fieldDef,$tablename);
1001
1002
		if ( isset($fieldDef['default']) &&
1003
			($fieldDef['dbType'] == 'text'
1004
				|| $fieldDef['dbType'] == 'blob'
1005
				|| $fieldDef['dbType'] == 'longtext'
1006
				|| $fieldDef['dbType'] == 'longblob' ))
1007
			unset($fieldDef['default']);
1008
		if ($fieldDef['dbType'] == 'uint')
1009
			$fieldDef['len'] = '10';
1010
		if ($fieldDef['dbType'] == 'ulong')
1011
			$fieldDef['len'] = '20';
1012
		if ($fieldDef['dbType'] == 'bool')
1013
			$fieldDef['type'] = 'tinyint';
1014
		if ($fieldDef['dbType'] == 'bool' && empty($fieldDef['default']) )
1015
			$fieldDef['default'] = '0';
1016
		if (($fieldDef['dbType'] == 'varchar' || $fieldDef['dbType'] == 'enum') && empty($fieldDef['len']) )
1017
			$fieldDef['len'] = '255';
1018
		if ($fieldDef['dbType'] == 'uint')
1019
			$fieldDef['len'] = '10';
1020
		if ($fieldDef['dbType'] == 'int' && empty($fieldDef['len']) )
1021
			$fieldDef['len'] = '11';
1022
1023
		if($fieldDef['dbType'] == 'decimal') {
1024
			if(isset($fieldDef['len'])) {
1025
				if(strstr($fieldDef['len'], ",") === false) {
1026
					$fieldDef['len'] .= ",0";
1027
				}
1028
			} else {
1029
				$fieldDef['len']  = '10,0';
1030
			}
1031
		}
1032
	}
1033
1034
	/**
1035
	 * Generates SQL for dropping a table.
1036
	 *
1037
	 * @param  string $name table name
1038
	 * @return string SQL statement
1039
	 */
1040
	public function dropTableNameSQL($name)
1041
	{
1042
		return "DROP TABLE IF EXISTS ".$name;
1043
	}
1044
1045
	public function dropIndexes($tablename, $indexes, $execute = true)
1046
	{
1047
		$sql = array();
1048
		foreach ($indexes as $index) {
1049
			$name =$index['name'];
1050
			if($execute) {
1051
			unset(self::$index_descriptions[$tablename][$name]);
1052
			}
1053
			if ($index['type'] == 'primary') {
1054
				$sql[] = 'DROP PRIMARY KEY';
1055
			} else {
1056
				$sql[] = "DROP INDEX $name";
1057
			}
1058
		}
1059
		if (!empty($sql)) {
1060
            $sql = "ALTER TABLE $tablename " . join(",", $sql) . ";";
1061
			if($execute)
1062
				$this->query($sql);
1063
		} else {
1064
			$sql = '';
1065
		}
1066
		return $sql;
1067
	}
1068
1069
	/**
1070
	 * List of available collation settings
1071
	 * @return string
1072
	 */
1073
	public function getDefaultCollation()
1074
	{
1075
		return "utf8_general_ci";
1076
	}
1077
1078
	/**
1079
	 * List of available collation settings
1080
	 * @return array
1081
	 */
1082
	public function getCollationList()
1083
	{
1084
		$q = "SHOW COLLATION LIKE 'utf8%'";
1085
		$r = $this->query($q);
1086
		$res = array();
1087
		while($a = $this->fetchByAssoc($r)) {
1088
			$res[] = $a['Collation'];
1089
		}
1090
		return $res;
1091
	}
1092
1093
	/**
1094
	 * (non-PHPdoc)
1095
	 * @see DBManager::renameColumnSQL()
1096
	 */
1097
	public function renameColumnSQL($tablename, $column, $newname)
1098
	{
1099
		$field = $this->describeField($column, $tablename);
1100
		$field['name'] = $newname;
1101
		return "ALTER TABLE $tablename CHANGE COLUMN $column ".$this->oneColumnSQLRep($field);
1102
	}
1103
1104 25
	public function emptyValue($type)
1105
	{
1106 25
		$ctype = $this->getColumnType($type);
1107 25
		if($ctype == "datetime") {
1108 7
			return $this->convert($this->quoted("0000-00-00 00:00:00"), "datetime");
1109
		}
1110 24
		if($ctype == "date") {
1111 2
			return $this->convert($this->quoted("0000-00-00"), "date");
1112
		}
1113 23
		if($ctype == "time") {
1114
			return $this->convert($this->quoted("00:00:00"), "time");
1115
		}
1116 23
		return parent::emptyValue($type);
1117
	}
1118
1119
	/**
1120
	 * (non-PHPdoc)
1121
	 * @see DBManager::lastDbError()
1122
	 */
1123
	public function lastDbError()
1124
	{
1125
		if($this->database) {
1126
		    if(mysql_errno($this->database)) {
1127
			    return "MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database);
1128
		    }
1129
		} else {
1130
			$err =  mysql_error();
1131
			if($err) {
1132
			    return $err;
1133
			}
1134
		}
1135
        return false;
1136
    }
1137
1138
	/**
1139
	 * Quote MySQL search term
1140
	 * @param unknown_type $term
1141
	 */
1142
	protected function quoteTerm($term)
1143
	{
1144
		if(strpos($term, ' ') !== false) {
1145
			return '"'.$term.'"';
1146
		}
1147
		return $term;
1148
	}
1149
1150
	/**
1151
	 * Generate fulltext query from set of terms
1152
	 * @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...
1153
	 * @param array $terms Search terms that may be or not be in the result
1154
	 * @param array $must_terms Search terms that have to be in the result
1155
	 * @param array $exclude_terms Search terms that have to be not in the result
1156
	 */
1157
	public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array())
1158
	{
1159
		$condition = array();
1160
		foreach($terms as $term) {
1161
			$condition[] = $this->quoteTerm($term);
1162
		}
1163
		foreach($must_terms as $term) {
1164
			$condition[] = "+".$this->quoteTerm($term);
1165
		}
1166
		foreach($exclude_terms as $term) {
1167
			$condition[] = "-".$this->quoteTerm($term);
1168
		}
1169
		$condition = $this->quoted(join(" ",$condition));
1170
		return "MATCH($field) AGAINST($condition IN BOOLEAN MODE)";
1171
	}
1172
1173
	/**
1174
	 * Get list of all defined charsets
1175
	 * @return array
1176
	 */
1177
	protected function getCharsetInfo()
1178
	{
1179
		$charsets = array();
1180
		$res = $this->query("show variables like 'character\\_set\\_%'");
1181
		while($row = $this->fetchByAssoc($res)) {
1182
			$charsets[$row['Variable_name']] = $row['Value'];
1183
		}
1184
		return $charsets;
1185
	}
1186
1187
	public function getDbInfo()
1188
	{
1189
		$charsets = $this->getCharsetInfo();
1190
		$charset_str = array();
1191
		foreach($charsets as $name => $value) {
1192
			$charset_str[] = "$name = $value";
1193
		}
1194
		return array(
1195
			"MySQL Version" => @mysql_get_client_info(),
1196
			"MySQL Host Info" => @mysql_get_host_info($this->database),
1197
			"MySQL Server Info" => @mysql_get_server_info($this->database),
1198
			"MySQL Client Encoding" =>  @mysql_client_encoding($this->database),
1199
			"MySQL Character Set Settings" => join(", ", $charset_str),
1200
		);
1201
	}
1202
1203
	public function validateQuery($query)
1204
	{
1205
		$res = $this->query("EXPLAIN $query");
1206
		return !empty($res);
1207
	}
1208
1209
	protected function makeTempTableCopy($table)
1210
	{
1211
		$this->log->debug("creating temp table for [$table]...");
1212
		$result = $this->query("SHOW CREATE TABLE {$table}");
1213
		if(empty($result)) {
1214
			return false;
1215
		}
1216
		$row = $this->fetchByAssoc($result);
1217
		if(empty($row) || empty($row['Create Table'])) {
1218
		    return false;
1219
		}
1220
		$create = $row['Create Table'];
1221
		// rewrite DDL with _temp name
1222
		$tempTableQuery = str_replace("CREATE TABLE `{$table}`", "CREATE TABLE `{$table}__uw_temp`", $create);
1223
		$r2 = $this->query($tempTableQuery);
1224
		if(empty($r2)) {
1225
			return false;
1226
		}
1227
1228
		// get sample data into the temp table to test for data/constraint conflicts
1229
		$this->log->debug('inserting temp dataset...');
1230
		$q3 = "INSERT INTO `{$table}__uw_temp` SELECT * FROM `{$table}` LIMIT 10";
1231
		$this->query($q3, false, "Preflight Failed for: {$q3}");
1232
		return true;
1233
	}
1234
1235
	/**
1236
	 * Tests an ALTER TABLE query
1237
	 * @param string table The table name to get DDL
1238
	 * @param string query The query to test.
1239
	 * @return string Non-empty if error found
1240
	 */
1241
	protected function verifyAlterTable($table, $query)
1242
	{
1243
		$this->log->debug("verifying ALTER TABLE");
1244
		// Skipping ALTER TABLE [table] DROP PRIMARY KEY because primary keys are not being copied
1245
		// over to the temp tables
1246
		if(strpos(strtoupper($query), 'DROP PRIMARY KEY') !== false) {
1247
			$this->log->debug("Skipping DROP PRIMARY KEY");
1248
			return '';
1249
		}
1250
		if(!$this->makeTempTableCopy($table)) {
1251
			return 'Could not create temp table copy';
1252
		}
1253
1254
		// test the query on the test table
1255
		$this->log->debug('testing query: ['.$query.']');
1256
		$tempTableTestQuery = str_replace("ALTER TABLE `{$table}`", "ALTER TABLE `{$table}__uw_temp`", $query);
1257
		if (strpos($tempTableTestQuery, 'idx') === false) {
1258
			if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1259
				return 'Could not use a temp table to test query!';
1260
			}
1261
1262
			$this->log->debug('testing query on temp table: ['.$tempTableTestQuery.']');
1263
			$this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1264
		} else {
1265
			// test insertion of an index on a table
1266
			$tempTableTestQuery_idx = str_replace("ADD INDEX `idx_", "ADD INDEX `temp_idx_", $tempTableTestQuery);
1267
			$this->log->debug('testing query on temp table: ['.$tempTableTestQuery_idx.']');
1268
			$this->query($tempTableTestQuery_idx, false, "Preflight Failed for: {$query}");
1269
		}
1270
		$mysqlError = $this->getL();
0 ignored issues
show
The method getL() does not seem to exist on object<MysqlManager>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1271
		if(!empty($mysqlError)) {
1272
			return $mysqlError;
1273
		}
1274
		$this->dropTableName("{$table}__uw_temp");
1275
1276
		return '';
1277
	}
1278
1279
	protected function verifyGenericReplaceQuery($querytype, $table, $query)
1280
	{
1281
		$this->log->debug("verifying $querytype statement");
1282
1283
		if(!$this->makeTempTableCopy($table)) {
1284
			return 'Could not create temp table copy';
1285
		}
1286
		// test the query on the test table
1287
		$this->log->debug('testing query: ['.$query.']');
1288
		$tempTableTestQuery = str_replace("$querytype `{$table}`", "$querytype `{$table}__uw_temp`", $query);
1289
		if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1290
			return 'Could not use a temp table to test query!';
1291
		}
1292
1293
		$this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1294
		$error = $this->lastError(); // empty on no-errors
1295
		$this->dropTableName("{$table}__uw_temp"); // just in case
1296
		return $error;
1297
	}
1298
1299
	/**
1300
	 * Tests a DROP TABLE query
1301
	 * @param string table The table name to get DDL
1302
	 * @param string query The query to test.
1303
	 * @return string Non-empty if error found
1304
	 */
1305
	public function verifyDropTable($table, $query)
1306
	{
1307
		return $this->verifyGenericReplaceQuery("DROP TABLE", $table, $query);
1308
	}
1309
1310
	/**
1311
	 * Tests an INSERT INTO query
1312
	 * @param string table The table name to get DDL
1313
	 * @param string query The query to test.
1314
	 * @return string Non-empty if error found
1315
	 */
1316
	public function verifyInsertInto($table, $query)
1317
	{
1318
		return $this->verifyGenericReplaceQuery("INSERT INTO", $table, $query);
1319
	}
1320
1321
	/**
1322
	 * Tests an UPDATE query
1323
	 * @param string table The table name to get DDL
1324
	 * @param string query The query to test.
1325
	 * @return string Non-empty if error found
1326
	 */
1327
	public function verifyUpdate($table, $query)
1328
	{
1329
		return $this->verifyGenericReplaceQuery("UPDATE", $table, $query);
1330
	}
1331
1332
	/**
1333
	 * Tests an DELETE FROM query
1334
	 * @param string table The table name to get DDL
1335
	 * @param string query The query to test.
1336
	 * @return string Non-empty if error found
1337
	 */
1338
	public function verifyDeleteFrom($table, $query)
1339
	{
1340
		return $this->verifyGenericReplaceQuery("DELETE FROM", $table, $query);
1341
	}
1342
1343
	/**
1344
	 * Check if certain database exists
1345
	 * @param string $dbname
1346
	 */
1347
	public function dbExists($dbname)
1348
	{
1349
		$db = $this->getOne("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ".$this->quoted($dbname));
1350
		return !empty($db);
1351
	}
1352
1353
	/**
1354
	 * Select database
1355
	 * @param string $dbname
1356
	 */
1357
	protected function selectDb($dbname)
1358
	{
1359
		return mysql_select_db($dbname);
1360
	}
1361
1362
	/**
1363
	 * Check if certain DB user exists
1364
	 * @param string $username
1365
	 */
1366
	public function userExists($username)
1367
	{
1368
		$db = $this->getOne("SELECT DATABASE()");
1369
		if(!$this->selectDb("mysql")) {
1370
			return false;
1371
		}
1372
		$user = $this->getOne("select count(*) from user where user = ".$this->quoted($username));
1373
		if(!$this->selectDb($db)) {
1374
			$this->checkError("Cannot select database $db", true);
1375
		}
1376
		return !empty($user);
1377
	}
1378
1379
	/**
1380
	 * Create DB user
1381
	 * @param string $database_name
1382
	 * @param string $host_name
1383
	 * @param string $user
1384
	 * @param string $password
1385
	 */
1386
	public function createDbUser($database_name, $host_name, $user, $password)
1387
	{
1388
		$qpassword = $this->quote($password);
1389
		$this->query("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX
1390
							ON `$database_name`.*
1391
							TO \"$user\"@\"$host_name\"
1392
							IDENTIFIED BY '{$qpassword}';", true);
1393
1394
		$this->query("SET PASSWORD FOR \"{$user}\"@\"{$host_name}\" = password('{$qpassword}');", true);
1395
		if($host_name != 'localhost') {
1396
			$this->createDbUser($database_name, "localhost", $user, $password);
1397
		}
1398
	}
1399
1400
	/**
1401
	 * Create a database
1402
	 * @param string $dbname
1403
	 */
1404
	public function createDatabase($dbname)
1405
	{
1406
		$this->query("CREATE DATABASE `$dbname` CHARACTER SET utf8 COLLATE utf8_general_ci", true);
1407
	}
1408
1409
	public function preInstall()
1410
	{
1411
		$db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT CHARACTER SET utf8", true);
1412
		$db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT COLLATE utf8_general_ci", true);
1413
1414
	}
1415
1416
	/**
1417
	 * Drop a database
1418
	 * @param string $dbname
1419
	 */
1420
	public function dropDatabase($dbname)
1421
	{
1422
		return $this->query("DROP DATABASE IF EXISTS `$dbname`", true);
1423
	}
1424
1425
	/**
1426
	 * Check if this driver can be used
1427
	 * @return bool
1428
	 */
1429
	public function valid()
1430
	{
1431
		return function_exists("mysql_connect");
1432
	}
1433
1434
	/**
1435
	 * Check DB version
1436
	 * @see DBManager::canInstall()
1437
	 */
1438
	public function canInstall()
1439
	{
1440
		$db_version = $this->version();
1441
		if(empty($db_version)) {
1442
			return array('ERR_DB_VERSION_FAILURE');
1443
		}
1444
		if(version_compare($db_version, '4.1.2') < 0) {
1445
			return array('ERR_DB_MYSQL_VERSION', $db_version);
1446
		}
1447
		return true;
1448
	}
1449
1450
	public function installConfig()
1451
	{
1452
		return array(
1453
			'LBL_DBCONFIG_MSG3' =>  array(
1454
				"setup_db_database_name" => array("label" => 'LBL_DBCONF_DB_NAME', "required" => true),
1455
			),
1456
			'LBL_DBCONFIG_MSG2' =>  array(
1457
				"setup_db_host_name" => array("label" => 'LBL_DBCONF_HOST_NAME', "required" => true),
1458
			),
1459
			'LBL_DBCONF_TITLE_USER_INFO' => array(),
1460
			'LBL_DBCONFIG_B_MSG1' => array(
1461
				"setup_db_admin_user_name" => array("label" => 'LBL_DBCONF_DB_ADMIN_USER', "required" => true),
1462
				"setup_db_admin_password" => array("label" => 'LBL_DBCONF_DB_ADMIN_PASSWORD', "type" => "password"),
1463
			)
1464
		);
1465
	}
1466
1467
	/**
1468
	 * Disable keys on the table
1469
	 * @abstract
1470
	 * @param string $tableName
1471
	 */
1472
	public function disableKeys($tableName)
1473
	{
1474
	    return $this->query('ALTER TABLE '.$tableName.' DISABLE KEYS');
1475
	}
1476
1477
	/**
1478
	 * Re-enable keys on the table
1479
	 * @abstract
1480
	 * @param string $tableName
1481
	 */
1482
	public function enableKeys($tableName)
1483
	{
1484
	    return $this->query('ALTER TABLE '.$tableName.' ENABLE KEYS');
1485
	}
1486
1487
    /**
1488
     * Returns a DB specific FROM clause which can be used to select against functions.
1489
     * Note that depending on the database that this may also be an empty string.
1490
     * @return string
1491
     */
1492
    public function getFromDummyTable()
1493
    {
1494
        return '';
1495
    }
1496
1497
    /**
1498
     * Returns a DB specific piece of SQL which will generate GUID (UUID)
1499
     * This string can be used in dynamic SQL to do multiple inserts with a single query.
1500
     * I.e. generate a unique Sugar id in a sub select of an insert statement.
1501
     * @return string
1502
     */
1503
1504
	public function getGuidSQL()
1505
    {
1506
      	return 'UUID()';
1507
    }
1508
}
1509