Test Setup Failed
Branch gcconnex (718fe4)
by Ilia
12:19
created

PrivateSettingsTable::getWhereSql()   F

Complexity

Conditions 30
Paths 1000

Size

Total Lines 154
Code Lines 83

Duplication

Lines 70
Ratio 45.45 %

Importance

Changes 0
Metric Value
cc 30
eloc 83
nc 1000
nop 6
dl 70
loc 154
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Elgg\Database;
3
4
use Elgg\Database;
5
use Elgg\Database\EntityTable;
6
7
/**
8
 * Private settings for entities
9
 *
10
 * Private settings provide metadata like storage of settings for plugins
11
 * and users.
12
 *
13
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
14
 *
15
 * @access private
16
 * @since 2.0.0
17
 */
18
class PrivateSettingsTable {
19
20
	/** @var Database */
21
	private $db;
22
23
	/** @var EntityTable */
24
	private $entities;
25
26
	/** @var Name of the database table */
27
	private $table;
28
29
	/**
30
	 * Constructor
31
	 *
32
	 * @param Database    $db       The database
33
	 * @param EntityTable $entities Entities table
34
	 */
35
	public function __construct(Database $db, EntityTable $entities) {
36
		$this->db = $db;
37
		$this->entities = $entities;
38
		$this->table = $this->db->getTablePrefix() . 'private_settings';
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->getTablePrefix() . 'private_settings' of type string is incompatible with the declared type object<Elgg\Database\Name> of property $table.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
39
	}
40
41
	/**
42
	 * Returns entities based upon private settings
43
	 *
44
	 * Also accepts all options available to elgg_get_entities(). Supports
45
	 * the singular option shortcut.
46
	 *
47
	 * @param array $options Array in format:
48
	 *
49
	 *  private_setting_names => null|ARR private setting names
50
	 *
51
	 *  private_setting_values => null|ARR metadata values
52
	 *
53
	 *  private_setting_name_value_pairs => null|ARR (
54
	 *                                       name => 'name',
55
	 *                                       value => 'value',
56
	 *                                       'operand' => '=',
57
	 *                                      )
58
	 *                               Currently if multiple values are sent via
59
	 *                               an array (value => array('value1', 'value2')
60
	 *                               the pair's operand will be forced to "IN".
61
	 *
62
	 *  private_setting_name_value_pairs_operator => null|STR The operator to
63
	 *                                 use for combining
64
	 *                                 (name = value) OPERATOR (name = value);
65
	 *                                 default AND
66
	 *
67
	 *  private_setting_name_prefix => STR A prefix to apply to all private
68
	 *                                 settings. Used to namespace plugin user
69
	 *                                 settings or by plugins to namespace their
70
	 *                                 own settings.
71
	 *
72
	 * @return mixed int If count, int. If not count, array. false on errors.
73
	 */
74
	public function getEntities(array $options = array()) {
75
		$defaults = array(
76
			'private_setting_names'                     => ELGG_ENTITIES_ANY_VALUE,
77
			'private_setting_values'                    => ELGG_ENTITIES_ANY_VALUE,
78
			'private_setting_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE,
79
			'private_setting_name_value_pairs_operator' => 'AND',
80
			'private_setting_name_prefix'               => '',
81
		);
82
83
		$options = array_merge($defaults, $options);
84
85
		$singulars = array(
86
			'private_setting_name',
87
			'private_setting_value',
88
			'private_setting_name_value_pair',
89
		);
90
91
		$options = _elgg_normalize_plural_options_array($options, $singulars);
92
93
		$clauses = $this->getWhereSql('e',
94
			$options['private_setting_names'],
95
			$options['private_setting_values'],
96
			$options['private_setting_name_value_pairs'],
97
			$options['private_setting_name_value_pairs_operator'],
98
			$options['private_setting_name_prefix']);
99
100 View Code Duplication
		if ($clauses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clauses of type array<string,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...
101
			// merge wheres to pass to get_entities()
102
			if (isset($options['wheres']) && !is_array($options['wheres'])) {
103
				$options['wheres'] = array($options['wheres']);
104
			} elseif (!isset($options['wheres'])) {
105
				$options['wheres'] = array();
106
			}
107
108
			$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
109
110
			// merge joins to pass to get_entities()
111
			if (isset($options['joins']) && !is_array($options['joins'])) {
112
				$options['joins'] = array($options['joins']);
113
			} elseif (!isset($options['joins'])) {
114
				$options['joins'] = array();
115
			}
116
117
			$options['joins'] = array_merge($options['joins'], $clauses['joins']);
118
		}
119
120
		return $this->entities->getEntities($options);
121
	}
122
123
	/**
124
	 * Returns private setting name and value SQL where/join clauses for entities
125
	 *
126
	 * @param string     $table         Entities table name
127
	 * @param array|null $names         Array of names
128
	 * @param array|null $values        Array of values
129
	 * @param array|null $pairs         Array of names / values / operands
130
	 * @param string     $pair_operator Operator for joining pairs where clauses
131
	 * @param string     $name_prefix   A string to prefix all names with
132
	 * @return array
133
	 */
134
	private function getWhereSql($table, $names = null, $values = null,
135
		$pairs = null, $pair_operator = 'AND', $name_prefix = '') {
136
137
		// @todo short circuit test
138
139
		$return = array (
140
			'joins' => array (),
141
			'wheres' => array(),
142
		);
143
144
		$return['joins'][] = "JOIN {$this->table} ps on
145
			{$table}.guid = ps.entity_guid";
146
147
		$wheres = array();
148
149
		// get names wheres
150
		$names_where = '';
151 View Code Duplication
		if ($names !== null) {
152
			if (!is_array($names)) {
153
				$names = array($names);
154
			}
155
156
			$sanitised_names = array();
157
			foreach ($names as $name) {
158
				$name = $name_prefix . $name;
159
				$sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
160
			}
161
162
			$names_str = implode(',', $sanitised_names);
163
			if ($names_str) {
164
				$names_where = "(ps.name IN ($names_str))";
165
			}
166
		}
167
168
		// get values wheres
169
		$values_where = '';
170 View Code Duplication
		if ($values !== null) {
171
			if (!is_array($values)) {
172
				$values = array($values);
173
			}
174
175
			$sanitised_values = array();
176
			foreach ($values as $value) {
177
				// normalize to 0
178
				if (!$value) {
179
					$value = 0;
180
				}
181
				$sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
182
			}
183
184
			$values_str = implode(',', $sanitised_values);
185
			if ($values_str) {
186
				$values_where = "(ps.value IN ($values_str))";
187
			}
188
		}
189
190 View Code Duplication
		if ($names_where && $values_where) {
191
			$wheres[] = "($names_where AND $values_where)";
192
		} elseif ($names_where) {
193
			$wheres[] = "($names_where)";
194
		} elseif ($values_where) {
195
			$wheres[] = "($values_where)";
196
		}
197
198
		// add pairs which must be in arrays.
199
		if (is_array($pairs)) {
200
			// join counter for incremental joins in pairs
201
			$i = 1;
202
203
			// check if this is an array of pairs or just a single pair.
204
			if (isset($pairs['name']) || isset($pairs['value'])) {
205
				$pairs = array($pairs);
206
			}
207
208
			$pair_wheres = array();
209
210
			foreach ($pairs as $index => $pair) {
211
				// @todo move this elsewhere?
212
				// support shortcut 'n' => 'v' method.
213
				if (!is_array($pair)) {
214
					$pair = array(
215
						'name' => $index,
216
						'value' => $pair
217
					);
218
				}
219
220
				// must have at least a name and value
221
				if (!isset($pair['name']) || !isset($pair['value'])) {
222
					// @todo should probably return false.
223
					continue;
224
				}
225
226 View Code Duplication
				if (isset($pair['operand'])) {
227
					$operand = $this->db->sanitizeString($pair['operand']);
228
				} else {
229
					$operand = ' = ';
230
				}
231
232
				// for comparing
233
				$trimmed_operand = trim(strtolower($operand));
234
235
				// if the value is an int, don't quote it because str '15' < str '5'
236
				// if the operand is IN don't quote it because quoting should be done already.
237
				if (is_numeric($pair['value'])) {
238
					$value = $this->db->sanitizeString($pair['value']);
239 View Code Duplication
				} else if (is_array($pair['value'])) {
240
					$values_array = array();
241
242
					foreach ($pair['value'] as $pair_value) {
243
						if (is_numeric($pair_value)) {
244
							$values_array[] = $this->db->sanitizeString($pair_value);
245
						} else {
246
							$values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
247
						}
248
					}
249
250
					if ($values_array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values_array 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...
251
						$value = '(' . implode(', ', $values_array) . ')';
252
					}
253
254
					// @todo allow support for non IN operands with array of values.
255
					// will have to do more silly joins.
256
					$operand = 'IN';
257
				} else if ($trimmed_operand == 'in') {
258
					$value = "({$pair['value']})";
259
				} else {
260
					$value = "'" . $this->db->sanitizeString($pair['value']) . "'";
261
				}
262
263
				$name = $this->db->sanitizeString($name_prefix . $pair['name']);
264
265
				// @todo The multiple joins are only needed when the operator is AND
266
				$return['joins'][] = "JOIN {$this->table} ps{$i}
267
					on {$table}.guid = ps{$i}.entity_guid";
268
269
				$pair_wheres[] = "(ps{$i}.name = '$name' AND ps{$i}.value
270
					$operand $value)";
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
271
272
				$i++;
273
			}
274
275
			$where = implode(" $pair_operator ", $pair_wheres);
276
			if ($where) {
277
				$wheres[] = "($where)";
278
			}
279
		}
280
281
		$where = implode(' AND ', $wheres);
282
		if ($where) {
283
			$return['wheres'][] = "($where)";
284
		}
285
286
		return $return;
287
	}
288
289
	/**
290
	 * Gets a private setting for an entity
291
	 *
292
	 * Plugin authors can set private data on entities. By default private
293
	 * data will not be searched or exported.
294
	 *
295
	 * @param int    $entity_guid The entity GUID
296
	 * @param string $name        The name of the setting
297
	 *
298
	 * @return mixed The setting value, or null if does not exist
299
	 */
300
	public function get($entity_guid, $name) {
301
		$entity_guid = (int) $entity_guid;
302
		$name = $this->db->sanitizeString($name);
303
304
		$entity = $this->entities->get($entity_guid);
305
306
		if (!$entity instanceof \ElggEntity) {
307
			return null;
308
		}
309
310
		$query = "SELECT value FROM {$this->table}
311
			where name = '{$name}' and entity_guid = {$entity_guid}";
312
		$setting = $this->db->getDataRow($query);
313
314
		if ($setting) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $setting 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...
315
			return $setting->value;
316
		}
317
318
		return null;
319
	}
320
321
	/**
322
	 * Return an array of all private settings.
323
	 *
324
	 * @param int $entity_guid The entity GUID
325
	 *
326
	 * @return string[] empty array if no settings
327
	 */
328
	function getAll($entity_guid) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
329
		$entity_guid = (int) $entity_guid;
330
		$entity = $this->entities->get($entity_guid);
331
332
		if (!$entity instanceof \ElggEntity) {
333
			return false;
334
		}
335
336
		$query = "SELECT * FROM {$this->table} WHERE entity_guid = {$entity_guid}";
337
		$result = $this->db->getData($query);
338
339
		if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
340
			$return = array();
341
			foreach ($result as $r) {
342
				$return[$r->name] = $r->value;
343
			}
344
345
			return $return;
346
		}
347
348
		return array();
349
	}
350
351
	/**
352
	 * Sets a private setting for an entity.
353
	 *
354
	 * @param int    $entity_guid The entity GUID
355
	 * @param string $name        The name of the setting
356
	 * @param string $value       The value of the setting
357
	 * @return bool
358
	 */
359
	public function set($entity_guid, $name, $value) {
360
		$entity_guid = (int) $entity_guid;
361
		$name = $this->db->sanitizeString($name);
362
		$value = $this->db->sanitizeString($value);
363
364
		$result = $this->db->insertData("INSERT into {$this->table}
365
			(entity_guid, name, value) VALUES
366
			($entity_guid, '$name', '$value')
367
			ON DUPLICATE KEY UPDATE value='$value'");
368
369
		return $result !== false;
370
	}
371
372
	/**
373
	 * Deletes a private setting for an entity.
374
	 *
375
	 * @param int    $entity_guid The Entity GUID
376
	 * @param string $name        The name of the setting
377
	 * @return bool
378
	 */
379
	function remove($entity_guid, $name) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
380
		$entity_guid = (int) $entity_guid;
381
382
		$entity = $this->entities->get($entity_guid);
383
384
		if (!$entity instanceof \ElggEntity) {
385
			return false;
386
		}
387
388
		$name = $this->db->sanitizeString($name);
389
390
		return $this->db->deleteData("DELETE FROM {$this->table}
391
			WHERE name = '{$name}'
392
			AND entity_guid = {$entity_guid}");
393
	}
394
395
	/**
396
	 * Deletes all private settings for an entity
397
	 *
398
	 * @param int $entity_guid The Entity GUID
399
	 * @return bool
400
	 */
401
	function removeAllForEntity($entity_guid) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
402
		$entity_guid = (int) $entity_guid;
403
404
		$entity = $this->entities->get($entity_guid);
405
406
		if (!$entity instanceof \ElggEntity) {
407
			return false;
408
		}
409
410
		return $this->db->deleteData("DELETE FROM {$this->table}
411
			WHERE entity_guid = {$entity_guid}");
412
	}
413
}