Issues (2473)

Branch: master

Security Analysis    no vulnerabilities found

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.

engine/classes/ElggBatch.php (3 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
/**
3
 * Efficiently run operations on batches of results for any function
4
 * that supports an options array.
5
 *
6
 * This is usually used with elgg_get_entities() and friends,
7
 * elgg_get_annotations(), and elgg_get_metadata().
8
 *
9
 * If you pass a valid PHP callback, all results will be run through that
10
 * callback. You can still foreach() through the result set after.  Valid
11
 * PHP callbacks can be a string, an array, or a closure.
12
 * {@link http://php.net/manual/en/language.pseudo-types.php}
13
 *
14
 * The callback function must accept 3 arguments: an entity, the getter
15
 * used, and the options used.
16
 *
17
 * Results from the callback are stored in callbackResult. If the callback
18
 * returns only booleans, callbackResults will be the combined result of
19
 * all calls. If no entities are processed, callbackResults will be null.
20
 *
21
 * If the callback returns anything else, callbackresult will be an indexed
22
 * array of whatever the callback returns.  If returning error handling
23
 * information, you should include enough information to determine which
24
 * result you're referring to.
25
 *
26
 * Don't combine returning bools and returning something else.
27
 *
28
 * Note that returning false will not stop the foreach.
29
 *
30
 * @warning If your callback or foreach loop deletes or disable entities
31
 * you MUST call setIncrementOffset(false) or set that when instantiating.
32
 * This forces the offset to stay what it was in the $options array.
33
 *
34
 * @example
35
 * <code>
36
 * // using foreach
37
 * $batch = new \ElggBatch('elgg_get_entities', array());
38
 * $batch->setIncrementOffset(false);
39
 *
40
 * foreach ($batch as $entity) {
41
 * 	$entity->disable();
42
 * }
43
 *
44
 * // using both a callback
45
 * $callback = function($result, $getter, $options) {
46
 * 	var_dump("Looking at annotation id: $result->id");
47
 *  return true;
48
 * }
49
 *
50
 * $batch = new \ElggBatch('elgg_get_annotations', array('guid' => 2), $callback);
51
 * </code>
52
 *
53
 * @package    Elgg.Core
54
 * @subpackage DataModel
55
 * @since      1.8
56
 */
57
class ElggBatch
58
	implements \Iterator {
59
60
	/**
61
	 * The objects to interator over.
62
	 *
63
	 * @var array
64
	 */
65
	private $results = array();
66
67
	/**
68
	 * The function used to get results.
69
	 *
70
	 * @var mixed A string, array, or closure, or lamda function
71
	 */
72
	private $getter = null;
73
74
	/**
75
	 * The number of results to grab at a time.
76
	 *
77
	 * @var int
78
	 */
79
	private $chunkSize = 25;
80
81
	/**
82
	 * A callback function to pass results through.
83
	 *
84
	 * @var mixed A string, array, or closure, or lamda function
85
	 */
86
	private $callback = null;
87
88
	/**
89
	 * Start after this many results.
90
	 *
91
	 * @var int
92
	 */
93
	private $offset = 0;
94
95
	/**
96
	 * Stop after this many results.
97
	 *
98
	 * @var int
99
	 */
100
	private $limit = 0;
101
102
	/**
103
	 * Number of processed results.
104
	 *
105
	 * @var int
106
	 */
107
	private $retrievedResults = 0;
108
109
	/**
110
	 * The index of the current result within the current chunk
111
	 *
112
	 * @var int
113
	 */
114
	private $resultIndex = 0;
115
116
	/**
117
	 * The index of the current chunk
118
	 *
119
	 * @var int
120
	 */
121
	private $chunkIndex = 0;
122
123
	/**
124
	 * The number of results iterated through
125
	 *
126
	 * @var int
127
	 */
128
	private $processedResults = 0;
129
130
	/**
131
	 * Is the getter a valid callback
132
	 *
133
	 * @var bool
134
	 */
135
	private $validGetter = null;
136
137
	/**
138
	 * The result of running all entities through the callback function.
139
	 *
140
	 * @var mixed
141
	 */
142
	public $callbackResult = null;
143
144
	/**
145
	 * If false, offset will not be incremented. This is used for callbacks/loops that delete.
146
	 *
147
	 * @var bool
148
	 */
149
	private $incrementOffset = true;
150
151
	/**
152
	 * Entities that could not be instantiated during a fetch
153
	 *
154
	 * @var \stdClass[]
155
	 */
156
	private $incompleteEntities = array();
157
158
	/**
159
	 * Total number of incomplete entities fetched
160
	 *
161
	 * @var int
162
	 */
163
	private $totalIncompletes = 0;
164
165
	/**
166
	 * Batches operations on any elgg_get_*() or compatible function that supports
167
	 * an options array.
168
	 *
169
	 * Instead of returning all objects in memory, it goes through $chunk_size
170
	 * objects, then requests more from the server.  This avoids OOM errors.
171
	 *
172
	 * @param string $getter     The function used to get objects.  Usually
173
	 *                           an elgg_get_*() function, but can be any valid PHP callback.
174
	 * @param array  $options    The options array to pass to the getter function. If limit is
175
	 *                           not set, 10 is used as the default. In most cases that is not
176
	 *                           what you want.
177
	 * @param mixed  $callback   An optional callback function that all results will be passed
178
	 *                           to upon load.  The callback needs to accept $result, $getter,
179
	 *                           $options.
180
	 * @param int    $chunk_size The number of entities to pull in before requesting more.
181
	 *                           You have to balance this between running out of memory in PHP
182
	 *                           and hitting the db server too often.
183
	 * @param bool   $inc_offset Increment the offset on each fetch. This must be false for
184
	 *                           callbacks that delete rows. You can set this after the
185
	 *                           object is created with {@link \ElggBatch::setIncrementOffset()}.
186
	 */
187
	public function __construct($getter, $options, $callback = null, $chunk_size = 25,
188
			$inc_offset = true) {
189
		
190
		$this->getter = $getter;
191
		$this->options = $options;
0 ignored issues
show
The property options does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
192
		$this->callback = $callback;
193
		$this->chunkSize = $chunk_size;
194
		$this->setIncrementOffset($inc_offset);
195
196
		if ($this->chunkSize <= 0) {
197
			$this->chunkSize = 25;
198
		}
199
200
		// store these so we can compare later
201
		$this->offset = elgg_extract('offset', $options, 0);
202
		$this->limit = elgg_extract('limit', $options, elgg_get_config('default_limit'));
203
204
		// if passed a callback, create a new \ElggBatch with the same options
205
		// and pass each to the callback.
206
		if ($callback && is_callable($callback)) {
207
			$batch = new \ElggBatch($getter, $options, null, $chunk_size, $inc_offset);
208
209
			$all_results = null;
210
211
			foreach ($batch as $result) {
212
				$result = call_user_func($callback, $result, $getter, $options);
213
214
				if (!isset($all_results)) {
215
					if ($result === true || $result === false || $result === null) {
216
						$all_results = $result;
217
					} else {
218
						$all_results = array();
219
					}
220
				}
221
222
				if (($result === true || $result === false || $result === null) && !is_array($all_results)) {
223
					$all_results = $result && $all_results;
224
				} else {
225
					$all_results[] = $result;
226
				}
227
			}
228
229
			$this->callbackResult = $all_results;
230
		}
231
	}
232
233
	/**
234
	 * Tell the process that an entity was incomplete during a fetch
235
	 *
236
	 * @param \stdClass $row
237
	 *
238
	 * @access private
239
	 */
240
	public function reportIncompleteEntity(\stdClass $row) {
241
		$this->incompleteEntities[] = $row;
242
	}
243
244
	/**
245
	 * Fetches the next chunk of results
246
	 *
247
	 * @return bool
248
	 */
249
	private function getNextResultsChunk() {
250
251
		// always reset results.
252
		$this->results = array();
253
254
		if (!isset($this->validGetter)) {
255
			$this->validGetter = is_callable($this->getter);
256
		}
257
258
		if (!$this->validGetter) {
259
			return false;
260
		}
261
262
		$limit = $this->chunkSize;
263
264
		// if someone passed limit = 0 they want everything.
265
		if ($this->limit != 0) {
266
			if ($this->retrievedResults >= $this->limit) {
267
				return false;
268
			}
269
270
			// if original limit < chunk size, set limit to original limit
271
			// else if the number of results we'll fetch if greater than the original limit
272
			if ($this->limit < $this->chunkSize) {
273
				$limit = $this->limit;
274
			} elseif ($this->retrievedResults + $this->chunkSize > $this->limit) {
275
				// set the limit to the number of results remaining in the original limit
276
				$limit = $this->limit - $this->retrievedResults;
277
			}
278
		}
279
280
		if ($this->incrementOffset) {
281
			$offset = $this->offset + $this->retrievedResults;
282
		} else {
283
			$offset = $this->offset + $this->totalIncompletes;
284
		}
285
286
		$current_options = array(
287
			'limit' => $limit,
288
			'offset' => $offset,
289
			'__ElggBatch' => $this,
290
		);
291
292
		$options = array_merge($this->options, $current_options);
293
294
		$this->incompleteEntities = array();
295
		$this->results = call_user_func($this->getter, $options);
0 ignored issues
show
Documentation Bug introduced by
It seems like call_user_func($this->getter, $options) of type * is incompatible with the declared type array of property $results.

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...
296
297
		// batch result sets tend to be large; we don't want to cache these.
298
		_elgg_services()->db->disableQueryCache();
299
300
		$num_results = count($this->results);
301
		$num_incomplete = count($this->incompleteEntities);
302
303
		$this->totalIncompletes += $num_incomplete;
304
305
		if ($this->incompleteEntities) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->incompleteEntities 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...
306
			// pad the front of the results with nulls representing the incompletes
307
			array_splice($this->results, 0, 0, array_pad(array(), $num_incomplete, null));
308
			// ...and skip past them
309
			reset($this->results);
310
			for ($i = 0; $i < $num_incomplete; $i++) {
311
				next($this->results);
312
			}
313
		}
314
315
		if ($this->results) {
316
			$this->chunkIndex++;
317
318
			// let the system know we've jumped past the nulls
319
			$this->resultIndex = $num_incomplete;
320
321
			$this->retrievedResults += ($num_results + $num_incomplete);
322
			if ($num_results == 0) {
323
				// This fetch was *all* incompletes! We need to fetch until we can either
324
				// offer at least one row to iterate over, or give up.
325
				return $this->getNextResultsChunk();
326
			}
327
			_elgg_services()->db->enableQueryCache();
328
			return true;
329
		} else {
330
			_elgg_services()->db->enableQueryCache();
331
			return false;
332
		}
333
	}
334
335
	/**
336
	 * Increment the offset from the original options array? Setting to
337
	 * false is required for callbacks that delete rows.
338
	 *
339
	 * @param bool $increment Set to false when deleting data
340
	 * @return void
341
	 */
342
	public function setIncrementOffset($increment = true) {
343
		$this->incrementOffset = (bool) $increment;
344
	}
345
346
	/**
347
	 * Implements Iterator
348
	 */
349
350
	/**
351
	 * PHP Iterator Interface
352
	 *
353
	 * @see Iterator::rewind()
354
	 * @return void
355
	 */
356
	public function rewind() {
357
		$this->resultIndex = 0;
358
		$this->retrievedResults = 0;
359
		$this->processedResults = 0;
360
361
		// only grab results if we haven't yet or we're crossing chunks
362
		if ($this->chunkIndex == 0 || $this->limit > $this->chunkSize) {
363
			$this->chunkIndex = 0;
364
			$this->getNextResultsChunk();
365
		}
366
	}
367
368
	/**
369
	 * PHP Iterator Interface
370
	 *
371
	 * @see Iterator::current()
372
	 * @return mixed
373
	 */
374
	public function current() {
375
		return current($this->results);
376
	}
377
378
	/**
379
	 * PHP Iterator Interface
380
	 *
381
	 * @see Iterator::key()
382
	 * @return int
383
	 */
384
	public function key() {
385
		return $this->processedResults;
386
	}
387
388
	/**
389
	 * PHP Iterator Interface
390
	 *
391
	 * @see Iterator::next()
392
	 * @return mixed
393
	 */
394
	public function next() {
395
		// if we'll be at the end.
396
		if (($this->processedResults + 1) >= $this->limit && $this->limit > 0) {
397
			$this->results = array();
398
			return false;
399
		}
400
401
		// if we'll need new results.
402
		if (($this->resultIndex + 1) >= $this->chunkSize) {
403
			if (!$this->getNextResultsChunk()) {
404
				$this->results = array();
405
				return false;
406
			}
407
408
			$result = current($this->results);
409
		} else {
410
			// the function above resets the indexes, so only inc if not
411
			// getting new set
412
			$this->resultIndex++;
413
			$result = next($this->results);
414
		}
415
416
		$this->processedResults++;
417
		return $result;
418
	}
419
420
	/**
421
	 * PHP Iterator Interface
422
	 *
423
	 * @see Iterator::valid()
424
	 * @return bool
425
	 */
426
	public function valid() {
427
		if (!is_array($this->results)) {
428
			return false;
429
		}
430
		$key = key($this->results);
431
		return ($key !== null && $key !== false);
432
	}
433
}
434