Completed
Push — master ( caf222...deba87 )
by Jeroen
72:32 queued 44:47
created

engine/classes/Elgg/Database/Seeds/Seeding.php (2 issues)

1
<?php
2
/**
3
 *
4
 */
5
6
namespace Elgg\Database\Seeds;
7
8
use ElggEntity;
9
use ElggGroup;
10
use ElggObject;
11
use ElggUser;
12
use Exception;
13
use Faker\Factory;
14
15
/**
16
 * Seeding trait
17
 * Can be used to easily create new random users, groups and objects in the database
18
 *
19
 * @access private
20
 */
21
trait Seeding {
22
23
	/**
24
	 * @var int Max number of items to be created by the seed
25
	 */
26
	protected $limit = 20;
27
28
	/**
29
	 * @var \Faker\Generator
30
	 */
31
	protected $faker;
32
33
	/**
34
	 * Returns an instance of faker
35
	 *
36
	 * @param string $locale Locale
37
	 *
38
	 * @return \Faker\Generator
39
	 */
40 98
	public function faker($locale = 'en_US') {
41 98
		if (!isset($this->faker)) {
42 98
			$this->faker = Factory::create($locale);
43
		}
44
45 98
		return $this->faker;
46
	}
47
48
	/**
49
	 * Get site domain
50
	 * @return string
51
	 */
52
	public function getDomain() {
53
		return elgg_get_site_entity()->getDomain();
54
	}
55
56
	/**
57
	 * Get valid domain for emails
58
	 * @return string
59
	 */
60 63
	public function getEmailDomain() {
61 63
		$email = elgg_get_site_entity()->email;
62 63
		if (!$email) {
63
			$email = "noreply@{$this->getDomain()}";
64
		}
65
66 63
		list(, $domain) = explode('@', $email);
67
68 63
		if (sizeof(explode('.', $domain)) <= 1) {
69
			$domain = 'example.net';
70
		}
71
72 63
		return $domain;
73
	}
74
75
	/**
76
	 * Returns random unique subtype
77
	 * @return bool|string
78
	 */
79 124
	public function getRandomSubtype() {
80 124
		return substr(sha1(microtime() . rand()), 0, 25);
81
	}
82
83
	/**
84
	 * Create a new fake user
85
	 *
86
	 * @param array $attributes User entity attributes
87
	 * @param array $metadata   User entity metadata
88
	 * @param array $options    Seeding options
89
	 *
90
	 * @return ElggUser
91
	 */
92 63
	public function createUser(array $attributes = [], array $metadata = [], array $options = []) {
93
94
		$create = function () use ($attributes, $metadata, $options) {
95 63
			$metadata['__faker'] = true;
96
97 63
			if (empty($metadata['password'])) {
98 63
				$metadata['password'] = generate_random_cleartext_password();
99
			}
100
101 63
			if (empty($metadata['name'])) {
102 63
				$metadata['name'] = $this->faker()->name;
103
			}
104
105 63
			if (empty($metadata['username'])) {
106 63
				$metadata['username'] = $this->getRandomUsername($metadata['name']);
107
			}
108
109 63
			if (empty($metadata['email'])) {
110 63
				$metadata['email'] = "{$metadata['username']}@{$this->getEmailDomain()}";
111
			}
112
113 63
			if (empty($attributes['subtype'])) {
114 4
				$attributes['subtype'] = null;
115
			}
116
117 63
			$user = false;
118
119
			try {
120 63
				$guid = register_user($metadata['username'], $metadata['password'], $metadata['name'], $metadata['email'], false, $attributes['subtype']);
121
				
122 63
				$user = get_entity($guid);
123
				/* @var $user ElggUser */
124
125 63
				if (!$user) {
126
					throw new Exception("Unable to create new user with attributes: " . print_r($attributes, true));
127
				}
128
129 63
				if (isset($metadata['admin'])) {
130 1
					if ($metadata['admin']) {
131 1
						$user->makeAdmin();
132
					} else {
133
						$user->removeAdmin();
134
					}
135
				}
136
137 63
				if (isset($metadata['banned'])) {
138
					if ($metadata['banned']) {
139
						$user->ban('Banned by seeder');
140
					} else {
141
						$user->unban('Unbanned by seeder');
142
					}
143
				}
144
								
145 63
				unset($metadata['username']);
146 63
				unset($metadata['password']);
147 63
				unset($metadata['name']);
148 63
				unset($metadata['email']);
149 63
				unset($metadata['banned']);
150 63
				unset($metadata['admin']);
151
152 63
				$user->setValidationStatus($this->faker()->boolean(), 'seeder');
153
				
154 63
				$user->setNotificationSetting('email', false);
155 63
				$user->setNotificationSetting('site', true);
156
157 63
				$profile_fields = elgg_extract('profile_fields', $options, []);
158
159 63
				$user = $this->populateMetadata($user, $profile_fields, $metadata);
160
161 63
				$user->save();
162
163 63
				$this->log("Created new user $user->name [guid: $user->guid]");
164
165 63
				return $user;
166
			} catch (\RegistrationException $e) {
167
				if ($user && $user->guid) {
168
					$user->delete();
169
				}
170
171
				$attr_log = print_r($attributes, true);
172
				$this->log("User creation failed with message {$e->getMessage()} [attributes: $attr_log]");
173
174
				return false;
175
			}
176 63
		};
177
178 63
		$ia = elgg_set_ignore_access(true);
179
180 63
		$user = false;
181 63
		while (!$user instanceof \ElggUser) {
182 63
			$user = $create();
183
		}
184
185 63
		elgg_set_ignore_access($ia);
186
187 63
		return $user;
188
189
	}
190
191
	/**
192
	 * Create a new fake group
193
	 *
194
	 * @param array $attributes Group entity attributes
195
	 * @param array $metadata   Group entity metadata
196
	 * @param array $options    Additional options
197
	 *
198
	 * @return ElggGroup
199
	 */
200 9
	public function createGroup(array $attributes = [], array $metadata = [], array $options = []) {
201
202
		$create = function () use ($attributes, $metadata, $options) {
203 9
			$metadata['__faker'] = true;
204
205 9
			if (empty($attributes['access_id'])) {
206 9
				$attributes['access_id'] = ACCESS_PUBLIC;
207
			}
208
209 9
			if (empty($metadata['content_access_mode'])) {
210 9
				$metadata['content_access_mode'] = ElggGroup::CONTENT_ACCESS_MODE_UNRESTRICTED;
211
			}
212
213 9
			if (empty($metadata['membership'])) {
214 9
				$metadata['membership'] = ACCESS_PUBLIC;
215
			}
216
217 9
			if (empty($metadata['name'])) {
218 9
				$metadata['name'] = $this->faker()->sentence();
219
			}
220
221 9 View Code Duplication
			if (empty($metadata['description'])) {
222 9
				$metadata['description'] = $this->faker()->text($this->faker()->numberBetween(500, 1000));
223
			}
224
225 9 View Code Duplication
			if (empty($attributes['owner_guid'])) {
226 9
				$user = elgg_get_logged_in_user_entity();
227 9
				if (!$user) {
228 1
					$user = $this->getRandomUser();
229
				}
230 9
				if (!$user) {
231
					$user = $this->createUser();
232
				}
233
234 9
				$attributes['owner_guid'] = $user->guid;
235
			}
236
237 9
			if (empty($attributes['container_guid'])) {
238 9
				$attributes['container_guid'] = $attributes['owner_guid'];
239
			}
240
241 9
			$owner = get_entity($attributes['owner_guid']);
242 9
			if (!$owner) {
243
				return false;
244
			}
245
246 9
			$container = get_entity($attributes['container_guid']);
247 9
			if (!$container) {
248
				return false;
249
			}
250
251 9
			$tool_options = elgg_extract('group_tools_options', $options, []);
252 9
			if ($tool_options) {
253
				foreach ($tool_options as $group_option) {
254
					$option_toggle_name = $group_option->name . "_enable";
255
					$option_default = $group_option->default_on ? 'yes' : 'no';
256
					$metadata[$option_toggle_name] = $option_default;
257
				}
258
			}
259
260 9
			if ($this->faker()->boolean(20)) {
261 2
				$metadata['featured_group'] = 'yes';
262
			}
263
264 9
			$group = new ElggGroup();
265 9
			foreach ($attributes as $name => $value) {
266 9
				$group->$name = $value;
267
			}
268
269 9
			$profile_fields = $profile_fields = elgg_extract('profile_fields', $options, []);
270 9
			$group = $this->populateMetadata($group, $profile_fields, $metadata);
271
272 9
			$group->save();
273
274 9
			if ($group->access_id == ACCESS_PRIVATE) {
275
				$group->access_id = $group->group_acl;
276
				$group->save();
277
			}
278
279 9
			$group->join(get_entity($attributes['owner_guid']));
280
281 9
			elgg_create_river_item([
282 9
				'view' => 'river/group/create',
283 9
				'action_type' => 'create',
284 9
				'subject_guid' => $owner->guid,
285 9
				'object_guid' => $group->guid,
286 9
				'target_guid' => $container->guid,
287
			]);
288
289 9
			$this->log("Created new group $group->name [guid: $group->guid]");
290
291 9
			return $group;
292 9
		};
293
294 9
		$ia = elgg_set_ignore_access(true);
295
296 9
		$group = false;
297 9
		while (!$group instanceof \ElggGroup) {
298 9
			$group = $create();
299
		}
300
301 9
		elgg_set_ignore_access($ia);
302
303 9
		return $group;
304
	}
305
306
	/**
307
	 * Create a new fake object
308
	 *
309
	 * @param array $attributes Object entity attributes
310
	 * @param array $metadata   Object entity metadata
311
	 * @param array $options    Additional options
312
	 *
313
	 * @return ElggObject
314
	 */
315 59
	public function createObject(array $attributes = [], array $metadata = [], array $options = []) {
316
317
		$create = function () use ($attributes, $metadata, $options) {
318 59
			$metadata['__faker'] = true;
319
320 59
			if (empty($metadata['title'])) {
321 59
				$metadata['title'] = $this->faker()->sentence();
322
			}
323
324 59 View Code Duplication
			if (empty($metadata['description'])) {
325 59
				$metadata['description'] = $this->faker()->text($this->faker()->numberBetween(500, 1000));
326
			}
327
328 59
			if (empty($attributes['subtype'])) {
329 1
				$attributes['subtype'] = $this->getRandomSubtype();
330
			}
331
332 59
			if (empty($metadata['tags'])) {
333 59
				$metadata['tags'] = $this->faker()->words(10);
334
			}
335
336 59 View Code Duplication
			if (empty($attributes['container_guid'])) {
337 59
				$container = elgg_get_logged_in_user_entity();
338 59
				if (!$container) {
339 3
					$container = $this->getRandomUser();
340
				}
341 59
				if (!$container) {
342
					$container = $this->createUser();
343
				}
344
345 59
				$attributes['container_guid'] = $container->guid;
346
			}
347
348 59
			$container = get_entity($attributes['container_guid']);
349 59
			if (!$container) {
350
				return false;
351
			}
352
353 59
			if (empty($attributes['owner_guid'])) {
354 46
				$owner = $container;
355 46
				$attributes['owner_guid'] = $owner->guid;
356
			}
357
358 59
			$owner = get_entity($attributes['owner_guid']);
359 59
			if (!$owner) {
360
				return false;
361
			}
362
363 59
			if (!isset($attributes['access_id'])) {
364 55
				$attributes['access_id'] = ACCESS_PUBLIC;
365
			}
366
367 59
			$class = get_subtype_class('object', $attributes['subtype']);
368 59
			if ($class && class_exists($class)) {
369 1
				$object = new $class();
370
			} else {
371 59
				$object = new ElggObject();
372
			}
373 59
			foreach ($attributes as $name => $value) {
374 59
				$object->$name = $value;
375
			}
376
377 59
			$profile_fields = elgg_extract('profile_fields', $options, []);
378 59
			$object = $this->populateMetadata($object, $profile_fields, $metadata);
379
380 59
			$object->save();
381
382 59
			$type_str = elgg_echo("item:object:{$object->getSubtype()}");
383
384 59
			$this->log("Created new item in $type_str $object->title [guid: $object->guid]");
385
386 59
			return $object;
387 59
		};
388
389 59
		$ia = elgg_set_ignore_access(true);
390
391 59
		$object = false;
392 59
		while (!$object instanceof \ElggObject) {
393 59
			$object = $create();
394
		}
395
396 59
		elgg_set_ignore_access($ia);
397
398 59
		return $object;
399
400
	}
401
402
	/**
403
	 * Create a new fake site
404
	 *
405
	 * @param array $attributes Object entity attributes
406
	 * @param array $metadata   Object entity metadata
407
	 *
408
	 * @return ElggObject
409
	 */
410
	public function createSite(array $attributes = [], array $metadata = []) {
411
		// We don't want to create more than one site
412
		return elgg_get_site_entity();
413
	}
414
415
	/**
416
	 * Returns random fake user
417
	 *
418
	 * @param int[] $exclude GUIDs to exclude
419
	 *
420
	 * @return ElggUser|false
421
	 */
422 5 View Code Duplication
	public function getRandomUser(array $exclude = []) {
0 ignored issues
show
This method seems to be duplicated in 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...
423
424 5
		$exclude[] = 0;
425
		$exclude_in = implode(',', array_map(function ($e) {
426 5
			return (int) $e;
427 5
		}, $exclude));
428
429 5
		$users = elgg_get_entities_from_metadata([
430 5
			'types' => 'user',
431
			'metadata_names' => ['__faker'],
432 5
			'limit' => 1,
433
			'wheres' => [
434 5
				"e.guid NOT IN ($exclude_in)",
435
			],
436 5
			'order_by' => 'RAND()',
437
		]);
438
439 5
		return $users ? $users[0] : false;
440
	}
441
442
	/**
443
	 * Returns random fake group
444
	 *
445
	 * @param int[] $exclude GUIDs to exclude
446
	 *
447
	 * @return ElggGroup|false
448
	 */
449 View Code Duplication
	public function getRandomGroup(array $exclude = []) {
0 ignored issues
show
This method seems to be duplicated in 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...
450
451
		$exclude[] = 0;
452
		$exclude_in = implode(',', array_map(function ($e) {
453
			return (int) $e;
454
		}, $exclude));
455
456
		$groups = elgg_get_entities_from_metadata([
457
			'types' => 'group',
458
			'metadata_names' => ['__faker'],
459
			'limit' => 1,
460
			'wheres' => [
461
				"e.guid NOT IN ($exclude_in)",
462
			],
463
			'order_by' => 'RAND()',
464
		]);
465
466
		return $groups ? $groups[0] : false;
467
	}
468
469
	/**
470
	 * Get random access id
471
	 *
472
	 * @param ElggUser   $user      User
473
	 * @param ElggEntity $container Container
474
	 *
475
	 * @return int
476
	 */
477
	public function getRandomAccessId(\ElggUser $user = null, ElggEntity $container = null) {
478
479
		$params = [
480
			'container_guid' => $container->guid,
481
		];
482
483
		$access_array = get_write_access_array($user->guid, null, null, $params);
484
485
		$access_key = array_rand($access_array, 1);
486
487
		return $access_array[$access_key];
488
	}
489
490
	/**
491
	 * Generates a unique available and valid username
492
	 *
493
	 * @param string $base_name Display name, email or other prefix to use as basis
494
	 *
495
	 * @return string
496
	 */
497 74
	public function getRandomUsername($base_name = 'user') {
498
499 74
		$available = false;
500
501 74
		$base_name = iconv('UTF-8', 'ASCII//TRANSLIT', $base_name);
502 74
		$blacklist = '/[\x{0080}-\x{009f}\x{00a0}\x{2000}-\x{200f}\x{2028}-\x{202f}\x{3000}\x{e000}-\x{f8ff}]/u';
503
		$blacklist2 = [
504 74
			' ',
505
			'\'',
506
			'/',
507
			'\\',
508
			'"',
509
			'*',
510
			'&',
511
			'?',
512
			'#',
513
			'%',
514
			'^',
515
			'(',
516
			')',
517
			'{',
518
			'}',
519
			'[',
520
			']',
521
			'~',
522
			'?',
523
			'<',
524
			'>',
525
			';',
526
			'|',
527
			'¬',
528
			'`',
529
			'@',
530
			'-',
531
			'+',
532
			'='
533
		];
534
535 74
		$base_name = preg_replace($blacklist, '', $base_name);
536 74
		$base_name = str_replace($blacklist2, '', $base_name);
537 74
		$base_name = str_replace('.', '_', $base_name);
538
539 74
		$ia = elgg_set_ignore_access(true);
540
541 74
		$ha = access_get_show_hidden_status();
542 74
		access_show_hidden_entities(true);
543
544 74
		$minlength = elgg_get_config('minusername') ? : 8;
545 74
		if ($base_name) {
546 74
			$fill = $minlength - strlen($base_name);
547
		} else {
548
			$fill = 8;
549
		}
550
551 74
		$separator = '';
552
553 74
		if ($fill > 0) {
554 14
			$suffix = (new \ElggCrypto())->getRandomString($fill);
555 14
			$base_name = "$base_name$separator$suffix";
556
		}
557
558 74
		$iterator = 0;
559 74
		while (!$available) {
560 74
			if ($iterator > 0) {
561
				$base_name = "$base_name$separator$iterator";
562
			}
563 74
			$user = get_user_by_username($base_name);
564 74
			$available = !$user;
565
			try {
566 74
				if ($available) {
567 74
					validate_username($base_name);
568
				}
569
			} catch (\Exception $e) {
570
				if ($iterator >= 10) {
571
					// too many failed attempts
572
					$base_name = (new \ElggCrypto())->getRandomString(8);
573
				}
574
			}
575
576 74
			$iterator++;
577
		}
578
579 74
		access_show_hidden_entities($ha);
580 74
		elgg_set_ignore_access($ia);
581
582 74
		return strtolower($base_name);
583
	}
584
585
	/**
586
	 * Set random metadata
587
	 *
588
	 * @param ElggEntity $entity   Entity
589
	 * @param array      $fields   An array of profile fields in $name => $input_type format
590
	 * @param array      $metadata Other metadata $name => $value pairs to set
591
	 *
592
	 * @return ElggEntity
593
	 */
594 98
	public function populateMetadata(ElggEntity $entity, array $fields = [], array $metadata = []) {
595
596 98
		foreach ($fields as $name => $type) {
597
			if (isset($metadata[$name])) {
598
				continue;
599
			}
600
601
			switch ($name) {
602
				case 'phone' :
603
				case 'mobile' :
604
					$metadata[$name] = $this->faker()->phoneNumber;
605
					break;
606
607
				default :
608
					switch ($type) {
609
						case 'plaintext' :
610
						case 'longtext' :
611
							$metadata[$name] = $this->faker()->text($this->faker()->numberBetween(500, 1000));
612
							break;
613
614
						case 'text' :
615
							$metadata[$name] = $this->faker()->sentence;
616
							break;
617
618
						case 'tags' :
619
							$metadata[$name] = $this->faker()->words(10);
620
							break;
621
622
						case 'url' :
623
							$metadata[$name] = $this->faker()->url;
624
625
						case 'email' :
626
							$metadata[$name] = $this->faker()->email;
627
							break;
628
629
						case 'number' :
630
							$metadata[$name] = $this->faker()->randomNumber();
631
							break;
632
633
						case 'date' :
634
							$metadata[$name] = $this->faker()->unixTime;
635
							break;
636
637
						case 'password' :
638
							$metadata[$name] = generate_random_cleartext_password();
639
							break;
640
641 View Code Duplication
						case 'location' :
642
							$metadata[$name] = $this->faker()->address;
643
							$metadata['geo:lat'] = $this->faker()->latitude;
644
							$metadata['geo:long'] = $this->faker()->longitude;
645
							break;
646
647 View Code Duplication
						case 'email' :
648
							$metadata[$name] = $this->faker()->address;
649
							$metadata['geo:lat'] = $this->faker()->latitude;
650
							$metadata['geo:long'] = $this->faker()->longitude;
651
							break;
652
653
						default :
654
							$metadata[$name] = '';
655
							break;
656
					}
657
658
					break;
659
			}
660
		}
661
662 98
		foreach ($metadata as $key => $value) {
663 98
			$entity->$key = $value;
664
		}
665
666 98
		return $entity;
667
	}
668
669
	/**
670
	 * Create an icon for an entity
671
	 *
672
	 * @param ElggEntity $entity Entity
673
	 *
674
	 * @return bool
675
	 */
676
	public function createIcon(ElggEntity $entity) {
677
678
		$icon_url = $this->faker()->imageURL();
679
680
		$file_contents = file_get_contents($icon_url);
681
682
		$tmp = new \ElggFile();
683
		$tmp->owner_guid = $entity->guid;
684
		$tmp->setFilename("tmp/icon_src.jpg");
685
		$tmp->open('write');
686
		$tmp->write($file_contents);
687
		$tmp->close();
688
689
		$result = $entity->saveIconFromElggFile($tmp);
690
691
		$tmp->delete();
692
693 View Code Duplication
		if ($result && $entity instanceof ElggUser) {
694
			elgg_create_river_item([
695
				'view' => 'river/user/default/profileiconupdate',
696
				'action_type' => 'update',
697
				'subject_guid' => $entity->guid,
698
				'object_guid' => $entity->guid,
699
			]);
700
		}
701
702
		return $result;
703
	}
704
705
	/**
706
	 * Create comments/replies
707
	 *
708
	 * @param ElggEntity $entity Entity to comment on
709
	 * @param int        $limit  Number of comments to create
710
	 *
711
	 * @return int Number of generated comments
712
	 */
713
	public function createComments(ElggEntity $entity, $limit = null) {
714
715
		$ia = elgg_set_ignore_access(true);
716
717
		$tries = 0;
718
		$success = 0;
719
720
		if (!$limit) {
721
			$limit = $this->faker()->numberBetween(1, 20);
722
		}
723
724
		while ($tries < $limit) {
725
			$comment = new \ElggComment();
726
			$comment->subtype = $entity->getSubtype() == 'discussion' ? 'discussion_reply' : 'comment';
727
			$comment->owner_guid = $this->getRandomUser()->guid ? : $entity->owner_guid;
728
			$comment->container_guid = $entity->guid;
729
			$comment->description = $this->faker()->paragraph;
730
731
			$tries++;
732
			if ($comment->save()) {
733
				$success++;
734
			}
735
		}
736
737
		elgg_set_ignore_access($ia);
738
739
		return $success;
740
741
	}
742
743
	/**
744
	 * Create likes
745
	 *
746
	 * @param ElggEntity $entity Entity to like
747
	 * @param int        $limit  Number of likes to create
748
	 *
749
	 * @return int
750
	 */
751
	public function createLikes(ElggEntity $entity, $limit = null) {
752
753
		$ia = elgg_set_ignore_access(true);
754
755
		$success = 0;
756
757
		if (!$limit) {
758
			$limit = $this->faker()->numberBetween(1, 20);
759
		}
760
761
		while ($success < $limit) {
762
			if ($entity->annotate('likes', true, $entity->access_id, $this->getRandomUser()->guid)) {
763
				$success++;
764
			}
765
		}
766
767
		elgg_set_ignore_access($ia);
768
769
		return $success;
770
	}
771
772
	/**
773
	 * Log a message
774
	 *
775
	 * @param string $msg   Message to log
776
	 * @param string $level Message level
777
	 *                      Note that 'ERROR' will terminate further code execution
778
	 *
779
	 * @return void
780
	 */
781 98
	public function log($msg, $level = 'NOTICE') {
782 98
		elgg_log($msg, $level);
783 98
	}
784
785
}