Passed
Push — solr-crawler-auth ( 59e0a1...f1f5f6 )
by Ilia
15:43
created

ElggSession   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 452
Duplicated Lines 0.22 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 35.38%

Importance

Changes 0
Metric Value
dl 1
loc 452
ccs 46
cts 130
cp 0.3538
rs 8.48
c 0
b 0
f 0
wmc 49
lcom 2
cbo 9

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A start() 0 5 1
A migrate() 0 3 1
A invalidate() 0 7 1
A isStarted() 0 3 1
A getId() 0 3 1
A setId() 0 3 1
A getName() 0 3 1
A setName() 0 3 1
A get() 0 3 1
A set() 0 3 1
A remove() 0 3 1
A del() 0 4 1
A has() 0 3 1
A setLoggedInUser() 0 4 1
A getLoggedInUser() 0 12 3
A getLoggedInUserGuid() 0 4 2
A isAdminLoggedIn() 0 5 2
A isLoggedIn() 1 4 3
A removeLoggedInUser() 0 4 1
A getIgnoreAccess() 0 3 1
A setIgnoreAccess() 0 8 1
A get_ignore_access() 0 3 1
A set_ignore_access() 0 3 1
A generateSessionToken() 0 6 2
A __isset() 0 5 1
A offsetSet() 0 4 1
B offsetGet() 0 36 9
A offsetUnset() 0 4 1
A offsetExists() 0 19 4
A getMock() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ElggSession often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ElggSession, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use Symfony\Component\HttpFoundation\Session\SessionInterface;
4
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
5
use Symfony\Component\HttpFoundation\Session\Session;
6
7 1
if (getenv('SOLR_CRAWLER') != '' && $_SERVER['HTTP_USER_AGENT'] === getenv('SOLR_CRAWLER')){
8
	class crawler_user {
9
		private $mock_false = array( 'isAdmin', 'canWriteToContainer', 'isBanned', 'canEdit' );
10
11
		public $user_type = 'bot';
12
13
		public function __call($method, $args)
14
		{
15
			if (isset($this->$method)) {
16
				$func = $this->$method;
17
				return call_user_func_array($func, $args);
18
			}
19
			else if ( in_array( $method, $this->mock_false) ) { return false; }
20
			else error_log("FUNCTION:   $method");
21
		}
22
	}
23
}
24
25
/**
26
 * Elgg Session Management
27
 *
28
 * Reserved keys: last_forward_from, msg, sticky_forms, user, guid, id, code, name, username
29
 * Deprecated keys: user, id, name, username
30
 * 
31
 * \ArrayAccess was deprecated in Elgg 1.9. This means you should use 
32
 * $session->get('foo') rather than $session['foo'].
33
 * Warning: You can not access multidimensional arrays through \ArrayAccess like
34
 * this $session['foo']['bar']
35
 *
36
 * @package    Elgg.Core
37
 * @subpackage Session
38
 * @see        elgg_get_session()
39
 */
40
class ElggSession implements \ArrayAccess {
41
42
	/**
43
	 * @var SessionInterface
44
	 */
45
	protected $storage;
46
47
	/**
48
	 * @var \ElggUser|null
49
	 */
50
	protected $logged_in_user;
51
52
	/**
53
	 * @var bool
54
	 */
55
	protected $ignore_access = false;
56
57
	/**
58
	 * Constructor
59
	 *
60
	 * @param SessionInterface $storage The underlying Session implementation
61
	 * @access private Use elgg_get_session()
62
	 */
63 79
	public function __construct(SessionInterface $storage) {
64 79
		$this->storage = $storage;
65 79
	}
66
67
	/**
68
	 * Start the session
69
	 *
70
	 * @return boolean
71
	 * @throws RuntimeException If session fails to start.
72
	 * @since 1.9
73
	 */
74 3
	public function start() {
75 3
		$result = $this->storage->start();
76 3
		$this->generateSessionToken();
77 3
		return $result;
78
	}
79
80
	/**
81
	 * Migrates the session to a new session id while maintaining session attributes
82
	 *
83
	 * @param boolean $destroy Whether to delete the session or let gc handle clean up
84
	 * @return boolean
85
	 * @since 1.9
86
	 */
87 2
	public function migrate($destroy = false) {
88 2
		return $this->storage->migrate($destroy);
89
	}
90
91
	/**
92
	 * Invalidates the session
93
	 *
94
	 * Deletes session data and session persistence. Starts a new session.
95
	 *
96
	 * @return boolean
97
	 * @since 1.9
98
	 */
99 1
	public function invalidate() {
100 1
		$this->storage->clear();
101 1
		$this->logged_in_user = null;
102 1
		$result = $this->migrate(true);
103 1
		$this->generateSessionToken();
104 1
		return $result;
105
	}
106
107
	/**
108
	 * Has the session been started
109
	 *
110
	 * @return boolean
111
	 * @since 1.9
112
	 */
113
	public function isStarted() {
114
		return $this->storage->isStarted();
115
	}
116
117
	/**
118
	 * Get the session ID
119
	 *
120
	 * @return string
121
	 * @since 1.9
122
	 */
123 2
	public function getId() {
124 2
		return $this->storage->getId();
125
	}
126
127
	/**
128
	 * Set the session ID
129
	 *
130
	 * @param string $id Session ID
131
	 * @return void
132
	 * @since 1.9
133
	 */
134
	public function setId($id) {
135
		$this->storage->setId($id);
136
	}
137
138
	/**
139
	 * Get the session name
140
	 *
141
	 * @return string
142
	 * @since 1.9
143
	 */
144
	public function getName() {
145
		return $this->storage->getName();
146
	}
147
148
	/**
149
	 * Set the session name
150
	 *
151
	 * @param string $name Session name
152
	 * @return void
153
	 * @since 1.9
154
	 */
155
	public function setName($name) {
156
		$this->storage->setName($name);
157
	}
158
159
	/**
160
	 * Get an attribute of the session
161
	 *
162
	 * @param string $name    Name of the attribute to get
163
	 * @param mixed  $default Value to return if attribute is not set (default is null)
164
	 * @return mixed
165
	 */
166 13
	public function get($name, $default = null) {
167 13
		return $this->storage->get($name, $default);
168
	}
169
170
	/**
171
	 * Set an attribute
172
	 *
173
	 * @param string $name  Name of the attribute to set
174
	 * @param mixed  $value Value to be set
175
	 * @return void
176
	 */
177 11
	public function set($name, $value) {
178 11
		$this->storage->set($name, $value);
179 11
	}
180
181
	/**
182
	 * Remove an attribute
183
	 *
184
	 * @param string $name The name of the attribute to remove
185
	 * @return mixed The removed attribute
186
	 * @since 1.9
187
	 */
188 2
	public function remove($name) {
189 2
		return $this->storage->remove($name);
190
	}
191
192
	/**
193
	 * Alias to offsetUnset()
194
	 *
195
	 * @param string $key Name
196
	 * @return void
197
	 * @deprecated 1.9 Use remove()
198
	 */
199
	public function del($key) {
200
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
201
		$this->remove($key);
202
	}
203
204
	/**
205
	 * Has the attribute been defined
206
	 *
207
	 * @param string $name Name of the attribute
208
	 * @return bool
209
	 * @since 1.9
210
	 */
211 3
	public function has($name) {
212 3
		return $this->storage->has($name);
213
	}
214
215
	/**
216
	 * Sets the logged in user
217
	 * 
218
	 * @param \ElggUser $user The user who is logged in
219
	 * @return void
220
	 * @since 1.9
221
	 */
222
	public function setLoggedInUser(\ElggUser $user) {
223
		$this->set('guid', $user->guid);
224
		$this->logged_in_user = $user;
225
	}
226
227
	/**
228
	 * Gets the logged in user
229
	 * 
230
	 * @return \ElggUser
231
	 * @since 1.9
232
	 */
233 37
	public function getLoggedInUser() {
0 ignored issues
show
Coding Style introduced by
getLoggedInUser uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
234 37
		if (getenv('SOLR_CRAWLER') != '' && $_SERVER['HTTP_USER_AGENT'] === getenv('SOLR_CRAWLER')){
235
			// create a mock user for the crawler
236
			$solr_user = new crawler_user();
237
			$solr_user->guid = getenv('SOLR_CRAWLER_USER');
0 ignored issues
show
Bug introduced by
The property guid does not seem to exist in crawler_user.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
238
			$solr_user->getGUID = function(){ return getenv('SOLR_CRAWLER_USER');};
0 ignored issues
show
Bug introduced by
The property getGUID does not seem to exist in crawler_user.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
239
240
			$this->set('guid', getenv('SOLR_CRAWLER_USER'));
241
			$this->logged_in_user = $solr_user;
0 ignored issues
show
Documentation Bug introduced by
It seems like $solr_user of type object<crawler_user> is incompatible with the declared type object<ElggUser>|null of property $logged_in_user.

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...
242
		}
243 37
		return $this->logged_in_user;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->logged_in_user; of type ElggUser|null|crawler_user adds the type crawler_user to the return on line 243 which is incompatible with the return type documented by ElggSession::getLoggedInUser of type ElggUser|null.
Loading history...
244
	}
245
246
	/**
247
	 * Return the current logged in user by guid.
248
	 *
249
	 * @see elgg_get_logged_in_user_entity()
250
	 * @return int
251
	 */
252 32
	public function getLoggedInUserGuid() {
253 32
		$user = $this->getLoggedInUser();
254 32
		return $user ? $user->guid : 0;
255
	}
256
	
257
	/**
258
	 * Returns whether or not the viewer is currently logged in and an admin user.
259
	 *
260
	 * @return bool
261
	 */
262
	public function isAdminLoggedIn() {
263
		$user = $this->getLoggedInUser();
264
	
265
		return $user && $user->isAdmin();
266
	}
267
	
268
	/**
269
	 * Returns whether or not the user is currently logged in
270
	 *
271
	 * @return bool
272
	 */
273
	public function isLoggedIn() {
0 ignored issues
show
Coding Style introduced by
isLoggedIn uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
274 View Code Duplication
		if (getenv('SOLR_CRAWLER') != '' && $_SERVER['HTTP_USER_AGENT'] === getenv('SOLR_CRAWLER')) return true;
275
		return (bool)$this->getLoggedInUser();
276
	}
277
278
	/**
279
	 * Remove the logged in user
280
	 * 
281
	 * @return void
282
	 * @since 1.9
283
	 */
284
	public function removeLoggedInUser() {
285
		$this->logged_in_user = null;
286
		$this->remove('guid');
287
	}
288
289
	/**
290
	 * Get current ignore access setting.
291
	 *
292
	 * @return bool
293
	 */
294
	public function getIgnoreAccess() {
295
		return $this->ignore_access;
296
	}
297
298
	/**
299
	 * Set ignore access.
300
	 *
301
	 * @param bool $ignore Ignore access
302
	 *
303
	 * @return bool Previous setting
304
	 */
305 3
	public function setIgnoreAccess($ignore = true) {
306 3
		_elgg_services()->accessCache->clear();
307
308 3
		$prev = $this->ignore_access;
309 3
		$this->ignore_access = $ignore;
310
311 3
		return $prev;
312
	}
313
314
	// @codingStandardsIgnoreStart
315
	/**
316
	 * Alias of getIgnoreAccess()
317
	 *
318
	 * @todo remove with elgg_get_access_object()
319
	 *
320
	 * @return bool
321
	 * @deprecated 1.8 Use elgg_get_ignore_access()
322
	 */
323
	public function get_ignore_access() {
324
		return $this->getIgnoreAccess();
325
	}
326
	// @codingStandardsIgnoreEnd
327
328
	// @codingStandardsIgnoreStart
329
	/**
330
	 * Alias of setIgnoreAccess()
331
	 *
332
	 * @todo remove with elgg_get_access_object()
333
	 *
334
	 * @param bool $ignore Ignore access
335
	 *
336
	 * @return bool Previous setting
337
	 *
338
	 * @deprecated 1.8 Use elgg_set_ignore_access()
339
	 */
340
	public function set_ignore_access($ignore = true) {
341
		return $this->setIgnoreAccess($ignore);
342
	}
343
	// @codingStandardsIgnoreEnd
344
345
	/**
346
	 * Adds a token to the session
347
	 * 
348
	 * This is used in creation of CSRF token, and is passed to the client to allow validating tokens
349
	 * later, even if the PHP session was destroyed.
350
	 * 
351
	 * @return void
352
	 */
353 3
	protected function generateSessionToken() {
354
		// Generate a simple token that we store server side
355 3
		if (!$this->has('__elgg_session')) {
356 3
			$this->set('__elgg_session', _elgg_services()->crypto->getRandomString(22));
357 3
		}
358 3
	}
359
360
	/**
361
	 * Test if property is set either as an attribute or metadata.
362
	 *
363
	 * @param string $key The name of the attribute or metadata.
364
	 *
365
	 * @return bool
366
	 * @deprecated 1.9 Use has()
367
	 */
368
	public function __isset($key) {
369
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
370
		// Note: We use offsetExists() for BC
371
		return $this->offsetExists($key);
0 ignored issues
show
Deprecated Code introduced by
The method ElggSession::offsetExists() has been deprecated with message: 1.9 Use has()

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...
372
	}
373
374
	/**
375
	 * Set a value, go straight to session.
376
	 *
377
	 * @param string $key   Name
378
	 * @param mixed  $value Value
379
	 *
380
	 * @return void
381
	 * @deprecated 1.9 Use set()
382
	 */
383
	public function offsetSet($key, $value) {
384
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
385
		$this->set($key, $value);
386
	}
387
388
	/**
389
	 * Get a variable from either the session, or if its not in the session
390
	 * attempt to get it from an api call.
391
	 *
392
	 * @see \ArrayAccess::offsetGet()
393
	 *
394
	 * @param mixed $key Name
395
	 *
396
	 * @return mixed
397
	 * @deprecated 1.9 Use get()
398
	 */
399
	public function offsetGet($key) {
400
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
401
402
		if (in_array($key, array('user', 'id', 'name', 'username'))) {
403
			elgg_deprecated_notice("Only 'guid' is stored in session for user now", 1.9);
404
			if ($this->logged_in_user) {
405
				switch ($key) {
406
					case 'user':
407
						return $this->logged_in_user;
408
						break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
409
					case 'id':
410
						return $this->logged_in_user->guid;
411
						break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
412
					case 'name':
413
					case 'username':
414
						return $this->logged_in_user->$key;
415
						break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
416
				}
417
			} else {
418
				return null;
419
			}
420
		}
421
422
		if ($this->has($key)) {
423
			return $this->get($key);
424
		}
425
426
		$orig_value = null;
427
		$value = _elgg_services()->hooks->trigger('session:get', $key, null, $orig_value);
428
		if ($orig_value !== $value) {
429
			elgg_deprecated_notice("Plugin hook session:get has been deprecated.", 1.9);
430
		}
431
432
		$this->set($key, $value);
433
		return $value;
434
	}
435
436
	/**
437
	 * Unset a value from the cache and the session.
438
	 *
439
	 * @see \ArrayAccess::offsetUnset()
440
	 *
441
	 * @param mixed $key Name
442
	 *
443
	 * @return void
444
	 * @deprecated 1.9 Use remove()
445
	 */
446
	public function offsetUnset($key) {
447
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
448
		$this->remove($key);
449
	}
450
451
	/**
452
	 * Return whether the value is set in either the session or the cache.
453
	 *
454
	 * @see \ArrayAccess::offsetExists()
455
	 *
456
	 * @param int $offset Offset
457
	 *
458
	 * @return bool
459
	 * @deprecated 1.9 Use has()
460
	 */
461
	public function offsetExists($offset) {
462
		elgg_deprecated_notice(__METHOD__ . " has been deprecated.", 1.9);
463
464
		if (in_array($offset, array('user', 'id', 'name', 'username'))) {
465
			elgg_deprecated_notice("Only 'guid' is stored in session for user now", 1.9);
466
			return (bool)$this->logged_in_user;
467
		}
468
469
		if ($this->has($offset)) {
470
			return true;
471
		}
472
473
		// Note: We use offsetGet() for BC
474
		if ($this->offsetGet($offset)) {
0 ignored issues
show
Deprecated Code introduced by
The method ElggSession::offsetGet() has been deprecated with message: 1.9 Use get()

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...
475
			return true;
476
		}
477
478
		return false;
479
	}
480
481
	/**
482
	 * Get an isolated ElggSession that does not persist between requests
483
	 *
484
	 * @return self
485
	 */
486 79
	public static function getMock() {
487 79
		$storage = new MockArraySessionStorage();
488 79
		$session = new Session($storage);
489 79
		return new self($session);
490
	}
491
}
492