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/DBManager.php (35 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 - 2016 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
 * Base database driver implementation
96
 * @api
97
 */
98
abstract class DBManager
99
{
100
	/**
101
	 * Name of database
102
	 * @var resource
103
	 */
104
	public $database = null;
105
106
	/**
107
	 * Indicates whether we should die when we get an error from the DB
108
	 */
109
	protected $dieOnError = false;
110
111
	/**
112
	 * Indicates whether we should html encode the results from a query by default
113
	 */
114
	protected $encode = true;
115
116
	/**
117
	 * Records the execution time of the last query
118
	 */
119
	protected $query_time = 0;
120
121
	/**
122
	 * Last error message from the DB backend
123
	 */
124
	protected $last_error = false;
125
126
	/**
127
	 * Registry of available result sets
128
	 */
129
	protected $lastResult;
130
131
	/**
132
	 * Current query count
133
	 */
134
	private static $queryCount = 0;
135
136
	/**
137
	 * Query threshold limit
138
	 */
139
	private static $queryLimit = 0;
140
141
	/**
142
	 * Array of prepared statements and their correspoding parsed tokens
143
	 */
144
	protected $preparedTokens = array();
145
146
	/**
147
	 * TimeDate instance
148
	 * @var TimeDate
149
	 */
150
	protected $timedate;
151
152
	/**
153
	 * PHP Logger
154
	 * @var Logger
155
	 */
156
	protected $log;
157
158
	/**
159
	 * Table descriptions
160
	 * @var array
161
	 */
162
	protected static $table_descriptions = array();
163
164
	/**
165
	 * Index descriptions
166
	 * @var array
167
	 */
168
	protected static $index_descriptions = array();
169
170
	/**
171
	 * Maximum length of identifiers
172
	 * @abstract
173
	 * @var array
174
	 */
175
	protected $maxNameLengths = array(
176
		'table' => 64,
177
		'column' => 64,
178
		'index' => 64,
179
		'alias' => 64
180
	);
181
182
	/**
183
	 * DB driver priority
184
	 * Higher priority drivers override lower priority ones
185
	 * @var int
186
	 */
187
	public $priority = 0;
188
189
	/**
190
	 * Driver name label, for install
191
	 * @absrtact
192
	 * @var string
193
	 */
194
	public $label = '';
195
196
	/**
197
	 * Type names map
198
	 * @abstract
199
	 * @var array
200
	 */
201
	protected $type_map = array();
202
203
	/**
204
	 * Type classification into:
205
	 * - int
206
	 * - bool
207
	 * - float
208
	 * - date
209
	 * @abstract
210
	 * @var array
211
	 */
212
	protected $type_class = array(
213
			'int'      => 'int',
214
			'double'   => 'float',
215
			'float'    => 'float',
216
			'uint'     => 'int',
217
			'ulong'    => 'bigint',
218
			'long'     => 'bigint',
219
			'short'    => 'int',
220
			'date'     => 'date',
221
			'datetime' => 'date',
222
			'datetimecombo' => 'date',
223
			'time'     => 'time',
224
			'bool'     => 'bool',
225
			'tinyint'  => 'int',
226
			'currency' => 'float',
227
			'decimal'  => 'float',
228
			'decimal2' => 'float',
229
	);
230
231
	/**
232
	 * Capabilities this DB supports. Supported list:
233
	 * affected_rows	Can report query affected rows for UPDATE/DELETE
234
	 * 					implement getAffectedRowCount()
235
	 * select_rows		Can report row count for SELECT
236
	 * 					implement getRowCount()
237
	 * case_sensitive	Supports case-sensitive text columns
238
	 * fulltext			Supports fulltext search indexes
239
	 * inline_keys		Supports defining keys together with the table
240
	 * auto_increment_sequence Autoincrement support implemented as sequence
241
	 * limit_subquery   Supports LIMIT clauses in subqueries
242
	 * create_user		Can create users for Sugar
243
	 * create_db		Can create databases
244
	 * collation		Supports setting collations
245
	 * disable_keys     Supports temporarily disabling keys (for upgrades, etc.)
246
	 *
247
	 * @abstract
248
	 * Special cases:
249
	 * fix:expandDatabase - needs expandDatabase fix, see expandDatabase.php
250
	 * TODO: verify if we need these cases
251
	 */
252
	protected $capabilities = array();
253
254
	/**
255
	 * Database options
256
	 * @var array
257
	 */
258
	protected $options = array();
259
260
    /**
261
     * Create DB Driver
262
     */
263 1
	public function __construct()
264
	{
265 1
		$this->timedate = TimeDate::getInstance();
266 1
		$this->log = isset($GLOBALS['log']) ? $GLOBALS['log'] : null;
267 1
		$this->helper = $this; // compatibility
268 1
	}
269
270
    /**
271
     * Wrapper for those trying to access the private and protected class members directly
272
     * @param string $p var name
273
     * @return mixed
274
     */
275
	public function __get($p)
276
	{
277
		$this->log->info('Call to DBManager::$'.$p.' is deprecated');
278
		return $this->$p;
279
	}
280
281
	/**
282
	 * Returns the current database handle
283
	 * @return resource
284
	 */
285 223
	public function getDatabase()
286
	{
287 223
		$this->checkConnection();
288 223
		return $this->database;
289
	}
290
291
	/**
292
	 * Returns this instance's DBHelper
293
	 * Actually now returns $this
294
	 * @deprecated
295
	 * @return DBManager
296
	 */
297
	public function getHelper()
298
	{
299
		return $this;
300
	}
301
302
	/**
303
	 * Checks for error happening in the database
304
	 *
305
	 * @param  string $msg        message to prepend to the error message
306
	 * @param  bool   $dieOnError true if we want to die immediately on error
307
	 * @return bool True if there was an error
308
	 */
309 896
	public function checkError($msg = '', $dieOnError = false)
310
	{
311 896
		if (empty($this->database)) {
312
			$this->registerError($msg, "Database Is Not Connected", $dieOnError);
313
			return true;
314
		}
315
316 896
		$dberror = $this->lastDbError();
317 896
		if($dberror === false) {
318 896
    		$this->last_error = false;
319 896
	    	return false;
320
		}
321 2
		$this->registerError($msg, $dberror, $dieOnError);
322 2
        return true;
323
	}
324
325
	/**
326
	 * Register database error
327
	 * If die-on-error flag is set, logs the message and dies,
328
	 * otherwise sets last_error to the message
329
	 * @param string $userMessage Message from function user
330
	 * @param string $message Message from SQL driver
331
	 * @param bool $dieOnError
332
	 */
333 2
	protected function registerError($userMessage, $message, $dieOnError = false)
334
	{
335 2
		if(!empty($message)) {
336 2
			if(!empty($userMessage)) {
337 2
				$message = "$userMessage: $message";
338
			}
339 2
			if(empty($message)) {
340
			    $message = "Database error";
341
			}
342 2
			$this->log->fatal($message);
343 2
			if ($dieOnError || $this->dieOnError) {
344
				if(isset($GLOBALS['app_strings']['ERR_DB_FAIL'])) {
345
					sugar_die($GLOBALS['app_strings']['ERR_DB_FAIL']);
346
				} else {
347
					sugar_die("Database error. Please check suitecrm.log for details.");
348
				}
349
			} else {
350 2
				$this->last_error = $message;
351
			}
352
		}
353 2
	}
354
355
	/**
356
	 * Return DB error message for the last query executed
357
	 * @return string Last error message
358
	 */
359
	public function lastError()
360
	{
361
		return $this->last_error;
362
	}
363
364
	/**
365
	 * This method is called by every method that runs a query.
366
	 * If slow query dumping is turned on and the query time is beyond
367
	 * the time limit, we will log the query. This function may do
368
	 * additional reporting or log in a different area in the future.
369
	 *
370
	 * @param  string  $query query to log
371
	 * @return boolean true if the query was logged, false otherwise
372
	 */
373
	protected function dump_slow_queries($query)
374
	{
375
		global $sugar_config;
376
377
		$do_the_dump = isset($sugar_config['dump_slow_queries'])
378
			? $sugar_config['dump_slow_queries'] : false;
379
		$slow_query_time_msec = isset($sugar_config['slow_query_time_msec'])
380
			? $sugar_config['slow_query_time_msec'] : 5000;
381
382
		if($do_the_dump) {
383
			if($slow_query_time_msec < ($this->query_time * 1000)) {
384
				// Then log both the query and the query time
385
				$this->log->fatal('Slow Query (time:'.$this->query_time."\n".$query);
386
				return true;
387
			}
388
		}
389
		return false;
390
	}
391
392
/**
393
	* Scans order by to ensure that any field being ordered by is.
394
	*
395
	* It will throw a warning error to the log file - fatal if slow query logging is enabled
396
	*
397
	* @param  string $sql         query to be run
398
	* @param  bool   $object_name optional, object to look up indices in
399
	* @return bool   true if an index is found false otherwise
400
	*/
401
protected function checkQuery($sql, $object_name = false)
402
{
403
	$match = array();
404
	preg_match_all("'.* FROM ([^ ]*).* ORDER BY (.*)'is", $sql, $match);
405
	$indices = false;
406
	if (!empty($match[1][0]))
407
		$table = $match[1][0];
408
	else
409
		return false;
410
411
	if (!empty($object_name) && !empty($GLOBALS['dictionary'][$object_name]))
412
		$indices = $GLOBALS['dictionary'][$object_name]['indices'];
413
414
	if (empty($indices)) {
415
		foreach ( $GLOBALS['dictionary'] as $current ) {
416
			if ($current['table'] == $table){
417
				$indices = $current['indices'];
418
				break;
419
			}
420
		}
421
	}
422
	if (empty($indices)) {
423
		$this->log->warn('CHECK QUERY: Could not find index definitions for table ' . $table);
424
		return false;
425
	}
426
	if (!empty($match[2][0])) {
427
		$orderBys = explode(' ', $match[2][0]);
428
		foreach ($orderBys as $orderBy){
429
			$orderBy = trim($orderBy);
430
			if (empty($orderBy))
431
				continue;
432
			$orderBy = strtolower($orderBy);
433
			if ($orderBy == 'asc' || $orderBy == 'desc')
434
				continue;
435
436
			$orderBy = str_replace(array($table . '.', ','), '', $orderBy);
437
438
			foreach ($indices as $index)
439
				if (empty($index['db']) || $index['db'] == $this->dbType)
0 ignored issues
show
The property dbType does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
440
					foreach ($index['fields'] as $field)
441
						if ($field == $orderBy)
442
							return true;
443
444
			$warning = 'Missing Index For Order By Table: ' . $table . ' Order By:' . $orderBy ;
445
			if (!empty($GLOBALS['sugar_config']['dump_slow_queries']))
446
				$this->log->fatal('CHECK QUERY:' .$warning);
447
			else
448
				$this->log->warn('CHECK QUERY:' .$warning);
449
		}
450
	}
451
	return false;
452
	}
453
454
	/**
455
	 * Returns the time the last query took to execute
456
	 *
457
	 * @return int
458
	 */
459
	public function getQueryTime()
460
	{
461
		return $this->query_time;
462
	}
463
464
	/**
465
	 * Checks the current connection; if it is not connected then reconnect
466
	 */
467 897
	public function checkConnection()
468
	{
469 897
		$this->last_error = '';
0 ignored issues
show
Documentation Bug introduced by
The property $last_error was declared of type boolean, but '' is of type string. 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...
470 897
		if (!isset($this->database)) {
471 84
            $this->connect();
472
        }else{
473
            try {
474 897
                $this->database->ping();
0 ignored issues
show
The method ping cannot be called on $this->database (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
475 10
            }catch(Exception $ex){
476 10
                unset($this->database);
477 10
                $this->connect();
478
            }
479
        }
480 897
	}
481
482
	/**
483
	 * Sets the dieOnError value
484
	 *
485
	 * @param bool $value
486
	 */
487
	public function setDieOnError($value)
488
	{
489
		$this->dieOnError = $value;
490
	}
491
492
    /**
493
     * Implements a generic insert for any bean.
494
     *
495
     * @param SugarBean $bean SugarBean instance
496
     * @return bool query result
497
     *
498
     */
499 63
	public function insert(SugarBean $bean)
500
	{
501 63
		$sql = $this->insertSQL($bean);
502 63
		$tablename =  $bean->getTableName();
503 63
		$msg = "Error inserting into table: $tablename:";
504 63
		return $this->query($sql,true,$msg);
0 ignored issues
show
It seems like $sql defined by $this->insertSQL($bean) on line 501 can also be of type boolean or resource; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
505
	}
506
507
	/**
508
	 * Insert data into table by parameter definition
509
	 * @param string $table Table name
510
	 * @param array $field_defs Definitions in vardef-like format
511
	 * @param array $data Key/value to insert
512
	 * @param array $field_map Fields map from SugarBean
513
	 * @param bool $execute Execute or return query?
514
     * @return bool query result
515
     */
516 63
	public function insertParams($table, $field_defs, $data, $field_map = null, $execute = true)
517
	{
518 63
		$values = array();
519 63
		foreach ($field_defs as $field => $fieldDef)
520
		{
521 63
			if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
522
			//custom fields handle there save seperatley
523 63
			if(!empty($field_map) && !empty($field_map[$field]['custom_type'])) continue;
524
525 63
			if(isset($data[$field])) {
526
				// clean the incoming value..
527 63
				$val = from_html($data[$field]);
528
			} else {
529 60
				if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
530
					$val = $fieldDef['default'];
531
				} else {
532 60
					$val = null;
533
				}
534
			}
535
536
			//handle auto increment values here - we may have to do something like nextval for oracle
537 63
			if (!empty($fieldDef['auto_increment'])) {
538 4
				$auto = $this->getAutoIncrementSQL($table, $fieldDef['name']);
539 4
				if(!empty($auto)) {
540 4
					$values[$field] = $auto;
541
				}
542 63
			} elseif ($fieldDef['name'] == 'deleted') {
543 63
				$values['deleted'] = (int)$val;
544
			} else {
545
				// need to do some thing about types of values
546 63
				if(!is_null($val) || !empty($fieldDef['required'])) {
547 63
					$values[$field] = $this->massageValue($val, $fieldDef);
548
				}
549
			}
550
		}
551
552 63
		if (empty($values))
553
			return $execute?true:''; // no columns set
554
555
		// get the entire sql
556 63
		$query = "INSERT INTO $table (".implode(",", array_keys($values)).")
557 63
					VALUES (".implode(",", $values).")";
558 63
		return $execute?$this->query($query):$query;
559
	}
560
561
    /**
562
     * Implements a generic update for any bean
563
     *
564
     * @param SugarBean $bean Sugarbean instance
565
     * @param array $where values with the keys as names of fields.
566
     * If we want to pass multiple values for a name, pass it as an array
567
     * If where is not passed, it defaults to id of table
568
     * @return bool query result
569
     *
570
     */
571 19
	public function update(SugarBean $bean, array $where = array())
572
	{
573 19
		$sql = $this->updateSQL($bean, $where);
574 19
		$tablename = $bean->getTableName();
575 19
		$msg = "Error updating table: $tablename:";
576 19
		return $this->query($sql,true,$msg);
577
	}
578
579
    /**
580
     * Implements a generic delete for any bean identified by id
581
     *
582
     * @param SugarBean $bean Sugarbean instance
583
     * @param array  $where values with the keys as names of fields.
584
     * If we want to pass multiple values for a name, pass it as an array
585
     * If where is not passed, it defaults to id of table
586
     * @return bool query result
587
     */
588
	public function delete(SugarBean $bean, array $where = array())
589
	{
590
		$sql = $this->deleteSQL($bean, $where);
591
		$tableName = $bean->getTableName();
592
		$msg = "Error deleting from table: ".$tableName. ":";
593
		return $this->query($sql,true,$msg);
594
	}
595
596
	/**
597
	 * Implements a generic retrieve for any bean identified by id
598
	 *
599
	 * If we want to pass multiple values for a name, pass it as an array
600
	 * If where is not passed, it defaults to id of table
601
	 *
602
	 * @param  SugarBean   $bean  Sugarbean instance
603
	 * @param  array    $where values with the keys as names of fields.
604
	 * @return resource result from the query
605
	 */
606
	public function retrieve(SugarBean $bean, array $where = array())
607
	{
608
		$sql = $this->retrieveSQL($bean, $where);
609
		$tableName = $bean->getTableName();
610
		$msg = "Error retriving values from table:".$tableName. ":";
611
		return $this->query($sql,true,$msg);
612
	}
613
614
	/**
615
	 * Implements a generic retrieve for a collection of beans.
616
	 *
617
	 * These beans will be joined in the sql by the key attribute of field defs.
618
	 * Currently, this function does support outer joins.
619
	 *
620
	 * @param  array $beans Sugarbean instance(s)
621
	 * @param  array $cols  columns to be returned with the keys as names of bean as identified by
622
	 * get_class of bean. Values of this array is the array of fieldDefs to be returned for a bean.
623
	 * If an empty array is passed, all columns are selected.
624
	 * @param  array $where  values with the keys as names of bean as identified by get_class of bean
625
	 * Each value at the first level is an array of values for that bean identified by name of fields.
626
	 * If we want to pass multiple values for a name, pass it as an array
627
	 * If where is not passed, all the rows will be returned.
628
	 * @return resource
629
	 */
630
	public function retrieveView(array $beans, array $cols = array(), array $where = array())
631
	{
632
		$sql = $this->retrieveViewSQL($beans, $cols, $where);
633
		$msg = "Error retriving values from View Collection:";
634
		return $this->query($sql,true,$msg);
635
	}
636
637
638
	/**
639
	 * Implements creation of a db table for a bean.
640
	 *
641
	 * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
642
	 * @param SugarBean $bean  Sugarbean instance
643
	 */
644
	public function createTable(SugarBean $bean)
645
	{
646
		$sql = $this->createTableSQL($bean);
647
		$tablename = $bean->getTableName();
648
		$msg = "Error creating table: $tablename:";
649
		$this->query($sql,true,$msg);
650
		if(!$this->supports("inline_keys")) {
651
		// handle constraints and indices
652
			$indicesArr = $this->createConstraintSql($bean);
653
			if (count($indicesArr) > 0)
654
				foreach ($indicesArr as $indexSql)
655
					$this->query($indexSql, true, $msg);
656
		}
657
	}
658
659
	/**
660
	 * returns SQL to create constraints or indices
661
	 *
662
	 * @param  SugarBean $bean SugarBean instance
663
	 * @return array list of SQL statements
664
	 */
665
	protected function createConstraintSql(SugarBean $bean)
666
	{
667
		return $this->getConstraintSql($bean->getIndices(), $bean->getTableName());
668
	}
669
670
	/**
671
	 * Implements creation of a db table
672
	 *
673
	 * @param string $tablename
674
	 * @param array  $fieldDefs  Field definitions, in vardef format
675
	 * @param array  $indices    Index definitions, in vardef format
676
	 * @param string $engine    Engine parameter, used for MySQL engine so far
677
     * @todo: refactor engine param to be more generic
678
     * @return bool success value
679
     */
680
	public function createTableParams($tablename, $fieldDefs, $indices, $engine = null)
681
	{
682
		if (!empty($fieldDefs)) {
683
			$sql = $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
0 ignored issues
show
The call to DBManager::createTableSQLParams() has too many arguments starting with $engine.

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...
684
			$res = true;
685
			if ($sql) {
686
				$msg = "Error creating table: $tablename";
687
				$res = ($res and $this->query($sql,true,$msg));
688
			}
689
			if(!$this->supports("inline_keys")) {
690
				// handle constraints and indices
691
				$indicesArr = $this->getConstraintSql($indices, $tablename);
692
				if (count($indicesArr) > 0)
693
					foreach ($indicesArr as $indexSql)
694
						$res = ($res and $this->query($indexSql, true, "Error creating indexes"));
695
			}
696
			return $res;
697
		}
698
		return false;
699
	}
700
701
	/**
702
	 * Implements repair of a db table for a bean.
703
	 *
704
	 * @param  SugarBean $bean    SugarBean instance
705
	 * @param  bool   $execute true if we want the action to take place, false if we just want the sql returned
706
	 * @return string SQL statement or empty string, depending upon $execute
707
	 */
708
	public function repairTable(SugarBean $bean, $execute = true)
709
	{
710
		$indices   = $bean->getIndices();
711
		$fielddefs = $bean->getFieldDefinitions();
712
		$tablename = $bean->getTableName();
713
714
		//Clean the indexes to prevent duplicate definitions
715
		$new_index = array();
716
		foreach($indices as $ind_def){
717
			$new_index[$ind_def['name']] = $ind_def;
718
		}
719
		//jc: added this for beans that do not actually have a table, namely
720
		//ForecastOpportunities
721
		if($tablename == 'does_not_exist' || $tablename == '')
722
			return '';
723
724
		global $dictionary;
725
		$engine=null;
726
		if (isset($dictionary[$bean->getObjectName()]['engine']) && !empty($dictionary[$bean->getObjectName()]['engine']) )
727
			$engine = $dictionary[$bean->getObjectName()]['engine'];
728
729
		return $this->repairTableParams($tablename, $fielddefs,$new_index,$execute,$engine);
730
	}
731
732
	/**
733
	 * Can this field be null?
734
	 * Auto-increment and ID fields can not be null
735
	 * @param array $vardef
736
     * @return bool
737
     */
738 10
	protected function isNullable($vardef)
739
	{
740
741 10
		if(isset($vardef['isnull']) && (strtolower($vardef['isnull']) == 'false' || $vardef['isnull'] === false)
742 10
			&& !empty($vardef['required'])) {
743
				/* required + is_null=false => not null */
744
			return false;
745
		}
746 10
		if(empty($vardef['auto_increment']) && (empty($vardef['type']) || $vardef['type'] != 'id')
747 10
					&& (empty($vardef['dbType']) || $vardef['dbType'] != 'id')
748 10
					&& (empty($vardef['name']) || ($vardef['name'] != 'id' && $vardef['name'] != 'deleted'))
749
		) {
750 10
			return true;
751
		}
752 8
		return false;
753
	}
754
755
756
	/**
757
	 * Builds the SQL commands that repair a table structure
758
	 *
759
	 * @param  string $tablename
760
	 * @param  array  $fielddefs Field definitions, in vardef format
761
	 * @param  array  $indices   Index definitions, in vardef format
762
	 * @param  bool   $execute   optional, true if we want the queries executed instead of returned
763
	 * @param  string $engine    optional, MySQL engine
764
     * @todo: refactor engine param to be more generic
765
     * @return string
766
     */
767
	public function repairTableParams($tablename, $fielddefs,  $indices, $execute = true, $engine = null)
768
	{
769
		//jc: had a bug when running the repair if the tablename is blank the repair will
770
		//fail when it tries to create a repair table
771
		if ($tablename == '' || empty($fielddefs))
772
			return '';
773
774
		//if the table does not exist create it and we are done
775
		$sql = "/* Table : $tablename */\n";
776
		if (!$this->tableExists($tablename)) {
777
			$createtablesql = $this->createTableSQLParams($tablename,$fielddefs,$indices,$engine);
0 ignored issues
show
The call to DBManager::createTableSQLParams() has too many arguments starting with $engine.

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...
778
			if($execute && $createtablesql){
779
				$this->createTableParams($tablename,$fielddefs,$indices,$engine);
780
			}
781
782
			$sql .= "/* MISSING TABLE: {$tablename} */\n";
783
			$sql .= $createtablesql . "\n";
784
			return $sql;
785
		}
786
787
		$compareFieldDefs = $this->get_columns($tablename);
788
		$compareIndices = $this->get_indices($tablename);
789
790
		$take_action = false;
791
792
		// do column comparisons
793
		$sql .=	"/*COLUMNS*/\n";
794
		foreach ($fielddefs as $name => $value) {
795
			if (isset($value['source']) && $value['source'] != 'db')
796
				continue;
797
798
            // Bug #42406. Skipping breaked vardef without type or name
799
            if (isset($value['name']) == false || $value['name'] == false)
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...
800
            {
801
                $sql .= "/* NAME IS MISSING IN VARDEF $tablename::$name */\n";
802
                continue;
803
            }
804
            else if (isset($value['type']) == false || $value['type'] == false)
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...
805
            {
806
                $sql .= "/* TYPE IS MISSING IN VARDEF $tablename::$name */\n";
807
                continue;
808
            }
809
810
			$name = strtolower($value['name']);
811
			// add or fix the field defs per what the DB is expected to give us back
812
			$this->massageFieldDef($value,$tablename);
813
814
			$ignorerequired=false;
815
816
			//Do not track requiredness in the DB, auto_increment, ID,
817
			// and deleted fields are always required in the DB, so don't force those
818
			if ($this->isNullable($value)) {
819
				$value['required'] = false;
820
			}
821
			//Should match the conditions in DBManager::oneColumnSQLRep for DB required fields, type='id' fields will sometimes
822
823
			//come into this function as 'type' = 'char', 'dbType' = 'id' without required set in $value. Assume they are correct and leave them alone.
824
			else if (($name == 'id' || $value['type'] == 'id' || (isset($value['dbType']) && $value['dbType'] == 'id'))
825
				&& (!isset($value['required']) && isset($compareFieldDefs[$name]['required'])))
826
			{
827
				$value['required'] = $compareFieldDefs[$name]['required'];
828
			}
829
830
			if ( !isset($compareFieldDefs[$name]) ) {
831
				// ok we need this field lets create it
832
				$sql .=	"/*MISSING IN DATABASE - $name -  ROW*/\n";
833
				$sql .= $this->addColumnSQL($tablename, $value) .  "\n";
834
				if ($execute)
835
					$this->addColumn($tablename, $value);
836
				$take_action = true;
837
			} elseif ( !$this->compareVarDefs($compareFieldDefs[$name],$value)) {
838
				//fields are different lets alter it
839
				$sql .=	"/*MISMATCH WITH DATABASE - $name -  ROW ";
840
				foreach($compareFieldDefs[$name] as $rKey => $rValue) {
841
					$sql .=	"[$rKey] => '$rValue'  ";
842
				}
843
				$sql .=	"*/\n";
844
				$sql .=	"/* VARDEF - $name -  ROW";
845
				foreach($value as $rKey => $rValue) {
846
					$sql .=	"[$rKey] => '$rValue'  ";
847
				}
848
				$sql .=	"*/\n";
849
850
				//jc: oracle will complain if you try to execute a statement that sets a column to (not) null
851
				//when it is already (not) null
852
				if ( isset($value['isnull']) && isset($compareFieldDefs[$name]['isnull']) &&
853
					$value['isnull'] === $compareFieldDefs[$name]['isnull']) {
854
					unset($value['required']);
855
					$ignorerequired=true;
856
				}
857
858
				//dwheeler: Once a column has been defined as null, we cannot try to force it back to !null
859
				if ((isset($value['required']) && ($value['required'] === true || $value['required'] == 'true' || $value['required'] === 1))
860
					&& (empty($compareFieldDefs[$name]['required']) || $compareFieldDefs[$name]['required'] != 'true'))
861
				{
862
					$ignorerequired = true;
863
				}
864
				$altersql = $this->alterColumnSQL($tablename, $value,$ignorerequired);
865
				if(is_array($altersql)) {
866
					$altersql = join("\n", $altersql);
867
				}
868
				$sql .= $altersql .  "\n";
869
				if($execute){
870
					$this->alterColumn($tablename, $value, $ignorerequired);
871
				}
872
				$take_action = true;
873
			}
874
		}
875
876
		// do index comparisons
877
		$sql .=	"/* INDEXES */\n";
878
		$correctedIndexs = array();
879
880
        $compareIndices_case_insensitive = array();
881
882
		// do indices comparisons case-insensitive
883
		foreach($compareIndices as $k => $value){
884
			$value['name'] = strtolower($value['name']);
885
			$compareIndices_case_insensitive[strtolower($k)] = $value;
886
		}
887
		$compareIndices = $compareIndices_case_insensitive;
888
		unset($compareIndices_case_insensitive);
889
890
		foreach ($indices as $value) {
891
			if (isset($value['source']) && $value['source'] != 'db')
892
				continue;
893
894
895
			$validDBName = $this->getValidDBName($value['name'], true, 'index', true);
896
			if (isset($compareIndices[$validDBName])) {
897
				$value['name'] = $validDBName;
898
			}
899
		    $name = strtolower($value['name']);
900
901
			//Don't attempt to fix the same index twice in one pass;
902
			if (isset($correctedIndexs[$name]))
903
				continue;
904
905
			//don't bother checking primary nothing we can do about them
906
			if (isset($value['type']) && $value['type'] == 'primary')
907
				continue;
908
909
			//database helpers do not know how to handle full text indices
910
			if ($value['type']=='fulltext')
911
				continue;
912
913
			if ( in_array($value['type'],array('alternate_key','foreign')) )
914
				$value['type'] = 'index';
915
916
			if ( !isset($compareIndices[$name]) ) {
917
				//First check if an index exists that doesn't match our name, if so, try to rename it
918
				$found = false;
919
				foreach ($compareIndices as $ex_name => $ex_value) {
920
					if($this->compareVarDefs($ex_value, $value, true)) {
921
						$found = $ex_name;
922
						break;
923
					}
924
				}
925
				if ($found) {
926
					$sql .=	 "/*MISSNAMED INDEX IN DATABASE - $name - $ex_name */\n";
0 ignored issues
show
The variable $ex_name seems to be defined by a foreach iteration on line 919. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
927
					$rename = $this->renameIndexDefs($ex_value, $value, $tablename);
0 ignored issues
show
The variable $ex_value seems to be defined by a foreach iteration on line 919. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
928
					if($execute) {
929
						$this->query($rename, true, "Cannot rename index");
930
					}
931
					$sql .= is_array($rename)?join("\n", $rename). "\n":$rename."\n";
932
933
				} else {
934
					// ok we need this field lets create it
935
					$sql .=	 "/*MISSING INDEX IN DATABASE - $name -{$value['type']}  ROW */\n";
936
					$sql .= $this->addIndexes($tablename,array($value), $execute) .  "\n";
937
				}
938
				$take_action = true;
939
				$correctedIndexs[$name] = true;
940
			} elseif ( !$this->compareVarDefs($compareIndices[$name],$value) ) {
941
				// fields are different lets alter it
942
				$sql .=	"/*INDEX MISMATCH WITH DATABASE - $name -  ROW ";
943
				foreach ($compareIndices[$name] as $n1 => $t1) {
944
					$sql .=	 "<$n1>";
945
					if ( $n1 == 'fields' )
946
						foreach($t1 as $rKey => $rValue)
947
							$sql .= "[$rKey] => '$rValue'  ";
948
					else
949
						$sql .= " $t1 ";
950
				}
951
				$sql .=	"*/\n";
952
				$sql .=	"/* VARDEF - $name -  ROW";
953
				foreach ($value as $n1 => $t1) {
954
					$sql .=	"<$n1>";
955
					if ( $n1 == 'fields' )
956
						foreach ($t1 as $rKey => $rValue)
957
							$sql .=	"[$rKey] => '$rValue'  ";
958
					else
959
						$sql .= " $t1 ";
960
				}
961
				$sql .=	"*/\n";
962
				$sql .= $this->modifyIndexes($tablename,array($value), $execute) .  "\n";
963
				$take_action = true;
964
				$correctedIndexs[$name] = true;
965
			}
966
		}
967
968
		return ($take_action === true) ? $sql : '';
969
	}
970
971
    /**
972
     * Compares two vardefs
973
     *
974
     * @param  array  $fielddef1 This is from the database
975
     * @param  array  $fielddef2 This is from the vardef
976
     * @param bool $ignoreName Ignore name-only differences?
977
     * @return bool   true if they match, false if they don't
978
     */
979
	public function compareVarDefs($fielddef1, $fielddef2, $ignoreName = false)
980
	{
981
		foreach ( $fielddef1 as $key => $value ) {
982
			if ($key == 'name' && $ignoreName)
983
				continue;
984
            if (isset($fielddef2[$key]))
985
            {
986
                if (!is_array($fielddef1[$key]) && !is_array($fielddef2[$key]))
987
                {
988
                    if (strtolower($fielddef1[$key]) == strtolower($fielddef2[$key]))
989
                    {
990
                        continue;
991
                    }
992
                }
993
                else
994
                {
995
                    if (array_map('strtolower', $fielddef1[$key]) == array_map('strtolower',$fielddef2[$key]))
996
                    {
997
                        continue;
998
                    }
999
                }
1000
            }
1001
			//Ignore len if its not set in the vardef
1002
			if ($key == 'len' && empty($fielddef2[$key]))
1003
				continue;
1004
            // if the length in db is greather than the vardef, ignore it
1005
            if ($key == 'len' && ($fielddef1[$key] >= $fielddef2[$key])) {
1006
                continue;
1007
            }
1008
			return false;
1009
		}
1010
1011
		return true;
1012
	}
1013
1014
	/**
1015
	 * Compare a field in two tables
1016
	 * @deprecated
1017
	 * @param  string $name   field name
1018
	 * @param  string $table1
1019
	 * @param  string $table2
1020
	 * @return array  array with keys 'msg','table1','table2'
1021
	 */
1022
	public function compareFieldInTables($name, $table1, $table2)
1023
	{
1024
		$row1 = $this->describeField($name, $table1);
1025
		$row2 = $this->describeField($name, $table2);
1026
		$returnArray = array(
1027
			'table1' => $row1,
1028
			'table2' => $row2,
1029
			'msg'    => 'error',
1030
			);
1031
1032
		$ignore_filter = array('Key'=>1);
1033
		if ($row1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row1 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1034
			if (!$row2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row2 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1035
				// Exists on table1 but not table2
1036
				$returnArray['msg'] = 'not_exists_table2';
1037
			}
1038
			else {
1039
				if (sizeof($row1) != sizeof($row2)) {
1040
					$returnArray['msg'] = 'no_match';
1041
				}
1042
				else {
1043
					$returnArray['msg'] = 'match';
1044
					foreach($row1 as $key => $value){
1045
						//ignore keys when checking we will check them when we do the index check
1046
						if( !isset($ignore_filter[$key]) && (!isset($row2[$key]) || $row1[$key] !== $row2[$key])){
1047
							$returnArray['msg'] = 'no_match';
1048
						}
1049
					}
1050
				}
1051
			}
1052
		}
1053
		else {
1054
			$returnArray['msg'] = 'not_exists_table1';
1055
		}
1056
1057
		return $returnArray;
1058
	}
1059
//
1060
//    /**
1061
//     * Compare an index in two different tables
1062
//     * @deprecated
1063
//     * @param  string $name   index name
1064
//     * @param  string $table1
1065
//     * @param  string $table2
1066
//     * @return array  array with keys 'msg','table1','table2'
1067
//     */
1068
//    public function compareIndexInTables($name, $table1, $table2)
1069
//    {
1070
//        $row1 = $this->describeIndex($name, $table1);
1071
//        $row2 = $this->describeIndex($name, $table2);
1072
//        $returnArray = array(
1073
//            'table1' => $row1,
1074
//            'table2' => $row2,
1075
//            'msg'    => 'error',
1076
//            );
1077
//        $ignore_filter = array('Table'=>1, 'Seq_in_index'=>1,'Cardinality'=>1, 'Sub_part'=>1, 'Packed'=>1, 'Comment'=>1);
1078
//
1079
//        if ($row1) {
1080
//            if (!$row2) {
1081
//                //Exists on table1 but not table2
1082
//                $returnArray['msg'] = 'not_exists_table2';
1083
//            }
1084
//            else {
1085
//                if (sizeof($row1) != sizeof($row2)) {
1086
//                    $returnArray['msg'] = 'no_match';
1087
//                }
1088
//                else {
1089
//                    $returnArray['msg'] = 'match';
1090
//                    foreach ($row1 as $fname => $fvalue) {
1091
//                        if (!isset($row2[$fname])) {
1092
//                            $returnArray['msg'] = 'no_match';
1093
//                        }
1094
//                        if(!isset($ignore_filter[$fname]) && $row1[$fname] != $row2[$fname]){
1095
//                            $returnArray['msg'] = 'no_match';
1096
//                        }
1097
//                    }
1098
//                }
1099
//            }
1100
//        } else {
1101
//            $returnArray['msg'] = 'not_exists_table1';
1102
//        }
1103
//
1104
//        return $returnArray;
1105
//    }
1106
1107
1108
	/**
1109
	 * Creates an index identified by name on the given fields.
1110
	 *
1111
	 * @param SugarBean $bean      SugarBean instance
1112
	 * @param array  $fieldDefs Field definitions, in vardef format
1113
	 * @param string $name      index name
1114
	 * @param bool   $unique    optional, true if we want to create an unique index
1115
     * @return bool query result
1116
     */
1117
	public function createIndex(SugarBean $bean, $fieldDefs, $name, $unique = true)
1118
	{
1119
		$sql = $this->createIndexSQL($bean, $fieldDefs, $name, $unique);
1120
		$tablename = $bean->getTableName();
1121
		$msg = "Error creating index $name on table: $tablename:";
1122
		return $this->query($sql,true,$msg);
1123
	}
1124
1125
	/**
1126
	 * returns a SQL query that creates the indices as defined in metadata
1127
	 * @param  array  $indices Assoc array with index definitions from vardefs
1128
	 * @param  string $table Focus table
1129
	 * @return array  Array of SQL queries to generate indices
1130
	 */
1131
	public function getConstraintSql($indices, $table)
1132
	{
1133
		if (!$this->isFieldArray($indices))
1134
			$indices = array($indices);
1135
1136
		$columns = array();
1137
1138
		foreach ($indices as $index) {
1139
			if(!empty($index['db']) && $index['db'] != $this->dbType)
0 ignored issues
show
The property dbType does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1140
				continue;
1141
			if (isset($index['source']) && $index['source'] != 'db')
1142
			continue;
1143
1144
			$sql = $this->add_drop_constraint($table, $index);
1145
1146
			if(!empty($sql)) {
1147
				$columns[] = $sql;
1148
			}
1149
		}
1150
1151
		return $columns;
1152
	}
1153
1154
	/**
1155
	 * Adds a new indexes
1156
	 *
1157
	 * @param  string $tablename
1158
	 * @param  array  $indexes   indexes to add
1159
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1160
	 * @return string SQL statement
1161
	 */
1162
	public function addIndexes($tablename, $indexes, $execute = true)
1163
	{
1164
		$alters = $this->getConstraintSql($indexes, $tablename);
1165
		if ($execute) {
1166
			foreach($alters as $sql) {
1167
				$this->query($sql, true, "Error adding index: ");
1168
			}
1169
		}
1170
		if(!empty($alters)) {
1171
			$sql = join(";\n", $alters).";\n";
1172
		} else {
1173
			$sql = '';
1174
		}
1175
		return $sql;
1176
	}
1177
1178
	/**
1179
	 * Drops indexes
1180
	 *
1181
	 * @param  string $tablename
1182
	 * @param  array  $indexes   indexes to drop
1183
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1184
	 * @return string SQL statement
1185
	 */
1186
	public function dropIndexes($tablename, $indexes, $execute = true)
1187
	{
1188
		$sqls = array();
1189
		foreach ($indexes as $index) {
1190
			$name =$index['name'];
1191
			$sqls[$name] = $this->add_drop_constraint($tablename,$index,true);
1192
		}
1193
		if (!empty($sqls) && $execute) {
1194
			foreach($sqls as $name => $sql) {
1195
				unset(self::$index_descriptions[$tablename][$name]);
1196
				$this->query($sql);
1197
			}
1198
		}
1199
		if(!empty($sqls)) {
1200
			return join(";\n",$sqls).";";
1201
		} else {
1202
			return '';
1203
		}
1204
	}
1205
1206
	/**
1207
	 * Modifies indexes
1208
	 *
1209
	 * @param  string $tablename
1210
	 * @param  array  $indexes   indexes to modify
1211
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1212
	 * @return string SQL statement
1213
	 */
1214
	public function modifyIndexes($tablename, $indexes, $execute = true)
1215
	{
1216
		return $this->dropIndexes($tablename, $indexes, $execute)."\n".
1217
			$this->addIndexes($tablename, $indexes, $execute);
1218
	}
1219
1220
	/**
1221
	 * Adds a column to table identified by field def.
1222
	 *
1223
	 * @param string $tablename
1224
	 * @param array  $fieldDefs
1225
     * @return bool query result
1226
     */
1227
	public function addColumn($tablename, $fieldDefs)
1228
	{
1229
		$sql = $this->addColumnSQL($tablename, $fieldDefs);
1230
		if ($this->isFieldArray($fieldDefs)){
1231
			$columns = array();
1232
			foreach ($fieldDefs as $fieldDef)
1233
				$columns[] = $fieldDef['name'];
1234
			$columns = implode(",", $columns);
1235
		}
1236
		else {
1237
			$columns = $fieldDefs['name'];
1238
		}
1239
		$msg = "Error adding column(s) $columns on table: $tablename:";
1240
		return $this->query($sql,true,$msg);
0 ignored issues
show
It seems like $sql defined by $this->addColumnSQL($tablename, $fieldDefs) on line 1229 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1241
	}
1242
1243
	/**
1244
	 * Alters old column identified by oldFieldDef to new fieldDef.
1245
	 *
1246
	 * @param string $tablename
1247
	 * @param array  $newFieldDef
1248
	 * @param bool   $ignoreRequired optional, true if we are ignoring this being a required field
1249
     * @return bool query result
1250
     */
1251
	public function alterColumn($tablename, $newFieldDef, $ignoreRequired = false)
1252
	{
1253
		$sql = $this->alterColumnSQL($tablename, $newFieldDef,$ignoreRequired);
1254
		if ($this->isFieldArray($newFieldDef)){
1255
			$columns = array();
1256
			foreach ($newFieldDef as $fieldDef) {
1257
				$columns[] = $fieldDef['name'];
1258
			}
1259
			$columns = implode(",", $columns);
1260
		}
1261
		else {
1262
			$columns = $newFieldDef['name'];
1263
		}
1264
1265
		$msg = "Error altering column(s) $columns on table: $tablename:";
1266
		$res = $this->query($sql,true,$msg);
0 ignored issues
show
It seems like $sql defined by $this->alterColumnSQL($t...ldDef, $ignoreRequired) on line 1253 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1267
		if($res) {
1268
			$this->getTableDescription($tablename, true); // reload table description after altering
1269
		}
1270
		return $res;
1271
	}
1272
1273
	/**
1274
	 * Drops the table associated with a bean
1275
	 *
1276
	 * @param SugarBean $bean SugarBean instance
1277
     * @return bool query result
1278
	 */
1279
	public function dropTable(SugarBean $bean)
1280
	{
1281
		return $this->dropTableName($bean->getTableName());
1282
	}
1283
1284
	/**
1285
	 * Drops the table by name
1286
	 *
1287
	 * @param string $name Table name
1288
     * @return bool query result
1289
	 */
1290
	public function dropTableName($name)
1291
	{
1292
		$sql = $this->dropTableNameSQL($name);
1293
		return $this->query($sql,true,"Error dropping table $name:");
1294
	}
1295
1296
    /**
1297
     * Deletes a column identified by fieldDef.
1298
     *
1299
     * @param SugarBean $bean   SugarBean containing the field
1300
     * @param array  $fieldDefs Vardef definition of the field
1301
     * @return bool query result
1302
     */
1303
	public function deleteColumn(SugarBean $bean, $fieldDefs)
1304
	{
1305
		$tablename = $bean->getTableName();
1306
		$sql = $this->dropColumnSQL($tablename, $fieldDefs);
1307
		$msg = "Error deleting column(s) on table: $tablename:";
1308
		return $this->query($sql,true,$msg);
0 ignored issues
show
It seems like $sql defined by $this->dropColumnSQL($tablename, $fieldDefs) on line 1306 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1309
	}
1310
1311
    /**
1312
     * Generate a set of Insert statements based on the bean given
1313
     *
1314
     * @deprecated
1315
     *
1316
     * @param  SugarBean $bean         the bean from which table we will generate insert stmts
1317
     * @param  string $select_query the query which will give us the set of objects we want to place into our insert statement
1318
     * @param  int    $start        the first row to query
1319
     * @param  int    $count        the number of rows to query
1320
     * @param  string $table        the table to query from
1321
     * @param bool $is_related_query
1322
     * @return string SQL insert statement
1323
     */
1324
	public function generateInsertSQL(SugarBean $bean, $select_query, $start, $count = -1, $table, $is_related_query = false)
1325
	{
1326
		$this->log->info('call to DBManager::generateInsertSQL() is deprecated');
1327
		global $sugar_config;
1328
1329
		$rows_found = 0;
1330
		$count_query = $bean->create_list_count_query($select_query);
1331
		if(!empty($count_query))
1332
		{
1333
			// We have a count query.  Run it and get the results.
1334
			$result = $this->query($count_query, true, "Error running count query for $this->object_name List: ");
0 ignored issues
show
The property object_name does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1335
			$assoc = $this->fetchByAssoc($result);
1336
			if(!empty($assoc['c']))
1337
			{
1338
				$rows_found = $assoc['c'];
1339
			}
1340
		}
1341
		if($count == -1){
1342
			$count 	= $sugar_config['list_max_entries_per_page'];
1343
		}
1344
		$next_offset = $start + $count;
1345
1346
		$result = $this->limitQuery($select_query, $start, $count);
1347
		// get basic insert
1348
		$sql = "INSERT INTO ".$table;
1349
		$custom_sql = "INSERT INTO ".$table."_cstm";
1350
1351
		// get field definitions
1352
		$fields = $bean->getFieldDefinitions();
1353
		$custom_fields = array();
1354
1355
		if($bean->hasCustomFields()){
1356
			foreach ($fields as $fieldDef){
1357
				if($fieldDef['source'] == 'custom_fields'){
1358
					$custom_fields[$fieldDef['name']] = $fieldDef['name'];
1359
				}
1360
			}
1361
			if(!empty($custom_fields)){
1362
				$custom_fields['id_c'] = 'id_c';
1363
				$id_field = array('name' => 'id_c', 'custom_type' => 'id',);
1364
				$fields[] = $id_field;
1365
			}
1366
		}
1367
1368
		// get column names and values
1369
		$row_array = array();
1370
		$columns = array();
1371
		$cstm_row_array = array();
1372
		$cstm_columns = array();
1373
		$built_columns = false;
1374
		while(($row = $this->fetchByAssoc($result)) != null)
1375
		{
1376
			$values = array();
1377
			$cstm_values = array();
1378
			if(!$is_related_query){
1379
				foreach ($fields as $fieldDef)
1380
				{
1381
					if(isset($fieldDef['source']) && $fieldDef['source'] != 'db' && $fieldDef['source'] != 'custom_fields') continue;
1382
					$val = $row[$fieldDef['name']];
1383
1384
					//handle auto increment values here only need to do this on insert not create
1385
					if ($fieldDef['name'] == 'deleted'){
1386
							$values['deleted'] = $val;
1387
							if(!$built_columns){
1388
							$columns[] = 'deleted';
1389
						}
1390
					}
1391
					else
1392
					{
1393
						$type = $fieldDef['type'];
1394
						if(!empty($fieldDef['custom_type'])){
1395
							$type = $fieldDef['custom_type'];
1396
						}
1397
						// need to do some thing about types of values
1398
						if($this->dbType == 'mysql' && $val == '' && ($type == 'datetime' ||  $type == 'date' || $type == 'int' || $type == 'currency' || $type == 'decimal')){
0 ignored issues
show
The property dbType does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1399
							if(!empty($custom_fields[$fieldDef['name']]))
1400
								$cstm_values[$fieldDef['name']] = 'null';
1401
							else
1402
								$values[$fieldDef['name']] = 'null';
1403
						}else{
1404
							if(isset($type) && $type=='int') {
1405
								if(!empty($custom_fields[$fieldDef['name']]))
1406
									$cstm_values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1407
								else
1408
									$values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1409
							} else {
1410
								if(!empty($custom_fields[$fieldDef['name']]))
1411
									$cstm_values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1412
								else
1413
									$values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1414
							}
1415
						}
1416
						if(!$built_columns){
1417
							if(!empty($custom_fields[$fieldDef['name']]))
1418
								$cstm_columns[] = $fieldDef['name'];
1419
							else
1420
								$columns[] = $fieldDef['name'];
1421
						}
1422
					}
1423
1424
				}
1425
			} else {
1426
			foreach ($row as $key=>$val)
0 ignored issues
show
The expression $row of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1427
			{
1428
					if($key != 'orc_row'){
1429
						$values[$key] = "'$val'";
1430
						if(!$built_columns){
1431
							$columns[] = $key;
1432
						}
1433
					}
1434
			}
1435
			}
1436
			$built_columns = true;
1437
			if(!empty($values)){
1438
				$row_array[] = $values;
1439
			}
1440
			if(!empty($cstm_values) && !empty($cstm_values['id_c']) && (strlen($cstm_values['id_c']) > 7)){
1441
				$cstm_row_array[] = $cstm_values;
1442
			}
1443
		}
1444
1445
		//if (sizeof ($values) == 0) return ""; // no columns set
1446
1447
		// get the entire sql
1448
		$sql .= "(".implode(",", $columns).") ";
1449
		$sql .= "VALUES";
1450
		for($i = 0; $i < count($row_array); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1451
			$sql .= " (".implode(",", $row_array[$i]).")";
1452
			if($i < (count($row_array) - 1)){
1453
				$sql .= ", ";
1454
			}
1455
		}
1456
		//custom
1457
		// get the entire sql
1458
		$custom_sql .= "(".implode(",", $cstm_columns).") ";
1459
		$custom_sql .= "VALUES";
1460
1461
		for($i = 0; $i < count($cstm_row_array); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1462
			$custom_sql .= " (".implode(",", $cstm_row_array[$i]).")";
1463
			if($i < (count($cstm_row_array) - 1)){
1464
				$custom_sql .= ", ";
1465
			}
1466
		}
1467
		return array('data' => $sql, 'cstm_sql' => $custom_sql, /*'result_count' => $row_count, */ 'total_count' => $rows_found, 'next_offset' => $next_offset);
1468
	}
1469
1470
	/**
1471
	 * @deprecated
1472
	 * Disconnects all instances
1473
	 */
1474
	public function disconnectAll()
1475
	{
1476
		DBManagerFactory::disconnectAll();
1477
	}
1478
1479
	/**
1480
	 * This function sets the query threshold limit
1481
	 *
1482
	 * @param int $limit value of query threshold limit
1483
	 */
1484
	public static function setQueryLimit($limit)
1485
	{
1486
		//reset the queryCount
1487
		self::$queryCount = 0;
1488
		self::$queryLimit = $limit;
1489
	}
1490
1491
	/**
1492
	 * Returns the static queryCount value
1493
	 *
1494
	 * @return int value of the queryCount static variable
1495
	 */
1496
	public static function getQueryCount()
1497
	{
1498
		return self::$queryCount;
1499
	}
1500
1501
1502
	/**
1503
	 * Resets the queryCount value to 0
1504
	 *
1505
	 */
1506
	public static function resetQueryCount()
1507
	{
1508
		self::$queryCount = 0;
1509
	}
1510
1511
	/**
1512
	 * This function increments the global $sql_queries variable
1513
	 */
1514 896
	public function countQuery()
1515
	{
1516 896
		if (self::$queryLimit != 0 && ++self::$queryCount > self::$queryLimit
1517 896
			&&(empty($GLOBALS['current_user']) || !is_admin($GLOBALS['current_user']))) {
1518
            require_once('include/resource/ResourceManager.php');
1519
            $resourceManager = ResourceManager::getInstance();
1520
            $resourceManager->notifyObservers('ERR_QUERY_LIMIT');
1521
		}
1522 896
	}
1523
1524
	/**
1525
	 * Pre-process string for quoting
1526
	 * @internal
1527
	 * @param string $string
1528
     * @return string
1529
     */
1530 220
	protected function quoteInternal($string)
1531
	{
1532 220
		return from_html($string);
1533
	}
1534
1535
	/**
1536
	 * Return string properly quoted with ''
1537
	 * @param string $string
1538
	 * @return string
1539
	 */
1540 165
	public function quoted($string)
1541
	{
1542 165
		return "'".$this->quote($string)."'";
1543
	}
1544
1545
	/**
1546
     * Quote value according to type
1547
     * Numerics aren't quoted
1548
     * Dates are converted and quoted
1549
     * Rest is just quoted
1550
     * @param string $type
1551
     * @param string $value
1552
     * @return string Quoted value
1553
     */
1554
    public function quoteType($type, $value)
1555
	{
1556
	    if($type == 'date') {
1557
	        return $this->convert($this->quoted($value), "date");
1558
	    }
1559
	    if($type == 'time') {
1560
	        return $this->convert($this->quoted($value), "time");
1561
	    }
1562
        if(isset($this->type_class[$type]) &&  $this->type_class[$type] == "date") {
1563
            return $this->convert($this->quoted($value), "datetime");
1564
        }
1565
        if($this->isNumericType($type)) {
1566
            return 0+$value; // ensure it's numeric
1567
        }
1568
1569
        return $this->quoted($value);
1570
	}
1571
1572
    /**
1573
     * Quote the strings of the passed in array
1574
     *
1575
     * The array must only contain strings
1576
     *
1577
     * @param array $array
1578
     * @return array Quoted strings
1579
     */
1580
	public function arrayQuote(array &$array)
1581
	{
1582
		foreach($array as &$val) {
1583
			$val = $this->quote($val);
1584
		}
1585
		return $array;
1586
	}
1587
1588
    /**
1589
     * Frees out previous results
1590
     *
1591
     * @param resource|bool $result optional, pass if you want to free a single result instead of all results
1592
     */
1593 1
	protected function freeResult($result = false)
1594
	{
1595 1
		if($result) {
1596 1
			$this->freeDbResult($result);
1597
		}
1598 1
		if($this->lastResult) {
1599
			$this->freeDbResult($this->lastResult);
1600
			$this->lastResult = null;
1601
		}
1602 1
	}
1603
1604
	/**
1605
	 * @abstract
1606
	 * Check if query has LIMIT clause
1607
	 * Relevant for now only for Mysql
1608
	 * @param string $sql
1609
	 * @return bool
1610
	 */
1611
	protected function hasLimit($sql)
1612
	{
1613
	    return false;
1614
	}
1615
1616
	/**
1617
	 * Runs a query and returns a single row containing single value
1618
	 *
1619
	 * @param  string   $sql        SQL Statement to execute
1620
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
1621
	 * @param  string   $msg        Message to log if error occurs
1622
	 * @return array    single value from the query
1623
	 */
1624 9
	public function getOne($sql, $dieOnError = false, $msg = '')
1625
	{
1626 9
		$this->log->info("Get One: |$sql|");
1627 9
		if(!$this->hasLimit($sql)) {
1628 9
		    $queryresult = $this->limitQuery($sql, 0, 1, $dieOnError, $msg);
1629
		} else {
1630
		    // support old code that passes LIMIT to sql
1631
		    // works only for mysql, so do not rely on this
1632
		    $queryresult = $this->query($sql, $dieOnError, $msg);
1633
		}
1634 9
		$this->checkError($msg.' Get One Failed:' . $sql, $dieOnError);
1635 9
		if (!$queryresult) return false;
1636 8
		$row = $this->fetchByAssoc($queryresult);
1637 8
		if(!empty($row)) {
1638 5
			return array_shift($row);
1639
		}
1640 3
		return false;
1641
	}
1642
1643
	/**
1644
	 * Runs a query and returns a single row
1645
	 *
1646
	 * @param  string   $sql        SQL Statement to execute
1647
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
1648
	 * @param  string   $msg        Message to log if error occurs
1649
	 * @param  bool     $suppress   Message to log if error occurs
1650
	 * @return array    single row from the query
1651
	 */
1652 3
	public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
1653
	{
1654 3
		$this->log->info("Fetch One: |$sql|");
1655 3
		$this->checkConnection();
1656 3
		$queryresult = $this->query($sql, $dieOnError, $msg);
1657 3
		$this->checkError($msg.' Fetch One Failed:' . $sql, $dieOnError);
1658
1659 3
		if (!$queryresult) return false;
1660
1661 1
		$row = $this->fetchByAssoc($queryresult);
1662 1
		if ( !$row ) return false;
1663
1664 1
		$this->freeResult($queryresult);
1665 1
		return $row;
1666
	}
1667
1668
    /**
1669
     * Returns the number of rows affected by the last query
1670
     * @abstract
1671
	 * See also affected_rows capability, will return 0 unless the DB supports it
1672
     * @param resource $result query result resource
1673
     * @return int
1674
     */
1675
	public function getAffectedRowCount($result)
1676
	{
1677
		return 0;
1678
	}
1679
1680
	/**
1681
	 * Returns the number of rows returned by the result
1682
	 *
1683
	 * This function can't be reliably implemented on most DB, do not use it.
1684
	 * @abstract
1685
	 * @deprecated
1686
	 * @param  resource $result
1687
	 * @return int
1688
	 */
1689
	public function getRowCount($result)
1690
	{
1691
	    return 0;
1692
	}
1693
1694
	/**
1695
     * Get table description
1696
     * @param string $tablename
1697
     * @param bool $reload true means load from DB, false allows using cache
1698
     * @return array Vardef-format table description
1699
     *
1700
     */
1701
	public function getTableDescription($tablename, $reload = false)
1702
	{
1703
		if($reload || empty(self::$table_descriptions[$tablename])) {
1704
			self::$table_descriptions[$tablename] = $this->get_columns($tablename);
1705
		}
1706
		return self::$table_descriptions[$tablename];
1707
	}
1708
1709
	/**
1710
	 * Returns the field description for a given field in table
1711
	 *
1712
	 * @param  string $name
1713
	 * @param  string $tablename
1714
	 * @return array
1715
	 */
1716
	protected function describeField($name, $tablename)
1717
	{
1718
		$table = $this->getTableDescription($tablename);
1719
		if(!empty($table) && isset($table[$name]))
1720
			return 	$table[$name];
1721
1722
		$table = $this->getTableDescription($tablename, true);
1723
1724
		if(isset($table[$name]))
1725
		return $table[$name];
1726
1727
		return array();
1728
	}
1729
1730
	/**
1731
	 * Returns the index description for a given index in table
1732
	 *
1733
	 * @param  string $name
1734
	 * @param  string $tablename
1735
	 * @return array
1736
	 */
1737
	protected function describeIndex($name, $tablename)
1738
	{
1739
		if(isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename][$name])){
1740
			return 	self::$index_descriptions[$tablename][$name];
1741
		}
1742
1743
		self::$index_descriptions[$tablename] = $this->get_indices($tablename);
1744
1745
		if(isset(self::$index_descriptions[$tablename][$name])){
1746
			return 	self::$index_descriptions[$tablename][$name];
1747
		}
1748
1749
		return array();
1750
	}
1751
1752
    /**
1753
     * Truncates a string to a given length
1754
     *
1755
     * @param string $string
1756
     * @param int    $len    length to trim to
1757
     * @return string
1758
     *
1759
     */
1760
	public function truncate($string, $len)
1761
	{
1762
		if ( is_numeric($len) && $len > 0)
1763
		{
1764
			$string = mb_substr($string,0,(int) $len, "UTF-8");
1765
		}
1766
		return $string;
1767
	}
1768
1769
    /**
1770
     * Returns the database string needed for concatinating multiple database strings together
1771
     *
1772
     * @param string $table table name of the database fields to concat
1773
     * @param array $fields fields in the table to concat together
1774
     * @param string $space Separator between strings, default is single space
1775
     * @return string
1776
     */
1777 14
	public function concat($table, array $fields, $space = ' ')
1778
	{
1779 14
		if(empty($fields)) return '';
1780 14
		$elems = array();
1781 14
		$space = $this->quoted($space);
1782 14
		foreach ( $fields as $field ) {
1783 14
			if(!empty($elems)) $elems[] = $space;
1784 14
			$elems[] = $this->convert("$table.$field", 'IFNULL', array("''"));
1785
		}
1786 14
		$first = array_shift($elems);
1787 14
		return "LTRIM(RTRIM(".$this->convert($first, 'CONCAT', $elems)."))";
1788
	}
1789
1790
	/**
1791
	 * Given a sql stmt attempt to parse it into the sql and the tokens. Then return the index of this prepared statement
1792
	 * Tokens can come in the following forms:
1793
	 * ? - a scalar which will be quoted
1794
	 * ! - a literal which will not be quoted
1795
	 * & - binary data to read from a file
1796
	 *
1797
	 * @param  string	$sql        The sql to parse
1798
	 * @return int index of the prepared statement to be used with execute
1799
	 */
1800
	public function prepareQuery($sql)
1801
	{
1802
		//parse out the tokens
1803
		$tokens = preg_split('/((?<!\\\)[&?!])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);
1804
1805
		//maintain a count of the actual tokens for quick reference in execute
1806
		$count = 0;
1807
1808
		$sqlStr = '';
1809
		foreach ($tokens as $key => $val) {
1810
			switch ($val) {
1811
				case '?' :
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...
1812
				case '!' :
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...
1813
				case '&' :
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...
1814
					$count++;
1815
					$sqlStr .= '?';
1816
					break;
1817
1818
				default :
0 ignored issues
show
There must be no space before the colon in a DEFAULT statement

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

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

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

Loading history...
1819
					//escape any special characters
1820
					$tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
1821
					$sqlStr .= $tokens[$key];
1822
					break;
1823
			} // switch
1824
		} // foreach
1825
1826
		$this->preparedTokens[] = array('tokens' => $tokens, 'tokenCount' => $count, 'sqlString' => $sqlStr);
1827
		end($this->preparedTokens);
1828
		return key($this->preparedTokens);
1829
	}
1830
1831
	/**
1832
	 * Takes a prepared stmt index and the data to replace and creates the query and runs it.
1833
	 *
1834
	 * @param  int		$stmt       The index of the prepared statement from preparedTokens
1835
	 * @param  array    $data 		The array of data to replace the tokens with.
1836
	 * @return resource result set or false on error
1837
	 */
1838
	public function executePreparedQuery($stmt, $data = array())
1839
	{
1840
		if(!empty($this->preparedTokens[$stmt])){
1841
			if(!is_array($data)){
1842
				$data = array($data);
1843
			}
1844
1845
			$pTokens = $this->preparedTokens[$stmt];
1846
1847
			//ensure that the number of data elements matches the number of replacement tokens
1848
			//we found in prepare().
1849
			if(count($data) != $pTokens['tokenCount']){
1850
				//error the data count did not match the token count
1851
				return false;
1852
			}
1853
1854
			$query = '';
1855
			$dataIndex = 0;
1856
			$tokens = $pTokens['tokens'];
1857
			foreach ($tokens as $val) {
1858
				switch ($val) {
1859
					case '?':
1860
						$query .= $this->quote($data[$dataIndex++]);
1861
						break;
1862
					case '&':
1863
						$filename = $data[$dataIndex++];
1864
						$query .= file_get_contents($filename);
1865
						break;
1866
					case '!':
1867
						$query .= $data[$dataIndex++];
1868
						break;
1869
					default:
1870
						$query .= $val;
1871
						break;
1872
				}//switch
1873
			}//foreach
1874
			return $this->query($query);
1875
		}else{
1876
			return false;
1877
		}
1878
	}
1879
1880
	/**
1881
	 * Run both prepare and execute without the client having to run both individually.
1882
	 *
1883
	 * @param  string	$sql        The sql to parse
1884
	 * @param  array    $data 		The array of data to replace the tokens with.
1885
	 * @return resource result set or false on error
1886
	 */
1887
	public function pQuery($sql, $data = array())
1888
	{
1889
		$stmt = $this->prepareQuery($sql);
1890
		return $this->executePreparedQuery($stmt, $data);
1891
	}
1892
1893
/********************** SQL FUNCTIONS ****************************/
1894
    /**
1895
     * Generates sql for create table statement for a bean.
1896
     *
1897
     * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
1898
     * @param SugarBean $bean SugarBean instance
1899
     * @return string SQL Create Table statement
1900
     */
1901
	public function createTableSQL(SugarBean $bean)
1902
	{
1903
		$tablename = $bean->getTableName();
1904
		$fieldDefs = $bean->getFieldDefinitions();
1905
		$indices = $bean->getIndices();
1906
		return $this->createTableSQLParams($tablename, $fieldDefs, $indices);
1907
	}
1908
1909
	/**
1910
	 * Generates SQL for insert statement.
1911
	 *
1912
	 * @param  SugarBean $bean SugarBean instance
1913
	 * @return string SQL Create Table statement
1914
	 */
1915 63
	public function insertSQL(SugarBean $bean)
1916
	{
1917
		// get column names and values
1918 63
		$sql = $this->insertParams($bean->getTableName(), $bean->getFieldDefinitions(), get_object_vars($bean),
1919 63
		        isset($bean->field_name_map)?$bean->field_name_map:null, false);
1920 63
		return $sql;
1921
	}
1922
1923
	/**
1924
	 * Generates SQL for update statement.
1925
	 *
1926
	 * @param  SugarBean $bean SugarBean instance
1927
	 * @param  array  $where Optional, where conditions in an array
1928
	 * @return string SQL Create Table statement
1929
	 */
1930 19
	public function updateSQL(SugarBean $bean, array $where = array())
1931
	{
1932 19
		$primaryField = $bean->getPrimaryFieldDefinition();
1933 19
		$columns = array();
1934 19
        $fields = $bean->getFieldDefinitions();
1935
		// get column names and values
1936 19
		foreach ($fields as $field => $fieldDef) {
1937 19
			if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
1938
			// Do not write out the id field on the update statement.
1939
    		// We are not allowed to change ids.
1940 19
    		if (empty($fieldDef['name']) || $fieldDef['name'] == $primaryField['name']) continue;
1941
1942
    		// If the field is an auto_increment field, then we shouldn't be setting it.  This was added
1943
    		// specially for Bugs and Cases which have a number associated with them.
1944 19
    		if (!empty($bean->field_name_map[$field]['auto_increment'])) continue;
1945
1946
    		//custom fields handle their save separately
1947 19
    		if(isset($bean->field_name_map) && !empty($bean->field_name_map[$field]['custom_type']))  continue;
1948
1949
    		// no need to clear deleted since we only update not deleted records anyway
1950 19
    		if($fieldDef['name'] == 'deleted' && empty($bean->deleted)) continue;
1951
1952 19
    		if(isset($bean->$field)) {
1953 19
    			$val = from_html($bean->$field);
1954
    		} else {
1955 18
    			continue;
1956
    		}
1957
1958 19
    		if(!empty($fieldDef['type']) && $fieldDef['type'] == 'bool'){
1959 10
    			$val = $bean->getFieldValue($field);
1960
    		}
1961
1962 19
    		if(strlen($val) == 0) {
1963 10
    			if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
1964
    				$val = $fieldDef['default'];
1965
    			} else {
1966 10
    				$val = null;
1967
    			}
1968
    		}
1969
1970 19
    		if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
1971
			    $val = $this->truncate($val, $fieldDef['len']);
1972
			}
1973 19
		$columnName = $this->quoteIdentifier($fieldDef['name']);
1974 19
    		if(!is_null($val) || !empty($fieldDef['required'])) {
1975 19
    			$columns[] = "{$columnName}=".$this->massageValue($val, $fieldDef);
1976 10
    		} elseif($this->isNullable($fieldDef)) {
1977 10
    			$columns[] = "{$columnName}=NULL";
1978
    		} else {
1979 19
    		    $columns[] = "{$columnName}=".$this->emptyValue($fieldDef['type']);
1980
    		}
1981
		}
1982
1983 19
		if ( sizeof($columns) == 0 )
1984
			return ""; // no columns set
1985
1986
		// build where clause
1987 19
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
1988 19
		if(isset($fields['deleted'])) {
1989 19
		    $where .= " AND deleted=0";
1990
		}
1991
1992 19
		return "UPDATE ".$bean->getTableName()."
1993 19
					SET ".implode(",", $columns)."
1994 19
					$where";
1995
	}
1996
1997
	/**
1998
	 * This method returns a where array so that it has id entry if
1999
	 * where is not an array or is empty
2000
	 *
2001
	 * @param  SugarBean $bean SugarBean instance
2002
	 * @param  array  $where Optional, where conditions in an array
2003
	 * @return array
2004
	 */
2005 19
	protected function updateWhereArray(SugarBean $bean, array $where = array())
2006
	{
2007 19
		if (count($where) == 0) {
2008 19
			$fieldDef = $bean->getPrimaryFieldDefinition();
2009 19
			$primaryColumn = $fieldDef['name'];
2010
2011 19
			$val = $bean->getFieldValue($fieldDef['name']);
2012 19
			if ($val != FALSE){
2013 19
				$where[$primaryColumn] = $val;
2014
			}
2015
		}
2016
2017 19
		return $where;
2018
	}
2019
2020
	/**
2021
	 * Returns a where clause without the 'where' key word
2022
	 *
2023
	 * The clause returned does not have an 'and' at the beginning and the columns
2024
	 * are joined by 'and'.
2025
	 *
2026
	 * @param  string $table table name
2027
	 * @param  array  $whereArray Optional, where conditions in an array
2028
	 * @return string
2029
	 */
2030 19
	protected function getColumnWhereClause($table, array $whereArray = array())
2031
	{
2032 19
		$where = array();
2033 19
		foreach ($whereArray as $name => $val) {
2034 19
			$op = "=";
2035 19
			if (is_array($val)) {
2036
				$op = "IN";
2037
				$temp = array();
2038
				foreach ($val as $tval){
2039
					$temp[] = $this->quoted($tval);
2040
				}
2041
				$val = implode(",", $temp);
2042
				$val = "($val)";
2043
			} else {
2044 19
				$val = $this->quoted($val);
2045
			}
2046
2047 19
			$where[] = " $table.$name $op $val";
2048
		}
2049
2050 19
		if (!empty($where))
2051 19
			return implode(" AND ", $where);
2052
2053
		return '';
2054
	}
2055
2056
	/**
2057
	 * This method returns a complete where clause built from the
2058
	 * where values specified.
2059
	 *
2060
	 * @param  SugarBean $bean SugarBean that describes the table
2061
	 * @param  array  $whereArray Optional, where conditions in an array
2062
	 * @return string
2063
	 */
2064 19
	protected function getWhereClause(SugarBean $bean, array $whereArray=array())
2065
	{
2066 19
	    return " WHERE " . $this->getColumnWhereClause($bean->getTableName(), $whereArray);
2067
	}
2068
2069
	/**
2070
	 * Outputs a correct string for the sql statement according to value
2071
	 *
2072
	 * @param  mixed $val
2073
	 * @param  array $fieldDef field definition
2074
	 * @return mixed
2075
	 */
2076 68
	public function massageValue($val, $fieldDef)
2077
	{
2078 68
		$type = $this->getFieldType($fieldDef);
2079
2080 68
		if(isset($this->type_class[$type])) {
2081
			// handle some known types
2082 68
			switch($this->type_class[$type]) {
2083 68
				case 'bool':
2084 68
				case 'int':
2085 45
					if (!empty($fieldDef['required']) && $val == ''){
2086 7
						if (isset($fieldDef['default'])){
2087
							return $fieldDef['default'];
2088
						}
2089 7
						return 0;
2090
					}
2091 45
					return intval($val);
2092 68
                case 'bigint' :
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...
2093 2
                    $val = (float)$val;
2094 2
					if (!empty($fieldDef['required']) && $val == false){
2095
						if (isset($fieldDef['default'])){
2096
							return $fieldDef['default'];
2097
						}
2098
						return 0;
2099
					}
2100 2
                    return $val;
2101 66
				case 'float':
2102 11
					if (!empty($fieldDef['required'])  && $val == ''){
2103 4
						if (isset($fieldDef['default'])){
2104
							return $fieldDef['default'];
2105
						}
2106 4
						return 0;
2107
					}
2108 11
					return floatval($val);
2109 66
				case 'time':
2110 66
				case 'date':
2111
					// empty date can't be '', so convert it to either NULL or empty date value
2112 66
					if($val == '') {
2113 9
						if (!empty($fieldDef['required'])) {
2114 9
							if (isset($fieldDef['default'])) {
2115
								return $fieldDef['default'];
2116
							}
2117 9
							return $this->emptyValue($type);
2118
						}
2119
						return "NULL";
2120
					}
2121 66
					break;
2122
			}
2123
		} else {
2124 67
		    if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
2125
			    $val = $this->truncate($val, $fieldDef['len']);
2126
			}
2127
		}
2128
2129 68
		if ( is_null($val) ) {
2130 19
			if(!empty($fieldDef['required'])) {
2131 19
				if (isset($fieldDef['default'])  && $fieldDef['default'] != ''){
2132
					return $fieldDef['default'];
2133
				}
2134 19
				return $this->emptyValue($type);
2135
			} else {
2136
				return "NULL";
2137
			}
2138
		}
2139 68
        if($type == "datetimecombo") {
2140 1
            $type = "datetime";
2141
        }
2142 68
		return $this->convert($this->quoted($val), $type);
2143
	}
2144
2145
	/**
2146
	 * Massages the field defintions to fill in anything else the DB backend may add
2147
	 *
2148
	 * @param  array  $fieldDef
2149
	 * @param  string $tablename
2150
	 * @return array
2151
	 */
2152
	public function massageFieldDef(&$fieldDef, $tablename)
2153
	{
2154
		if ( !isset($fieldDef['dbType']) ) {
2155
			if ( isset($fieldDef['dbtype']) )
2156
				$fieldDef['dbType'] = $fieldDef['dbtype'];
2157
			else
2158
				$fieldDef['dbType'] = $fieldDef['type'];
2159
		}
2160
		$type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
0 ignored issues
show
The call to DBManager::getColumnType() has too many arguments starting with $fieldDef['name'].

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...
2161
		$matches = array();
2162
        // len can be a number or a string like 'max', for example, nvarchar(max)
2163
        preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*|\w+)\)|)/i', $type, $matches);
2164
		if ( isset($matches[1][0]) )
2165
			$fieldDef['type'] = $matches[1][0];
2166
		if ( isset($matches[2][0]) && empty($fieldDef['len']) )
2167
			$fieldDef['len'] = $matches[2][0];
2168
		if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) && !strstr($fieldDef['len'],',') )
2169
			$fieldDef['len'] .= ",{$fieldDef['precision']}";
2170
		if (!empty($fieldDef['required']) || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) ) {
2171
			$fieldDef['required'] = 'true';
2172
		}
2173
	}
2174
2175
	/**
2176
	 * Take an SQL statement and produce a list of fields used in that select
2177
	 * @param string $selectStatement
2178
	 * @return array
2179
	 */
2180
	public function getSelectFieldsFromQuery($selectStatement)
2181
	{
2182
		$selectStatement = trim($selectStatement);
2183
		if (strtoupper(substr($selectStatement, 0, 6)) == "SELECT")
2184
			$selectStatement = trim(substr($selectStatement, 6));
2185
2186
		//Due to sql functions existing in many selects, we can't use php explode
2187
		$fields = array();
2188
		$level = 0;
2189
		$selectField = "";
2190
		$strLen = strlen($selectStatement);
2191
		for($i = 0; $i < $strLen; $i++)
2192
		{
2193
			$char = $selectStatement[$i];
2194
2195
			if ($char == "," && $level == 0)
2196
			{
2197
				$field = $this->getFieldNameFromSelect(trim($selectField));
2198
				$fields[$field] = $selectField;
2199
				$selectField = "";
2200
			}
2201
			else if ($char == "("){
2202
				$level++;
2203
				$selectField .= $char;
2204
			}
2205
			else if($char == ")"){
2206
				$level--;
2207
				$selectField .= $char;
2208
2209
2210
			}else{
2211
				$selectField .= $char;
2212
			}
2213
2214
		}
2215
		$fields[$this->getFieldNameFromSelect($selectField)] = $selectField;
2216
		return $fields;
2217
	}
2218
2219
	/**
2220
	 * returns the field name used in a select
2221
	 * @param string $string SELECT query
2222
     * @return string
2223
     */
2224
	protected function getFieldNameFromSelect($string)
2225
	{
2226
		if(strncasecmp($string, "DISTINCT ", 9) == 0) {
2227
			$string = substr($string, 9);
2228
		}
2229
		if (stripos($string, " as ") !== false)
2230
			//"as" used for an alias
2231
			return trim(substr($string, strripos($string, " as ") + 4));
2232
		else if (strrpos($string, " ") != 0)
2233
			//Space used as a delimiter for an alias
2234
			return trim(substr($string, strrpos($string, " ")));
2235
		else if (strpos($string, ".") !== false)
2236
			//No alias, but a table.field format was used
2237
			return substr($string, strpos($string, ".") + 1);
2238
		else
2239
			//Give up and assume the whole thing is the field name
2240
			return $string;
2241
	}
2242
2243
	/**
2244
	 * Generates SQL for delete statement identified by id.
2245
	 *
2246
	 * @param  SugarBean $bean SugarBean instance
2247
	 * @param  array  $where where conditions in an array
2248
	 * @return string SQL Update Statement
2249
	 */
2250
	public function deleteSQL(SugarBean $bean, array $where)
2251
	{
2252
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2253
		return "UPDATE ".$bean->getTableName()." SET deleted=1 $where";
2254
	}
2255
2256
    /**
2257
     * Generates SQL for select statement for any bean identified by id.
2258
     *
2259
     * @param  SugarBean $bean SugarBean instance
2260
     * @param  array  $where where conditions in an array
2261
     * @return string SQL Select Statement
2262
     */
2263
	public function retrieveSQL(SugarBean $bean, array $where)
2264
	{
2265
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2266
		return "SELECT * FROM ".$bean->getTableName()." $where AND deleted=0";
2267
	}
2268
2269
    /**
2270
     * This method implements a generic sql for a collection of beans.
2271
     *
2272
     * Currently, this function does not support outer joins.
2273
     *
2274
     * @param array $beans Array of values returned by get_class method as the keys and a bean as
2275
     *      the value for that key. These beans will be joined in the sql by the key
2276
     *      attribute of field defs.
2277
     * @param  array $cols Optional, columns to be returned with the keys as names of bean
2278
     *      as identified by get_class of bean. Values of this array is the array of fieldDefs
2279
     *      to be returned for a bean. If an empty array is passed, all columns are selected.
2280
     * @param  array $whereClause Optional, values with the keys as names of bean as identified
2281
     *      by get_class of bean. Each value at the first level is an array of values for that
2282
     *      bean identified by name of fields. If we want to pass multiple values for a name,
2283
     *      pass it as an array. If where is not passed, all the rows will be returned.
2284
     *
2285
     * @return string SQL Select Statement
2286
     */
2287
	public function retrieveViewSQL(array $beans, array $cols = array(), array $whereClause = array())
2288
	{
2289
		$relations = array(); // stores relations between tables as they are discovered
2290
		$where = $select = array();
2291
		foreach ($beans as $beanID => $bean) {
2292
			$tableName = $bean->getTableName();
2293
			$beanTables[$beanID] = $tableName;
2294
2295
			$table = $beanID;
2296
			$tables[$table] = $tableName;
2297
			$aliases[$tableName][] = $table;
2298
2299
			// build part of select for this table
2300
			if (is_array($cols[$beanID]))
2301
				foreach ($cols[$beanID] as $def) $select[] = $table.".".$def['name'];
2302
2303
			// build part of where clause
2304
			if (is_array($whereClause[$beanID])){
2305
				$where[] = $this->getColumnWhereClause($table, $whereClause[$beanID]);
2306
			}
2307
			// initialize so that it can be used properly in form clause generation
2308
			$table_used_in_from[$table] = false;
2309
2310
			$indices = $bean->getIndices();
2311
			foreach ($indices as $index){
2312
				if ($index['type'] == 'foreign') {
2313
					$relationship[$table][] = array('foreignTable'=> $index['foreignTable']
2314
												,'foreignColumn'=>$index['foreignField']
2315
												,'localColumn'=> $index['fields']
2316
												);
2317
				}
2318
			}
2319
			$where[] = " $table.deleted = 0";
2320
		}
2321
2322
		// join these clauses
2323
		$select = !empty($select) ? implode(",", $select) : "*";
2324
		$where = implode(" AND ", $where);
2325
2326
		// generate the from clause. Use relations array to generate outer joins
2327
		// all the rest of the tables will be used as a simple from
2328
		// relations table define relations between table1 and table2 through column on table 1
2329
		// table2 is assumed to joining through primary key called id
2330
		$separator = "";
2331
		$from = ''; $table_used_in_from = array();
2332
		foreach ($relations as $table1 => $rightsidearray){
2333
			if ($table_used_in_from[$table1]) continue; // table has been joined
2334
2335
			$from .= $separator." ".$table1;
2336
			$table_used_in_from[$table1] = true;
2337
			foreach ($rightsidearray as $tablearray){
2338
				$table2 = $tablearray['foreignTable']; // get foreign table
2339
				$tableAlias = $aliases[$table2]; // get a list of aliases for this table
2340
				foreach ($tableAlias as $table2) {
2341
					//choose first alias that does not match
2342
					// we are doing this because of self joins.
2343
					// in case of self joins, the same table will have many aliases.
2344
					if ($table2 != $table1) break;
2345
				}
2346
2347
				$col = $tablearray['foreingColumn'];
2348
				$name = $tablearray['localColumn'];
2349
				$from .= " LEFT JOIN $table on ($table1.$name = $table2.$col)";
2350
				$table_used_in_from[$table2] = true;
2351
			}
2352
			$separator = ",";
2353
		}
2354
2355
		return "SELECT $select FROM $from WHERE $where";
2356
	}
2357
2358
	/**
2359
	 * Generates SQL for create index statement for a bean.
2360
	 *
2361
	 * @param  SugarBean $bean SugarBean instance
2362
	 * @param  array  $fields fields used in the index
2363
	 * @param  string $name index name
2364
	 * @param  bool   $unique Optional, set to true if this is an unique index
2365
	 * @return string SQL Select Statement
2366
	 */
2367
	public function createIndexSQL(SugarBean $bean, array $fields, $name, $unique = true)
2368
	{
2369
		$unique = ($unique) ? "unique" : "";
2370
		$tablename = $bean->getTableName();
2371
		$columns = array();
2372
		// get column names
2373
		foreach ($fields as $fieldDef)
2374
			$columns[] = $fieldDef['name'];
2375
2376
		if (empty($columns))
2377
			return "";
2378
2379
		$columns = implode(",", $columns);
2380
2381
		return "CREATE $unique INDEX $name ON $tablename ($columns)";
2382
	}
2383
2384
	/**
2385
	 * Returns the type of the variable in the field
2386
	 *
2387
	 * @param  array $fieldDef Vardef-format field def
2388
	 * @return string
2389
	 */
2390 127
	public function getFieldType($fieldDef)
2391
	{
2392
		// get the type for db type. if that is not set,
2393
		// get it from type. This is done so that
2394
		// we do not have change a lot of existing code
2395
		// and add dbtype where type is being used for some special
2396
		// purposes like referring to foreign table etc.
2397 127
		if(!empty($fieldDef['dbType']))
2398 109
			return  $fieldDef['dbType'];
2399 123
		if(!empty($fieldDef['dbtype']))
2400
			return  $fieldDef['dbtype'];
2401 123
		if (!empty($fieldDef['type']))
2402 123
			return  $fieldDef['type'];
2403
		if (!empty($fieldDef['Type']))
2404
			return  $fieldDef['Type'];
2405
		if (!empty($fieldDef['data_type']))
2406
			return  $fieldDef['data_type'];
2407
2408
		return null;
2409
	}
2410
2411
    /**
2412
     * retrieves the different components from the passed column type as it is used in the type mapping and vardefs
2413
     * type format: <baseType>[(<len>[,<scale>])]
2414
     * @param string $type Column type
2415
     * @return array|bool array containing the different components of the passed in type or false in case the type contains illegal characters
2416
     */
2417
    public function getTypeParts($type)
2418
    {
2419
        if(preg_match("#(?P<type>\w+)\s*(?P<arg>\((?P<len>\w+)\s*(,\s*(?P<scale>\d+))*\))*#", $type, $matches))
2420
        {
2421
            $return = array();  // Not returning matches array as such as we don't want to expose the regex make up on the interface
2422
            $return['baseType'] = $matches['type'];
2423
            if( isset($matches['arg'])) {
2424
                $return['arg'] = $matches['arg'];
2425
            }
2426
            if( isset($matches['len'])) {
2427
                $return['len'] = $matches['len'];
2428
            }
2429
            if( isset($matches['scale'])) {
2430
                $return['scale'] = $matches['scale'];
2431
            }
2432
            return $return;
2433
        } else {
2434
            return false;
2435
        }
2436
    }
2437
2438
	/**
2439
	 * Returns the defintion for a single column
2440
	 *
2441
	 * @param  array  $fieldDef Vardef-format field def
2442
	 * @param  bool   $ignoreRequired  Optional, true if we should ignore this being a required field
2443
	 * @param  string $table           Optional, table name
2444
	 * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
2445
	 * @return string or array if $return_as_array is true
2446
	 */
2447
	protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
2448
	{
2449
		$name = $fieldDef['name'];
2450
		$type = $this->getFieldType($fieldDef);
2451
        $colType = $this->getColumnType($type);
2452
2453
        if($parts = $this->getTypeParts($colType))
2454
        {
2455
            $colBaseType = $parts['baseType'];
2456
            $defLen =  isset($parts['len']) ? $parts['len'] : '255'; // Use the mappings length (precision) as default if it exists
2457
        }
2458
2459
        if(!empty($fieldDef['len'])) {
2460
            if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char',
2461
                                          'clob', 'blob', 'text'))) {
2462
          	    $colType = "$colBaseType(${fieldDef['len']})";
2463
            } elseif(($colBaseType == 'decimal' || $colBaseType == 'float')){
2464
                  if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
2465
                      if(strpos($fieldDef['len'],',') === false){
2466
                          $colType = $colBaseType . "(".$fieldDef['len'].",".$fieldDef['precision'].")";
2467
                      }else{
2468
                          $colType = $colBaseType . "(".$fieldDef['len'].")";
2469
                      }
2470
                  else
2471
                          $colType = $colBaseType . "(".$fieldDef['len'].")";
2472
              }
2473
        } else {
2474
            if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char'))) {
2475
                $colType = "$colBaseType($defLen)";
2476
            }
2477
        }
2478
2479
        $default = '';
2480
2481
        // Bug #52610 We should have ability don't add DEFAULT part to query for boolean fields
2482
        if (!empty($fieldDef['no_default']))
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...
2483
        {
2484
            // nothing to do
2485
        }
2486
        elseif (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0)
2487
        {
2488
            $default = " DEFAULT ".$this->quoted($fieldDef['default']);
2489
        }
2490
        elseif (!isset($default) && $type == 'bool')
2491
        {
2492
            $default = " DEFAULT 0 ";
2493
        }
2494
2495
		$auto_increment = '';
2496
		if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
2497
			$auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);
2498
2499
		$required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
2500
		//Starting in 6.0, only ID and auto_increment fields will be NOT NULL in the DB.
2501
		if ((empty($fieldDef['isnull']) || strtolower($fieldDef['isnull']) == 'false') &&
2502
			(!empty($auto_increment) || $name == 'id' || ($fieldDef['type'] == 'id' && !empty($fieldDef['required'])))) {
2503
			$required =  "NOT NULL";
2504
		}
2505
		// If the field is marked both required & isnull=>false - alwqys make it not null
2506
		// Use this to ensure primary key fields never defined as null
2507
		if(isset($fieldDef['isnull']) && (strtolower($fieldDef['isnull']) == 'false' || $fieldDef['isnull'] === false)
2508
			&& !empty($fieldDef['required'])) {
2509
			$required =  "NOT NULL";
2510
		}
2511
		if ($ignoreRequired)
2512
			$required = "";
2513
2514
		if ( $return_as_array ) {
2515
			return array(
2516
				'name' => $name,
2517
				'colType' => $colType,
2518
                'colBaseType' => $colBaseType,  // Adding base type for easier processing in derived classes
2519
				'default' => $default,
2520
				'required' => $required,
2521
				'auto_increment' => $auto_increment,
2522
				'full' => "$name $colType $default $required $auto_increment",
2523
				);
2524
		} else {
2525
			return "$name $colType $default $required $auto_increment";
2526
		}
2527
	}
2528
2529
	/**
2530
	 * Returns SQL defintions for all columns in a table
2531
	 *
2532
	 * @param  array  $fieldDefs  Vardef-format field def
2533
	 * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
2534
	 * @param  string $tablename      Optional, table name
2535
	 * @return string SQL column definitions
2536
	 */
2537
	protected function columnSQLRep($fieldDefs, $ignoreRequired = false, $tablename)
2538
	{
2539
		$columns = array();
2540
2541
		if ($this->isFieldArray($fieldDefs)) {
2542
			foreach ($fieldDefs as $fieldDef) {
2543
				if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
2544
					$columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
2545
				}
2546
			}
2547
			$columns = implode(",", $columns);
2548
		}
2549
		else {
2550
			$columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
2551
		}
2552
2553
		return $columns;
2554
	}
2555
2556
	/**
2557
	 * Returns the next value for an auto increment
2558
	 * @abstract
2559
	 * @param  string $table Table name
2560
	 * @param  string $field_name Field name
2561
	 * @return string
2562
	 */
2563
	public function getAutoIncrement($table, $field_name)
2564
	{
2565
		return "";
2566
	}
2567
2568
	/**
2569
	 * Returns the sql for the next value in a sequence
2570
	 * @abstract
2571
	 * @param  string $table  Table name
2572
	 * @param  string $field_name  Field name
2573
	 * @return string
2574
	 */
2575
	public function getAutoIncrementSQL($table, $field_name)
2576
	{
2577 4
		return "";
2578
	}
2579
2580
	/**
2581
	 * Either creates an auto increment through queries or returns sql for auto increment
2582
	 * that can be appended to the end of column defination (mysql)
2583
	 * @abstract
2584
	 * @param  string $table Table name
2585
	 * @param  string $field_name Field name
2586
	 * @return string
2587
	 */
2588
	protected function setAutoIncrement($table, $field_name)
2589
	{
2590
		$this->deleteAutoIncrement($table, $field_name);
0 ignored issues
show
The call to the method DBManager::deleteAutoIncrement() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
2591
		return "";
2592
	}
2593
2594
    /**
2595
     * Sets the next auto-increment value of a column to a specific value.
2596
     * @abstract
2597
     * @param  string $table Table name
2598
     * @param  string $field_name Field name
2599
     * @param  int $start_value  Starting autoincrement value
2600
     * @return string
2601
     *
2602
     */
2603
	public function setAutoIncrementStart($table, $field_name, $start_value)
2604
	{
2605
		return "";
2606
	}
2607
2608
	/**
2609
	 * Deletes an auto increment
2610
	 * @abstract
2611
	 * @param string $table tablename
2612
	 * @param string $field_name
2613
	 */
2614
	public function deleteAutoIncrement($table, $field_name)
2615
	{
2616
		return;
2617
	}
2618
2619
	/**
2620
	 * This method generates sql for adding a column to table identified by field def.
2621
	 *
2622
	 * @param  string $tablename
2623
	 * @param  array  $fieldDefs
2624
	 * @return string SQL statement
2625
	 */
2626
	public function addColumnSQL($tablename, $fieldDefs)
2627
	{
2628
	    return $this->changeColumnSQL($tablename, $fieldDefs, 'add');
2629
	}
2630
2631
	/**
2632
	 * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
2633
	 *
2634
	 * @param  string $tablename
2635
	 * @param  array  $newFieldDefs
2636
	 * @param  bool  $ignorerequired Optional, true if we should ignor this being a required field
2637
	 * @return string|array SQL statement(s)
2638
	 */
2639
	public function alterColumnSQL($tablename, $newFieldDefs, $ignorerequired = false)
2640
	{
2641
		return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
2642
	}
2643
2644
	/**
2645
	 * Generates SQL for dropping a table.
2646
	 *
2647
	 * @param  SugarBean $bean Sugarbean instance
2648
	 * @return string SQL statement
2649
	 */
2650
	public function dropTableSQL(SugarBean $bean)
2651
	{
2652
		return $this->dropTableNameSQL($bean->getTableName());
2653
	}
2654
2655
	/**
2656
	 * Generates SQL for dropping a table.
2657
	 *
2658
	 * @param  string $name table name
2659
	 * @return string SQL statement
2660
	 */
2661
	public function dropTableNameSQL($name)
2662
	{
2663
		return "DROP TABLE ".$name;
2664
	}
2665
2666
	/**
2667
	 * Generates SQL for truncating a table.
2668
	 * @param  string $name  table name
2669
	 * @return string
2670
	 */
2671
	public function truncateTableSQL($name)
2672
	{
2673
		return "TRUNCATE $name";
2674
	}
2675
2676
	/**
2677
	 * This method generates sql that deletes a column identified by fieldDef.
2678
	 *
2679
	 * @param  SugarBean $bean      Sugarbean instance
2680
	 * @param  array  $fieldDefs
2681
	 * @return string SQL statement
2682
	 */
2683
	public function deleteColumnSQL(SugarBean $bean, $fieldDefs)
2684
	{
2685
		return $this->dropColumnSQL($bean->getTableName(), $fieldDefs);
2686
	}
2687
2688
	/**
2689
	 * This method generates sql that drops a column identified by fieldDef.
2690
	 * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
2691
	 *
2692
	 * @param  string $tablename
2693
	 * @param  array  $fieldDefs
2694
	 * @return string SQL statement
2695
	 */
2696
	public function dropColumnSQL($tablename, $fieldDefs)
2697
	{
2698
		return $this->changeColumnSQL($tablename, $fieldDefs, 'drop');
2699
	}
2700
2701
    /**
2702
     * Return a version of $proposed that can be used as a column name in any of our supported databases
2703
     * Practically this means no longer than 25 characters as the smallest identifier length for our supported DBs is 30 chars for Oracle plus we add on at least four characters in some places (for indicies for example)
2704
     * @param string|array $name Proposed name for the column
2705
     * @param bool|string $ensureUnique Ensure the name is unique
2706
     * @param string $type Name type (table, column)
2707
     * @param bool $force Force new name
2708
     * @return string|array Valid column name trimmed to right length and with invalid characters removed
2709
     */
2710
	public function getValidDBName($name, $ensureUnique = false, $type = 'column', $force = false)
2711
	{
2712 21
		if(is_array($name)) {
2713
			$result = array();
2714
			foreach($name as $field) {
2715
				$result[] = $this->getValidDBName($field, $ensureUnique, $type);
2716
			}
2717
			return $result;
2718
		} else {
2719 21
		    if(strchr($name, ".")) {
2720
		        // this is a compound name with dots, handle separately
2721
		        $parts = explode(".", $name);
2722
		        if(count($parts) > 2) {
2723
		            // some weird name, cut to table.name
2724
		            array_splice($parts, 0, count($parts)-2);
2725
		        }
2726
		        $parts = $this->getValidDBName($parts, $ensureUnique, $type, $force);
2727
                return join(".", $parts);
2728
		    }
2729
			// first strip any invalid characters - all but word chars (which is alphanumeric and _)
2730 21
			$name = preg_replace( '/[^\w]+/i', '', $name ) ;
2731 21
			$len = strlen( $name ) ;
2732 21
			$maxLen = empty($this->maxNameLengths[$type]) ? $this->maxNameLengths[$type]['column'] : $this->maxNameLengths[$type];
2733 21
			if ($len <= $maxLen && !$force) {
2734 21
				return strtolower($name);
2735
			}
2736
			if ($ensureUnique) {
2737
				$md5str = md5($name);
2738
				$tail = substr ( $name, -11) ;
2739
				$temp = substr($md5str , strlen($md5str)-4 );
2740
				$result = substr( $name, 0, 10) . $temp . $tail ;
2741
			} else {
2742
				$result = substr( $name, 0, 11) . substr( $name, 11 - $maxLen);
2743
			}
2744
2745
			return strtolower( $result ) ;
2746
		}
2747
	}
2748
2749
	/**
2750
	 * Returns the valid type for a column given the type in fieldDef
2751
	 *
2752
	 * @param  string $type field type
2753
	 * @return string valid type for the given field
2754
	 */
2755
	public function getColumnType($type)
2756
	{
2757 38
		return isset($this->type_map[$type])?$this->type_map[$type]:$type;
2758
	}
2759
2760
	/**
2761
	 * Checks to see if passed array is truely an array of defitions
2762
	 *
2763
	 * Such an array may have type as a key but it will point to an array
2764
	 * for a true array of definitions an to a col type for a definition only
2765
	 *
2766
	 * @param  mixed $defArray
2767
	 * @return bool
2768
	 */
2769
	public function isFieldArray($defArray)
2770
	{
2771
		if ( !is_array($defArray) )
2772
			return false;
2773
2774
		if ( isset($defArray['type']) ){
2775
			// type key exists. May be an array of defs or a simple definition
2776
			return is_array($defArray['type']); // type is not an array => definition else array
2777
		}
2778
2779
		// type does not exist. Must be array of definitions
2780
		return true;
2781
	}
2782
2783
	/**
2784
	 * returns true if the type can be mapped to a valid column type
2785
	 *
2786
	 * @param  string $type
2787
	 * @return bool
2788
	 */
2789
	protected function validColumnType($type)
2790
	{
2791
		$type = $this->getColumnType($type);
2792
		return !empty($type);
2793
	}
2794
2795
	/**
2796
	 * Generate query for audit table
2797
	 * @param SugarBean $bean SugarBean that was changed
2798
	 * @param array $changes List of changes, contains 'before' and 'after'
2799
     * @return string  Audit table INSERT query
2800
     */
2801
	protected function auditSQL(SugarBean $bean, $changes)
2802
	{
2803
		global $current_user;
2804
		$sql = "INSERT INTO ".$bean->get_audit_table_name();
2805
		//get field defs for the audit table.
2806
		require('metadata/audit_templateMetaData.php');
2807
		$fieldDefs = $dictionary['audit']['fields'];
2808
2809
		$values=array();
2810
		$values['id'] = $this->massageValue(create_guid(), $fieldDefs['id']);
2811
		$values['parent_id']= $this->massageValue($bean->id, $fieldDefs['parent_id']);
2812
		$values['field_name']= $this->massageValue($changes['field_name'], $fieldDefs['field_name']);
2813
		$values['data_type'] = $this->massageValue($changes['data_type'], $fieldDefs['data_type']);
2814
		if ($changes['data_type']=='text') {
2815
			$values['before_value_text'] = $this->massageValue($changes['before'], $fieldDefs['before_value_text']);
2816
			$values['after_value_text'] = $this->massageValue($changes['after'], $fieldDefs['after_value_text']);
2817
		} else {
2818
			$values['before_value_string'] = $this->massageValue($changes['before'], $fieldDefs['before_value_string']);
2819
			$values['after_value_string'] = $this->massageValue($changes['after'], $fieldDefs['after_value_string']);
2820
		}
2821
		$values['date_created'] = $this->massageValue(TimeDate::getInstance()->nowDb(), $fieldDefs['date_created'] );
2822
		$values['created_by'] = $this->massageValue($current_user->id, $fieldDefs['created_by']);
2823
2824
		$sql .= "(".implode(",", array_keys($values)).") ";
2825
		$sql .= "VALUES(".implode(",", $values).")";
2826
		return $sql;
2827
	}
2828
2829
    /**
2830
     * Saves changes to module's audit table
2831
     *
2832
     * @param SugarBean $bean Sugarbean instance that was changed
2833
     * @param array $changes List of changes, contains 'before' and 'after'
2834
     * @return bool query result
2835
     *
2836
     */
2837
	public function save_audit_records(SugarBean $bean, $changes)
2838
	{
2839
		return $this->query($this->auditSQL($bean, $changes));
2840
	}
2841
2842
    /**
2843
     * Finds fields whose value has changed.
2844
     * The before and after values are stored in the bean.
2845
     * Uses $bean->fetched_row && $bean->fetched_rel_row to compare
2846
     *
2847
     * @param SugarBean $bean Sugarbean instance that was changed
2848
     * @param array|null $field_filter Array of filter names to be inspected (NULL means all fields)
2849
     * @return array
2850
     */
2851
    public function getDataChanges(SugarBean &$bean, array $field_filter = null)
2852
	{
2853 31
        $changed_values=array();
2854
2855 31
        $fetched_row = array();
2856 31
        if (is_array($bean->fetched_row))
2857
        {
2858 3
            $fetched_row = array_merge($bean->fetched_row, $bean->fetched_rel_row);
2859
        }
2860
2861 31
        if ($fetched_row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fetched_row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
2862
2863 3
            $field_defs = $bean->field_defs;
2864
2865 3
            if (is_array($field_filter)) {
2866 3
                $field_defs = array_intersect_key($field_defs, array_flip($field_filter));
2867
            }
2868
2869
            // remove fields which do not present in fetched row
2870 3
            $field_defs = array_intersect_key($field_defs, $fetched_row);
2871
2872
            // remove fields which do not exist as bean property
2873 3
            $field_defs = array_intersect_key($field_defs, (array) $bean);
2874
2875 3
            foreach ($field_defs as $field => $properties) {
2876 3
                $before_value = $fetched_row[$field];
2877 3
                $after_value=$bean->$field;
2878 3
                if (isset($properties['type'])) {
2879 3
                    $field_type=$properties['type'];
2880
                } else {
2881
                    if (isset($properties['dbType'])) {
2882
                        $field_type=$properties['dbType'];
2883
                    }
2884
                    else if(isset($properties['data_type'])) {
2885
                        $field_type=$properties['data_type'];
2886
                    }
2887
                    else {
2888
                        $field_type=$properties['dbtype'];
2889
                    }
2890
                }
2891
2892
                //Because of bug #25078(sqlserver haven't 'date' type, trim extra "00:00:00" when insert into *_cstm table).
2893
                // so when we read the audit datetime field from sqlserver, we have to replace the extra "00:00:00" again.
2894 3
                if(!empty($field_type) && $field_type == 'date'){
2895
                    $before_value = $this->fromConvert($before_value , $field_type);
2896
                }
2897
                //if the type and values match, do nothing.
2898 3
                if (!($this->_emptyValue($before_value,$field_type) && $this->_emptyValue($after_value,$field_type))) {
2899
                    $change = false;
2900
                    if (trim($before_value) !== trim($after_value)) {
2901
                        // Bug #42475: Don't directly compare numeric values, instead do the subtract and see if the comparison comes out to be "close enough", it is necessary for floating point numbers.
2902
                        // Manual merge of fix 95727f2eed44852f1b6bce9a9eccbe065fe6249f from DBHelper
2903
                        // This fix also fixes Bug #44624 in a more generic way and therefore eliminates the need for fix 0a55125b281c4bee87eb347709af462715f33d2d in DBHelper
2904
                        if ($this->isNumericType($field_type)) {
2905
                            $numerator = abs(2*((trim($before_value)+0)-(trim($after_value)+0)));
2906
                            $denominator = abs(((trim($before_value)+0)+(trim($after_value)+0)));
2907
                            // detect whether to use absolute or relative error. use absolute if denominator is zero to avoid division by zero
2908
                            $error = ($denominator == 0) ? $numerator : $numerator / $denominator;
2909
                            if ($error >= 0.0000000001) {    // Smaller than 10E-10
2910
                                $change = true;
2911
                            }
2912
                        }
2913
                        else if ($this->isBooleanType($field_type)) {
2914
                            if ($this->_getBooleanValue($before_value) != $this->_getBooleanValue($after_value)) {
2915
                                $change = true;
2916
                            }
2917
                        }
2918
                        else {
2919
                            $change = true;
2920
                        }
2921
                        if ($change) {
2922
                            $changed_values[$field]=array('field_name'=>$field,
2923
                                'data_type'=>$field_type,
2924
                                'before'=>$before_value,
2925 3
                                'after'=>$after_value);
2926
                        }
2927
                    }
2928
                }
2929
			}
2930
		}
2931 31
		return $changed_values;
2932
	}
2933
2934
    /**
2935
     * Uses the audit enabled fields array to find fields whose value has changed.
2936
     * The before and after values are stored in the bean.
2937
     * Uses $bean->fetched_row && $bean->fetched_rel_row to compare
2938
     *
2939
     * @param SugarBean $bean Sugarbean instance that was changed
2940
     * @return array
2941
     */
2942
    public function getAuditDataChanges(SugarBean $bean)
2943
    {
2944 31
        $audit_fields = $bean->getAuditEnabledFieldDefinitions();
2945 31
        return $this->getDataChanges($bean, array_keys($audit_fields));
2946
    }
2947
2948
	/**
2949
	 * Setup FT indexing
2950
	 * @abstract
2951
	 */
2952
	public function full_text_indexing_setup()
2953
	{
2954
		// Most DBs have nothing to setup, so provide default empty function
2955
	}
2956
2957
	/**
2958
	 * Quotes a string for storing in the database
2959
	 * @deprecated
2960
	 * Return value will be not surrounded by quotes
2961
	 *
2962
	 * @param  string $string
2963
	 * @return string
2964
	 */
2965
	public function escape_quote($string)
2966
	{
2967
		return $this->quote($string);
2968
	}
2969
2970
	/**
2971
	 * Quotes a string for storing in the database
2972
	 * @deprecated
2973
	 * Return value will be not surrounded by quotes
2974
	 *
2975
	 * @param  string $string
2976
	 * @return string
2977
	 */
2978
	public function quoteFormEmail($string)
2979
	{
2980
		return $this->quote($string);
2981
	}
2982
2983
    /**
2984
     * Renames an index using fields definition
2985
     *
2986
     * @param  array  $old_definition
2987
     * @param  array  $new_definition
2988
     * @param  string $table_name
2989
     * @return string SQL statement
2990
     */
2991
	public function renameIndexDefs($old_definition, $new_definition, $table_name)
2992
	{
2993
		return array($this->add_drop_constraint($table_name,$old_definition,true),
2994
				$this->add_drop_constraint($table_name,$new_definition), false);
2995
	}
2996
2997
	/**
2998
	 * Check if type is boolean
2999
	 * @param string $type
3000
     * @return bool
3001
     */
3002
	public function isBooleanType($type)
3003
	{
3004
		return 'bool' == $type;
3005
	}
3006
3007
	/**
3008
	 * Get truth value for boolean type
3009
	 * Allows 'off' to mean false, along with all 'empty' values
3010
	 * @param mixed $val
3011
     * @return bool
3012
	 */
3013
	protected function _getBooleanValue($val)
3014
	{
3015
		//need to put the === sign here otherwise true == 'non empty string'
3016
		if (empty($val) or $val==='off')
3017
			return false;
3018
3019
		return true;
3020
	}
3021
3022
	/**
3023
	 * Check if type is a number
3024
	 * @param string $type
3025
     * @return bool
3026
	 */
3027
	public function isNumericType($type)
3028
	{
3029
	    if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
3030
	        return true;
3031
	    }
3032
		return false;
3033
	}
3034
3035
    /**
3036
     * Check if the value is empty value for this type
3037
     * @param mixed $val Value
3038
     * @param string $type Type (one of vardef types)
3039
     * @return bool true if the value if empty
3040
     */
3041
	protected function _emptyValue($val, $type)
3042
	{
3043 3
		if (empty($val))
3044 3
			return true;
3045
3046
		if($this->emptyValue($type) == $val) {
3047
			return true;
3048
		}
3049
		switch ($type) {
3050
			case 'decimal':
3051
			case 'decimal2':
3052
			case 'int':
3053
			case 'double':
3054
			case 'float':
3055
			case 'uint':
3056
			case 'ulong':
3057
			case 'long':
3058
			case 'short':
3059
				return ($val == 0);
3060
			case 'date':
3061
				if ($val == '0000-00-00')
3062
					return true;
3063
				if ($val == 'NULL')
3064
					return true;
3065
				return false;
3066
		}
3067
3068
		return false;
3069
	}
3070
3071
	/**
3072
     * @abstract
3073
	 * Does this type represent text (i.e., non-varchar) value?
3074
	 * @param string $type
3075
     * @return bool
3076
	 */
3077
	public function isTextType($type)
3078
	{
3079
		return false;
3080
	}
3081
3082
	/**
3083
	 * Check if this DB supports certain capability
3084
	 * See $this->capabilities for the list
3085
	 * @param string $cap
3086
     * @return bool
3087
	 */
3088
	public function supports($cap)
3089
	{
3090
		return !empty($this->capabilities[$cap]);
3091
	}
3092
3093
	/**
3094
	 * Create ORDER BY clause for ENUM type field
3095
	 * @param string $order_by Field name
3096
	 * @param array $values Possible enum value
3097
	 * @param string $order_dir Order direction, ASC or DESC
3098
     * @return string
3099
     */
3100
	public function orderByEnum($order_by, $values, $order_dir)
3101
	{
3102
		$i = 0;
3103
		$order_by_arr = array();
3104
		foreach ($values as $key => $value) {
3105
			if($key == '') {
3106
				$order_by_arr[] = "WHEN ($order_by='' OR $order_by IS NULL) THEN $i";
3107
			} else {
3108
				$order_by_arr[] = "WHEN $order_by=".$this->quoted($key)." THEN $i";
3109
			}
3110
			$i++;
3111
		}
3112
		return "CASE ".implode("\n", $order_by_arr)." ELSE $i END $order_dir\n";
3113
	}
3114
3115
	/**
3116
	 * Return representation of an empty value depending on type
3117
	 * The value is fully quoted, converted, etc.
3118
	 * @param string $type
3119
     * @return mixed Empty value
3120
     */
3121
	public function emptyValue($type)
3122
	{
3123 23
		if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'bool' || $this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
3124
			return 0;
3125
		}
3126
3127 23
		return "''";
3128
	}
3129
3130
	/**
3131
	 * List of available collation settings
3132
     * @abstract
3133
	 * @return string
3134
	 */
3135
	public function getDefaultCollation()
3136
	{
3137
		return null;
3138
	}
3139
3140
	/**
3141
	 * List of available collation settings
3142
     * @abstract
3143
	 * @return array
3144
	 */
3145
	public function getCollationList()
3146
	{
3147
		return null;
3148
	}
3149
3150
	/**
3151
	 * Returns the number of columns in a table
3152
	 *
3153
	 * @param  string $table_name
3154
	 * @return int
3155
	 */
3156
	public function number_of_columns($table_name)
3157
	{
3158
		$table = $this->getTableDescription($table_name);
3159
		return count($table);
3160
	}
3161
3162
	/**
3163
	 * Return limit query based on given query
3164
	 * @param string $sql
3165
	 * @param int $start
3166
	 * @param int $count
3167
	 * @param bool $dieOnError
3168
	 * @param string $msg
3169
     * @return resource|bool query result
3170
     * @see DBManager::limitQuery()
3171
	 */
3172
	public function limitQuerySql($sql, $start, $count, $dieOnError=false, $msg='')
3173
	{
3174
		return $this->limitQuery($sql,$start,$count,$dieOnError,$msg,false);
3175
	}
3176
3177
	/**
3178
	 * Return current time in format fit for insertion into DB (with quotes)
3179
	 * @return string
3180
	 */
3181
	public function now()
3182
	{
3183 2
		return $this->convert($this->quoted(TimeDate::getInstance()->nowDb()), "datetime");
3184
	}
3185
3186
	/**
3187
	 * Check if connecting user has certain privilege
3188
	 * @param string $privilege
3189
     * @return bool Privilege allowed?
3190
     */
3191
	public function checkPrivilege($privilege)
3192
	{
3193
		switch($privilege) {
3194
			case "CREATE TABLE":
3195
				$this->query("CREATE TABLE temp (id varchar(36))");
3196
				break;
3197
			case "DROP TABLE":
3198
				$sql = $this->dropTableNameSQL("temp");
3199
				$this->query($sql);
3200
				break;
3201
			case "INSERT":
3202
				$this->query("INSERT INTO temp (id) VALUES ('abcdef0123456789abcdef0123456789abcd')");
3203
				break;
3204
			case "UPDATE":
3205
				$this->query("UPDATE temp SET id = '100000000000000000000000000000000000' WHERE id = 'abcdef0123456789abcdef0123456789abcd'");
3206
				break;
3207
			case 'SELECT':
3208
				return $this->getOne('SELECT id FROM temp WHERE id=\'100000000000000000000000000000000000\'', false);
3209
			case 'DELETE':
3210
				$this->query("DELETE FROM temp WHERE id = '100000000000000000000000000000000000'");
3211
				break;
3212
			case "ADD COLUMN":
3213
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 50));
3214
				$sql = 	$this->changeColumnSQL("temp", $test, "add");
3215
				$this->query($sql);
0 ignored issues
show
It seems like $sql defined by $this->changeColumnSQL('temp', $test, 'add') on line 3214 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3216
				break;
3217
			case "CHANGE COLUMN":
3218
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3219
				$sql = 	$this->changeColumnSQL("temp", $test, "modify");
3220
				$this->query($sql);
0 ignored issues
show
It seems like $sql defined by $this->changeColumnSQL('temp', $test, 'modify') on line 3219 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3221
				break;
3222
			case "DROP COLUMN":
3223
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3224
				$sql = 	$this->changeColumnSQL("temp", $test, "drop");
3225
				$this->query($sql);
0 ignored issues
show
It seems like $sql defined by $this->changeColumnSQL('temp', $test, 'drop') on line 3224 can also be of type array; however, DBManager::query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3226
				break;
3227
			default:
3228
				return false;
3229
		}
3230
		if($this->checkError("Checking privileges")) {
3231
			return false;
3232
		}
3233
		return true;
3234
	}
3235
3236
	/**
3237
	 * Check if the query is a select query
3238
	 * @param string $query
3239
     * @return bool  Is query SELECT?
3240
     */
3241
	protected function isSelect($query)
3242
	{
3243
		$query = trim($query);
3244
		$select_check = strpos(strtolower($query), strtolower("SELECT"));
3245
		//Checks to see if there is union select which is valid
3246
		$select_check2 = strpos(strtolower($query), strtolower("(SELECT"));
3247
		if($select_check==0 || $select_check2==0){
3248
			//Returning false means query is ok!
3249
			return true;
3250
		}
3251
		return false;
3252
	}
3253
3254
	/**
3255
	 * Parse fulltext search query with mysql syntax:
3256
	 *  terms quoted by ""
3257
	 *  + means the term must be included
3258
	 *  - means the term must be excluded
3259
	 *  * or % at the end means wildcard
3260
	 * @param string $query
3261
	 * @return array of 3 elements - query terms, mandatory terms and excluded terms
3262
	 */
3263
	public function parseFulltextQuery($query)
3264
	{
3265
		/* split on space or comma, double quotes with \ for escape */
3266
		if(strpbrk($query, " ,")) {
3267
			// ("([^"]*?)"|[^" ,]+)((, )+)?
3268
			// '/([^" ,]+|".*?[^\\\\]")(,|\s)\s*/'
3269
			if(!preg_match_all('/("([^"]*?)"|[^"\s,]+)((,\s)+)?/', $query, $m)) {
3270
				return false;
3271
			}
3272
			$qterms = $m[1];
3273
		} else {
3274
			$qterms = array($query);
3275
		}
3276
		$terms = $must_terms = $not_terms = array();
3277
		foreach($qterms as $item) {
3278
			if($item[0] == '"') {
3279
				$item = trim($item, '"');
3280
			}
3281
			if($item[0] == '+') {
3282
                if (strlen($item) > 1) {
3283
                    $must_terms[] = substr($item, 1);
3284
                }
3285
                continue;
3286
			}
3287
			if($item[0] == '-') {
3288
                if (strlen($item) > 1) {
3289
				    $not_terms[] = substr($item, 1);
3290
                }
3291
                continue;
3292
			}
3293
			$terms[] = $item;
3294
		}
3295
		return array($terms, $must_terms, $not_terms);
3296
	}
3297
3298
    // Methods to check respective queries
3299
	protected $standardQueries = array(
3300
		'ALTER TABLE' => 'verifyAlterTable',
3301
		'DROP TABLE' => 'verifyDropTable',
3302
		'CREATE TABLE' => 'verifyCreateTable',
3303
		'INSERT INTO' => 'verifyInsertInto',
3304
		'UPDATE' => 'verifyUpdate',
3305
		'DELETE FROM' => 'verifyDeleteFrom',
3306
	);
3307
3308
3309
    /**
3310
     * Extract table name from a query
3311
     * @param string $query SQL query
3312
     * @return string
3313
     */
3314
	protected function extractTableName($query)
3315
	{
3316
        $query = preg_replace('/[^A-Za-z0-9_\s]/', "", $query);
3317
        $query = trim(str_replace(array_keys($this->standardQueries), '', $query));
3318
3319
        $firstSpc = strpos($query, " ");
3320
        $end = ($firstSpc > 0) ? $firstSpc : strlen($query);
3321
        $table = substr($query, 0, $end);
3322
3323
        return $table;
3324
	}
3325
3326
    /**
3327
     * Verify SQl statement using per-DB verification function
3328
     * provided the function exists
3329
     * @param string $query Query to verify
3330
     * @param array $skipTables List of blacklisted tables that aren't checked
3331
     * @return string
3332
     */
3333
	public function verifySQLStatement($query, $skipTables)
3334
	{
3335
		$query = trim($query);
3336
		foreach($this->standardQueries as $qstart => $check) {
3337
			if(strncasecmp($qstart, $query, strlen($qstart)) == 0) {
3338
				if(is_callable(array($this, $check))) {
3339
					$table = $this->extractTableName($query);
3340
					if(!in_array($table, $skipTables)) {
3341
						return call_user_func(array($this, $check), $table, $query);
3342
					} else {
3343
						$this->log->debug("Skipping table $table as blacklisted");
3344
					}
3345
				} else {
3346
					$this->log->debug("No verification for $qstart on {$this->dbType}");
0 ignored issues
show
The property dbType does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3347
				}
3348
				break;
3349
			}
3350
		}
3351
		return "";
3352
	}
3353
3354
	/**
3355
	 * Tests an CREATE TABLE query
3356
	 * @param string $table The table name to get DDL
3357
	 * @param string $query The query to test.
3358
	 * @return string Non-empty if error found
3359
	 */
3360
	protected function verifyCreateTable($table, $query)
3361
	{
3362
		$this->log->debug('verifying CREATE statement...');
3363
3364
		// rewrite DDL with _temp name
3365
		$this->log->debug('testing query: ['.$query.']');
3366
		$tempname = $table."__uw_temp";
3367
		$tempTableQuery = str_replace("CREATE TABLE {$table}", "CREATE TABLE $tempname", $query);
3368
3369
		if(strpos($tempTableQuery, '__uw_temp') === false) {
3370
			return 'Could not use a temp table to test query!';
3371
		}
3372
3373
		$this->query($tempTableQuery, false, "Preflight Failed for: {$query}");
3374
3375
		$error = $this->lastError(); // empty on no-errors
3376
		if(!empty($error)) {
3377
			return $error;
3378
		}
3379
3380
		// check if table exists
3381
		$this->log->debug('testing for table: '.$table);
3382
		if(!$this->tableExists($tempname)) {
3383
			return "Failed to create temp table!";
3384
		}
3385
3386
		$this->dropTableName($tempname);
3387
		return '';
3388
	}
3389
3390
	/**
3391
	 * Execute multiple queries one after another
3392
	 * @param array $sqls Queries
3393
	 * @param bool $dieOnError Die on error, passed to query()
3394
	 * @param string $msg Error message, passed to query()
3395
	 * @param bool $suppress Supress errors, passed to query()
3396
	 * @return resource|bool result set or success/failure bool
3397
	 */
3398
	public function queryArray(array $sqls, $dieOnError = false, $msg = '', $suppress = false)
3399
	{
3400
		$last = true;
3401
		foreach($sqls as $sql) {
3402
			if(!($last = $this->query($sql, $dieOnError, $msg, $suppress))) {
3403
				break;
3404
			}
3405
		}
3406
		return $last;
3407
	}
3408
3409
	/**
3410
	 * Fetches the next row in the query result into an associative array
3411
	 *
3412
	 * @param  resource $result
3413
	 * @param  bool $encode Need to HTML-encode the result?
3414
	 * @return array    returns false if there are no more rows available to fetch
3415
	 */
3416
	public function fetchByAssoc($result, $encode = true)
3417
	{
3418 890
	    if (empty($result))	return false;
3419
3420 427
	    if(is_int($encode) && func_num_args() == 3) {
3421
	        // old API: $result, $rowNum, $encode
3422
	        $GLOBALS['log']->deprecated("Using row number in fetchByAssoc is not portable and no longer supported. Please fix your code.");
3423
	        $encode = func_get_arg(2);
3424
	    }
3425 427
	    $row = $this->fetchRow($result);
3426 427
	    if (!empty($row) && $encode && $this->encode) {
3427 224
	    	return array_map('to_html', $row);
3428
	    } else {
3429 410
	       return $row;
3430
	    }
3431
	}
3432
3433
	/**
3434
	 * Get DB driver name used for install/upgrade scripts
3435
	 * @return string
3436
	 */
3437
	public function getScriptName()
3438
	{
3439
		// Usually the same name as dbType
3440
		return $this->dbType;
0 ignored issues
show
The property dbType does not exist on object<DBManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3441
	}
3442
3443
	/**
3444
	 * Set database options
3445
	 * Options are usually db-dependant and derive from $config['dbconfigoption']
3446
	 * @param array $options
3447
	 * @return DBManager
3448
	 */
3449
	public function setOptions($options)
3450
	{
3451
	    $this->options = $options;
3452
	    return $this;
3453
	}
3454
3455
	/**
3456
	 * Get DB options
3457
	 * @return array
3458
	 */
3459
	public function getOptions()
3460
	{
3461
	    return $this->options;
3462
	}
3463
3464
	/**
3465
	 * Get DB option by name
3466
	 * @param string $option Option name
3467
	 * @return mixed Option value or null if doesn't exist
3468
	 */
3469
	public function getOption($option)
3470
	{
3471 94
	    if(isset($this->options[$option])) {
3472
	        return $this->options[$option];
3473
	    }
3474 94
	    return null;
3475
	}
3476
3477
	/**
3478
	 * Commits pending changes to the database when the driver is setup to support transactions.
3479
	 * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3480
	 * @abstract
3481
	 * @return bool true if commit succeeded, false if it failed
3482
	 */
3483
	public function commit()
3484
	{
3485
		$this->log->info("DBManager.commit() stub");
3486
		return true;
3487
	}
3488
3489
	/**
3490
	 * Rollsback pending changes to the database when the driver is setup to support transactions.
3491
	 * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3492
	 * Since rollbacks cannot be done, this implementation always returns false.
3493
	 * @abstract
3494
	 * @return bool true if rollback succeeded, false if it failed
3495
	 */
3496
	public function rollback()
3497
	{
3498
		$this->log->info("DBManager.rollback() stub");
3499
		return false;
3500
	}
3501
3502
	/**
3503
	 * Check if this DB name is valid
3504
	 *
3505
	 * @param string $name
3506
	 * @return bool
3507
	 */
3508
	public function isDatabaseNameValid($name)
3509
	{
3510
		// Generic case - no slashes, no dots
3511 1
		return preg_match('#[/.\\\\]#', $name)==0;
3512
	}
3513
3514
	/**
3515
	 * Check special requirements for DB installation.
3516
	 * @abstract
3517
	 * If everything is OK, return true.
3518
	 * If something's wrong, return array of error code and parameters
3519
	 * @return mixed
3520
	 */
3521
	public function canInstall()
3522
	{
3523
		return true;
3524
	}
3525
3526
	/**
3527
	 * @abstract
3528
     * Code run on new database before installing
3529
	 */
3530
	public function preInstall()
3531
	{
3532
	}
3533
3534
	/**
3535
     * @abstract
3536
	 * Code run on new database after installing
3537
	 */
3538
	public function postInstall()
3539
	{
3540
	}
3541
3542
	/**
3543
	 * Disable keys on the table
3544
	 * @abstract
3545
	 * @param string $tableName
3546
	 */
3547
	public function disableKeys($tableName)
3548
	{
3549
	}
3550
3551
	/**
3552
	 * Re-enable keys on the table
3553
	 * @abstract
3554
	 * @param string $tableName
3555
	 */
3556
	public function enableKeys($tableName)
3557
	{
3558
	}
3559
3560
	/**
3561
	 * Quote string in DB-specific manner
3562
	 * @param string $string
3563
	 * @return string
3564
	 */
3565
	abstract public function quote($string);
3566
3567
    abstract public function quoteIdentifier($string);
3568
3569
	/**
3570
	 * Use when you need to convert a database string to a different value; this function does it in a
3571
	 * database-backend aware way
3572
	 * Supported conversions:
3573
	 *      today		return current date
3574
	 *      left		Take substring from the left
3575
	 *      date_format	Format date as string, supports %Y-%m-%d, %Y-%m, %Y
3576
     *      time_format Format time as string
3577
     *      date        Convert date string to datetime value
3578
     *      time        Convert time string to datetime value
3579
	 *      datetime	Convert datetime string to datetime value
3580
	 *      ifnull		If var is null, use default value
3581
	 *      concat		Concatenate strings
3582
	 *      quarter		Quarter number of the date
3583
	 *      length		Length of string
3584
	 *      month		Month number of the date
3585
	 *      add_date	Add specified interval to a date
3586
     *      add_time    Add time interval to a date
3587
     *      text2char   Convert text field to varchar
3588
	 *
3589
	 * @param string $string database string to convert
3590
	 * @param string $type type of conversion to do
3591
	 * @param array  $additional_parameters optional, additional parameters to pass to the db function
3592
	 * @return string
3593
	 */
3594
	abstract public function convert($string, $type, array $additional_parameters = array());
3595
3596
	/**
3597
	 * Converts from Database data to app data
3598
	 *
3599
	 * Supported types
3600
	 * - date
3601
	 * - time
3602
	 * - datetime
3603
     * - datetimecombo
3604
     * - timestamp
3605
	 *
3606
	 * @param string $string database string to convert
3607
	 * @param string $type type of conversion to do
3608
	 * @return string
3609
	 */
3610
	abstract public function fromConvert($string, $type);
3611
3612
    /**
3613
     * Parses and runs queries
3614
     *
3615
     * @param  string   $sql        SQL Statement to execute
3616
     * @param  bool     $dieOnError True if we want to call die if the query returns errors
3617
     * @param  string   $msg        Message to log if error occurs
3618
     * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
3619
     * @param  bool     $keepResult Keep query result in the object?
3620
     * @return resource|bool result set or success/failure bool
3621
     */
3622
	abstract public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false);
3623
3624
    /**
3625
     * Runs a limit query: one where we specify where to start getting records and how many to get
3626
     *
3627
     * @param  string   $sql     SELECT query
3628
     * @param  int      $start   Starting row
3629
     * @param  int      $count   How many rows
3630
     * @param  boolean  $dieOnError  True if we want to call die if the query returns errors
3631
     * @param  string   $msg     Message to log if error occurs
3632
     * @param  bool     $execute Execute or return SQL?
3633
     * @return resource query result
3634
     */
3635
	abstract function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true);
3636
3637
3638
	/**
3639
	 * Free Database result
3640
	 * @param resource $dbResult
3641
	 */
3642
	abstract protected function freeDbResult($dbResult);
3643
3644
	/**
3645
	 * Rename column in the DB
3646
	 * @param string $tablename
3647
	 * @param string $column
3648
	 * @param string $newname
3649
	 */
3650
	abstract function renameColumnSQL($tablename, $column, $newname);
3651
3652
	/**
3653
	 * Returns definitions of all indies for passed table.
3654
	 *
3655
	 * return will is a multi-dimensional array that
3656
	 * categorizes the index definition by types, unique, primary and index.
3657
	 * <code>
3658
	 * <?php
3659
	 * array(                                                              O
3660
	 *       'index1'=> array (
3661
	 *           'name'   => 'index1',
3662
	 *           'type'   => 'primary',
3663
	 *           'fields' => array('field1','field2')
3664
	 *           )
3665
	 *       )
3666
	 * ?>
3667
	 * </code>
3668
	 * This format is similar to how indicies are defined in vardef file.
3669
	 *
3670
	 * @param  string $tablename
3671
	 * @return array
3672
	 */
3673
	abstract public function get_indices($tablename);
3674
3675
	/**
3676
	 * Returns definitions of all indies for passed table.
3677
	 *
3678
	 * return will is a multi-dimensional array that
3679
	 * categorizes the index definition by types, unique, primary and index.
3680
	 * <code>
3681
	 * <?php
3682
	 * array(
3683
	 *       'field1'=> array (
3684
	 *           'name'   => 'field1',
3685
	 *           'type'   => 'varchar',
3686
	 *           'len' => '200'
3687
	 *           )
3688
	 *       )
3689
	 * ?>
3690
	 * </code>
3691
	 * This format is similar to how indicies are defined in vardef file.
3692
	 *
3693
	 * @param  string $tablename
3694
	 * @return array
3695
	 */
3696
	abstract public function get_columns($tablename);
3697
3698
	/**
3699
	 * Generates alter constraint statement given a table name and vardef definition.
3700
	 *
3701
	 * Supports both adding and droping a constraint.
3702
	 *
3703
	 * @param  string $table      tablename
3704
	 * @param  array  $definition field definition
3705
	 * @param  bool   $drop       true if we are dropping the constraint, false if we are adding it
3706
	 * @return string SQL statement
3707
	 */
3708
	abstract public function add_drop_constraint($table, $definition, $drop = false);
3709
3710
	/**
3711
	 * Returns the description of fields based on the result
3712
	 *
3713
	 * @param  resource $result
3714
	 * @param  boolean  $make_lower_case
3715
	 * @return array field array
3716
	 */
3717
	abstract public function getFieldsArray($result, $make_lower_case = false);
3718
3719
	/**
3720
	 * Returns an array of tables for this database
3721
	 *
3722
	 * @return	array|false 	an array of with table names, false if no tables found
3723
	 */
3724
	abstract public function getTablesArray();
3725
3726
	/**
3727
	 * Return's the version of the database
3728
	 *
3729
	 * @return string
3730
	 */
3731
	abstract public function version();
3732
3733
	/**
3734
	 * Checks if a table with the name $tableName exists
3735
	 * and returns true if it does or false otherwise
3736
	 *
3737
	 * @param  string $tableName
3738
	 * @return bool
3739
	 */
3740
	abstract public function tableExists($tableName);
3741
3742
	/**
3743
	 * Fetches the next row in the query result into an associative array
3744
	 *
3745
	 * @param  resource $result
3746
	 * @return array    returns false if there are no more rows available to fetch
3747
	 */
3748
	abstract public function fetchRow($result);
3749
3750
	/**
3751
	 * Connects to the database backend
3752
	 *
3753
	 * Takes in the database settings and opens a database connection based on those
3754
	 * will open either a persistent or non-persistent connection.
3755
	 * If a persistent connection is desired but not available it will defualt to non-persistent
3756
	 *
3757
	 * configOptions must include
3758
	 * db_host_name - server ip
3759
	 * db_user_name - database user name
3760
	 * db_password - database password
3761
	 *
3762
	 * @param array   $configOptions
3763
	 * @param boolean $dieOnError
3764
	 */
3765
	abstract public function connect(array $configOptions = null, $dieOnError = false);
3766
3767
	/**
3768
	 * Generates sql for create table statement for a bean.
3769
	 *
3770
	 * @param  string $tablename
3771
	 * @param  array  $fieldDefs
3772
	 * @param  array  $indices
3773
	 * @return string SQL Create Table statement
3774
	 */
3775
	abstract public function createTableSQLParams($tablename, $fieldDefs, $indices);
3776
3777
	/**
3778
	 * Generates the SQL for changing columns
3779
	 *
3780
	 * @param string $tablename
3781
	 * @param array  $fieldDefs
3782
	 * @param string $action
3783
	 * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
3784
	 * @return string|array
3785
	 */
3786
	abstract protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false);
3787
3788
	/**
3789
	 * Disconnects from the database
3790
	 *
3791
	 * Also handles any cleanup needed
3792
	 */
3793
	abstract public function disconnect();
3794
3795
	/**
3796
	 * Get last database error
3797
	 * This function should return last error as reported by DB driver
3798
	 * and should return false if no error condition happened
3799
	 * @return string|false Error message or false if no error happened
3800
	 */
3801
	abstract public function lastDbError();
3802
3803
    /**
3804
     * Check if this query is valid
3805
     * Validates only SELECT queries
3806
     * @param string $query
3807
     * @return bool
3808
     */
3809
	abstract public function validateQuery($query);
3810
3811
	/**
3812
	 * Check if this driver can be used
3813
	 * @return bool
3814
	 */
3815
	abstract public function valid();
3816
3817
	/**
3818
	 * Check if certain database exists
3819
	 * @param string $dbname
3820
	 */
3821
	abstract public function dbExists($dbname);
3822
3823
	/**
3824
	 * Get tables like expression
3825
	 * @param string $like Expression describing tables
3826
	 * @return array
3827
	 */
3828
	abstract public function tablesLike($like);
3829
3830
	/**
3831
	 * Create a database
3832
	 * @param string $dbname
3833
	 */
3834
	abstract public function createDatabase($dbname);
3835
3836
	/**
3837
	 * Drop a database
3838
	 * @param string $dbname
3839
	 */
3840
	abstract public function dropDatabase($dbname);
3841
3842
	/**
3843
	 * Get database configuration information (DB-dependent)
3844
	 * @return array|null
3845
	 */
3846
	abstract public function getDbInfo();
3847
3848
	/**
3849
	 * Check if certain DB user exists
3850
	 * @param string $username
3851
	 */
3852
	abstract public function userExists($username);
3853
3854
	/**
3855
	 * Create DB user
3856
	 * @param string $database_name
3857
	 * @param string $host_name
3858
	 * @param string $user
3859
	 * @param string $password
3860
	 */
3861
	abstract public function createDbUser($database_name, $host_name, $user, $password);
3862
3863
	/**
3864
	 * Check if the database supports fulltext indexing
3865
	 * Note that database driver can be capable of supporting FT (see supports('fulltext))
3866
	 * but particular instance can still have it disabled
3867
	 * @return bool
3868
	 */
3869
	abstract public function full_text_indexing_installed();
3870
3871
	/**
3872
	 * Generate fulltext query from set of terms
3873
	 * @param string $field Field to search against
3874
	 * @param array $terms Search terms that may be or not be in the result
3875
	 * @param array $must_terms Search terms that have to be in the result
3876
	 * @param array $exclude_terms Search terms that have to be not in the result
3877
	 */
3878
	abstract public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array());
3879
3880
	/**
3881
	 * Get install configuration for this DB
3882
	 * @return array
3883
	 */
3884
	abstract public function installConfig();
3885
3886
    /**
3887
     * Returns a DB specific FROM clause which can be used to select against functions.
3888
     * Note that depending on the database that this may also be an empty string.
3889
     * @abstract
3890
     * @return string
3891
     */
3892
    abstract public function getFromDummyTable();
3893
3894
    /**
3895
     * Returns a DB specific piece of SQL which will generate GUID (UUID)
3896
     * This string can be used in dynamic SQL to do multiple inserts with a single query.
3897
     * I.e. generate a unique Sugar id in a sub select of an insert statement.
3898
     * @abstract
3899
     * @return string
3900
     */
3901
	abstract public function getGuidSQL();
3902
}
3903