diff --git a/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/Profile.php b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/Profile.php
new file mode 100644
index 0000000..bfd07ba
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/Profile.php
@@ -0,0 +1,126 @@
+pid) ? $this->pid : NULL;
+ }
+
+ /**
+ * Overrides Entity::bundle().
+ */
+ public function bundle() {
+ return $this->type;
+ }
+
+ /**
+ * Overrides Entity::label().
+ */
+ public function label($langcode = NULL) {
+ // If this profile has a custom label, use it. Otherwise, use the label of
+ // the profile type.
+ if (isset($this->label) && $this->label !== '') {
+ return $this->label;
+ }
+ else {
+ return entity_load('profile2_type', $this->type)->label($langcode);
+ }
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/ProfileType.php b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/ProfileType.php
new file mode 100644
index 0000000..a671e42
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/ProfileType.php
@@ -0,0 +1,71 @@
+access($profile, 'view', $langcode, $account);
+ }
+
+ /**
+ * Implements EntityAccessControllerInterface::createAccess().
+ */
+ public function createAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+ // Create and update operations are folded into edit access for profiles.
+ return $this->access($profile, 'edit', $langcode, $account);
+ }
+
+ /**
+ * Implements EntityAccessControllerInterface::updateAccess().
+ */
+ public function updateAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+ // Create and update operations are folded into edit access for profiles.
+ return $this->access($profile, 'edit', $langcode, $account);
+ }
+
+ /**
+ * Implements EntityAccessControllerInterface::deleteAccess().
+ */
+ public function deleteAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+ return $this->access($profile, 'delete', $langcode, $account);
+ }
+
+ /**
+ * Determines whether the given user has access to a profile.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $profile
+ * A profile to check access for.
+ * @param string $operation
+ * The operation being performed. One of 'view', 'create', 'update', or
+ * 'delete'.
+ * @param string $langcode
+ * The language code for which to check access.
+ * @param \Drupal\user\Plugin\Core\Entity\User $account
+ * (optional) The user to check for. Omit to check access for the global
+ * user.
+ *
+ * @return bool
+ * TRUE if access is allowed, FALSE otherwise.
+ *
+ * @see hook_profile2_access()
+ * @see profile2_profile2_access()
+ */
+ protected function access(EntityInterface $profile, $operation, $langcode, User $account = NULL) {
+ if (!isset($account)) {
+ $account = entity_load('user', $GLOBALS['user']->uid);
+ }
+ // Check for the bypass access permission first. No need to cache this,
+ // since user_access() is cached already.
+ if (user_access('bypass profile access', $account)) {
+ return TRUE;
+ }
+ $uid = $account->id();
+ // For existing profiles, check access for the particular profile ID. When
+ // creating a new profile, check access for the profile's bundle.
+ $pid = $profile->id() ?: $profile->bundle();
+
+ if (isset($this->accessCache[$uid][$operation][$pid][$langcode])) {
+ return $this->accessCache[$uid][$operation][$pid][$langcode];
+ }
+
+ $access = NULL;
+ // Ask modules to grant or deny access.
+ foreach (module_implements('profile2_access', $operation, $profile, $account) as $module) {
+ $return = module_invoke($module, 'profile2_access', $operation, $profile, $account);
+ // If a module denies access, there is no point in asking further.
+ if ($return === FALSE) {
+ $access = FALSE;
+ break;
+ }
+ // A module may grant access, but others may still deny.
+ if ($return === TRUE) {
+ $access = TRUE;
+ }
+ }
+ // Final access is only TRUE if any module explicitly returned TRUE. If at
+ // least one returned FALSE, $access will be FALSE. If no module returned
+ // anything, $access will be NULL, which means access is denied.
+ // @see hook_profile2_access()
+ $this->accessCache[$uid][$operation][$pid][$langcode] = ($access === TRUE);
+
+ return $this->accessCache[$uid][$operation][$pid][$langcode];
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php
new file mode 100644
index 0000000..2f1f11d
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php
@@ -0,0 +1,50 @@
+getEntity($form_state)->access('delete');
+ return $element;
+ }
+
+ /**
+ * Overrides EntityFormController::save().
+ */
+ public function save(array $form, array &$form_state) {
+ $profile = $this->getEntity($form_state);
+ $profile->save();
+
+ if ($GLOBALS['user']->uid == $profile->uid) {
+ drupal_set_message(t('Your profile has been saved.'));
+ }
+ else {
+ drupal_set_message(t("%name's profile has been updated.", array('%name' => user_format_name(user_load($profile->uid)))));
+ }
+ }
+
+ /**
+ * Overrides EntityFormController::delete().
+ */
+ public function delete(array $form, array &$form_state) {
+ $profile = $this->getEntity($form_state);
+ // Redirect to the deletion confirmation form.
+ $form_state['redirect'] = 'user/' . $profile->uid . '/edit/' . $profile->bundle() . '/delete';
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileStorageController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileStorageController.php
new file mode 100644
index 0000000..ceb7559
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/ProfileStorageController.php
@@ -0,0 +1,40 @@
+created) {
+ $entity->created = REQUEST_TIME;
+ }
+
+ return $entity;
+ }
+
+ /**
+ * Overrides DatabaseStorageController::preSave().
+ */
+ protected function preSave(EntityInterface $entity) {
+ // Before saving the profile set the 'changed' timestamp.
+ $entity->changed = REQUEST_TIME;
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileTypeFormController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeFormController.php
new file mode 100644
index 0000000..0bdf5dd
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeFormController.php
@@ -0,0 +1,69 @@
+ t('Label'),
+ '#type' => 'textfield',
+ '#default_value' => $type->label(),
+ '#description' => t('The human-readable name of this profile type.'),
+ '#required' => TRUE,
+ '#size' => 30,
+ );
+ $form['id'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $type->id(),
+ '#maxlength' => 32,
+ '#machine_name' => array(
+ 'exists' => 'profile2_type_load',
+ ),
+ );
+ $form['registration'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show during user account registration.'),
+ '#default_value' => $type->get('registration'),
+ );
+ return $form;
+ }
+
+ /**
+ * Overrides EntityFormController::save().
+ */
+ public function save(array $form, array &$form_state) {
+ $type = $this->getEntity($form_state);
+ $status = $type->save();
+
+ if ($status == SAVED_UPDATED) {
+ drupal_set_message(t('%label profile type has been updated.', array('%label' => $type->label())));
+ }
+ else {
+ drupal_set_message(t('%label profile type has been created.', array('%label' => $type->label())));
+ }
+ $form_state['redirect'] = 'admin/people/profiles';
+ }
+
+ /**
+ * Overrides EntityFormController::delete().
+ */
+ public function delete(array $form, array &$form_state) {
+ $type = $this->getEntity($form_state);
+ $form_state['redirect'] = 'admin/people/profiles/manage/' . $type->id() . '/delete';
+ }
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php
new file mode 100644
index 0000000..d21f713
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php
@@ -0,0 +1,41 @@
+uri();
+ $operations['manage-fields'] = array(
+ 'title' => t('Manage fields'),
+ 'href' => $uri['path'] . '/fields',
+ 'options' => $uri['options'],
+ 'weight' => 11,
+ );
+ $operations['manage-display'] = array(
+ 'title' => t('Manage display'),
+ 'href' => $uri['path'] . '/display',
+ 'options' => $uri['options'],
+ 'weight' => 12,
+ );
+ }
+ return $operations;
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileTypeStorageController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeStorageController.php
new file mode 100644
index 0000000..fddc533
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeStorageController.php
@@ -0,0 +1,55 @@
+id());
+ }
+ elseif ($entity->original->id() != $entity->id()) {
+ field_attach_rename_bundle('profile2', $entity->original->id(), $entity->id());
+ }
+ }
+
+ /**
+ * Overrides ConfigStorageController::preDelete().
+ */
+ protected function preDelete($entities) {
+ parent::preDelete($entities);
+
+ // Delete all profiles of this type.
+ if ($profiles = entity_load_multiple_by_properties('profile2', array('type' => array_keys($entities)))) {
+ entity_get_controller('profile2')->delete($profiles);
+ }
+ }
+
+ /**
+ * Overrides ConfigStorageController::postDelete().
+ */
+ protected function postDelete($entities) {
+ parent::postDelete($entities);
+
+ foreach ($entities as $entity) {
+ field_attach_delete_bundle('profile2', $entity->id());
+ }
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAccessTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAccessTest.php
new file mode 100644
index 0000000..4263afe
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAccessTest.php
@@ -0,0 +1,144 @@
+ 'Profile access',
+ 'description' => 'Tests profile access handling.',
+ 'group' => 'Profile2',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->type = entity_create('profile2_type', array(
+ 'id' => 'test',
+ 'label' => 'Test profile',
+ ));
+ $this->type->save();
+ $id = $this->type->id();
+
+ $this->field = array(
+ 'field_name' => 'profile_fullname',
+ 'type' => 'text',
+ 'cardinality' => 1,
+ 'translatable' => FALSE,
+ );
+ $this->field = field_create_field($this->field);
+ $this->instance = array(
+ 'entity_type' => 'profile2',
+ 'field_name' => $this->field['field_name'],
+ 'bundle' => $this->type->id(),
+ 'label' => 'Full name',
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ ),
+ );
+ $this->instance = field_create_instance($this->instance);
+ $this->display = entity_get_display('profile2', 'test', 'default')
+ ->setComponent($this->field['field_name'], array(
+ 'type' => 'text_default',
+ ));
+ $this->display->save();
+
+ $this->checkPermissions(array(), TRUE);
+
+ user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access user profiles'));
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'administer profile types',
+ "view any $id profile",
+ "edit any $id profile",
+ "delete any $id profile",
+ ));
+ }
+
+ /**
+ * Tests administrative-only profiles.
+ */
+ function testAdminOnlyProfiles() {
+ $id = $this->type->id();
+ $field_name = $this->field['field_name'];
+
+ // Create a test user account.
+ $web_user = $this->drupalCreateUser(array('access user profiles'));
+ $uid = $web_user->id();
+ $value = $this->randomName();
+
+ // Administratively enter profile field values for the new account.
+ $this->drupalLogin($this->admin_user);
+ $edit = array(
+ "{$field_name}[und][0][value]" => $value,
+ );
+ $this->drupalPost("user/$uid/edit/$id", $edit, t('Save'));
+
+ // Verify that the administrator can see the profile.
+ $this->drupalGet("user/$uid");
+ $this->assertText($this->type->label());
+ $this->assertText($value);
+
+ // Verify that the user can not access or edit the profile.
+ $this->drupalLogin($web_user);
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+ $this->drupalGet("user/$uid/edit/$id");
+ $this->assertResponse(403);
+
+ // Allow users to edit own profiles.
+ user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array("edit own $id profile"));
+
+ // Verify that the user is able to edit the own profile.
+ $value = $this->randomName();
+ $edit = array(
+ "{$field_name}[und][0][value]" => $value,
+ );
+ $this->drupalPost("user/$uid/edit/$id", $edit, t('Save'));
+ $this->assertText(t('Your profile has been saved.'));
+
+ // Verify that the own profile is still not visible on the account page.
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+
+ // Allow users to view own profiles.
+ user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array("view own $id profile"));
+
+ // Verify that the own profile is visible on the account page.
+ $this->drupalGet("user/$uid");
+ $this->assertText($this->type->label());
+ $this->assertText($value);
+
+ // Allow users to delete own profiles.
+ user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array("delete own $id profile"));
+
+ // Verify that the user can delete the own profile.
+ $this->drupalPost("user/$uid/edit/$id", array(), t('Delete'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+ $this->assertRaw(t('Your %label profile has been deleted.', array('%label' => $this->type->label())));
+ $this->assertUrl("user/$uid");
+
+ // Verify that the profile is gone.
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+ $this->drupalGet("user/$uid/edit/$id");
+ $this->assertNoText($value);
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAttachTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAttachTest.php
new file mode 100644
index 0000000..34edb72
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileAttachTest.php
@@ -0,0 +1,113 @@
+ 'Profile form attachment',
+ 'description' => 'Tests attaching of profile entity forms to other forms.',
+ 'group' => 'Profile2',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->type = entity_create('profile2_type', array(
+ 'id' => 'test',
+ 'label' => 'Test profile',
+ 'weight' => 0,
+ 'registration' => TRUE,
+ ));
+ $this->type->save();
+
+ $this->field = array(
+ 'field_name' => 'profile_fullname',
+ 'type' => 'text',
+ 'cardinality' => 1,
+ 'translatable' => FALSE,
+ );
+ $this->field = field_create_field($this->field);
+ $this->instance = array(
+ 'entity_type' => 'profile2',
+ 'field_name' => $this->field['field_name'],
+ 'bundle' => $this->type->id(),
+ 'label' => 'Full name',
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'text_textfield',
+ ),
+ );
+ $this->instance = field_create_instance($this->instance);
+ $this->display = entity_get_display('profile2', 'test', 'default')
+ ->setComponent($this->field['field_name'], array(
+ 'type' => 'text_default',
+ ));
+ $this->display->save();
+
+ $this->checkPermissions(array(), TRUE);
+ }
+
+ /**
+ * Test user registration integration.
+ */
+ function testUserRegisterForm() {
+ $id = $this->type->id();
+ $field_name = $this->field['field_name'];
+
+ // Allow registration without administrative approval and log in user
+ // directly after registering.
+ config('user.settings')
+ ->set('register', USER_REGISTER_VISITORS)
+ ->set('verify_mail', 0)
+ ->save();
+ user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('view own test profile'));
+
+ // Verify that the additional profile field is attached and required.
+ $name = $this->randomName();
+ $pass_raw = $this->randomName();
+ $edit = array(
+ 'name' => $name,
+ 'mail' => $this->randomName() . '@example.com',
+ 'pass[pass1]' => $pass_raw,
+ 'pass[pass2]' => $pass_raw,
+ );
+ $this->drupalPost('user/register', $edit, t('Create new account'));
+ $this->assertRaw(t('@name field is required.', array('@name' => $this->instance['label'])));
+
+ // Verify that we can register.
+ $edit["profile[$id][$field_name][und][0][value]"] = $this->randomName();
+ $this->drupalPost(NULL, $edit, t('Create new account'));
+ $this->assertText(t('Registration successful. You are now logged in.'));
+
+ $new_user = user_load_by_name($name);
+ $this->assertTrue($new_user->status, 'New account is active after registration.');
+
+ // Verify that a new profile was created for the new user ID.
+ $profiles = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $new_user->id(),
+ 'type' => $this->type->id(),
+ ));
+ $profile = reset($profiles);
+ $this->assertEqual($profile->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['value'], $edit["profile[$id][$field_name][und][0][value]"], 'Field value found in loaded profile.');
+
+ // Verify that the profile field value appears on the user account page.
+ $this->drupalGet('user');
+ $this->assertText($edit["profile[$id][$field_name][und][0][value]"], 'Field value found on user account page.');
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileCRUDTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileCRUDTest.php
new file mode 100644
index 0000000..5f31e61
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileCRUDTest.php
@@ -0,0 +1,159 @@
+ 'Profile CRUD operations',
+ 'description' => 'Tests basic CRUD functionality of profiles.',
+ 'group' => 'Profile2',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->installSchema('system', 'url_alias');
+ $this->installSchema('system', 'sequences');
+ $this->enableModules(array('field', 'user', 'profile2'));
+ }
+
+ /**
+ * Tests CRUD operations.
+ */
+ function testCRUD() {
+ $types_data = array(
+ 0 => array('label' => $this->randomName()),
+ 1 => array('label' => $this->randomName()),
+ );
+ foreach ($types_data as $id => $values) {
+ $types[$id] = entity_create('profile2_type', array('id' => $id) + $values);
+ $types[$id]->save();
+ }
+ $this->user1 = entity_create('user', array(
+ 'name' => $this->randomName(),
+ 'mail' => $this->randomName() . '@example.com',
+ ));
+ $this->user1->save();
+ $this->user2 = entity_create('user', array(
+ 'name' => $this->randomName(),
+ 'mail' => $this->randomName() . '@example.com',
+ ));
+ $this->user2->save();
+
+ // Create a new profile.
+ $profile = entity_create('profile2', $expected = array(
+ 'type' => $types[0]->id(),
+ 'uid' => $this->user1->id(),
+ ));
+ $this->assertIdentical($profile->id(), NULL);
+ $this->assertTrue($profile->uuid());
+ $this->assertIdentical($profile->type, $expected['type']);
+ $this->assertIdentical($profile->label(), $types[0]->label());
+ $this->assertIdentical($profile->uid, $this->user1->id());
+ $this->assertIdentical($profile->created, REQUEST_TIME);
+ $this->assertIdentical($profile->changed, NULL);
+
+ // Save the profile.
+ $status = $profile->save();
+ $this->assertIdentical($status, SAVED_NEW);
+ $this->assertTrue($profile->id());
+ $this->assertIdentical($profile->changed, REQUEST_TIME);
+
+ // List profiles for the user and verify that the new profile appears.
+ $list = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $this->user1->uid,
+ ));
+ $this->assertEqual($list, array(
+ $profile->id() => $profile,
+ ));
+
+ // Reload and update the profile.
+ $profile = entity_load('profile2', $profile->id());
+ $profile->changed -= 1000;
+ $original = clone $profile;
+ $status = $profile->save();
+ $this->assertIdentical($status, SAVED_UPDATED);
+ $this->assertIdentical($profile->id(), $original->id());
+ $this->assertEqual($profile->created, REQUEST_TIME);
+ $this->assertEqual($original->changed, REQUEST_TIME - 1000);
+ $this->assertEqual($profile->changed, REQUEST_TIME);
+
+ // Create a second profile.
+ $user1_profile1 = $profile;
+ $profile = entity_create('profile2', array(
+ 'type' => $types[1]->id(),
+ 'uid' => $this->user1->id(),
+ ));
+ $status = $profile->save();
+ $this->assertIdentical($status, SAVED_NEW);
+ $user1_profile2 = $profile;
+
+ // List profiles for the user and verify that both profiles appear.
+ $list = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $this->user1->uid,
+ ));
+ $this->assertEqual($list, array(
+ $user1_profile1->id() => $user1_profile1,
+ $user1_profile2->id() => $user1_profile2,
+ ));
+
+ // Delete the second profile and verify that the first still exists.
+ $user1_profile2->delete();
+ $this->assertFalse(entity_load('profile2', $user1_profile2->id()));
+ $list = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $this->user1->uid,
+ ));
+ $this->assertEqual($list, array(
+ $user1_profile1->id() => $user1_profile1,
+ ));
+
+ // Create a new second profile.
+ $user1_profile2 = entity_create('profile2', array(
+ 'type' => $types[1]->id(),
+ 'uid' => $this->user1->id(),
+ ));
+ $status = $user1_profile2->save();
+ $this->assertIdentical($status, SAVED_NEW);
+
+ // Create a profile for the second user.
+ $user2_profile1 = entity_create('profile2', array(
+ 'type' => $types[0]->id(),
+ 'uid' => $this->user2->id(),
+ ));
+ $status = $user2_profile1->save();
+ $this->assertIdentical($status, SAVED_NEW);
+
+ // Delete the first user and verify that all of its profiles are deleted.
+ $this->user1->delete();
+ $this->assertFalse(entity_load('user', $this->user1->id()));
+ $list = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $this->user1->uid,
+ ));
+ $this->assertEqual($list, array());
+
+ // List profiles for the second user and verify that they still exist.
+ $list = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $this->user2->uid,
+ ));
+ $this->assertEqual($list, array(
+ $user2_profile1->id() => $user2_profile1,
+ ));
+
+ // @todo Rename a profile type; verify that existing profiles are updated.
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileFieldAccessTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileFieldAccessTest.php
new file mode 100644
index 0000000..acc7638
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileFieldAccessTest.php
@@ -0,0 +1,103 @@
+ 'Field access',
+ 'description' => 'Tests profile field access functionality.',
+ 'group' => 'Profile2',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->type = entity_create('profile2_type', array(
+ 'id' => 'personal',
+ 'label' => 'Personal data',
+ 'weight' => 0,
+ 'registration' => TRUE,
+ ));
+ $this->type->save();
+
+ $this->checkPermissions(array(), TRUE);
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'access user profiles',
+ 'administer profile types',
+ 'administer profile2 fields',
+ 'administer profile2 display',
+ 'bypass profile access',
+ ));
+ $user_permissions = array(
+ 'access user profiles',
+ 'edit own personal profile',
+ 'view any personal profile',
+ );
+ $this->web_user = $this->drupalCreateUser($user_permissions);
+ $this->other_user = $this->drupalCreateUser($user_permissions);
+ }
+
+ /**
+ * Tests private profile field access.
+ */
+ function testPrivateField() {
+ $id = $this->type->id();
+
+ $this->drupalLogin($this->admin_user);
+
+ // Create a private profile field.
+ $edit = array(
+ 'fields[_add_new_field][label]' => 'Secret',
+ 'fields[_add_new_field][field_name]' => 'secret',
+ 'fields[_add_new_field][type]' => 'text',
+ 'fields[_add_new_field][widget_type]' => 'text_textfield',
+ );
+ $this->drupalPost("admin/people/profiles/manage/$id/fields", $edit, t('Save'));
+
+ $edit = array(
+ 'field[settings][profile2_private]' => 1,
+ );
+ $this->drupalPost(NULL, $edit, t('Save field settings'));
+
+ $this->drupalPost(NULL, array(), t('Save settings'));
+
+ // Fill in a field value.
+ $this->drupalLogin($this->web_user);
+ $uid = $this->web_user->id();
+ $secret = $this->randomName();
+ $edit = array(
+ 'field_secret[und][0][value]' => $secret,
+ );
+ $this->drupalPost("user/$uid/edit/$id", $edit, t('Save'));
+
+ // Verify that the private field value appears for the profile owner.
+ $this->drupalGet("user/$uid");
+ $this->assertText($secret);
+
+ // Verify that the private field value appears for the administrator.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet("user/$uid");
+ $this->assertText($secret);
+
+ // Verify that the private field value does not appear for other users.
+ $this->drupalLogin($this->other_user);
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($secret);
+ }
+
+}
diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileTypeCRUDTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileTypeCRUDTest.php
new file mode 100644
index 0000000..55c54db
--- /dev/null
+++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileTypeCRUDTest.php
@@ -0,0 +1,99 @@
+ 'Profile type CRUD operations',
+ 'description' => 'Tests basic CRUD functionality of profile types.',
+ 'group' => 'Profile2',
+ );
+ }
+
+ /**
+ * Tests CRUD operations for profile types through the UI.
+ */
+ function testCRUDUI() {
+ $this->drupalLogin($this->root_user);
+
+ // Create a new profile type.
+ $this->drupalGet('admin/people/profiles');
+ $this->clickLink(t('Add profile type'));
+ $this->assertUrl('admin/people/profiles/add');
+ $id = drupal_strtolower($this->randomName());
+ $label = $this->randomString();
+ $edit = array(
+ 'id' => $id,
+ 'label' => $label,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertUrl('admin/people/profiles');
+ $this->assertRaw(t('%label profile type has been created.', array('%label' => $label)));
+ $this->assertLinkByHref("admin/people/profiles/manage/$id/edit");
+ $this->assertLinkByHref("admin/people/profiles/manage/$id/fields");
+ $this->assertLinkByHref("admin/people/profiles/manage/$id/display");
+ $this->assertLinkByHref("admin/people/profiles/manage/$id/delete");
+
+ // Edit the new profile type.
+ $this->drupalGet("admin/people/profiles/manage/$id/edit");
+ $this->assertRaw(t('Edit %label profile type', array('%label' => $label)));
+ $edit = array(
+ 'registration' => 1,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertUrl('admin/people/profiles');
+ $this->assertRaw(t('%label profile type has been updated.', array('%label' => $label)));
+
+ // Add a field to the profile type.
+ $this->drupalGet("admin/people/profiles/manage/$id/fields");
+ $field_name = drupal_strtolower($this->randomName());
+ $field_label = $this->randomString();
+ $edit = array(
+ 'fields[_add_new_field][label]' => $field_name,
+ 'fields[_add_new_field][field_name]' => $field_name,
+ 'fields[_add_new_field][type]' => 'text',
+ 'fields[_add_new_field][widget_type]' => 'text_textfield',
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->drupalPost(NULL, array(), t('Save field settings'));
+ $this->drupalPost(NULL, array(), t('Save settings'));
+ $this->assertUrl("admin/people/profiles/manage/$id/fields");
+
+ // Rename the profile type ID.
+ $this->drupalGet("admin/people/profiles/manage/$id/edit");
+ $new_id = drupal_strtolower($this->randomName());
+ $edit = array(
+ 'id' => $new_id,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertUrl('admin/people/profiles');
+ $this->assertRaw(t('%label profile type has been updated.', array('%label' => $label)));
+ $this->assertLinkByHref("admin/people/profiles/manage/$new_id/edit");
+ $this->assertNoLinkByHref("admin/people/profiles/manage/$id/edit");
+ $id = $new_id;
+
+ // Verify that the field is still associated with it.
+ $this->drupalGet("admin/people/profiles/manage/$id/fields");
+ // @todo D8 core: This assertion fails for an unknown reason. Database
+ // contains the right values, so field_attach_rename_bundle() works
+ // correctly. The pre-existing field does not appear on the Manage
+ // fields page of the renamed bundle. Not even flushing all caches
+ // helps. Can be reproduced manually.
+ //$this->assertText(check_plain($field_label));
+ }
+
+}
diff --git a/core/modules/profile2/profile2.admin.inc b/core/modules/profile2/profile2.admin.inc
new file mode 100644
index 0000000..be4f156
--- /dev/null
+++ b/core/modules/profile2/profile2.admin.inc
@@ -0,0 +1,70 @@
+render();
+}
+
+/**
+ * Page callback: Presents the form for creating a profile type.
+ *
+ * @return array
+ * A form array as expected by drupal_render().
+ */
+function profile2_type_add() {
+ $type = entity_create('profile2_type', array());
+ return entity_get_form($type);
+}
+
+/**
+ * Page callback: Presents the form for editing a profile type.
+ *
+ * @param Drupal\profile2\Plugin\Core\Entity\ProfileType $type
+ * The profile type to edit.
+ *
+ * @return array
+ * A form array as expected by drupal_render().
+ */
+function profile2_type_edit(ProfileType $type) {
+ drupal_set_title(t('Edit %label profile type', array('%label' => $type->label())), PASS_THROUGH);
+ return entity_get_form($type);
+}
+
+/**
+ * Form constructor to delete a ProfileType object.
+ *
+ * @param Drupal\profile2\Plugin\Core\Entity\ProfileType $type
+ * The ProfileType object to delete.
+ */
+function profile2_type_delete_form($form, &$form_state, ProfileType $type) {
+ $form_state['profile2_type'] = $type;
+
+ $form['id'] = array('#type' => 'value', '#value' => $type->id());
+ return confirm_form($form,
+ t('Are you sure you want to delete %label and all of its associated profiles?', array('%label' => $type->label())),
+ 'admin/people/profiles',
+ NULL,
+ t('Delete')
+ );
+}
+
+/**
+ * Form submission handler for profile2_type_delete_form().
+ */
+function profile2_type_delete_form_submit($form, &$form_state) {
+ $form_state['profile2_type']->delete();
+ drupal_set_message(t('The profile type %label has been deleted.', array(
+ '%label' => $form_state['profile2_type']->label(),
+ )));
+ $form_state['redirect'] = 'admin/people/profiles';
+}
diff --git a/core/modules/profile2/profile2.api.php b/core/modules/profile2/profile2.api.php
new file mode 100644
index 0000000..d1cbaf6
--- /dev/null
+++ b/core/modules/profile2/profile2.api.php
@@ -0,0 +1,50 @@
+type == 'secret' && !user_access('custom permission')) {
+ return FALSE;
+ }
+ // For profiles other than the default profile grant access.
+ if ($profile->type != 'main' && user_access('custom permission')) {
+ return TRUE;
+ }
+ // In other cases do not alter access.
+}
+
+/**
+ * @}
+ */
diff --git a/core/modules/profile2/profile2.info b/core/modules/profile2/profile2.info
new file mode 100644
index 0000000..046076d
--- /dev/null
+++ b/core/modules/profile2/profile2.info
@@ -0,0 +1,6 @@
+name = Profile2
+description = Provides configurable user profiles.
+core = 8.x
+configure = admin/people/profiles
+dependencies[] = user
+dependencies[] = field
diff --git a/core/modules/profile2/profile2.install b/core/modules/profile2/profile2.install
new file mode 100644
index 0000000..9a7a3a6
--- /dev/null
+++ b/core/modules/profile2/profile2.install
@@ -0,0 +1,82 @@
+ 'Stores profile items.',
+ 'fields' => array(
+ 'pid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique profile item ID.',
+ ),
+ 'uuid' => array(
+ 'description' => 'Unique Key: Universally unique identifier for this entity.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => FALSE,
+ ),
+ 'type' => array(
+ 'description' => 'The profile type ID of this profile.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'uid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ 'description' => "The {users}.uid of the associated user.",
+ ),
+ 'langcode' => array(
+ 'description' => 'The {language}.langcode of this profile.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'label' => array(
+ 'description' => 'A human-readable label for this profile.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the profile was created.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the profile was most recently saved.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ ),
+ ),
+ 'indexes' => array(
+ 'uid' => array('uid'),
+ 'type' => array('type'),
+ ),
+ 'foreign keys' => array(
+ 'uid' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ ),
+ 'primary key' => array('pid'),
+ 'unique keys' => array(
+ 'uuid' => array('uuid'),
+ ),
+ );
+ return $schema;
+}
+
diff --git a/core/modules/profile2/profile2.module b/core/modules/profile2/profile2.module
new file mode 100644
index 0000000..4010fcc
--- /dev/null
+++ b/core/modules/profile2/profile2.module
@@ -0,0 +1,550 @@
+get('id');
+ $label = $config->get('label');
+ $info['profile2']['bundles'][$id] = array(
+ 'label' => $label,
+ 'admin' => array(
+ 'path' => 'admin/people/profiles/manage/%profile2_type',
+ 'real path' => 'admin/people/profiles/manage/' . $id,
+ 'bundle argument' => 4,
+ 'access arguments' => array('administer profile types'),
+ ),
+ );
+ }
+}
+
+/**
+ * Entity URI callback for profiles.
+ *
+ * @param Drupal\profile2\Plugin\Core\Entity\Profile $profile
+ * A profile entity.
+ */
+function profile2_profile_uri(Profile $profile) {
+ $uri = entity_load('user', $profile->uid)->uri();
+ $uri['options']['fragment'] = 'profile-' . $profile->bundle();
+ return $uri;
+}
+
+/**
+ * Entity URI callback for profile types.
+ *
+ * @param Drupal\profile2\Plugin\Core\Entity\ProfileType $profile_type
+ * A profile type entity.
+ */
+function profile2_profile_type_uri(ProfileType $profile_type) {
+ return array(
+ 'path' => 'admin/people/profiles/manage/' . $profile_type->id(),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function profile2_menu() {
+ $items['admin/people/profiles'] = array(
+ 'title' => 'Profile types',
+ 'description' => 'Manage profile types, including fields.',
+ 'page callback' => 'profile2_type_list_page',
+ 'access arguments' => array('administer profile types'),
+ 'type' => MENU_LOCAL_TASK,
+ // @todo User module: Apply custom/higher weights to Permissions and Roles.
+ 'weight' => -1,
+ 'file' => 'profile2.admin.inc',
+ );
+ $items['admin/people/profiles/add'] = array(
+ 'title' => 'Add profile type',
+ 'page callback' => 'profile2_type_add',
+ 'access arguments' => array('administer profile types'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'profile2.admin.inc',
+ );
+ $items['admin/people/profiles/manage/%profile2_type'] = array(
+ 'title' => 'Edit profile type',
+ 'title callback' => 'entity_page_label',
+ 'title arguments' => array(4),
+ 'page callback' => 'profile2_type_edit',
+ 'page arguments' => array(4),
+ 'access arguments' => array('administer profile types'),
+ 'file' => 'profile2.admin.inc',
+ );
+ $items['admin/people/profiles/manage/%profile2_type/edit'] = array(
+ 'title' => 'Edit',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/people/profiles/manage/%profile2_type/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('profile2_type_delete_form', 4),
+ 'access arguments' => array('administer profile types'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'profile2.admin.inc',
+ );
+
+ // @todo Move into User module.
+ $items['user/%user/edit/account'] = array(
+ 'title' => 'Account',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['user/%user/edit/%profile2_menu_arg'] = array(
+ // @see http://drupal.org/node/1863502
+ 'load arguments' => array(1, 'edit'),
+ 'load arguments' => array('%map', 'edit'),
+ 'title' => 'Edit profile',
+ 'title callback' => 'entity_page_label',
+ 'title arguments' => array(3),
+ 'access callback' => 'profile2_access',
+ 'access arguments' => array(3, 'edit'),
+ 'page callback' => 'entity_get_form',
+ 'page arguments' => array(3),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['user/%user/edit/%profile2_menu_arg/delete'] = array(
+ // @see http://drupal.org/node/1863502
+ 'load arguments' => array(1, 'delete'),
+ 'load arguments' => array('%map', 'delete'),
+ 'title' => 'Delete profile',
+ 'access callback' => 'profile2_access',
+ 'access arguments' => array(3, 'delete'),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('profile2_delete_confirm_form', 3),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ 'file' => 'profile2.pages.inc',
+ );
+ return $items;
+}
+
+/**
+ * Menu argument callback; Loads a profile of a certain type for a given user.
+ *
+ * @param string $type_id
+ * The profile type ID to load.
+ * @param Drupal\user\Plugin\Core\Entity\User $account
+ * The user account for which to load the profile.
+ * @param string $op
+ * (optional) The operation to perform. If 'edit' and if there is no profile
+ * for the user account yet, a new profile entity will be created on the fly.
+ *
+ * @return Drupal\profile2\Plugin\Core\Entity\Profile|false
+ * The profile of type $type_id of the user account, or FALSE.
+ */
+// @see http://drupal.org/node/1863502
+//function profile2_menu_arg_load($type_id, User $account, $op = '') {
+function profile2_menu_arg_load($type_id, $map, $op = '') {
+ if (is_array($map)) {
+ if (!isset($map[1]) || !($map[1] instanceof User)) {
+ return FALSE;
+ }
+ $account = $map[1];
+ }
+ else {
+ $account = $map;
+ }
+ if ($type_id === '' || !$account->id()) {
+ return FALSE;
+ }
+ $profiles = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $account->id(),
+ 'type' => $type_id,
+ ));
+ $profile = reset($profiles);
+ if ($op == 'edit' && !$profile) {
+ $profile = entity_create('profile2', array(
+ 'type' => $type_id,
+ 'uid' => $account->id(),
+ ));
+ }
+ return $profile;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function profile2_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ if ($root_path === 'user/%/edit' || $root_path === 'user/%/edit/%') {
+ $tabs = &$data['tabs'][1]['output'];
+ // Determine the currently selected tab, if any.
+ $selected_index = -1;
+ $selected_id = '';
+ foreach ($tabs as $index => &$tab) {
+ if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'user/%/edit/%') {
+ $selected_index = $index;
+ $selected_id = $router_item['original_map'][3];
+ }
+ }
+ // Expand the dynamic %profile_menu argument into a tab for each type.
+ $types = entity_load_multiple('profile2_type');
+ foreach ($types as $type) {
+ // If the current page is the active tab registered in hook_menu(), then
+ // the menu router item with the dynamic argument will be exposed already.
+ // We must not duplicate that tab, but in order to ensure that all of our
+ // tabs appear in a consistent order when switching between tabs, we need
+ // to re-inject it.
+ if ($type->id() === $selected_id) {
+ $tabs[$selected_index]['#link']['title'] = $type->label();
+ $tabs[] = $tabs[$selected_index];
+ unset($tabs[$selected_index]);
+ continue;
+ }
+ $tabs[] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => array(
+ 'title' => $type->label(),
+ 'href' => 'user/' . $router_item['original_map'][1] . '/edit/' . $type->id(),
+ 'localized_options' => array('html' => FALSE),
+ ),
+ );
+ }
+ if ($types) {
+ $data['tabs'][1]['count']++;
+ }
+ }
+}
+
+/**
+ * Menu argument loader; Load a profile type by string.
+ *
+ * @param string $id
+ * The machine-readable name of a profile type to load.
+ *
+ * @return Drupal\profile2\Plugin\Core\Entity\ProfileType|false
+ * A profile type array or FALSE if $type does not exist.
+ */
+function profile2_type_load($id) {
+ return entity_load('profile2_type', $id);
+}
+
+/**
+ * Implements hook_permission().
+ */
+function profile2_permission() {
+ $permissions = array(
+ 'administer profile types' => array(
+ 'title' => t('Administer profile types'),
+ 'restrict access' => TRUE,
+ ),
+ 'bypass profile access' => array(
+ 'title' => t('Bypass profile access'),
+ 'description' => t('View and edit all user profiles, including private field values.'),
+ 'restrict access' => TRUE,
+ ),
+ );
+ // Generate per profile type permissions.
+ foreach (entity_load_multiple('profile2_type') as $type) {
+ $type_id = $type->id();
+ $permissions += array(
+ "view own $type_id profile" => array(
+ 'title' => t('%type: View own profile', array('%type' => $type->label())),
+ ),
+ "view any $type_id profile" => array(
+ 'title' => t('%type: View any profile', array('%type' => $type->label())),
+ ),
+ "edit own $type_id profile" => array(
+ 'title' => t('%type: Edit own profile', array('%type' => $type->label())),
+ ),
+ "edit any $type_id profile" => array(
+ 'title' => t('%type: Edit any profile', array('%type' => $type->label())),
+ ),
+ "delete own $type_id profile" => array(
+ 'title' => t('%type: Delete own profile', array('%type' => $type->label())),
+ ),
+ "delete any $type_id profile" => array(
+ 'title' => t('%type: Delete any profile', array('%type' => $type->label())),
+ ),
+ );
+ }
+ return $permissions;
+}
+
+/**
+ * Implements hook_user_predelete().
+ */
+function profile2_user_predelete(User $account) {
+ if ($profiles = entity_load_multiple_by_properties('profile2', array('uid' => $account->id()))) {
+ entity_get_controller('profile2')->delete($profiles);
+ }
+}
+
+/**
+ * Implements hook_user_view().
+ */
+function profile2_user_view(User $account, EntityDisplay $display, $view_mode, $langcode) {
+ // Only attach profiles for the full account view.
+ if ($view_mode != 'full') {
+ return;
+ }
+ foreach (entity_load_multiple('profile2_type') as $id => $type) {
+ $profiles = entity_load_multiple_by_properties('profile2', array(
+ 'uid' => $account->id(),
+ 'type' => $id,
+ ));
+ foreach ($profiles as $profile) {
+ if ($profile->access('view')) {
+ $build = entity_render_controller('profile2')->view($profile, 'account');
+ $build += array(
+ '#prefix' => '',
+ );
+ $account->content['profile'][$id][$profile->id()] = $build;
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for user_register_form().
+ */
+function profile2_form_user_register_form_alter(&$form, &$form_state) {
+ foreach (entity_load_multiple('profile2_type') as $id => $type) {
+ if ($type->get('registration')) {
+ if (empty($form_state['profiles'][$id])) {
+ $form_state['profiles'][$id] = entity_create('profile2', array(
+ 'type' => $id,
+ ));
+ }
+ profile2_attach_form($form, $form_state);
+
+ // Wrap each profile form in a fieldset.
+ $form['profile'][$id] += array(
+ '#type' => 'fieldset',
+ '#title' => check_plain($type->label()),
+ );
+ }
+ }
+ if (!empty($form_state['profiles'])) {
+ $form['#validate'][] = 'profile2_user_form_validate';
+ $form['actions']['submit']['#submit'][] = 'profile2_user_form_submit';
+ }
+}
+
+/**
+ * Attaches the profile forms of the profiles set in $form_state['profiles'].
+ *
+ * Modules may alter the profile2 entity form regardless to which form it is
+ * attached by making use of hook_form_profile2_form_alter().
+ *
+ * @param $form
+ * The form to which to attach the profile2 form. For each profile the form
+ * is added to @code $form['profile_' . $profile->type] @endcode. This helper
+ * also adds in a validation and a submit handler caring for the attached
+ * profile forms.
+ *
+ * @see profile2_user_form_validate()
+ * @see profile2_user_form_submit()
+ */
+function profile2_attach_form(&$form, &$form_state) {
+ foreach ($form_state['profiles'] as $bundle => $profile) {
+ $form['profile'][$bundle]['#tree'] = TRUE;
+ $form['profile'][$bundle]['#parents'] = array('profile', $bundle);
+
+ field_attach_form($profile, $form['profile'][$bundle], $form_state);
+
+ if (count(field_info_instances('profile2', $bundle)) == 0) {
+ $form['profile'][$bundle]['message'] = array(
+ '#access' => user_access('administer profile types'),
+ '#markup' => t('No fields have been associated with this profile type. Go to the Profile types page to add some fields.', array('!url' => url('admin/people/profiles'))),
+ );
+ }
+
+ // Provide a central place for modules to alter the profile forms, but
+ // skip that in case the caller cares about invoking the hooks.
+ if (!isset($form_state['profile2_skip_hook'])) {
+ $hooks = array();
+ $hooks[] = 'form_' . $bundle . '_profile2_form';
+ $hooks[] = 'form_profile2_form';
+ drupal_alter($hooks, $form['profile'][$bundle], $form_state);
+ }
+ }
+}
+
+/**
+ * Validation handler for the profile form.
+ *
+ * @see profile2_attach_form()
+ */
+function profile2_user_form_validate(&$form, &$form_state) {
+ foreach ($form_state['profiles'] as $bundle => $profile) {
+ if (isset($form_state['values']['profile'][$bundle])) {
+ // @see entity_form_field_validate()
+ $pseudo_entity = entity_create('profile2', array_merge($form_state['values']['profile'][$bundle], array(
+ 'type' => $bundle,
+ )));
+ field_attach_form_validate($pseudo_entity, $form['profile'][$bundle], $form_state);
+ }
+ }
+}
+
+/**
+ * Submit handler that builds and saves all profiles in the form.
+ *
+ * @see profile2_attach_form()
+ */
+function profile2_user_form_submit(&$form, &$form_state) {
+ profile2_form_submit_build_profile($form, $form_state);
+
+ foreach ($form_state['profiles'] as $bundle => $profile) {
+ // During registration set the uid field of the newly created user.
+ if (empty($profile->uid) && isset($form_state['user']->uid)) {
+ $profile->uid = $form_state['user']->uid;
+ }
+ $profile->save();
+ }
+}
+
+/**
+ * Submit builder. Extracts the form values and updates the profile entities.
+ *
+ * @see profile2_attach_form()
+ */
+function profile2_form_submit_build_profile(&$form, &$form_state) {
+ foreach ($form_state['profiles'] as $bundle => $profile) {
+ // @see entity_form_submit_build_entity()
+ if (isset($form['profile'][$bundle]['#entity_builders'])) {
+ foreach ($form['profile'][$bundle]['#entity_builders'] as $function) {
+ $function('profile2', $profile, $form['profile'][$bundle], $form_state);
+ }
+ }
+ field_attach_submit($profile, $form['profile'][$bundle], $form_state);
+ }
+}
+
+/**
+ * Helper function for checking profile access.
+ */
+function profile2_access(Profile $profile, $op, User $account = NULL) {
+ if ($op == 'edit') {
+ $op = ($profile->isNew() ? 'create' : 'update');
+ }
+ return $profile->access($op, $account);
+}
+
+/**
+ * Implements hook_profile2_access().
+ */
+function profile2_profile2_access($op, Profile $profile, User $account) {
+ if (user_access("$op any $profile->type profile", $account)) {
+ return TRUE;
+ }
+ if (isset($profile->uid) && $profile->uid == $account->uid && user_access("$op own $profile->type profile", $account)) {
+ return TRUE;
+ }
+ // Do not explicitly deny access so others may still grant access.
+}
+
+/**
+ * Implements hook_theme().
+ */
+function profile2_theme() {
+ return array(
+ 'profile2' => array(
+ 'render element' => 'elements',
+ 'template' => 'profile2',
+ ),
+ );
+}
+
+/**
+ * Processes variables for profile2.tpl.php.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - elements: An array of elements to display in view mode.
+ * - profile: The profile object.
+ * - view_mode: View mode; e.g., 'full', 'account'...
+ *
+ * @see profile2.tpl.php
+ */
+function template_preprocess_profile2(&$variables) {
+ $variables['view_mode'] = $variables['elements']['#view_mode'];
+ $variables['profile'] = $variables['elements']['#profile2'];
+ $profile = $variables['profile'];
+
+ $variables['name'] = theme('username', array(
+ 'account' => $profile,
+ 'link_attributes' => array('rel' => 'author'),
+ ));
+
+ $uri = $profile->uri();
+ $variables['url'] = url($uri['path'], $uri['options']);
+ $variables['title'] = check_plain($profile->label());
+ $variables['page'] = $variables['view_mode'] == 'full';
+ $variables['type'] = $profile->bundle();
+
+ // Helpful $content variable for templates.
+ $variables += array('content' => array());
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+
+ // Make the field variables available with the appropriate language.
+ field_attach_preprocess($profile, $variables['content'], $variables);
+
+ // Add article ARIA role.
+ $variables['attributes']['role'] = 'article';
+
+ // Gather template classes.
+ $variables['attributes']['class'][] = 'profile';
+ $variables['attributes']['class'][] = drupal_html_class('profile-' . $profile->bundle());
+ if ($variables['view_mode']) {
+ $variables['attributes']['class'][] = drupal_html_class('view-mode-' . $variables['view_mode']);
+ }
+ if (isset($variables['preview'])) {
+ $variables['attributes']['class'][] = 'preview';
+ }
+
+ $variables['theme_hook_suggestions'][] = 'profile2__' . $profile->bundle();
+ $variables['theme_hook_suggestions'][] = 'profile2__' . $profile->uid;
+}
+
+/**
+ * Implements hook_form_FORMID_alter().
+ *
+ * Adds a checkbox for controlling field view access to fields added to
+ * profiles.
+ */
+function profile2_form_field_ui_field_settings_form_alter(&$form, &$form_state) {
+ // Only add the field setting if this field is attached to a profile entity
+ // bundle.
+ if (isset($form['#field']['bundles']['profile2'])) {
+ $form['field']['settings']['profile2_private'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Private field'),
+ '#default_value' => !empty($form['#field']['settings']['profile2_private']),
+ // Only expose the setting when editing the settings for a field attached
+ // to a profile entity bundle. For other entity types, we just ensure that
+ // the existing value is retained.
+ '#access' => $form['#entity_type'] == 'profile2',
+ '#description' => t('Only show the field content to the profile owner and administrators.'),
+ );
+ }
+}
+
+/**
+ * Implements hook_field_access().
+ */
+function profile2_field_access($op, $field, $entity_type, $profile, $account) {
+ if ($entity_type == 'profile2' && $op == 'view' && !empty($field['settings']['profile2_private']) && !user_access('bypass profile access', $account)) {
+ // Deny view access, if someone else views a private profile field.
+ if ($account->uid != $profile->uid) {
+ return FALSE;
+ }
+ }
+}
diff --git a/core/modules/profile2/profile2.pages.inc b/core/modules/profile2/profile2.pages.inc
new file mode 100644
index 0000000..1df2ba6
--- /dev/null
+++ b/core/modules/profile2/profile2.pages.inc
@@ -0,0 +1,62 @@
+uid);
+ $form['pid'] = array('#type' => 'value', '#value' => $profile->id());
+
+ if ($GLOBALS['user']->uid == $profile->uid) {
+ $confirm_question = t('Are you sure you want to delete your %label profile?', array(
+ '%label' => $profile->label(),
+ ));
+ }
+ else {
+ $confirm_question = t("Are you sure you want to delete %name's %label profile?", array(
+ '%name' => user_format_name($form_state['account']),
+ '%label' => $profile->label(),
+ ));
+ }
+ return confirm_form($form, $confirm_question, $profile->uri(), NULL, t('Delete'));
+}
+
+/**
+ * Form submission handler for profile2_delete_confirm_form().
+ */
+function profile2_delete_confirm_form_submit(array $form, array &$form_state) {
+ $form_state['profile']->delete();
+
+ if ($GLOBALS['user']->uid == $form_state['profile']->uid) {
+ $message = t('Your %label profile has been deleted.', array(
+ '%label' => $form_state['profile']->label(),
+ ));
+ }
+ else {
+ $message = t("%name's %label profile has been deleted.", array(
+ '%name' => user_format_name($form_state['account']),
+ '%label' => $form_state['profile']->label(),
+ ));
+ }
+ drupal_set_message($message);
+
+ // Redirect to the user page.
+ $uri = $form_state['account']->uri();
+ $form_state['redirect'] = array($uri['path'], $uri['options']);
+}
diff --git a/core/modules/profile2/templates/profile2.tpl.php b/core/modules/profile2/templates/profile2.tpl.php
new file mode 100644
index 0000000..52c1d25
--- /dev/null
+++ b/core/modules/profile2/templates/profile2.tpl.php
@@ -0,0 +1,47 @@
+
+>
+
+
+
+ >
+
+
+
+ >
+
+
+
+