diff --git a/core/modules/profile2/lib/Drupal/profile2/MetadataController.php b/core/modules/profile2/lib/Drupal/profile2/MetadataController.php deleted file mode 100644 index 4ad55f6..0000000 --- a/core/modules/profile2/lib/Drupal/profile2/MetadataController.php +++ /dev/null @@ -1,69 +0,0 @@ -type]['properties']; - - $properties['label'] = array( - 'label' => t('Label'), - 'description' => t('The profile label.'), - 'setter callback' => 'entity_property_verbatim_set', - 'setter permission' => 'administer profiles', - 'schema field' => 'label', - ); - - $properties['type'] = array( - 'type' => 'profile2_type', - 'getter callback' => 'entity_property_getter_method', - 'setter callback' => 'entity_property_verbatim_set', - 'setter permission' => 'administer profiles', - 'required' => TRUE, - 'description' => t('The profile type.'), - ) + $properties['type']; - - unset($properties['uid']); - - $properties['user'] = array( - 'label' => t("User"), - 'type' => 'user', - 'description' => t("The owner of the profile."), - 'getter callback' => 'entity_property_getter_method', - 'setter callback' => 'entity_property_setter_method', - 'setter permission' => 'administer profiles', - 'required' => TRUE, - 'schema field' => 'uid', - ); - - $properties['created'] = array( - 'label' => t("Date created"), - 'type' => 'date', - 'description' => t("The date the profile was created."), - 'setter callback' => 'entity_property_verbatim_set', - 'setter permission' => 'administer profiles', - 'schema field' => 'created', - ); - $properties['changed'] = array( - 'label' => t("Date changed"), - 'type' => 'date', - 'schema field' => 'changed', - 'description' => t("The date the profile was most recently updated."), - ); - - return $info; - } -} - 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..fd569b8 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/Profile.php @@ -0,0 +1,124 @@ +pid) ? $this->pid : NULL; + } + + /** + * Overrides Entity::bundle(). + */ + public function bundle() { + return $this->type; + } + + /** + * Overrides Entity::label(). + */ + public function label($langcode = NULL) { + 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..da869ab --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/Plugin/Core/Entity/ProfileType.php @@ -0,0 +1,71 @@ +setUser($values['user']); - unset($values['user']); - } - if (isset($values['type']) && is_object($values['type'])) { - $values['type'] = $values['type']->type; - } - if (!isset($values['label']) && isset($values['type']) && $type = profile2_type_load($values['type'])) { - // Initialize the label with the type label, so newly created profiles - // have that as interim label. - $values['label'] = $type->label; - } - parent::__construct($values, $entity_type); - } - - /** - * Returns the user owning this profile. - */ - public function user() { - return user_load($this->uid); - } - - /** - * Sets a new user owning this profile. - * - * @param $account - * The user account object or the user account id (uid). - */ - public function setUser($account) { - $this->uid = is_object($account) ? $account->uid : $account; - } - - /** - * Gets the associated profile type object. - * - * @return ProfileType - */ - public function type() { - return profile2_type_load($this->type); - } - - /** - * Overwrites EntityInterface::id(). - */ - public function id() { - return isset($this->pid) ? $this->pid : NULL; - } - - /** - * Overwrites EntityInterface::bundle(). - */ - public function bundle() { - return $this->type; - } - - /** - * Returns the full url() for the profile. - */ - public function url() { - $uri = $this->uri(); - return url($uri['path'], $uri); - } - - /** - * Returns the drupal path to this profile. - */ - public function path() { - $uri = $this->uri(); - return $uri['path']; - } - - public function defaultUri() { - return array( - 'path' => 'user/' . $this->uid, - 'options' => array('fragment' => 'profile-' . $this->type), - ); - } - - public function defaultLabel() { - if (module_exists('profile2_i18n')) { - // Run the label through i18n_string() using the profile2_type label - // context, so the default label (= the type's label) gets translated. - return entity_i18n_string('profile2:profile2_type:' . $this->type . ':label', $this->label); - } - return $this->label; - } - - public function save() { - // Care about setting created and changed values. But do not automatically - // set a created values for already existing profiles. - if (empty($this->created) && (!empty($this->is_new) || !$this->pid)) { - $this->created = REQUEST_TIME; - } - $this->changed = REQUEST_TIME; - - parent::save(); - // Update the static cache from profile2_load_by_user(). - $cache = &drupal_static('profile2_load_by_user', array()); - if (isset($cache[$this->uid])) { - $cache[$this->uid][$this->type] = $this->pid; - } - } -} - diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileAccessController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileAccessController.php new file mode 100644 index 0000000..c80824b --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileAccessController.php @@ -0,0 +1,128 @@ +access($profile, 'view', $account); + } + + /** + * Implements EntityAccessControllerInterface::createAccess(). + */ + public function createAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) { + // Map to 'edit' access. + return $this->editAccess($profile, $langcode, $account); + } + + /** + * Implements EntityAccessControllerInterface::updateAccess(). + */ + public function updateAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) { + // Map to 'edit' access. + return $this->editAccess($profile, $langcode, $account); + } + + /** + * Implements EntityAccessControllerInterface::deleteAccess(). + */ + public function deleteAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) { + return $this->access($profile, 'delete', $account); + } + + /** + * Checks 'edit' access for a profile. + * + * @param \Drupal\Core\Entity\EntityInterface $profile + * The profile for which to check 'edit' access. + * @param string $langcode + * (optional) The language code for which to check access. Defaults to + * LANGUAGE_DEFAULT. + * @param \Drupal\user\Plugin\Core\Entity\User $account + * (optional) The user for which to check access, or NULL to check access + * for the current user. Defaults to NULL. + * + * @return bool + * TRUE if access was granted, FALSE otherwise. + */ + public function editAccess(EntityInterface $profile, $langcode = LANGUAGE_DEFAULT, User $account = NULL) { + return $this->access($profile, 'edit', $account); + } + + /** + * Determines whether the given user has access to a profile. + * + * @param \Drupal\Core\Entity\EntityInterface $profile + * (optional) A profile to check access for. If nothing is given, access for + * all profiles is determined. + * @param string $operation + * The operation being performed. One of 'view', 'update', 'create', + * 'delete' or just 'edit' (being the same as 'create' or 'update'). + * @param \Drupal\user\Plugin\Core\Entity\User $account + * The user to check for. Leave it to NULL to check for the global user. + * @return boolean + * TRUE if access was granted, FALSE otherwise. + * + * @see hook_profile2_access() + * @see profile2_profile2_access() + */ + protected function access(EntityInterface $profile, $operation, User $account = NULL) { + if (!isset($account)) { + $account = entity_load('user', $GLOBALS['user']->uid); + } + + // Try to retrieve from cache first. + $pid = $profile->id(); + $uid = $account->id(); + if (isset($this->accessCache[$uid][$operation][$pid])) { + return $this->accessCache[$uid][$operation][$pid]; + } + + // Administrators can access any profile. + if (user_access('administer profiles', $account)) { + $this->accessCache[$uid][$operation][$pid] = TRUE; + return TRUE; + } + + $access = NULL; + // Allow modules to grant / deny access. + foreach (module_implements('profile2_access', $operation, $profile, $account) as $module) { + $return = module_invoke($module, 'profile2_access', $operation, $profile, $account); + if ($return === FALSE) { + // Directly return FALSE if a module denies access. + $this->accessCache[$uid][$operation][$pid] = FALSE; + return FALSE; + } + if ($return === TRUE) { + $access = TRUE; + } + } + + $this->accessCache[$uid][$operation][$pid] = $access === TRUE ?: FALSE; + return $this->accessCache[$uid][$operation][$pid]; + } + +} diff --git a/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php b/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php index 5226e09..ccc4bb0 100644 --- a/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileFormController.php @@ -15,12 +15,12 @@ class ProfileFormController extends EntityFormController { /** - * Overrides Drupal\Core\Entity\EntityFormController::actionsElement(). + * Overrides EntityFormController::actionsElement(). */ protected function actionsElement(array $form, array &$form_state) { $element = parent::actionsElement($form, $form_state); - if (!user_access('administer profiles')) { + if (!$this->getEntity($form_state)->access('delete')) { unset($element['delete']); } @@ -28,19 +28,27 @@ protected function actionsElement(array $form, array &$form_state) { } /** - * Overrides Drupal\Core\Entity\EntityFormController::save(). + * Overrides EntityFormController::save(). */ public function save(array $form, array &$form_state) { - global $user; - $profile = $this->getEntity($form_state); $profile->save(); - if ($user->uid == $profile->uid) { + 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..b96959b --- /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/ProfileType.php b/core/modules/profile2/lib/Drupal/profile2/ProfileType.php deleted file mode 100644 index c458069..0000000 --- a/core/modules/profile2/lib/Drupal/profile2/ProfileType.php +++ /dev/null @@ -1,41 +0,0 @@ -getEntity($form_state); $status = $type->save(); if ($status == SAVED_UPDATED) { - drupal_set_message(t('%label configuration has been updated.', array('%label' => $type->label()))); + drupal_set_message(t('%label profile type has been updated.', array('%label' => $type->label()))); } else { - drupal_set_message(t('%label configuration has been inserted.', array('%label' => $type->label()))); + drupal_set_message(t('%label profile type has been created.', array('%label' => $type->label()))); } - $form_state['redirect'] = 'admin/structure/profiles'; + $form_state['redirect'] = 'admin/people/profiles'; } /** - * Overrides Drupal\Core\Entity\EntityFormController::delete(). + * Overrides EntityFormController::delete(). */ public function delete(array $form, array &$form_state) { $type = $this->getEntity($form_state); - $form_state['redirect'] = 'admin/structure/profiles/manage/' . $type->id() . '/delete'; + $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 index 982ee19..7012079 100644 --- a/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeListController.php @@ -16,7 +16,7 @@ class ProfileTypeListController extends EntityListController { /** - * Overrides Drupal\Core\Entity\EntityListController::getOperations(). + * Overrides EntityListController::getOperations(). */ public function getOperations(EntityInterface $entity) { $operations = parent::getOperations($entity); 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..90b4ac8 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/ProfileTypeStorageController.php @@ -0,0 +1,53 @@ +id()); + } + elseif ($entity->original->id() != $entity->id()) { + field_attach_rename_bundle('profile2', $entity->original->id(), $entity->id()); + } + } + + /** + * Overrides ConfigStorageController::preDelete(). + */ + protected function 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/ProfileCRUDTest.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileCRUDTest.php new file mode 100644 index 0000000..8cf3e85 --- /dev/null +++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileCRUDTest.php @@ -0,0 +1,161 @@ + '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 and verify that existing profiles are updated. + // Verify expected behavior of 'uid' => NULL; Profile2 supported this for whatever reason. + } + +} diff --git a/core/modules/profile2/lib/Drupal/profile2/Tests/Profile2CRUDTestCase.php b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileEditTest.php similarity index 66% rename from core/modules/profile2/lib/Drupal/profile2/Tests/Profile2CRUDTestCase.php rename to core/modules/profile2/lib/Drupal/profile2/Tests/ProfileEditTest.php index af282e6..28f220f 100644 --- a/core/modules/profile2/lib/Drupal/profile2/Tests/Profile2CRUDTestCase.php +++ b/core/modules/profile2/lib/Drupal/profile2/Tests/ProfileEditTest.php @@ -1,21 +1,25 @@ 'Editing profiles', - 'description' => 'Tests basic CRUD and editing of Profile2 profiles.', + 'name' => 'Profile editing', + 'description' => 'Tests creating and editing of profiles.', 'group' => 'Profile2', ); } @@ -26,18 +30,17 @@ function setUp() { $type = entity_create('profile2_type', array( 'id' => 'test', 'label' => 'label', - 'weight' => 0 + 'weight' => 0, + 'registration' => TRUE, )); $type->save(); $type = entity_create('profile2_type', array( 'id' => 'test2', 'label' => 'label2', - 'weight' => 2 + 'weight' => 2, )); $type->save(); - entity_load_multiple('profile2', array(), TRUE); - // Add a field to main type, which is created during module installation. $field = array( 'field_name' => 'profile_fullname', 'type' => 'text', @@ -48,7 +51,7 @@ function setUp() { $instance = array( 'entity_type' => 'profile2', 'field_name' => 'profile_fullname', - 'bundle' => 'main', + 'bundle' => 'test', 'label' => 'Full name', 'description' => 'Specify your first and last name.', 'widget' => array( @@ -57,6 +60,8 @@ function setUp() { ), ); field_create_instance($instance); + + $this->checkPermissions(array(), TRUE); } /** @@ -70,19 +75,29 @@ function testCRUD() { $profile = entity_create('profile2', array('type' => 'test', 'uid' => NULL)); $profile->save(); - $profiles = profile2_load_by_user($user1); + $entities = entity_load_multiple_by_properties('profile2', array( + 'uid' => $user1->uid, + )); + $profiles = array(); + foreach ($entities as $item) { + $profiles[$item->type] = $item; + } + $this->assertEqual($profiles['test']->label(), 'label', 'Created and loaded profile 1.'); $this->assertEqual($profiles['test2']->label(), 'label2', 'Created and loaded profile 2.'); - // Test looking up from static cache works also. - $profiles = profile2_load_by_user($user1); - $this->assertEqual($profiles['test']->label, 'label', 'Looked up profiles again.'); - $loaded = entity_load('profile2', $profile->pid); $this->assertEqual($loaded->pid, $profile->pid, 'Loaded profile unrelated to a user.'); $profiles['test']->delete(); - $profiles2 = profile2_load_by_user($user1); + $entities = entity_load_multiple_by_properties('profile2', array( + 'uid' => $user1->uid, + )); + $profiles2 = array(); + foreach ($entities as $item) { + $profiles2[$item->type] = $item; + } + $this->assertEqual(array_keys($profiles2), array('test2'), 'Profile successfully deleted.'); $profiles2['test2']->save(); @@ -106,40 +121,52 @@ function testRegistrationIntegration() { $edit = array(); $edit['name'] = $name = $this->randomName(); $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $edit['profile_main[profile_fullname][und][0][value]'] = $this->randomName(); + $edit['profile_test[profile_fullname][und][0][value]'] = $this->randomName(); $this->drupalPost('user/register', $edit, t('Create new account')); $this->assertText(t('A welcome message with further instructions has been sent to your e-mail address.'), t('User registered successfully.')); $new_user = user_load_by_name($name); $this->assertTrue((bool) $new_user->status, t('New account is active after registration.')); - $this->assertEqual(profile2_load_by_user($new_user, 'main')->profile_fullname[LANGUAGE_NOT_SPECIFIED][0]['value'], $edit['profile_main[profile_fullname][und][0][value]'], 'Profile created.'); + + $profiles = entity_load_multiple_by_properties('profile2', array( + 'uid' => $new_user->uid, + 'type' => 'test', + )); + $profile = reset($profiles); + $this->assertEqual($profile->profile_fullname[LANGUAGE_NOT_SPECIFIED][0]['value'], $edit['profile_test[profile_fullname][und][0][value]'], 'Profile created.'); } /** * Test basic edit and display. */ function testEditAndDisplay() { - user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('edit own main profile', 'view own main profile')); + user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('edit own test profile', 'view own test profile')); $user1 = $this->drupalCreateUser(); $this->drupalLogin($user1); // Make sure access is denied to the profile. - $this->drupalGet('user/' . $user1->uid . '/edit/main'); + $this->drupalGet('user/' . $user1->uid . '/edit/test'); $this->assertText(t('Access denied'), 'Access has been denied.'); // Test creating a profile manually (e.g. by an admin) and ensure the user // may not see it. - entity_create('profile2', array('type' => 'main', 'uid' => $user1->uid))->save(); + entity_create('profile2', array('type' => 'test', 'uid' => $user1->uid))->save(); $this->drupalGet('user/' . $user1->uid); - $this->assertNoText(t('Main profile'), 'Profile data is not visible to the owner.'); + $this->assertNoText('label', 'Profile data is not visible to the owner.'); - $user2 = $this->drupalCreateUser(array('edit own main profile', 'view own main profile')); + $user2 = $this->drupalCreateUser(array('edit own test profile', 'view own test profile')); $this->drupalLogin($user2); // Create profiles for the user2. $edit['profile_fullname[und][0][value]'] = $this->randomName(); - $this->drupalPost('user/' . $user2->uid . '/edit/main', $edit, t('Save')); + $this->drupalPost('user/' . $user2->uid . '/edit/test', $edit, t('Save')); $this->assertText(t('Your profile has been saved.'), 'Profile saved.'); - $this->assertEqual(profile2_load_by_user($user2, 'main')->profile_fullname[LANGUAGE_NOT_SPECIFIED][0]['value'], $edit['profile_fullname[und][0][value]'], 'Profile edited.'); + + $profiles = entity_load_multiple_by_properties('profile2', array( + 'uid' => $user2->uid, + 'type' => 'test', + )); + $profile = reset($profiles); + $this->assertEqual($profile->profile_fullname[LANGUAGE_NOT_SPECIFIED][0]['value'], $edit['profile_fullname[und][0][value]'], 'Profile edited.'); $this->drupalGet('user/' . $user2->uid); $this->assertText(check_plain($edit['profile_fullname[und][0][value]']), 'Profile displayed.'); 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..5abb07a --- /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/lib/Drupal/profile2/TypeUIController.php b/core/modules/profile2/lib/Drupal/profile2/TypeUIController.php deleted file mode 100644 index 1b3c526..0000000 --- a/core/modules/profile2/lib/Drupal/profile2/TypeUIController.php +++ /dev/null @@ -1,26 +0,0 @@ -path]['description'] = 'Manage profiles, including fields.'; - return $items; - } -} - diff --git a/core/modules/profile2/profile2.admin.inc b/core/modules/profile2/profile2.admin.inc index e27858f..34e4d61 100644 --- a/core/modules/profile2/profile2.admin.inc +++ b/core/modules/profile2/profile2.admin.inc @@ -5,7 +5,7 @@ * Profile type editing UI. */ -use Drupal\profile2\ProfileType; +use Drupal\profile2\Plugin\Core\Entity\ProfileType; /** * Page callback: Presents the form for creating a profile type. @@ -21,21 +21,21 @@ function profile2_type_add() { /** * Page callback: Presents the form for editing a profile type. * - * @param Drupal\profile2\ProfileType $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 %name profile type', array('%name' => $type->label))); + 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\ProfileType $type + * @param Drupal\profile2\Plugin\Core\Entity\ProfileType $type * The ProfileType object to delete. */ function profile2_type_delete_form($form, &$form_state, ProfileType $type) { @@ -44,7 +44,7 @@ function profile2_type_delete_form($form, &$form_state, ProfileType $type) { $form['id'] = array('#type' => 'value', '#value' => $type->id()); return confirm_form($form, t('Are you sure you want to delete %label', array('%label' => $type->label())), - 'admin/structure/profiles', + 'admin/people/profiles', NULL, t('Delete') ); @@ -55,5 +55,5 @@ function profile2_type_delete_form($form, &$form_state, ProfileType $type) { */ function profile2_type_delete_form_submit($form, &$form_state) { $form_state['profile2_type']->delete(); - $form_state['redirect'] = 'admin/structure/profiles'; + $form_state['redirect'] = 'admin/people/profiles'; } diff --git a/core/modules/profile2/profile2.api.php b/core/modules/profile2/profile2.api.php index 02cdffb..ffe8a85 100644 --- a/core/modules/profile2/profile2.api.php +++ b/core/modules/profile2/profile2.api.php @@ -7,271 +7,15 @@ * Drupal manner. */ +use Drupal\profile2\Plugin\Core\Entity\Profile; +use Drupal\user\Plugin\Core\Entity\User; + /** * @addtogroup hooks * @{ */ /** -* Act on profiles being loaded from the database. -* -* This hook is invoked during profile loading, which is handled by -* entity_load(), via the EntityCRUDController. -* -* @param $entities -* An array of profile2 entities being loaded, keyed by id. -* -* @see hook_entity_load() -*/ -function hook_profile2_load($entities) { - $result = db_query('SELECT pid, foo FROM {mytable} WHERE pid IN(:ids)', array(':ids' => array_keys($entities))); - foreach ($result as $record) { - $entities[$record->pid]->foo = $record->foo; - } -} - -/** -* Respond when a profile is inserted. -* -* This hook is invoked after the profile is inserted into the database. -* -* @param profile -* The profile that is being inserted. -* -* @see hook_entity_insert() -*/ -function hook_profile2_insert($profile) { - db_insert('mytable') - ->fields(array( - 'pid' => $profile->pid, - 'extra' => $profile->extra, - )) - ->execute(); -} - -/** -* Act on a profile being inserted or updated. -* -* This hook is invoked before the profile is saved to the database. -* -* @param $profile -* The profile that is being inserted or updated. -* -* @see hook_entity_presave() -*/ -function hook_profile2_presave($profile) { - $profile->extra = 'foo'; -} - -/** -* Respond to a profile being updated. -* -* This hook is invoked after the profile has been updated in the database. -* -* @param $profile -* The $profile that is being updated. -* -* @see hook_entity_update() -*/ -function hook_profile2_update($profile) { - db_update('mytable') - ->fields(array('extra' => $profile->extra)) - ->condition('pid', $profile->pid) - ->execute(); -} - -/** -* Respond to profile deletion. -* -* This hook is invoked after the profile has been removed from the database. -* -* @param $profile -* The profile that is being deleted. -* -* @see hook_entity_delete() -*/ -function hook_profile2_delete($profile) { - db_delete('mytable') - ->condition('pid', $profile->pid) - ->execute(); -} - -/** -* Act on a profile that is being assembled before rendering. -* -* @param $profile -* The profile entity. -* @param $view_mode -* The view mode the profile is rendered in. -* @param $langcode -* The language code used for rendering. -* -* The module may add elements to $profile->content prior to rendering. The -* structure of $profile->content is a renderable array as expected by -* drupal_render(). -* -* @see hook_entity_prepare_view() -* @see hook_entity_view() -*/ -function hook_profile2_view($profile, $view_mode, $langcode) { - $profile->content['my_additional_field'] = array( - '#markup' => $additional_field, - '#weight' => 10, - '#theme' => 'mymodule_my_additional_field', - ); -} - -/** -* Alter the results of entity_view() for profiles. -* -* @param $build -* A renderable array representing the profile content. -* -* This hook is called after the content has been assembled in a structured -* array and may be used for doing processing which requires that the complete -* profile content structure has been built. -* -* If the module wishes to act on the rendered HTML of the profile rather than -* the structured content array, it may use this hook to add a #post_render -* callback. Alternatively, it could also implement hook_preprocess_profile2(). -* See drupal_render() and theme() documentation respectively for details. -* -* @see hook_entity_view_alter() -*/ -function hook_profile2_view_alter($build) { - if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) { - // Change its weight. - $build['an_additional_field']['#weight'] = -10; - - // Add a #post_render callback to act on the rendered HTML of the entity. - $build['#post_render'][] = 'my_module_post_render'; - } -} - -/** - * Act on profile type being loaded from the database. - * - * This hook is invoked during profile type loading, which is handled by - * entity_load(), via the EntityCRUDController. - * - * @param $types - * An array of profiles being loaded, keyed by profile type names. - */ -function hook_profile2_type_load($types) { - if (isset($types['main'])) { - $types['main']->userCategory = FALSE; - $types['main']->userView = FALSE; - } -} - -/** - * Respond when a profile type is inserted. - * - * This hook is invoked after the profile type is inserted into the database. - * - * @param $type - * The profile type that is being inserted. - */ -function hook_profile2_type_insert($type) { - db_insert('mytable') - ->fields(array( - 'id' => $type->id, - 'extra' => $type->extra, - )) - ->execute(); -} - -/** - * Act on a profile type being inserted or updated. - * - * This hook is invoked before the profile type is saved to the database. - * - * @param $type - * The profile type that is being inserted or updated. - */ -function hook_profile2_type_presave($type) { - $type->extra = 'foo'; -} - -/** - * Respond to updates to a profile. - * - * This hook is invoked after the profile type has been updated in the database. - * - * @param $type - * The profile type that is being updated. - */ -function hook_profile2_type_update($type) { - db_update('mytable') - ->fields(array('extra' => $type->extra)) - ->condition('id', $type->id) - ->execute(); -} - -/** - * Respond to profile type deletion. - * - * This hook is invoked after the profile type has been removed from the - * database. - * - * @param $type - * The profile type that is being deleted. - */ -function hook_profile2_type_delete($type) { - db_delete('mytable') - ->condition('id', $type->id) - ->execute(); -} - -/** - * Define default profile type configurations. - * - * @return - * An array of default profile types, keyed by profile type names. - */ -function hook_default_profile2_type() { - $types['main'] = new ProfileType(array( - 'type' => 'main', - 'label' => t('Profile'), - 'weight' => 0, - 'locked' => TRUE, - )); - return $types; -} - -/** -* Alter default profile type configurations. -* -* @param $defaults -* An array of default profile types, keyed by type names. -* -* @see hook_default_profile2_type() -*/ -function hook_default_profile2_type_alter(&$defaults) { - $defaults['main']->label = 'custom label'; -} - -/** - * Alter profile2 forms. - * - * Modules may alter the profile2 entity form regardless to which form it is - * attached by making use of this hook or the profile type specifiy - * hook_form_profile2_edit_PROFILE_TYPE_form_alter(). #entity_builders may be - * used in order to copy the values of added form elements to the entity, just - * as described by entity_form_submit_build_entity(). - * - * @param $form - * Nested array of form elements that comprise the form. - * @param $form_state - * A keyed array containing the current state of the form. - * - * @see profile2_attach_form() - */ -function hook_form_profile2_form_alter(&$form, &$form_state) { - // Your alterations. -} - -/** * Control access to profiles. * * Modules may implement this hook if they want to have a say in whether or not @@ -281,31 +25,25 @@ function hook_form_profile2_form_alter(&$form, &$form_state) { * The operation being performed. One of 'view', 'edit' (being the same as * 'create' or 'update') and 'delete'. * @param $profile - * (optional) A profile to check access for. If nothing is given, access for - * all profiles is determined. + * A profile to check access for. * @param $account - * (optional) The user to check for. If no account is passed, access is - * determined for the global user. + * (optional) The user to check for. Defaults to the currently logged in user. * @return boolean * Return TRUE to grant access, FALSE to explicitly deny access. Return NULL * or nothing to not affect the operation. * Access is granted as soon as a module grants access and no one denies * access. Thus if no module explicitly grants access, access will be denied. - * - * @see profile2_access() */ -function hook_profile2_access($op, $profile = NULL, $account = NULL) { - if (isset($profile)) { - // Explicitly deny access for a 'secret' profile type. - if ($profile->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. +function hook_profile2_access($op, Profile $profile, User $account = NULL) { + // Explicitly deny access for a 'secret' profile type. + if ($profile->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 index ee70a83..b0cbee3 100644 --- a/core/modules/profile2/profile2.info +++ b/core/modules/profile2/profile2.info @@ -2,4 +2,5 @@ name = Profile2 description = Provides configurable user profiles. core = 8.x dependencies[] = user -configure = admin/structure/profiles +dependencies[] = field +configure = admin/people/profiles diff --git a/core/modules/profile2/profile2.info.inc b/core/modules/profile2/profile2.info.inc deleted file mode 100644 index e786084..0000000 --- a/core/modules/profile2/profile2.info.inc +++ /dev/null @@ -1,23 +0,0 @@ - $type) { - $properties['profile_' . $type_name] = array( - 'type' => 'profile2', - 'label' => t('@type_name profile', array('@type_name' => drupal_ucfirst($type->label))), - 'description' => t("The users's @type_name profile.", array('@type_name' => $type->label)), - 'getter callback' => 'profile2_user_get_properties', - 'bundle' => $type_name, - ); - } -} diff --git a/core/modules/profile2/profile2.install b/core/modules/profile2/profile2.install index 9ebabe1..1fbb5ae 100644 --- a/core/modules/profile2/profile2.install +++ b/core/modules/profile2/profile2.install @@ -6,26 +6,6 @@ */ /** - * Implements hook_install(). - */ -function profile2_install() { - // Add an initial profile type, but only if installed manually. In case the - // module is installed via an installation profile, skip that. - if (!drupal_installation_attempted()) { - entity_info_cache_clear(); - $type = entity_create('profile2_type', array( - 'id' => 'main', - 'label' => t('Main profile'), - 'weight' => 0, - 'registration' => TRUE, - )); - $type->save(); - user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('edit own main profile', 'view own main profile')); - drupal_set_message(t('A main profile type has been created and assigned to all users. Go to the Profile types page to add some fields or to configure further profile types.', array('!url' => url('admin/structure/profiles')))); - } -} - -/** * Implements hook_schema(). */ function profile2_schema() { @@ -37,6 +17,12 @@ function profile2_schema() { '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', @@ -71,6 +57,7 @@ function profile2_schema() { ), 'indexes' => array( 'uid' => array('uid'), + 'type' => array('type'), ), 'foreign keys' => array( 'uid' => array( @@ -79,6 +66,9 @@ function profile2_schema() { ), ), '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 index 24c93f8..9cfabae 100644 --- a/core/modules/profile2/profile2.module +++ b/core/modules/profile2/profile2.module @@ -5,107 +5,51 @@ * Support for configurable user profiles. */ -use Drupal\profile2\Profile; -use Drupal\profile2\ProfileType; -use Drupal\user\User; +use Drupal\profile2\Plugin\Core\Entity\Profile; +use Drupal\profile2\Plugin\Core\Entity\ProfileType; +use Drupal\user\Plugin\Core\Entity\User; /** - * Implements hook_entity_info(). + * Implements hook_entity_info_alter(). */ -function profile2_entity_info() { - $return = array( - 'profile2' => array( - 'label' => t('Profile'), - 'entity class' => 'Drupal\profile2\Profile', - 'base table' => 'profile', - 'uri callback' => 'profile2_profile_uri', - 'fieldable' => TRUE, - 'form controller class' => array( - 'default' => 'Drupal\profile2\ProfileFormController', - ), - 'render controller class' => 'Drupal\Core\Entity\EntityRenderController', - 'view modes' => array( - 'account' => array( - 'label' => t('User account'), - 'custom settings' => FALSE, - ), - ), - 'entity keys' => array( - 'id' => 'pid', - 'bundle' => 'type', - 'label' => 'label', - ), - 'bundles' => array(), - 'bundle keys' => array( - 'bundle' => 'id', - ), - 'access callback' => 'profile2_access', - ), - ); - +function profile2_entity_info_alter(&$info) { // Add bundle info but bypass entity_load() as we cannot use it here. $config_names = config_get_storage_names_with_prefix('profile2.type.'); foreach ($config_names as $config_name) { $config = config($config_name); - - $return['profile2']['bundles'][$config->get('id')] = array( + $info['profile2']['bundles'][$config->get('id')] = array( 'label' => $config->get('label'), 'admin' => array( - 'path' => 'admin/structure/profiles/manage/%profile2_type', - 'real path' => 'admin/structure/profiles/manage/' . $config->get('id'), + 'path' => 'admin/people/profiles/manage/%profile2_type', + 'real path' => 'admin/people/profiles/manage/' . $config->get('id'), 'bundle argument' => 4, 'access arguments' => array('administer profiles'), ), ); } - - // Support entity cache module. - if (module_exists('entitycache')) { - $return['profile2']['field cache'] = FALSE; - $return['profile2']['entity cache'] = TRUE; - } - - $return['profile2_type'] = array( - 'label' => t('Profile type'), - 'entity class' => 'Drupal\profile2\ProfileType', - 'uri callback' => 'profile2_profile_type_uri', - 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController', - 'list controller class' => 'Drupal\profile2\ProfileTypeListController', - 'form controller class' => array( - 'default' => 'Drupal\profile2\ProfileTypeFormController', - ), - 'config prefix' => 'profile2.type', - 'entity keys' => array( - 'id' => 'id', - 'label' => 'label', - ), - ); - - return $return; } /** * Entity URI callback for profiles. * - * @param Drupal\profile2\Profile $profile - * A Profile entity. + * @param Drupal\profile2\Plugin\Core\Entity\Profile $profile + * A profile entity. */ function profile2_profile_uri(Profile $profile) { - // @todo Call into user_uri() to ensure consistent URIs? - return array( - 'path' => 'user/' . $profile->uid, - ); + $uri = entity_load('user', $profile->uid)->uri(); + $uri['options']['fragment'] = 'profile-' . $profile->bundle(); + return $uri; } /** * Entity URI callback for profile types. * - * @param Drupal\profile2\ProfileType $profile_type + * @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/structure/profiles/manage/' . $profile_type->id(), + 'path' => 'admin/people/profiles/manage/' . $profile_type->id(), ); } @@ -113,37 +57,35 @@ function profile2_profile_type_uri(ProfileType $profile_type) { * Implements hook_menu(). */ function profile2_menu() { - $items['admin/structure/profiles'] = array( + $items['admin/people/profiles'] = array( 'title' => 'Profile types', 'description' => 'Manage profiles, 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, ); - $items['admin/structure/profiles/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -10, - ); - $items['admin/structure/profiles/add'] = array( + $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/structure/profiles/manage/%profile2_type'] = array( + $items['admin/people/profiles/manage/%profile2_type'] = array( 'title' => 'Edit profile type', 'page callback' => 'profile2_type_edit', 'page arguments' => array(4), 'access arguments' => array('administer profile types'), 'file' => 'profile2.admin.inc', ); - $items['admin/structure/profiles/manage/%profile2_type/edit'] = array( + $items['admin/people/profiles/manage/%profile2_type/edit'] = array( 'title' => 'Edit', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); - $items['admin/structure/profiles/manage/%profile2_type/delete'] = array( + $items['admin/people/profiles/manage/%profile2_type/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('profile2_type_delete_form', 4), @@ -162,12 +104,24 @@ function profile2_menu() { $items['user/%user/edit/%profile2_menu'] = array( 'load arguments' => array('%map', 'edit'), 'title' => 'Edit profile', - 'access callback' => 'profile2_profile_edit_access', - 'access arguments' => array(3), + 'title callback' => 'profile2_menu_title_profile', + 'title arguments' => array(3), + 'access callback' => 'profile2_access', + 'access arguments' => array('edit', 3), 'page callback' => 'profile2_profile_edit', 'page arguments' => array(3), 'type' => MENU_LOCAL_TASK, ); + $items['user/%user/edit/%profile2_menu/delete'] = array( + 'load arguments' => array('%map', 'edit'), + 'title' => 'Delete profile', + 'access callback' => 'profile2_access', + 'access arguments' => array('delete', 3), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('profile2_delete_confirm_form', 3), + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + 'file' => 'profile2.pages.inc', + ); return $items; } @@ -183,7 +137,7 @@ function profile2_menu() { * (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\Profile|false + * @return Drupal\profile2\Plugin\Core\Entity\Profile|false * The profile of type $type_id of the user account, or FALSE. */ function profile2_menu_load($type_id, $map, $op = '') { @@ -194,8 +148,11 @@ function profile2_menu_load($type_id, $map, $op = '') { return FALSE; } $account = $map[1]; - $profile = profile2_load_by_user($account, $type_id); - if ($op == 'edit' && empty($profile)) { + $profiles = entity_load_multiple_by_properties('profile2', array( + 'uid' => $account->id(), + 'type' => $type_id, + )); + if ($op == 'edit' && !$profile = reset($profiles)) { $profile = entity_create('profile2', array( 'type' => $type_id, 'uid' => $account->uid, @@ -205,6 +162,19 @@ function profile2_menu_load($type_id, $map, $op = '') { } /** + * Menu title callback for a profile edit link. + * + * @param Drupal\profile2\Plugin\Core\Entity\Profile $profile + * The profile to return a title for. + * + * @return string + * The unsanitized profile label. + */ +function profile2_menu_title_profile(Profile $profile) { + return $profile->label(); +} + +/** * Implements hook_menu_local_tasks_alter(). */ function profile2_menu_local_tasks_alter(&$data, $router_item, $root_path) { @@ -220,7 +190,7 @@ function profile2_menu_local_tasks_alter(&$data, $router_item, $root_path) { } } // Expand the dynamic %profile_menu argument into a tab for each type. - $types = profile2_get_types(); + $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. @@ -262,7 +232,7 @@ function profile2_type_list_page() { * @param string $id * The machine-readable name of a profile type to load. * - * @return Drupal\profile2\ProfileType|false + * @return Drupal\profile2\Plugin\Core\Entity\ProfileType|false * A profile type array or FALSE if $type does not exist. */ function profile2_type_load($id) { @@ -284,7 +254,7 @@ function profile2_permission() { ), ); // Generate per profile type permissions. - foreach (profile2_get_types() as $type) { + foreach (entity_load_multiple('profile2_type') as $type) { $type_name = $type->id(); $permissions += array( "edit own $type_name profile" => array( @@ -299,90 +269,23 @@ function profile2_permission() { "view any $type_name profile" => array( 'title' => t('%type_name: View any profile', array('%type_name' => $type->label())), ), + "delete own $type_name profile" => array( + 'title' => t('%type_name: Delete own profile', array('%type_name' => $type->label())), + ), + "delete any $type_name profile" => array( + 'title' => t('%type_name: Delete any profile', array('%type_name' => $type->label())), + ), ); } return $permissions; } /** - * Gets an array of all profile types, keyed by the machine name. - * - * @return array - * An array of profile type objects. - */ -function profile2_get_types() { - return entity_load_multiple('profile2_type'); -} - -/** - * Fetch profiles by account. - * - * @param $account - * The user account to load profiles for, or its uid. - * @param $type_name - * To load a single profile, pass the type name of the profile to load. - * @return - * Either a single profile or an array of profiles keyed by profile type. - * - * @see profile2_profile2_delete() - * @see Profile::save() - */ -function profile2_load_by_user($account, $type_name = NULL) { - // Use a separate query to determine all profile ids per user and cache them. - // That way we can look up profiles by id and benefit from the static cache - // of the entity loader. - $cache = &drupal_static(__FUNCTION__, array()); - $uid = is_object($account) ? $account->uid : $account; - - if (!isset($cache[$uid])) { - if (empty($type_name)) { - $profiles = entity_load_multiple_by_properties('profile2', array('uid' => $uid)); - // Cache ids for further lookups. - $cache[$uid] = array(); - foreach ($profiles as $pid => $profile) { - $cache[$uid][$profile->type] = $pid; - } - return $profiles ? array_combine(array_keys($cache[$uid]), $profiles) : array(); - } - $cache[$uid] = db_select('profile', 'p') - ->fields('p', array('type', 'pid')) - ->condition('uid', $uid) - ->execute() - ->fetchAllKeyed(); - } - if (isset($type_name)) { - return isset($cache[$uid][$type_name]) ? entity_load('profile2', $cache[$uid][$type_name]) : FALSE; - } - // Return an array containing profiles keyed by profile type. - return $cache[$uid] ? array_combine(array_keys($cache[$uid]), entity_load_multiple('profile2', $cache[$uid])) : $cache[$uid]; -} - -/** - * Implements hook_profile2_delete(). - */ -function profile2_profile2_delete($profile) { - // Clear the static cache from profile2_load_by_user(). - $cache = &drupal_static('profile2_load_by_user', array()); - unset($cache[$profile->uid][$profile->type]); -} - -/** - * Implements hook_user_delete(). + * Implements hook_user_predelete(). */ -function profile2_user_delete($account) { - foreach (profile2_load_by_user($account) as $profile) { - $profile->delete(); - } -} - -/** -* Implements hook_profile2_type_delete() -*/ -function profile2_profile2_type_delete(ProfileType $type) { - // Delete all profiles of this type. - $pids = array_keys(entity_load_multiple_by_properties('profile2', array('type' => $type->id()))); - if ($pids) { - entity_delete_multiple('profile2', $pids); +function profile2_user_predelete($account) { + if ($profiles = entity_load_multiple_by_properties('profile2', array('uid' => $account->id()))) { + entity_get_controller('profile2')->delete($profiles); } } @@ -390,16 +293,21 @@ function profile2_profile2_type_delete(ProfileType $type) { * Implements hook_user_view(). */ function profile2_user_view($account, $view_mode, $langcode) { - foreach (profile2_get_types() as $id => $type) { - if ($type->userView && $profile = profile2_load_by_user($account, $id)) { - if (profile2_access('view', $profile)) { - $account->content['profile_' . $id] = array( - '#type' => 'user_profile_category', - '#title' => $profile->label, - '#prefix' => '', - ); + foreach (entity_load_multiple('profile2_type') as $id => $type) { + $profiles = entity_load_multiple_by_properties('profile2', array( + 'uid' => $account->id(), + 'type' => $id, + )); + + if ($profile = reset($profiles)) { + if ($profile->access('view')) { entity_render_controller('profile2')->buildContent(array($profile), 'account'); - $account->content['profile_' . $id]['view'] = $profile->content; + $account->content['profile_' . $id] = array( + '#theme' => 'profile2', + '#title' => $profile->label(), + '#prefix' => '', + '#profile' => $profile, + ) + $profile->content; } } } @@ -409,7 +317,7 @@ function profile2_user_view($account, $view_mode, $langcode) { * Implements hook_form_FORM_ID_alter() for the registration form. */ function profile2_form_user_register_form_alter(&$form, &$form_state) { - foreach (profile2_get_types() as $id => $type) { + 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)); @@ -428,23 +336,9 @@ function profile2_form_user_register_form_alter(&$form, &$form_state) { } /** - * Menu access callback; Determines edit access for a requested profile. - * - * @param Drupal\profile2\Profile $profile - * The profile to check for edit access. - * - * @return bool - * - * @see profile2_access() - */ -function profile2_profile_edit_access(Profile $profile) { - return profile2_access('edit', $profile); -} - -/** * Page callback; Presents the form to edit a profile of a user. * - * @param Drupal\profile2\Profile $profile + * @param Drupal\profile2\Plugin\Core\Entity\Profile $profile * The user account profile to edit. * * @return array @@ -483,7 +377,7 @@ function profile2_attach_form(&$form, &$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/structure/profiles'))), + '#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'))), ); } @@ -568,57 +462,29 @@ function profile2_form_submit_cleanup(&$form, &$form_state) { } /** - * Determines whether the given user has access to a profile. + * Helper function for checking profile access. * - * @param $op - * The operation being performed. One of 'view', 'update', 'create', 'delete' - * or just 'edit' (being the same as 'create' or 'update'). - * @param $profile - * (optional) A profile to check access for. If nothing is given, access for - * all profiles is determined. - * @param $account - * The user to check for. Leave it to NULL to check for the global user. - * @return boolean - * Whether access is allowed or not. - * - * @see hook_profile2_access() - * @see profile2_profile2_access() + * @todo Remove once the routes are converted to the new routing system. */ -function profile2_access($op, Profile $profile = NULL, User $account = NULL) { - if (user_access('administer profiles', $account)) { - return TRUE; - } - if ($op == 'create' || $op == 'update') { - $op = 'edit'; - } - // Allow modules to grant / deny access. - $access = module_invoke_all('profile2_access', $op, $profile, $account); - - // Only grant access if at least one module granted access and no one denied - // access. - if (in_array(FALSE, $access, TRUE)) { - return FALSE; - } - elseif (in_array(TRUE, $access, TRUE)) { - return TRUE; - } - return FALSE; +function profile2_access($op, Profile $profile, User $account = NULL) { + return $profile->access($op, $account); } /** * Implements hook_profile2_access(). */ -function profile2_profile2_access($op, Profile $profile = NULL, User $account = NULL) { +function profile2_profile2_access($op, Profile $profile, User $account) { // Don't grant access for users to delete their profile. - if (isset($profile) && ($type_name = $profile->type) && $op != 'delete') { - if (user_access("$op any $type_name profile", $account)) { - return TRUE; - } - $account = isset($account) ? $account : $GLOBALS['user']; + if ($op == 'delete') { + return; + } - if (isset($profile->uid) && $profile->uid == $account->uid && user_access("$op own $type_name profile", $account)) { - return TRUE; - } + 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. } @@ -636,6 +502,59 @@ function profile2_theme() { } /** + * 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']['#profile']; + $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('profile2', $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 @@ -675,13 +594,3 @@ function profile2_field_access($op, $field, $entity_type, $profile = NULL, $acco } } } - -/** - * Entity metadata callback to load profiles for the given user account. - */ -function profile2_user_get_properties($account, array $options, $name) { - // Remove the leading 'profile_' from the property name to get the type name. - $profile = profile2_load_by_user($account, substr($name, 8)); - return $profile ? $profile : NULL; -} - 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/profile2.tpl.php b/core/modules/profile2/profile2.tpl.php deleted file mode 100644 index bd5bd4d..0000000 --- a/core/modules/profile2/profile2.tpl.php +++ /dev/null @@ -1,44 +0,0 @@ - -
> - - - > - - - - -
> - -
-
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 @@ + +
> + + + + > + + + +
> + +
+ +