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..fe04982 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/Profile.php @@ -0,0 +1,127 @@ +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..c3570c9 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeFormController.php @@ -0,0 +1,91 @@ + 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('Include in user registration form'), + '#default_value' => $type->get('registration'), + ); + return $form; + } + + /** + * Overrides EntityFormController::actions(). + */ + protected function actions(array $form, array &$form_state) { + $actions = parent::actions($form, $form_state); + if (module_exists('field_ui') && $this->getEntity($form_state)->isNew()) { + $actions['save_continue'] = $actions['submit']; + $actions['save_continue']['#value'] = t('Save and manage fields'); + $actions['save_continue']['#submit'][] = array($this, 'redirectToFieldUI'); + } + return $actions; + } + + /** + * 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'; + } + + /** + * Form submission handler to redirect to Manage fields page of Field UI. + */ + public function redirectToFieldUI(array $form, array &$form_state) { + $type = $this->getEntity($form_state); + $form_state['redirect'] = field_ui_bundle_admin_path('profile2', $type->id()) . '/fields'; + } + + /** + * 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..a94c0a4 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php @@ -0,0 +1,65 @@ +get('registration') ? t('Yes') : t('No'); + $row['operations'] = $operations; + return $row; + } + + /** + * Overrides \Drupal\Core\Entity\EntityListController::getOperations(). + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + if (module_exists('field_ui')) { + // Unlike other bundle entities, the most common operation for profile + // types is to manage fields, so we suggest that as default operation. + $uri = $entity->uri(); + $operations['manage-fields'] = array( + 'title' => t('Manage fields'), + 'href' => $uri['path'] . '/fields', + 'options' => $uri['options'], + 'weight' => 5, + ); + $operations['manage-display'] = array( + 'title' => t('Manage display'), + 'href' => $uri['path'] . '/display', + 'options' => $uri['options'], + 'weight' => 6, + ); + } + 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..18756ba --- /dev/null +++ b/core/modules/profile2/profile2.admin.inc @@ -0,0 +1,71 @@ +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() { + drupal_set_title(t('Add profile type')); + $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..e3b48f4 --- /dev/null +++ b/core/modules/profile2/profile2.info @@ -0,0 +1,8 @@ +name = Profile +description = Provides configurable user profiles. +package = Core +core = 8.x +version = VERSION +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..adaf6e0 --- /dev/null +++ b/core/modules/profile2/profile2.module @@ -0,0 +1,551 @@ +get('id'); + $label = $config->get('label'); + $info['profile2'][$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'), + ), + ); + } + return $info; +} + +/** + * 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, + ); + $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, + 'context' => MENU_CONTEXT_INLINE, + 'file' => 'profile2.admin.inc', + ); + + // @todo Move into User module. + $items['user/%user/edit/account'] = array( + 'title' => 'Account', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $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(). + */ +function profile2_menu_local_tasks(&$data, $router_item, $root_path) { + if ($root_path === 'user/%/edit' || $root_path === 'user/%/edit/%') { + $tabs = &$data['tabs'][1]; + // 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) { + // Do not expose profile types that do not have any fields attached yet. + if (!field_info_instances('profile2', $type->id())) { + continue; + } + // 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' => $router_item['tab_root_href'] . '/edit/' . $type->id(), + ), + ); + } + } +} + +/** + * 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_extract_form_values($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 @@ + +
> + + + + > + + + +
> + +
+ +