Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

classes/Elgg/Database/PrivateSettingsTable.php (1 issue)

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 196
	public function __construct(Database $db, EntityTable $entities, PluginSettingsCache $cache) {
49 196
		$this->db = $db;
50 196
		$this->entities = $entities;
51 196
		$this->cache = $cache;
52 196
		$this->table = $this->db->prefix . 'private_settings';
53 196
	}
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) {
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) . '\'';
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) {
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) . '\'';
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) {
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'])) {
241
					$operand = $this->db->sanitizeString($pair['operand']);
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']);
253 1 View Code Duplication
				} else if (is_array($pair['value'])) {
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);
259
						} else {
260
							$values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
261
						}
262
					}
263
264
					if ($values_array) {
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']) . "'";
275
				}
276
277 1
				$name = $this->db->sanitizeString($name_prefix . $pair['name']);
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)";
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) {
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) {
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