UploadUsers::setUpdateExistingUsersFlag()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Upload users helper class
5
 *
6
 * @package upload_users
7
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
8
 * @author Jaakko Naakka / Mediamaisteri Group
9
 * @author Ismayil Khayredinov / Arck Interactive
10
 * @copyright Mediamaisteri Group 2009
11
 * @copyright ArckInteractive 2013
12
 * @link http://www.mediamaisteri.com/
13
 * @link http://arckinteractive.com/
14
 */
15
class UploadUsers {
16
17
	var $records;
18
	var $mapped_headers;
19
	var $mapped_required_fields;
20
	var $notification = false;
21
	var $update_existing_users = false;
22
	var $fix_usernames = false;
23
	var $fix_passwords = false;
24
25
	function __construct() {
26
		$this->records = new stdClass();
27
	}
28
29
	/**
30
	 * Set alternative mapping of CSV headers to Elgg metadata
31
	 *
32
	 * @param $mapping array An array of $key => $value pairs, where $key is the CSV header and $value is an Elgg metadata mapped to it
33
	 */
34
	function setHeaderMapping($mapping) {
35
		$this->mapped_headers = $mapping;
36
	}
37
38
	/**
39
	 * Set alternative mapping of required user entity attributes to CSV headers
40
	 *
41
	 * @param array $mapping An array of $key => $value pairs, where $key is username, name or email and $value is an array of CSV headers which will be used to concatinate the value
42
	 */
43
	function setRequiredFieldMapping($mapping) {
44
		$this->mapped_required_fields = $mapping;
45
	}
46
47
	/**
48
	 * Send an email notification to new user accounts
49
	 *
50
	 * @param $flag boolean
51
	 */
52
	function setNotificationFlag($flag) {
53
		$this->notification = $flag;
54
	}
55
56
	/**
57
	 * Update user profile information if user account already exists
58
	 *
59
	 * @param $flag boolean
60
	 */
61
	function setUpdateExistingUsersFlag($flag) {
62
		$this->update_existing_users = $flag;
63
	}
64
65
	/**
66
	 * Fix usernames to meet Elgg requirements
67
	 *
68
	 * @param $flag boolean
69
	 */
70
	function setFixUsernamesFlag($flag) {
71
		$this->fix_usernames = $flag;
72
	}
73
74
	/**
75
	 * Fix passwords to meet Elgg requirements
76
	 *
77
	 * @param $flag boolean
78
	 */
79
	function setFixPasswordsFlag($flag) {
80
		$this->fix_passwords = $flag;
81
	}
82
83
	/**
84
	 * Add unmapped user data
85
	 *
86
	 * @param array $data
87
	 */
88
	function setRecords($data) {
89
		$this->records->source = $data;
90
	}
91
92
	/**
93
	 * Map user records
94
	 *
95
	 * @param $data
96
	 * @return boolean
97
	 */
98
	function mapRecords($data = null) {
99
100
		if (!$this->records->source) {
101
			$this->setRecords($data);
102
		}
103
104
		$this->records->mapped = array();
105
106
		foreach ($this->records->source as $record) {
107
			$this->records->mapped[] = $this->mapRecord($record);
108
		}
109
	}
110
111
	/**
112
	 * Map an individual user record
113
	 *
114
	 * @param array $record
115
	 * @return array
116
	 */
117
	function mapRecord($record) {
118
119
		// Set required keys
120
		$mapped_record = array(
121
			'__upload_users_status' => array(),
122
			'__upload_users_messages' => array(),
123
			'guid' => null,
124
			'email' => '',
125
			'username' => '',
126
			'name' => '',
127
			'password' => '',
128
		);
129
130
		// Map values
131
		$headers = $this->mapped_headers;
132
		if ($headers) {
133
			foreach ($headers as $original_header => $new_header) {
134
				if (is_array($new_header)) {
135
					$new_header = $new_header['metadata_name'];
136
				}
137
				if (!isset($mapped_record[$new_header])) {
138
					$mapped_record[$new_header] = $record[$original_header];
139
				}
140
			}
141
		} else {
142
			foreach ($record as $key => $value) {
143
				$mapped_record[$key] = $value;
144
			}
145
		}
146
147
		// Map required values
148
		$mapped_record['username'] = $this->getUsername($record);
149
		$mapped_record['name'] = $this->getName($record);
150
		$mapped_record['email'] = $this->getEmail($record);
151
		$mapped_record['password'] = $this->getPassword($record);
152
153
		return $mapped_record;
154
	}
155
156
	/**
157
	 * Validate records and create update and create queues
158
	 *
159
	 * @param mixed $data
160
	 */
161
	function queueRecords($data = null) {
162
163
		if (!$this->records->mapped) {
164
			$this->mapRecords($data);
165
		}
166
167
		$this->records->queue = array();
168
169
		foreach ($this->records->mapped as $record) {
170
171
			$create = true;
172
			$update = false;
173
			$messages = array();
174
175
			// First check if the user already exists
176
			if ($record['guid']) {
177
				$create = false;
178
179
				$record_entity = get_entity($record['guid']);
180
				if (elgg_instanceof($record_entity, 'user')) {
181
					$messages[] = elgg_echo('upload_users:error:userexists');
182
					if ($this->update_existing_users) {
183
						$update = true;
184
					}
185
				} else if ($this->update_existing_users) {
186
					$messages[] = elgg_echo('upload_users:error:invalid_guid');
187
				}
188
			} else {
189
190
				try {
191
					validate_email_address($record['email']);
192
193
					$record_by_username = get_user_by_username($record['username']);
194
					$record_by_email = get_user_by_email($record['email']);
195
196
					if (elgg_instanceof($record_by_username, 'user') || elgg_instanceof($record_by_email[0], 'user')) {
197
						$create = false;
198
						if ($record_by_username->guid != $record_by_email[0]->guid) {
199
							if ($this->fix_usernames && !$this->update_existing_users) {
200
								$create = true;
201
								while (get_user_by_username($record['username'])) {
202
									$record['username'] = $record['username'] . rand(1000, 9999);
203
								}
204
							} else {
205
								$messages[] = elgg_echo('upload_users:error:update_email_username_mismatch'); // username does not match with the email we have in the database
206
							}
207
						} else {
208
							$messages[] = elgg_echo('upload_users:error:userexists');
209
							if ($this->update_existing_users) {
210
								$record['guid'] = $record_by_username->guid;
211
								$update = true;
212
							}
213
						}
214
					}
215
				} catch (RegistrationException $r) {
216
					$create = false;
217
					$messages[] = $r->getMessage();
218
				}
219
			}
220
221
			// No existing accounts found; validate details for registration
222
			if ($create) {
223
				if (!$record['name']) {
224
					$create = false;
225
					$messages[] = elgg_echo('upload_users:error:empty_name');
226
				}
227
228
				try {
229
					validate_username($record['username']);
230
				} catch (RegistrationException $r) {
231
					$create = false;
232
					$messages[] = $r->getMessage();
233
				}
234
235
				if ($record['password']) {
236
					try {
237
						validate_password($record['password']);
238
					} catch (RegistrationException $r) {
239
						$create = false;
240
						$messages[] = $r->getMessage();
241
					}
242
				}
243
			}
244
245
			$record['__upload_users_messages'] = $messages;
246
			$record['__upload_users_status'] = false;
247
248
			if ($create || $update) {
249
				$record['__upload_users_status'] = true;
250
			}
251
252
			$this->records->queue[] = $record;
253
		}
254
	}
255
256
	/**
257
	 * Process queue
258
	 *
259
	 * @param mixed $data
260
	 */
261
	function processRecords($data = null) {
262
		if (!$this->records->queue) {
263
			$this->queueRecords($data);
264
		}
265
266
		foreach ($this->records->queue as $record) {
267
			if ($record['__upload_users_status']) {
268
				$record = $this->uploadUser($record);
269
			}
270
271
			if ($record['__upload_users_status']) {
272
				$record['__upload_users_status'] = 'complete';
273
			} else {
274
				$record['__upload_users_status'] = 'error';
275
			}
276
277
			$record['__upload_users_messages'] = implode("\n", $record["__upload_users_messages"]);
278
279
			$this->record->uploaded[] = $record;
0 ignored issues
show
Bug introduced by
The property record does not seem to exist. Did you mean records?

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...
280
		}
281
282
		return $this->record->uploaded;
0 ignored issues
show
Bug introduced by
The property record does not seem to exist. Did you mean records?

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...
283
	}
284
285
	/**
286
	 * Create a new user or update an existing user from record
287
	 * 
288
	 * @param array $record User record 
289
	 * @return array User record with status report
290
	 */
291
	function uploadUser($record) {
292
293
294
		if (!$record['guid']) {
295
296
			// No user, try registering
297
			try {
298
299
				if (!$record['password']) {
300
					$record['password'] = generate_random_cleartext_password();
301
				}
302
303
				$record['guid'] = register_user($record['username'], $record['password'], $record['name'], $record['email']);
304
305
				$user = get_entity($record['guid']);
0 ignored issues
show
Bug introduced by
It seems like $record['guid'] can also be of type false or null; however, get_entity() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
306
307
				set_user_validation_status($record['guid'], true, 'upload_users');
308
309
				$hook_params = $record;
310
				$hook_params['user'] = $user;
311
312 View Code Duplication
				if (!elgg_trigger_plugin_hook('register', 'user', $hook_params, TRUE)) {
313
					$ia = elgg_set_ignore_access(true);
314
					$user->delete();
315
					elgg_set_ignore_access($ia);
316
					throw new RegistrationException(elgg_echo('registerbad'));
317
				}
318
319
				if ($this->notification) {
320
					$subject = elgg_echo('upload_users:email:subject', elgg_get_config('sitename'));
321
					$message = elgg_echo('upload_users:email:message', array($record['name'], elgg_get_config('sitename'), $record['username'], $record['password'], elgg_get_site_url()));
322
					notify_user($record['guid'], elgg_get_site_entity()->guid, $subject, $message);
323
				}
324
			} catch (RegistrationException $e) {
325
				$record['__upload_users_status'] = false;
326
				$record['__upload_users_messages'][] = $e->getMessage();
327
			}
328
		} else {
329
			$user = get_entity($record['guid']);
330
		}
331
332
		if (!elgg_instanceof($user, 'user')) {
0 ignored issues
show
Bug introduced by
The variable $user 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...
333
			$record['__upload_users_status'] = false;
334
			return $record;
335
		}
336
337
		foreach ($record as $metadata_name => $metadata_value) {
338
339
			switch ($metadata_name) {
340
341
				case '__upload_users_status' :
342
				case '__upload_users_messages' :
343
				case 'guid' :
344
				case 'username' :
345
				case 'password' :
346
				case 'name' :
347
				case 'email' :
348
					continue;
349
					break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
350
351
				default :
352
353
					$metadata_value = trim($metadata_value);
354
355
					$header = $this->getHeaderForMetadataName($metadata_name);
356
357
					$hook_params = array(
358
						'header' => $header,
359
						'metadata_name' => $metadata_name,
360
						'value' => $metadata_value,
361
						'record' => $record,
362
						'user' => $user
363
					);
364
365
					if (elgg_trigger_plugin_hook('header:custom_method', 'upload_users', $hook_params, false)) {
366
						continue;
367
					}
368
369
					$access_id = $this->getHeaderAccess($header);
370
					$value_type = $this->getHeaderValueType($header);
371
372
					switch ($value_type) {
373
374
						default :
375
						case 'text' :
376
							// keep the value
377
							break;
378
379
						case 'tags' :
380
							$metadata_value = string_to_tag_array($metadata_value);
381
							break;
382
383
						case 'timestamp' :
384
							$metadata_value = strtotime($metadata_value);
385
							break;
386
					}
387
388
					$md_report = array();
389
390
					if (is_array($metadata_value)) {
391
						foreach ($metadata_value as $value) {
392
							$value = trim($value);
393 View Code Duplication
							if (create_metadata($user->guid, $metadata_name, trim($value), '', $user->guid, $access_id, true)) {
394
								$md_report[] = $value;
395
							}
396
						}
397 View Code Duplication
					} else {
398
						if (create_metadata($user->guid, $metadata_name, $metadata_value, '', $user->guid, $access_id)) {
399
							$md_report[] = $metadata_value;
400
						}
401
					}
402
403
					$metadata_value = implode(',', $md_report);
404
405
					elgg_log("Upload users: creating $metadata_name with value $metadata_value (as $value_type) and access_id $access_id for user $user->guid");
406
					$record[$metadata_name] = $metadata_value;
407
408
					break;
409
			}
410
		}
411
412
		return $record;
413
	}
414
415
	/**
416
	 * Reverse CSV header lookup
417
	 *
418
	 * @param string $metadata_name
419
	 * @return string header name
420
	 */
421
	function getHeaderForMetadataName($metadata_name) {
422
		if ($this->mapped_headers) {
423
			foreach ($this->mapped_headers as $mapped_header => $mapping) {
424
				if ($mapping == $metadata_name || $mapping['metadata_name'] == $metadata_name) {
425
					return $mapped_header;
426
				}
427
			}
428
		}
429
		return $metadata_name;
430
	}
431
432
	/**
433
	 * Get header access id from mapping
434
	 *
435
	 * @param string $header
436
	 * @return int Access id
437
	 */
438
	function getHeaderAccess($header) {
439
440
		$access_id = $this->mapped_headers[$header]['access_id'];
441
		if (is_null($access_id)) {
442
			$access_id = ACCESS_PRIVATE;
443
		}
444
445
		return $access_id;
446
	}
447
448
	/**
449
	 * Get header value type
450
	 *
451
	 * @param string $header
452
	 * @return boolean
453
	 */
454
	function getHeaderValueType($header) {
455
456
		$value_type = $this->mapped_headers[$header]['value_type'];
457
		return ($value_type) ? $value_type : 'text';
458
	}
459
460
	/**
461
	 * Get username from mapping
462
	 *
463
	 * @param array $record
464
	 * @return string
465
	 */
466
	function getUsername($record) {
467
468
		$required_fields = $this->mapped_required_fields;
469
		$components = $required_fields['username'];
470
471
		if (!$required_fields || !is_array($components)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $required_fields 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...
472
			return $record['username'];
473
		}
474
475
		$value = array();
476
		foreach ($components as $header) {
477
			$value[] = strtolower(trim($record[$header]));
478
		}
479
480
		$value = implode('.', $value);
481
482
		if ($this->fix_usernames) {
483
			$value = iconv('UTF-8', 'ASCII//TRANSLIT', $value);
484
			$blacklist = '/[\x{0080}-\x{009f}\x{00a0}\x{2000}-\x{200f}\x{2028}-\x{202f}\x{3000}\x{e000}-\x{f8ff}]/u';
485
			$blacklist2 = array(' ', '\'', '/', '\\', '"', '*', '&', '?', '#', '%', '^', '(', ')', '{', '}', '[', ']', '~', '?', '<', '>', ';', '|', '¬', '`', '@', '-', '+', '=');
486
			$value = preg_replace($blacklist, '', $value);
487
			$value = str_replace($blacklist2, '.', $value);
488
		}
489
490
		return $value;
491
	}
492
493
	/**
494
	 * Get name from mapping
495
	 *
496
	 * @param array $record
497
	 * @return string
498
	 */
499
	function getName($record) {
500
501
		$required_fields = $this->mapped_required_fields;
502
		$components = $required_fields['name'];
503
504
		if (!$required_fields || !is_array($components)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $required_fields 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...
505
			return $record['name'];
506
		}
507
508
		$value = array();
509
		foreach ($components as $header) {
510
			$value[] = trim($record[$header]);
511
		}
512
513
		if (count($value) > 1) {
514
			$value = implode(' ', $value);
515
			$value = ucwords(strtolower($value));
516
		} else {
517
			$value = implode(' ', $value);
518
		}
519
		return $value;
520
	}
521
522
	/**
523
	 * Get email from mapping
524
	 *
525
	 * @param array $record
526
	 * @return string
527
	 */
528
	function getEmail($record) {
529
530
		$required_fields = $this->mapped_required_fields;
531
		$component = $required_fields['email'];
532
533
		if (!$required_fields || !$component) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $required_fields 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...
534
			return $record['email'];
535
		}
536
		if (is_array($component)) {
537
			$component = $component[0];
538
		}
539
540
		return trim($record[$component]);
541
	}
542
543
	/**
544
	 * Get password from mapping
545
	 *
546
	 * @param array $record
547
	 * @return string
548
	 */
549
	function getPassword($record) {
550
551
		$required_fields = $this->mapped_required_fields;
552
		$component = $required_fields['password'];
553
554
		if (!$required_fields || !is_array($component)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $required_fields 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...
555
			return $record['password'];
556
		}
557
		if (is_array($component)) {
558
			$component = $component[0];
559
		}
560
561
		$value = trim($record[$component]);
562
563
		if ($value && $this->fix_passwords) {
564
			$minlength = elgg_get_config('min_password_length');
565
			if (strlen($value) < $minlength) {
566
				$value = '';
567
			}
568
		}
569
570
		return $value;
571
	}
572
573
}
574
575