Passed
Push — master ( f13f78...5c1b24 )
by Ismayil
04:22
created

classes/Elgg/Database/PrivateSettingsTable.php (19 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
namespace Elgg\Database;
3
4
use Elgg\Database;
5
use Elgg\Database\EntityTable;
6
use Elgg\Cache\PluginSettingsCache;
7
8
/**
9
 * Private settings for entities
10
 *
11
 * Private settings provide metadata like storage of settings for plugins
12
 * and users.
13
 *
14
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
15
 *
16
 * @access private
17
 * @since 2.0.0
18
 */
19
class PrivateSettingsTable {
20
21
	/**
22
	 * @var Database
23
	 */
24
	protected $db;
25
26
	/**
27
	 * @var EntityTable
28
	 */
29
	protected $entities;
30
31
	/**
32
	 * @var string Name of the database table
33
	 */
34
	protected $table;
35
36
	/**
37
	 * @var PluginSettingsCache cache for settings
38
	 */
39
	protected $cache;
40
41
	/**
42
	 * Constructor
43
	 *
44
	 * @param Database            $db       The database
45
	 * @param EntityTable         $entities Entities table
46
	 * @param PluginSettingsCache $cache    Settings cache
47
	 */
48 197
	public function __construct(Database $db, EntityTable $entities, PluginSettingsCache $cache) {
49 197
		$this->db = $db;
50 197
		$this->entities = $entities;
51 197
		$this->cache = $cache;
52 197
		$this->table = $this->db->prefix . 'private_settings';
53 197
	}
54
55
	/**
56
	 * Returns entities based upon private settings
57
	 *
58
	 * Also accepts all options available to elgg_get_entities(). Supports
59
	 * the singular option shortcut.
60
	 *
61
	 * @param array $options Array in format:
62
	 *
63
	 *  private_setting_names => null|ARR private setting names
64
	 *
65
	 *  private_setting_values => null|ARR metadata values
66
	 *
67
	 *  private_setting_name_value_pairs => null|ARR (
68
	 *                                       name => 'name',
69
	 *                                       value => 'value',
70
	 *                                       'operand' => '=',
71
	 *                                      )
72
	 *                               Currently if multiple values are sent via
73
	 *                               an array (value => array('value1', 'value2')
74
	 *                               the pair's operand will be forced to "IN".
75
	 *
76
	 *  private_setting_name_value_pairs_operator => null|STR The operator to
77
	 *                                 use for combining
78
	 *                                 (name = value) OPERATOR (name = value);
79
	 *                                 default AND
80
	 *
81
	 *  private_setting_name_prefix => STR A prefix to apply to all private
82
	 *                                 settings. Used to namespace plugin user
83
	 *                                 settings or by plugins to namespace their
84
	 *                                 own settings.
85
	 *
86
	 * @return mixed int If count, int. If not count, array. false on errors.
87
	 */
88 1
	public function getEntities(array $options = []) {
89
		$defaults = [
90 1
			'private_setting_names'                     => ELGG_ENTITIES_ANY_VALUE,
91 1
			'private_setting_values'                    => ELGG_ENTITIES_ANY_VALUE,
92 1
			'private_setting_name_value_pairs'          => ELGG_ENTITIES_ANY_VALUE,
93 1
			'private_setting_name_value_pairs_operator' => 'AND',
94 1
			'private_setting_name_prefix'               => '',
95
		];
96
97 1
		$options = array_merge($defaults, $options);
98
99
		$singulars = [
100 1
			'private_setting_name',
101
			'private_setting_value',
102
			'private_setting_name_value_pair',
103
		];
104
105 1
		$options = _elgg_normalize_plural_options_array($options, $singulars);
106
107 1
		$clauses = $this->getWhereSql('e',
108 1
			$options['private_setting_names'],
109 1
			$options['private_setting_values'],
110 1
			$options['private_setting_name_value_pairs'],
111 1
			$options['private_setting_name_value_pairs_operator'],
112 1
			$options['private_setting_name_prefix']);
113
114 1 View Code Duplication
		if ($clauses) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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...
115
			// merge wheres to pass to get_entities()
116 1
			if (isset($options['wheres']) && !is_array($options['wheres'])) {
117
				$options['wheres'] = [$options['wheres']];
118 1
			} elseif (!isset($options['wheres'])) {
119 1
				$options['wheres'] = [];
120
			}
121
122 1
			$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
123
124
			// merge joins to pass to get_entities()
125 1
			if (isset($options['joins']) && !is_array($options['joins'])) {
126
				$options['joins'] = [$options['joins']];
127 1
			} elseif (!isset($options['joins'])) {
128 1
				$options['joins'] = [];
129
			}
130
131 1
			$options['joins'] = array_merge($options['joins'], $clauses['joins']);
132
		}
133
134 1
		return $this->entities->getEntities($options);
135
	}
136
137
	/**
138
	 * Returns private setting name and value SQL where/join clauses for entities
139
	 *
140
	 * @param string     $table         Entities table name
141
	 * @param array|null $names         Array of names
142
	 * @param array|null $values        Array of values
143
	 * @param array|null $pairs         Array of names / values / operands
144
	 * @param string     $pair_operator Operator for joining pairs where clauses
145
	 * @param string     $name_prefix   A string to prefix all names with
146
	 * @return array
147
	 */
148 1
	private function getWhereSql($table, $names = null, $values = null,
149
		$pairs = null, $pair_operator = 'AND', $name_prefix = '') {
150
151
		// @todo short circuit test
152
153
		$return =  [
154 1
			'joins' =>  [],
155
			'wheres' => [],
156
		];
157
158 1
		$return['joins'][] = "JOIN {$this->table} ps on
159 1
			{$table}.guid = ps.entity_guid";
160
161 1
		$wheres = [];
162
163
		// get names wheres
164 1
		$names_where = '';
165 1 View Code Duplication
		if ($names !== null) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
			if (!is_array($names)) {
167
				$names = [$names];
168
			}
169
170
			$sanitised_names = [];
171
			foreach ($names as $name) {
172
				$name = $name_prefix . $name;
173
				$sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
174
			}
175
176
			$names_str = implode(',', $sanitised_names);
177
			if ($names_str) {
178
				$names_where = "(ps.name IN ($names_str))";
179
			}
180
		}
181
182
		// get values wheres
183 1
		$values_where = '';
184 1 View Code Duplication
		if ($values !== null) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
			if (!is_array($values)) {
186
				$values = [$values];
187
			}
188
189
			$sanitised_values = [];
190
			foreach ($values as $value) {
191
				// normalize to 0
192
				if (!$value) {
193
					$value = 0;
194
				}
195
				$sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
196
			}
197
198
			$values_str = implode(',', $sanitised_values);
199
			if ($values_str) {
200
				$values_where = "(ps.value IN ($values_str))";
201
			}
202
		}
203
204 1 View Code Duplication
		if ($names_where && $values_where) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
			$wheres[] = "($names_where AND $values_where)";
206 1
		} elseif ($names_where) {
207
			$wheres[] = "($names_where)";
208 1
		} elseif ($values_where) {
209
			$wheres[] = "($values_where)";
210
		}
211
212
		// add pairs which must be in arrays.
213 1
		if (is_array($pairs)) {
214
			// join counter for incremental joins in pairs
215 1
			$i = 1;
216
217
			// check if this is an array of pairs or just a single pair.
218 1
			if (isset($pairs['name']) || isset($pairs['value'])) {
219
				$pairs = [$pairs];
220
			}
221
222 1
			$pair_wheres = [];
223
224 1
			foreach ($pairs as $index => $pair) {
225
				// @todo move this elsewhere?
226
				// support shortcut 'n' => 'v' method.
227 1
				if (!is_array($pair)) {
228
					$pair = [
229
						'name' => $index,
230
						'value' => $pair
231
					];
232
				}
233
234
				// must have at least a name and value
235 1
				if (!isset($pair['name']) || !isset($pair['value'])) {
236
					// @todo should probably return false.
237
					continue;
238
				}
239
240 1 View Code Duplication
				if (isset($pair['operand'])) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
					$operand = $this->db->sanitizeString($pair['operand']);
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
242
				} else {
243 1
					$operand = ' = ';
244
				}
245
246
				// for comparing
247 1
				$trimmed_operand = trim(strtolower($operand));
248
249
				// if the value is an int, don't quote it because str '15' < str '5'
250
				// if the operand is IN don't quote it because quoting should be done already.
251 1
				if (is_numeric($pair['value'])) {
252
					$value = $this->db->sanitizeString($pair['value']);
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
253 1 View Code Duplication
				} else if (is_array($pair['value'])) {
1 ignored issue
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
					$values_array = [];
255
256
					foreach ($pair['value'] as $pair_value) {
257
						if (is_numeric($pair_value)) {
258
							$values_array[] = $this->db->sanitizeString($pair_value);
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
259
						} else {
260
							$values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
261
						}
262
					}
263
264
					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...
265
						$value = '(' . implode(', ', $values_array) . ')';
266
					}
267
268
					// @todo allow support for non IN operands with array of values.
269
					// will have to do more silly joins.
270
					$operand = 'IN';
271 1
				} else if ($trimmed_operand == 'in') {
272
					$value = "({$pair['value']})";
273
				} else {
274 1
					$value = "'" . $this->db->sanitizeString($pair['value']) . "'";
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
275
				}
276
277 1
				$name = $this->db->sanitizeString($name_prefix . $pair['name']);
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
278
279
				// @todo The multiple joins are only needed when the operator is AND
280 1
				$return['joins'][] = "JOIN {$this->table} ps{$i}
281 1
					on {$table}.guid = ps{$i}.entity_guid";
282
283 1
				$pair_wheres[] = "(ps{$i}.name = '$name' AND ps{$i}.value
284 1
					$operand $value)";
0 ignored issues
show
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...
285
286 1
				$i++;
287
			}
288
289 1
			$where = implode(" $pair_operator ", $pair_wheres);
290 1
			if ($where) {
291 1
				$wheres[] = "($where)";
292
			}
293
		}
294
295 1
		$where = implode(' AND ', $wheres);
296 1
		if ($where) {
297 1
			$return['wheres'][] = "($where)";
298
		}
299
300 1
		return $return;
301
	}
302
303
	/**
304
	 * Gets a private setting for an entity
305
	 *
306
	 * Plugin authors can set private data on entities. By default private
307
	 * data will not be searched or exported.
308
	 *
309
	 * @param int    $entity_guid The entity GUID
310
	 * @param string $name        The name of the setting
311
	 *
312
	 * @return mixed The setting value, or null if does not exist
313
	 */
314 5
	public function get($entity_guid, $name) {
315
316 5
		$values = $this->cache->getAll($entity_guid);
317 5
		if (isset($values[$name])) {
318
			return $values[$name];
319
		}
320
321 5
		if (!$this->entities->exists($entity_guid)) {
322
			return false;
323
		}
324
325
		$query = "
326 5
			SELECT value FROM {$this->table}
327
			WHERE name = :name
328
			AND entity_guid = :entity_guid
329
		";
330
		$params = [
331 5
			':entity_guid' => (int) $entity_guid,
332 5
			':name' => (string) $name,
333
		];
334
335 5
		$setting = $this->db->getDataRow($query, null, $params);
336
337 5
		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...
338 5
			return $setting->value;
339
		}
340
341 3
		return null;
342
	}
343
344
	/**
345
	 * Return an array of all private settings.
346
	 *
347
	 * @param int $entity_guid The entity GUID
348
	 *
349
	 * @return string[] empty array if no settings
350
	 */
351
	public function getAll($entity_guid) {
352
		if (!$this->entities->exists($entity_guid)) {
353
			return [];
354
		}
355
356
		$query = "
357
			SELECT * FROM {$this->table}
358
			WHERE entity_guid = :entity_guid
359
		";
360
		$params = [
361
			':entity_guid' => (int) $entity_guid,
362
		];
363
364
		$result = $this->db->getData($query, null, $params);
365
366
		$return = [];
367
368
		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...
369
			foreach ($result as $r) {
370
				$return[$r->name] = $r->value;
371
			}
372
		}
373
374
		return $return;
375
	}
376
377
	/**
378
	 * Sets a private setting for an entity.
379
	 *
380
	 * @param int    $entity_guid The entity GUID
381
	 * @param string $name        The name of the setting
382
	 * @param string $value       The value of the setting
383
	 * @return bool
384
	 */
385 5
	public function set($entity_guid, $name, $value) {
386 5
		$this->cache->clear($entity_guid);
387 5
		_elgg_services()->boot->invalidateCache();
388
389 5
		if (!$this->entities->exists($entity_guid)) {
390
			return false;
391
		}
392
393
		$query = "
394 5
			INSERT into {$this->table}
395
			(entity_guid, name, value) VALUES
396
			(:entity_guid, :name, :value)
397
			ON DUPLICATE KEY UPDATE value = :value
398
		";
399
		$params = [
400 5
			':entity_guid' => (int) $entity_guid,
401 5
			':name' => (string) $name,
402 5
			':value' => (string) $value,
403
		];
404
405 5
		$result = $this->db->insertData($query, $params);
406
407 5
		return $result !== false;
408
	}
409
410
	/**
411
	 * Deletes a private setting for an entity.
412
	 *
413
	 * @param int    $entity_guid The Entity GUID
414
	 * @param string $name        The name of the setting
415
	 * @return bool
416
	 */
417 View Code Duplication
	public function remove($entity_guid, $name) {
418
		$this->cache->clear($entity_guid);
419
		_elgg_services()->boot->invalidateCache();
420
421
		$query = "
422
			DELETE FROM {$this->table}
423
			WHERE name = :name
424
			AND entity_guid = :entity_guid
425
		";
426
		$params = [
427
			':entity_guid' => (int) $entity_guid,
428
			':name' => (string) $name,
429
		];
430
		
431
		return $this->db->deleteData($query, $params);
432
	}
433
434
	/**
435
	 * Deletes all private settings for an entity
436
	 *
437
	 * @param int $entity_guid The Entity GUID
438
	 * @return bool
439
	 */
440 View Code Duplication
	public function removeAllForEntity($entity_guid) {
441
		$this->cache->clear($entity_guid);
442
		_elgg_services()->boot->invalidateCache();
443
444
		$query = "
445
			DELETE FROM {$this->table}
446
			WHERE entity_guid = :entity_guid
447
		";
448
		$params = [
449
			':entity_guid' => (int) $entity_guid,
450
		];
451
452
		return $this->db->deleteData($query, $params);
453
	}
454
455
}
456