a few more base modules

This commit is contained in:
Bachir Soussi Chiadmi
2016-09-06 16:05:07 +02:00
parent c6cff46234
commit 027aa99b32
455 changed files with 43606 additions and 0 deletions
@@ -0,0 +1,72 @@
<?php
namespace Drupal\profile\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Drupal\profile\Entity\ProfileTypeInterface;
/**
* Checks access to add, edit and delete profiles.
*/
class ProfileAccessCheck implements AccessInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a ProfileAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* Checks access to the profile add page for the profile type.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* The profile type entity.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, AccountInterface $account, ProfileTypeInterface $profile_type = NULL) {
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('profile');
if ($account->hasPermission('administer profile types')) {
return AccessResult::allowed()->cachePerPermissions();
}
$operation = $route->getRequirement('_profile_access_check');
if ($operation == 'add') {
return $access_control_handler->access($profile_type, $operation, $account, TRUE);
}
if ($profile_type) {
return $access_control_handler->createAccess($profile_type->id(), $account, [], TRUE);
}
// If checking whether a profile of any type may be created.
foreach ($this->entityTypeManager->getStorage('profile_type')->loadMultiple() as $profile_type) {
if (($access = $access_control_handler->createAccess($profile_type->id(), $account, [], TRUE)) && $access->isAllowed()) {
return $access;
}
}
// No opinion.
return AccessResult::neutral();
}
}
@@ -0,0 +1,177 @@
<?php
namespace Drupal\profile\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\profile\Entity\ProfileInterface;
use Drupal\profile\Entity\ProfileTypeInterface;
use Drupal\profile\Entity\Profile;
use Drupal\user\UserInterface;
/**
* Returns responses for ProfileController routes.
*/
class ProfileController extends ControllerBase implements ContainerInjectionInterface {
/**
* Provides the profile submission form.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\user\UserInterface $user
* The user account.
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* The profile type entity for the profile.
*
* @return array
* A profile submission form.
*/
public function addProfile(RouteMatchInterface $route_match, UserInterface $user, ProfileTypeInterface $profile_type) {
$profile = $this->entityTypeManager()->getStorage('profile')->create([
'uid' => $user->id(),
'type' => $profile_type->id(),
]);
return $this->entityFormBuilder()->getForm($profile, 'add', ['uid' => $user->id(), 'created' => REQUEST_TIME]);
}
/**
* Provides the profile edit form.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\user\UserInterface $user
* The user account.
* @param \Drupal\profile\Entity\ProfileInterface $profile
* The profile entity to edit.
*
* @return array
* The profile edit form.
*/
public function editProfile(RouteMatchInterface $route_match, UserInterface $user, ProfileInterface $profile) {
return $this->entityFormBuilder()->getForm($profile, 'edit');
}
/**
* Provides profile delete form.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\user\UserInterface $user
* The user account.
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* The profile type entity for the profile.
* @param int $id
* The id of the profile to delete.
*
* @return array
* Returns form array.
*/
public function deleteProfile(RouteMatchInterface $route_match, UserInterface $user, ProfileTypeInterface $profile_type, $id) {
return $this->entityFormBuilder()->getForm(Profile::load($id), 'delete');
}
/**
* The _title_callback for the entity.profile.add_form route.
*
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* The current profile type.
*
* @return string
* The page title.
*/
public function addPageTitle(ProfileTypeInterface $profile_type) {
// @todo: edit profile uses this form too?
return $this->t('Create @label', ['@label' => $profile_type->label()]);
}
/**
* Provides profile create form.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\user\UserInterface $user
* The user account.
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* The profile type entity for the profile.
*
* @return array
* Returns form array.
*/
public function userProfileForm(RouteMatchInterface $route_match, UserInterface $user, ProfileTypeInterface $profile_type) {
/** @var \Drupal\profile\Entity\ProfileType $profile_type */
/** @var \Drupal\profile\Entity\ProfileInterface|bool $active_profile */
$active_profile = $this->entityTypeManager()->getStorage('profile')
->loadByUser($user, $profile_type->id());
// If the profile type does not support multiple, only display an add form
// if there are no entities, or an edit for the current.
if (!$profile_type->getMultiple()) {
// If there is an active profile, provide edit form.
if ($active_profile) {
return $this->editProfile($route_match, $user, $active_profile);
}
// Else show the add form.
return $this->addProfile($route_match, $user, $profile_type);
}
// Display active, and link to create a profile.
else {
$build = [];
// If there is no active profile, display add form.
if (!$active_profile) {
return $this->addProfile($route_match, $user, $profile_type);
}
$build['add_profile'] = Link::createFromRoute(
$this->t('Add new @type', ['@type' => $profile_type->label()]),
"entity.profile.type.{$profile_type->id()}.user_profile_form.add",
['user' => \Drupal::currentUser()->id(), 'profile_type' => $profile_type->id()])
->toRenderable();
// Render the active profiles.
$build['active_profiles'] = [
'#type' => 'view',
'#name' => 'profiles',
'#display_id' => 'profile_type_listing',
'#arguments' => [$user->id(), $profile_type->id(), 1],
'#embed' => TRUE,
'#title' => $this->t('Active @type', ['@type' => $profile_type->label()]),
'#pre_render' => [
['\Drupal\views\Element\View', 'preRenderViewElement'],
'profile_views_add_title_pre_render',
],
];
return $build;
}
}
/**
* Mark profile as default.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The route match.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect back to the currency listing.
*/
public function setDefault(RouteMatchInterface $routeMatch) {
$profile = $routeMatch->getParameter('profile');
$profile->setDefault(TRUE);
$profile->save();
drupal_set_message($this->t('The %label profile has been marked as default.', ['%label' => $profile->label()]));
$url = $profile->urlInfo('collection');
return $this->redirect($url->getRouteName(), $url->getRouteParameters(), $url->getOptions());
}
}
@@ -0,0 +1,61 @@
<?php
namespace Drupal\profile\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
/**
* Defines a controller to render a single profile entity.
*/
class ProfileViewController extends EntityViewController {
/**
* {@inheritdoc}
*/
public function view(EntityInterface $profile, $view_mode = 'full', $langcode = NULL) {
$build = [
'profiles' => \Drupal::entityTypeManager()
->getViewBuilder($profile->getEntityTypeId())
->view($profile, $view_mode, $langcode),
];
$build['#title'] = $profile->label();
foreach ($profile->uriRelationships() as $rel) {
// Set the profile path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = [
[
'rel' => $rel,
'href' => $profile->toUrl($rel)->toString(),
],
TRUE,
];
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = [
[
'rel' => 'shortlink',
'href' => $profile->toUrl($rel, ['alias' => TRUE])->toString(),
],
TRUE,
];
}
}
return $build;
}
/**
* The _title_callback for the page that renders a profile entity.
*
* @param \Drupal\Core\Entity\EntityInterface $profile
* The current profile.
*
* @return string
* The page title.
*/
public function title(EntityInterface $profile) {
return $this->entityManager->getTranslationFromContext($profile)->label();
}
}
@@ -0,0 +1,275 @@
<?php
namespace Drupal\profile\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\user\UserInterface;
/**
* Defines the profile entity class.
*
* @ContentEntityType(
* id = "profile",
* label = @Translation("Profile"),
* bundle_label = @Translation("Profile"),
* handlers = {
* "storage" = "Drupal\profile\ProfileStorage",
* "view_builder" = "Drupal\profile\ProfileViewBuilder",
* "views_data" = "Drupal\profile\ProfileViewsData",
* "access" = "Drupal\profile\ProfileAccessControlHandler",
* "list_builder" = "Drupal\profile\ProfileListBuilder",
* "form" = {
* "default" = "Drupal\profile\Form\ProfileForm",
* "add" = "Drupal\profile\Form\ProfileForm",
* "edit" = "Drupal\profile\Form\ProfileForm",
* "delete" = "Drupal\profile\Form\ProfileDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\profile\ProfileHtmlRouteProvider",
* },
* },
* bundle_entity_type = "profile_type",
* field_ui_base_route = "entity.profile_type.edit_form",
* admin_permission = "administer profiles",
* base_table = "profile",
* revision_table = "profile_revision",
* fieldable = TRUE,
* entity_keys = {
* "id" = "profile_id",
* "revision" = "revision_id",
* "bundle" = "type",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/profile/{profile}",
* "edit-form" = "/profile/{profile}/edit",
* "delete-form" = "/profile/{profile}/delete",
* "collection" = "/admin/config/people/profiles",
* "set-default" = "/profile/{profile}/set-default"
* },
* common_reference_target = TRUE,
* )
*/
class Profile extends ContentEntityBase implements ProfileInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Owner'))
->setDescription(t('The user that owns this profile.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setSetting('handler', 'default');
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Active status'))
->setDescription(t('A boolean indicating whether the profile is active.'))
->setDefaultValue(TRUE)
->setRevisionable(TRUE);
$fields['is_default'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Default'))
->setDescription(t('A boolean indicating whether the profile is the default one.'))
->setRevisionable(TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the profile was created.'))
->setRevisionable(TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the profile was last edited.'))
->setRevisionable(TRUE);
return $fields;
}
/**
* Overrides Entity::id().
*/
public function id() {
return $this->get('profile_id')->value;
}
/**
* {@inheritdoc}
*/
public function label() {
$profile_type = ProfileType::load($this->bundle());
return
t('@type profile of @username (uid: @uid)',
[
'@type' => $profile_type->label(),
'@username' => $this->getOwner()->getDisplayName(),
'@uid' => $this->getOwnerId(),
]);
}
/**
* {@inheritdoc}
*/
public function getType() {
return $this->bundle();
}
/**
* {@inheritdoc}
*/
public function setType($type) {
$this->set('type', $this->bundle());
return $this;
}
/**
* {@inheritdoc}
*/
public function getOwnerId() {
return $this->get('uid')->target_id;
}
/**
* {@inheritdoc}
*/
public function setOwnerId($uid) {
$this->set('uid', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public function getOwner() {
return $this->get('uid')->entity;
}
/**
* {@inheritdoc}
*/
public function setOwner(UserInterface $account) {
$this->set('uid', $account->id());
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
$this->set('created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionCreationTime() {
return $this->get('revision_timestamp')->value;
}
/**
* {@inheritdoc}
*/
public function setRevisionCreationTime($timestamp) {
$this->set('revision_timestamp', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionAuthor() {
return $this->get('revision_uid')->entity;
}
/**
* {@inheritdoc}
*/
public function setRevisionAuthorId($uid) {
$this->set('revision_uid', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public function isActive() {
return (bool) $this->get('status')->value;
}
/**
* {@inheritdoc}
*/
public function setActive($active) {
$this->set('status', $active ? PROFILE_ACTIVE : PROFILE_NOT_ACTIVE);
return $this;
}
/**
* {@inheritdoc}
*/
public function isDefault() {
return (bool) $this->get('is_default')->value;
}
/**
* {@inheritdoc}
*/
public function setDefault($is_default) {
$this->set('is_default', $is_default ? PROFILE_DEFAULT : PROFILE_NOT_DEFAULT);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheTagsToInvalidate() {
$tags = parent::getCacheTagsToInvalidate();
return Cache::mergeTags($tags, [
'user:' . $this->getOwnerId(),
'user_view',
]);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
/** @var \Drupal\profile\ProfileStorage $storage */
parent::postSave($storage, $update);
// Check if this profile is, or became the default.
if ($this->isDefault()) {
/** @var \Drupal\profile\Entity\ProfileInterface[] $profiles */
$profiles = $storage->loadMultipleByUser($this->getOwner(), $this->getType());
// Ensure that all other profiles are set to not default.
foreach ($profiles as $profile) {
if ($profile->id() != $this->id() && $profile->isDefault()) {
$profile->setDefault(FALSE);
$profile->save();
}
}
}
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\profile\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an interface defining a profile entity.
*/
interface ProfileInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
/**
* Returns the profile type.
*
* @return string
* The profile type name.
*/
public function getType();
/**
* Sets the profile type.
*
* @param string $type
* The profile type.
*
* @return $this
*/
public function setType($type);
/**
* Returns the profile creation timestamp.
*/
public function getCreatedTime();
/**
* Sets the profile creation timestamp.
*
* @param int $timestamp
* The profile creation timestamp.
*
* @return $this
*/
public function setCreatedTime($timestamp);
/**
* Returns the profile revision creation timestamp.
*
* @return int
* The UNIX timestamp of when this revision was created.
*/
public function getRevisionCreationTime();
/**
* Sets the profile revision creation timestamp.
*
* @param int $timestamp
* The UNIX timestamp of when this revision was created.
*
* @return $this
*/
public function setRevisionCreationTime($timestamp);
/**
* Returns the profile revision author.
*
* @return \Drupal\user\UserInterface
* The user entity for the revision author.
*/
public function getRevisionAuthor();
/**
* Sets the profile revision author.
*
* @param int $uid
* The user ID of the revision author.
*
* @return $this
*/
public function setRevisionAuthorId($uid);
/**
* Returns a label for the profile.
*/
public function label();
/**
* Returns the node published status indicator.
*
* Unpublished profiles are only visible to their authors and administrators.
*
* @return bool
* TRUE if the profile is active.
*/
public function isActive();
/**
* Sets the published status of a profile.
*
* @param bool $active
* TRUE to set this profile to active, FALSE to set it to inactive.
*
* @return $this
*/
public function setActive($active);
/**
* Returns the profile default status indicator.
*
* @return bool
* TRUE if the profile is default.
*/
public function isDefault();
/**
* Sets the default status of a profile.
*
* @param bool $is_default
* TRUE to set this profile to default, FALSE to set it to not default.
*
* @return $this
*/
public function setDefault($is_default);
}
@@ -0,0 +1,174 @@
<?php
namespace Drupal\profile\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
/**
* Defines the profile type entity class.
*
* @ConfigEntityType(
* id = "profile_type",
* label = @Translation("Profile type"),
* handlers = {
* "list_builder" = "Drupal\profile\ProfileTypeListBuilder",
* "form" = {
* "default" = "Drupal\profile\Form\ProfileTypeForm",
* "add" = "Drupal\profile\Form\ProfileTypeForm",
* "edit" = "Drupal\profile\Form\ProfileTypeForm",
* "delete" = "Drupal\profile\Form\ProfileTypeDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* admin_permission = "administer profile types",
* config_prefix = "type",
* bundle_of = "profile",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "registration",
* "multiple",
* "roles",
* "weight",
* "status",
* "langcode"
* },
* links = {
* "add-form" = "/admin/config/people/profiles/types/add",
* "delete-form" = "/admin/config/people/profiles/types/manage/{profile_type}/delete",
* "edit-form" = "/admin/config/people/profiles/types/manage/{profile_type}",
* "admin-form" = "/admin/config/people/profiles/types/manage/{profile_type}",
* "collection" = "/admin/config/people/profiles/types"
* }
* )
*/
class ProfileType extends ConfigEntityBundleBase implements ProfileTypeInterface {
/**
* The primary identifier of the profile type.
*
* @var integer
*/
protected $id;
/**
* The universally unique identifier of the profile type.
*
* @var string
*/
protected $uuid;
/**
* The human-readable name of the profile type.
*
* @var string
*/
protected $label;
/**
* Whether the profile type is shown during registration.
*
* @var boolean
*/
protected $registration = FALSE;
/**
* Whether the profile type allows multiple profiles.
*
* @var boolean
*/
protected $multiple = FALSE;
/**
* Which roles a user needs to have to attach profiles of this type.
*
* @var array
*/
protected $roles = [];
/**
* The weight of the profile type compared to others.
*
* @var integer
*/
protected $weight = 0;
/**
* {@inheritdoc}
*/
public function getRegistration() {
return $this->registration;
}
/**
* {@inheritdoc}
*/
public function setRegistration($registration) {
$this->registration = $registration;
return $this;
}
/**
* {@inheritdoc}
*/
public function getMultiple() {
return $this->multiple;
}
/**
* {@inheritdoc}
*/
public function setMultiple($multiple) {
$this->multiple = $multiple;
return $this;
}
/**
* {@inheritdoc}
*/
public function getRoles() {
return $this->roles;
}
/**
* {@inheritdoc}
*/
public function setRoles($roles) {
$this->roles = $roles;
return $this;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->weight = $weight;
return $this;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// @todo Setting ->setRebuildNeeded isn't enough. Investigate.
\Drupal::service('router.builder')->rebuild();
}
}
@@ -0,0 +1,73 @@
<?php
namespace Drupal\profile\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a profile type entity.
*/
interface ProfileTypeInterface extends ConfigEntityInterface {
/**
* Return the registration form flag.
*
* For allowing creation of profile type at user registration.
*/
public function getRegistration();
/**
* Return the allow multiple flag.
*
* @return bool
* TRUE if multiple allowed.
*/
public function getMultiple();
/**
* Set the allow multiple flag.
*
* @param bool $multiple
* Boolean for the allow multiple flag.
*
* @return $this
*/
public function setMultiple($multiple);
/**
* Return the user roles allowed by this profile type.
*
* @return array
* Array of Drupal user roles ids.
*/
public function getRoles();
/**
* Set the user roles allowed by this profile type.
*
* @param array $roles
* Array of Drupal user roles ids.
*
* @return $this
*/
public function setRoles($roles);
/**
* Returns the profile type's weight.
*
* @return int
* The weight.
*/
public function getWeight();
/**
* Sets the profile type's weight.
*
* @param int $weight
* The profile type's weight.
*
* @return $this
*/
public function setWeight($weight);
}
@@ -0,0 +1,124 @@
<?php
namespace Drupal\profile\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a node deletion confirmation form.
*/
class DeleteMultiple extends ConfirmFormBase {
/**
* The array of nodes to delete.
*
* @var array
*/
protected $profiles = [];
/**
* The private_tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $privateTempStoreFactory;
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $manager;
/**
* Constructs a DeleteMultiple form object.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
* The entity manager.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $manager) {
$this->privateTempStoreFactory = $temp_store_factory;
$this->storage = $manager->getStorage('profile');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.private_tempstore'),
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'profile_multiple_delete_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return \Drupal::translation()->formatPlural(count($this->profiles), 'Are you sure you want to delete this profile?', 'Are you sure you want to delete these profiles?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.profile.collection');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Delete');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->profiles = $this->privateTempStoreFactory->get('profile_multiple_delete_confirm')->get(\Drupal::currentUser()->id());
if (empty($this->profiles)) {
return new RedirectResponse(\Drupal::url('entity.profile.collection', [], ['absolute' => TRUE]));
}
$form['profiles'] = [
'#theme' => 'item_list',
'#items' => array_map(function ($profile) {
return $profile->label();
}, $this->profiles),
];
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('confirm') && !empty($this->profiles)) {
$this->storage->delete($this->profiles);
$this->privateTempStoreFactory->get('profile_multiple_delete_confirm')->delete(\Drupal::currentUser()->id());
$count = count($this->profiles);
$this->logger('content')->notice('Deleted @count profiles.', ['@count' => $count]);
drupal_set_message(\Drupal::translation()->formatPlural($count, 'Deleted 1 profile.', 'Deleted @count profiles.'));
}
$form_state->setRedirect('entity.profile.collection');
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\profile\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Url;
/**
* Provides a confirmation form for deleting a profile entity.
*/
class ProfileDeleteForm extends ContentEntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.user.canonical', [
'user' => $this->entity->getOwnerId(),
]);
}
/**
* {@inheritdoc}
*/
protected function getRedirectUrl() {
return $this->getCancelUrl();
}
}
@@ -0,0 +1,89 @@
<?php
namespace Drupal\profile\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\profile\Entity\ProfileType;
/**
* Form controller for profile forms.
*/
class ProfileForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
$entity = parent::buildEntity($form, $form_state);
if ($entity->isNew()) {
$entity->setCreatedTime(REQUEST_TIME);
}
return $entity;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$element = parent::actions($form, $form_state);
/** @var \Drupal\profile\Entity\ProfileInterface $profile */
$profile = $this->entity;
// Add an "Activate" button.
$element['set_default'] = $element['submit'];
$element['set_default']['#value'] = t('Save and make default');
$element['set_default']['#weight'] = 10;
$element['set_default']['#access'] = !$profile->isDefault();
array_unshift($element['set_default']['#submit'], [$this, 'setDefault']);
return $element;
}
/**
* Form submission handler for the 'set_default' action.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* A reference to a keyed array containing the current state of the form.
*/
public function setDefault(array $form, FormStateInterface $form_state) {
$form_state->setValue('is_default', TRUE);
}
/**
* Form submission handler for the 'deactivate' action.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* A reference to a keyed array containing the current state of the form.
*/
public function deactivate(array $form, FormStateInterface $form_state) {
$form_state->setValue('status', FALSE);
$form_state->setValue('is_default', TRUE);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$profile_type = ProfileType::load($this->entity->bundle());
switch ($this->entity->save()) {
case SAVED_NEW:
drupal_set_message($this->t('%label profile has been created.', ['%label' => $profile_type->label()]));
break;
case SAVED_UPDATED:
drupal_set_message($this->t('%label profile has been updated.', ['%label' => $profile_type->label()]));
break;
}
$form_state->setRedirect('entity.user.canonical', [
'user' => $this->entity->getOwnerId(),
]);
}
}
@@ -0,0 +1,60 @@
<?php
namespace Drupal\profile\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Entity\Query\QueryFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a confirmation form for deleting a Profile type entity.
*/
class ProfileTypeDeleteForm extends EntityDeleteForm {
/**
* The query factory to create entity queries.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $queryFactory;
/**
* Constructs a new ProductTypeDeleteForm object.
*
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
* The entity query object.
*/
public function __construct(QueryFactory $query_factory) {
$this->queryFactory = $query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$num_profiles = $this->queryFactory->get('profile')
->condition('type', $this->entity->id())
->count()
->execute();
if ($num_profiles) {
$caption = '<p>' . \Drupal::translation()
->formatPlural($num_profiles, '%type is used by 1 profile on your site. You can not remove this profile type until you have removed all of the %type profiles.', '%type is used by @count profiles on your site. You may not remove %type until you have removed all of the %type profiles.', ['%type' => $this->entity->label()]) . '</p>';
$form['#title'] = $this->entity->label();
$form['description'] = ['#markup' => $caption];
return $form;
}
return parent::buildForm($form, $form_state);
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\profile\Form;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field_ui\FieldUI;
use Drupal\user\Entity\Role;
/**
* Form controller for profile type forms.
*/
class ProfileTypeForm extends BundleEntityFormBase {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$type = $this->entity;
if ($this->operation == 'add') {
$form['#title'] = $this->t('Add profile type');
}
else {
$form['#title'] = $this->t('Edit %label profile type', ['%label' => $type->label()]);
}
$form['label'] = [
'#title' => t('Label'),
'#type' => 'textfield',
'#default_value' => $type->label(),
'#description' => t('The human-readable name of this profile type.'),
'#required' => TRUE,
'#size' => 30,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $type->id(),
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
'#machine_name' => [
'exists' => '\Drupal\profile\Entity\ProfileType::load',
'source' => ['label'],
],
];
$form['registration'] = [
'#type' => 'checkbox',
'#title' => t('Include in user registration form'),
'#default_value' => $type->getRegistration(),
];
$form['multiple'] = [
'#type' => 'checkbox',
'#title' => t('Allow multiple profiles'),
'#default_value' => $type->getMultiple(),
];
$form['roles'] = [
'#type' => 'checkboxes',
'#title' => t('Allowed roles'),
'#description' => $this->t('Limit the users that can have this profile by role.</br><em>None will indicate that all users can have this profile type.</em>'),
'#options' => [],
'#default_value' => $type->getRoles(),
];
foreach (Role::loadMultiple() as $role) {
/** @var \Drupal\user\Entity\Role $role */
// We aren't interested in anon role.
if ($role->id() !== Role::ANONYMOUS_ID) {
$form['roles']['#options'][$role->id()] = $role->label();
}
}
return $this->protectBundleIdElement($form);
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
if (\Drupal::moduleHandler()->moduleExists('field_ui') &&
$this->getEntity()->isNew()
) {
$actions['save_continue'] = $actions['submit'];
$actions['save_continue']['#value'] = t('Save and manage fields');
$actions['save_continue']['#submit'][] = [$this, 'redirectToFieldUI'];
}
return $actions;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$type = $this->entity;
$status = $type->save();
if ($status == SAVED_UPDATED) {
drupal_set_message(t('%label profile type has been updated.', ['%label' => $type->label()]));
}
else {
drupal_set_message(t('%label profile type has been created.', ['%label' => $type->label()]));
}
$form_state->setRedirect('entity.profile_type.collection');
}
/**
* Form submission handler to redirect to Manage fields page of Field UI.
*/
public function redirectToFieldUI(array $form, FormStateInterface $form_state) {
if ($form_state->getTriggeringElement()['#parents'][0] === 'save_continue' && $route_info = FieldUI::getOverviewRouteInfo('profile', $this->entity->id())) {
$form_state->setRedirectUrl($route_info);
}
}
/**
* {@inheritdoc}
*/
public function delete(array $form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.profile_type.delete_form', [
'profile_type' => $this->entity->id(),
]);
}
}
@@ -0,0 +1,77 @@
<?php
namespace Drupal\profile\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Redirects to a profile deletion form.
*
* @Action(
* id = "profile_delete_action",
* label = @Translation("Delete selected profile"),
* type = "profile",
* confirm_form_route_name = "entity.profile.multiple_delete_confirm"
* )
*/
class DeleteProfile extends ActionBase implements ContainerFactoryPluginInterface {
/**
* The private tempstore object.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $privateTempStore;
/**
* Constructs a new DeleteNode object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\user\PrivateTempStoreFactory $private_temp_store
* The tempstore factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $private_temp_store) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->privateTempStore = $private_temp_store->get('profile_multiple_delete_confirm');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('user.private_tempstore'));
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
$this->privateTempStore->set(\Drupal::currentUser()->id(), $entities);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\profile\Entity\ProfileInterface $object */
return $object->access('delete', $account, $return_as_object);
}
}
@@ -0,0 +1,39 @@
<?php
namespace Drupal\profile\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Publishes a profile.
*
* @Action(
* id = "profile_publish_action",
* label = @Translation("Publish selected profile"),
* type = "profile"
* )
*/
class PublishProfile extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
/** @var \Drupal\profile\Entity\ProfileInterface $entity */
$entity->setActive(TRUE);
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\profile\Entity\ProfileInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}
@@ -0,0 +1,39 @@
<?php
namespace Drupal\profile\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a profile.
*
* @Action(
* id = "profile_unpublish_action",
* label = @Translation("Unpublish selected profile"),
* type = "profile"
* )
*/
class UnpublishProfile extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
/** @var \Drupal\profile\Entity\ProfileInterface $entity */
$entity->setActive(FALSE);
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\profile\Entity\ProfileInterface $object */
$access = $object->access('update', $account, TRUE)
->andIf($object->status->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\profile\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic routes to add/edit/list profiles.
*/
class ProfileLocalTask extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new ProfileAddLocalTask.
*
* @param string $base_plugin_definition
* The base plugin definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct($base_plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_definition) {
return new static(
$base_plugin_definition,
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
// Starting weight for ordering the local tasks.
$weight = 10;
foreach ($this->entityTypeManager->getStorage('profile_type')->loadMultiple() as $profile_type_id => $profile_type) {
$this->derivatives["profile.type.$profile_type_id"] = [
'title' => $profile_type->label(),
'route_name' => "entity.profile.type.$profile_type_id.user_profile_form",
'base_route' => 'entity.user.canonical',
'route_parameters' => ['profile_type' => $profile_type_id],
'weight' => ++$weight,
] + $base_plugin_definition;
}
return $this->derivatives;
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\profile\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides specific access control for the profile entity type.
*
* @EntityReferenceSelection(
* id = "default:profile",
* label = @Translation("Profile selection"),
* entity_types = {"profile"},
* group = "default",
* weight = 1
* )
*/
class ProfileSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['target_bundles']['#title'] = $this->t('Profile types');
return $form;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Drupal\profile\Plugin\views\field;
use Drupal\system\Plugin\views\field\BulkForm;
/**
* Defines a profile operations bulk form element.
*
* @ViewsField("profile_bulk_form")
*/
class ProfileBulkForm extends BulkForm {
/**
* {@inheritdoc}
*/
protected function emptySelectedMessage() {
return t('No profile selected.');
}
}
@@ -0,0 +1,112 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Drupal\profile\Entity\ProfileType;
/**
* Defines the access control handler for the profile entity type.
*
* @see \Drupal\profile\Entity\Profile
*/
class ProfileAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) {
$account = $this->prepareUser($account);
if ($account->hasPermission('bypass profile access')) {
$result = AccessResult::allowed()->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
$result = parent::createAccess($entity_bundle, $account, $context, TRUE)->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*
* When the $operation is 'add' then the $entity is of type 'profile_type',
* otherwise $entity is of type 'profile'.
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
$account = $this->prepareUser($account);
$user_page = \Drupal::request()->attributes->get('user');
// Some times, operation edit is called update.
// Use edit in any case.
if ($operation == 'update') {
$operation = 'edit';
}
// Check that if profile type has require roles, the user the profile is
// being added to has any of the required roles.
if ($entity->getEntityTypeId() == 'profile') {
$profile_roles = ProfileType::load($entity->bundle())->getRoles();
// Retrieve all user roles including locked roles.
$user_roles = $entity->getOwner()->getRoles();
if (!empty(array_filter($profile_roles)) && !array_intersect($user_roles, $profile_roles)) {
return AccessResult::forbidden();
}
}
elseif ($entity->getEntityTypeId() == 'profile_type') {
$profile_roles = $entity->getRoles();
// Retrieve all user roles including locked roles.
$user_roles = User::load($user_page->id())->getRoles();
if (!empty(array_filter($profile_roles)) && !array_intersect($user_roles, $profile_roles)) {
return AccessResult::forbidden();
}
}
if ($account->hasPermission('bypass profile access')) {
return AccessResult::allowed()->cachePerPermissions();
}
elseif (
(
$operation == 'add'
&& (
(
$user_page->id() == $account->id()
&& $account->hasPermission($operation . ' own ' . $entity->id() . ' profile')
)
|| $account->hasPermission($operation . ' any ' . $entity->id() . ' profile')
)
) || (
$operation != 'add'
&& (
(
$entity->getOwnerId() == $account->id()
&& $account->hasPermission($operation . ' own ' . $entity->getType() . ' profile')
)
|| $account->hasPermission($operation . ' any ' . $entity->getType() . ' profile')
)
)
){
return AccessResult::allowed()->cachePerPermissions();
}
else {
// No opinion.
return AccessResult::neutral()->cachePerPermissions();
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermissions($account, [
'add any ' . $entity_bundle . ' profile',
'add own ' . $entity_bundle . ' profile',
], 'OR');
}
}
@@ -0,0 +1,59 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
use Drupal\profile\Entity\ProfileType;
use Symfony\Component\Routing\Route;
/**
* Provides HTML routes for the profile entity type.
*/
class ProfileHtmlRouteProvider extends DefaultHtmlRouteProvider {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = parent::getRoutes($entity_type);
/** @var \Drupal\profile\Entity\ProfileTypeInterface $profile_type */
foreach (ProfileType::loadMultiple() as $profile_type) {
$route = (new Route(
"/user/{user}/{profile_type}",
['_controller' => '\Drupal\profile\Controller\ProfileController::userProfileForm',
'_title_callback' => '\Drupal\profile\Controller\ProfileController::addPageTitle'
],
['_profile_access_check' => 'add'],
[
'parameters' => [
'user' => ['type' => 'entity:user'],
'profile_type' => ['type' => 'entity:profile_type'],
],
])
);
$collection->add("entity.profile.type.{$profile_type->id()}.user_profile_form", $route);
// If the profile type supports multiple, we need an additional route for
// adding new profiles.
if ($profile_type->getMultiple()) {
$route = (new Route(
"/user/{user}/{profile_type}/add",
['_controller' => '\Drupal\profile\Controller\ProfileController::addProfile'],
['_profile_access_check' => 'add'],
[
'parameters' => [
'user' => ['type' => 'entity:user'],
'profile_type' => ['type' => 'entity:profile_type'],
],
])
);
$collection->add("entity.profile.type.{$profile_type->id()}.user_profile_form.add", $route);
}
}
return $collection;
}
}
@@ -0,0 +1,140 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* List controller for profiles.
*
* @see \Drupal\profile\Entity\Profile
*/
class ProfileListBuilder extends EntityListBuilder {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatter
*/
protected $dateFormatter;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new ProfileListController object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
* The date formatter service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatter $date_formatter, RendererInterface $renderer) {
parent::__construct($entity_type, $storage);
$this->dateFormatter = $date_formatter;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('date.formatter'),
$container->get('renderer')
);
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header = [
'label' => $this->t('Label'),
'type' => [
'data' => $this->t('Type'),
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
],
'owner' => [
'data' => $this->t('Owner'),
'class' => [RESPONSIVE_PRIORITY_LOW],
],
'status' => $this->t('Status'),
'is_default' => $this->t('Default'),
'changed' => [
'data' => $this->t('Updated'),
'class' => [RESPONSIVE_PRIORITY_LOW],
],
];
if (\Drupal::languageManager()->isMultilingual()) {
$header['language_name'] = [
'data' => $this->t('Language'),
'class' => [RESPONSIVE_PRIORITY_LOW],
];
}
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\profile\Entity\ProfileInterface $entity */
$langcode = $entity->language()->getId();
$uri = $entity->toUrl();
$options = $uri->getOptions();
$options += ($langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED && isset($languages[$langcode]) ? ['language' => $languages[$langcode]] : []);
$uri->setOptions($options);
$row['label'] = $entity->toLink();
$row['type'] = $entity->getType();
$row['owner']['data'] = [
'#theme' => 'username',
'#account' => $entity->getOwner(),
];
$row['status'] = $entity->isActive() ? $this->t('active') : $this->t('not active');
$row['is_default'] = $entity->isDefault() ? $this->t('default') : $this->t('not default');
$row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');
$language_manager = \Drupal::languageManager();
if ($language_manager->isMultilingual()) {
$row['language_name'] = $language_manager->getLanguageName($langcode);
}
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
if (!$entity->isDefault()) {
$operations['set_default'] = [
'title' => $this->t('Mark as default'),
'url' => $entity->toUrl('set-default'),
'parameter' => $entity,
];
}
return $operations;
}
}
@@ -0,0 +1,72 @@
<?php
namespace Drupal\profile;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\profile\Entity\ProfileType;
/**
* Defines a class containing permission callbacks.
*/
class ProfilePermissions {
use StringTranslationTrait;
/**
* Returns an array of profile type permissions.
*
* @return array
* Returns an array of permissions.
*/
public function profileTypePermissions() {
$perms = [];
// Generate profile permissions for all profile types.
foreach (ProfileType::loadMultiple() as $type) {
$perms += $this->buildPermissions($type);
}
return $perms;
}
/**
* Builds a standard list of permissions for a given profile type.
*
* @param \Drupal\profile\Entity\ProfileType $profile_type
* The machine name of the profile type.
*
* @return array
* An array of permission names and descriptions.
*/
protected function buildPermissions(ProfileType $profile_type) {
$type_id = $profile_type->id();
$type_params = ['%type' => $profile_type->label()];
return [
"add own $type_id profile" => [
'title' => $this->t('%type: Add own profile', $type_params),
],
"add any $type_id profile" => [
'title' => $this->t('%type: Add any profile', $type_params),
],
"view own $type_id profile" => [
'title' => $this->t('%type: View own profile', $type_params),
],
"view any $type_id profile" => [
'title' => $this->t('%type: View any profile', $type_params),
],
"edit own $type_id profile" => [
'title' => $this->t('%type: Edit own profile', $type_params),
],
"edit any $type_id profile" => [
'title' => $this->t('%type: Edit any profile', $type_params),
],
"delete own $type_id profile" => [
'title' => $this->t('%type: Delete own profile', $type_params),
],
"delete any $type_id profile" => [
'title' => $this->t('%type: Delete any profile', $type_params),
],
];
}
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the entity storage for profile.
*/
class ProfileStorage extends SqlContentEntityStorage implements ProfileStorageInterface {
/**
* {@inheritdoc}
*/
public function loadByUser(AccountInterface $account, $profile_type, $active = PROFILE_ACTIVE) {
$result = $this->loadByProperties([
'uid' => $account->id(),
'type' => $profile_type,
'status' => $active,
]);
return reset($result);
}
/**
* {@inheritdoc}
*/
public function loadMultipleByUser(AccountInterface $account, $profile_type, $active = PROFILE_ACTIVE) {
return $this->loadByProperties([
'uid' => $account->id(),
'type' => $profile_type,
'status' => $active,
]);
}
/**
* {@inheritdoc}
*/
public function loadDefaultByUser(AccountInterface $account, $profile_type) {
$result = $this->loadByProperties([
'uid' => $account->id(),
'type' => $profile_type,
'status' => PROFILE_ACTIVE,
'is_default' => TRUE,
]);
return reset($result);
}
}
@@ -0,0 +1,56 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for profile entity storage.
*/
interface ProfileStorageInterface extends EntityStorageInterface {
/**
* Loads the given user's profile.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user entity.
* @param string $profile_type
* The profile type.
* @param bool $active
* Boolean representing if profile active or not.
*
* @return \Drupal\profile\Entity\ProfileInterface
* The loaded profile entity.
*/
public function loadByUser(AccountInterface $account, $profile_type, $active);
/**
* Loads the given user's profiles.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user entity.
* @param string $profile_type
* The profile type.
* @param bool $active
* Boolean representing if profile active or not.
*
* @return \Drupal\profile\Entity\ProfileInterface[]
* An array of loaded profile entities.
*/
public function loadMultipleByUser(AccountInterface $account, $profile_type, $active);
/**
* Loads the default user profile.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user entity.
* @param string $profile_type
* The profile type.
*
* @return \Drupal\profile\Entity\ProfileInterface
* An array of loaded profile entities.
*/
public function loadDefaultByUser(AccountInterface $account, $profile_type);
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\profile;
use Drupal\profile\Entity\ProfileTypeInterface;
use Drupal\profile\Entity\ProfileType;
use Drupal\profile\Entity\Profile;
use Drupal\user\UserInterface;
/**
* Provides methods to create additional profiles and profile_types.
*
* This trait is meant to be used only by test classes.
*/
trait ProfileTestTrait {
/**
* Creates a profile type for tests.
*
* @param string $id
* The profile type machine name.
* @param string $label
* The profile type human display name.
* @param bool|FALSE $registration
* Boolean if profile type shows on registration form.
* @param array $roles
* Array of user role machine names.
*
* @return \Drupal\profile\Entity\ProfileTypeInterface
* Returns a profile type entity.
*/
protected function createProfileType($id = NULL, $label = NULL, $registration = FALSE, $roles = []) {
$id = !empty($id) ? $id : $this->randomMachineName();
$label = !empty($label) ? $label : $this->randomMachineName();
$type = ProfileType::create([
'id' => $id,
'label' => $label,
'registration' => $registration,
'roles' => $roles,
]);
$type->save();
return $type;
}
/**
* Create a user, and optionally a profile.
*
* @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
* A profile type for the created profile entity.
* @param \Drupal\user\UserInterface $user
* A user to create a profile.
*
* @return \Drupal\profile\Entity\ProfileInterface
* A profile for a user.
*/
protected function createProfile(ProfileTypeInterface $profile_type, UserInterface $user) {
$profile = Profile::create([
'type' => $profile_type->id(),
'uid' => $user->id(),
]);
$profile->save();
return $profile;
}
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
/**
* List controller for profile types.
*/
class ProfileTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['type'] = t('Profile type');
$header['registration'] = t('Registration');
$header['multiple'] = t('Allow multiple profiles');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['type'] = $entity->toLink(NULL, 'edit-form');
$row['registration'] = $entity->getRegistration() ? t('Yes') : t('No');
$row['multiple'] = $entity->getMultiple() ? t('Yes') : t('No');
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {
$operations['edit'] = [
'title' => t('Edit'),
'weight' => 30,
'url' => $entity->toUrl('edit-form'),
];
}
if (isset($operations['delete'])) {
$operations['delete'] = [
'title' => t('Delete'),
'weight' => 35,
'url' => $entity->toUrl('delete-form'),
];
}
// Sort the operations to normalize link order.
uasort($operations, [
'Drupal\Component\Utility\SortArray',
'sortByWeightElement',
]);
return $operations;
}
}
@@ -0,0 +1,22 @@
<?php
namespace Drupal\profile;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityViewBuilder;
/**
* Render controller for profile entities.
*/
class ProfileViewBuilder extends EntityViewBuilder {
/**
* {@inheritdoc}
*/
protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
$defaults = parent::getBuildDefaults($entity, $view_mode);
$defaults['#theme'] = 'profile';
return $defaults;
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\profile;
use Drupal\views\EntityViewsData;
/**
* Provides the views data for the node entity type.
*/
class ProfileViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['profile']['profile_bulk_form'] = [
'title' => t('Profile operations bulk form'),
'help' => t('Add a form element that lets you run operations on multiple profiles.'),
'field' => [
'id' => 'profile_bulk_form',
],
];
return $data;
}
}
@@ -0,0 +1,411 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\profile\Entity\Profile;
use Drupal\user\Entity\User;
/**
* Tests basic CRUD functionality of profiles.
*
* @group profile
*/
class ProfileDefaultTest extends ProfileTestBase {
/**
* Testing demo user 1.
*
* @var \Drupal\user\UserInterface
*/
public $user1;
/**
* Testing demo user 2.
*
* @var \Drupal\user\UserInterface;
*/
public $user2;
/**
* Profile entity storage.
*
* @var \Drupal\profile\ProfileStorageInterface
*/
public $profileStorage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access user profiles',
'administer profiles',
'administer profile types',
'bypass profile access',
'access administration pages',
]);
$this->user1 = User::create([
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
]);
$this->user1->save();
$this->user2 = User::create([
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
]);
$this->user2->save();
}
/**
* Tests profiles are active by default.
*/
public function testProfileActive() {
$profile_type = $this->createProfileType('test_defaults', 'test_defaults');
// Create new profiles.
$profile1 = Profile::create($expected = [
'type' => $profile_type->id(),
'uid' => $this->user1->id(),
]);
$profile1->save();
$this->assertTrue($profile1->isActive());
$profile1->setActive(PROFILE_NOT_ACTIVE);
$profile1->save();
$this->assertFalse($profile1->isActive());
}
/**
* Tests default profile functionality.
*/
public function testDefaultProfile() {
$profile_type = $this->createProfileType('test_defaults', 'test_defaults');
// Create new profiles.
$profile1 = Profile::create($expected = [
'type' => $profile_type->id(),
'uid' => $this->user1->id(),
]);
$profile1->save();
$profile2 = Profile::create($expected = [
'type' => $profile_type->id(),
'uid' => $this->user1->id(),
]);
$profile2->setDefault(TRUE);
$profile2->save();
$this->assertFalse($profile1->isDefault());
$this->assertTrue($profile2->isDefault());
$profile1->setDefault(TRUE)->save();
$this->assertFalse(Profile::load($profile2->id())->isDefault());
$this->assertTrue(Profile::load($profile1->id())->isDefault());
}
/**
* Tests loading default from storage handler.
*/
public function testLoadDefaultProfile() {
$profile_type = $this->createProfileType('test_defaults', 'test_defaults');
// Create new profiles.
$profile1 = Profile::create($expected = [
'type' => $profile_type->id(),
'uid' => $this->user1->id(),
]);
$profile1->setActive(TRUE);
$profile1->save();
$profile2 = Profile::create($expected = [
'type' => $profile_type->id(),
'uid' => $this->user1->id(),
]);
$profile2->setActive(TRUE);
$profile2->setDefault(TRUE);
$profile2->save();
/** @var \Drupal\profile\ProfileStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('profile');
$default_profile = $storage->loadDefaultByUser($this->user1, $profile_type->id());
$this->assertEqual($profile2->id(), $default_profile->id());
}
/**
* Tests mark as default action.
*/
public function testDefaultAction() {
$types_data = [
'profile_type_0' => [
'label' => $this->randomMachineName(),
'multiple' => TRUE,
],
'profile_type_1' => [
'label' => $this->randomMachineName(),
'multiple' => TRUE,
],
];
/** @var ProfileType[] $types */
$types = [];
foreach ($types_data as $id => $values) {
$types[$id] = $this->createProfileType($id, $values['label']);
}
$restricted_user = $this->drupalCreateUser([
'administer profiles',
'edit own ' . $types['profile_type_0']->id() . ' profile',
'edit own ' . $types['profile_type_1']->id() . ' profile',
]);
$admin_user = $this->drupalCreateUser([
'administer profiles',
'edit any ' . $types['profile_type_0']->id() . ' profile',
'edit any ' . $types['profile_type_1']->id() . ' profile',
]);
// Create new profiles.
$profile_profile_type_0_restricted_user = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $restricted_user->id(),
]);
$profile_profile_type_0_restricted_user->setActive(TRUE);
$profile_profile_type_0_restricted_user->save();
$profile_profile_type_0_user1_1 = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$profile_profile_type_0_user1_1->setActive(TRUE);
$profile_profile_type_0_user1_1->save();
$profile_profile_type_0_user1_2 = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$profile_profile_type_0_user1_2->setActive(TRUE);
$profile_profile_type_0_user1_2->save();
$profile_profile_type_1_user1 = Profile::create($expected = [
'type' => $types['profile_type_1']->id(),
'uid' => $this->user1->id(),
]);
$profile_profile_type_1_user1->setActive(TRUE);
$profile_profile_type_1_user1->save();
$profile_profile_type_1_user2 = Profile::create($expected = [
'type' => $types['profile_type_1']->id(),
'uid' => $this->user2->id(),
]);
$profile_profile_type_1_user2->setActive(TRUE);
$profile_profile_type_1_user2->save();
$profile_profile_type_1_user1_inactive = Profile::create($expected = [
'type' => $types['profile_type_1']->id(),
'uid' => $this->user1->id(),
]);
$profile_profile_type_1_user1_inactive->setActive(FALSE);
$profile_profile_type_1_user1_inactive->save();
$profile_profile_type_1_user1_active = Profile::create($expected = [
'type' => $types['profile_type_1']->id(),
'uid' => $this->user1->id(),
]);
$profile_profile_type_1_user1_active->isActive(TRUE);
$profile_profile_type_1_user1_active->save();
// Make sure that $restricted_user is allowed to set default his own profile
// and not others'.
$this->drupalLogin($restricted_user);
$this->drupalGet('admin/config/people/profiles');
$this->clickLink('Mark as default', 0);
$this->assertTrue(Profile::load($profile_profile_type_0_restricted_user->id())->isDefault());
$this->clickLink('Mark as default', 1);
$this->assertResponse(403);
$this->drupalLogout();
$this->drupalLogin($admin_user);
$this->drupalGet('admin/config/people/profiles');
// Mark $profile_profile_type_0_user1_1 as default
// $profile_profile_type_0_user1_2 should stay not default.
$this->clickLink('Mark as default', 0);
$this->assertTrue(Profile::load($profile_profile_type_0_user1_1->id())->isDefault());
$this->assertFalse($profile_profile_type_0_user1_2->isDefault());
// Mark $profile_profile_type_0_user1_2 as default
// $profile_profile_type_0_user1_1 should become not default.
$profile_profile_type_0_user1_2->setDefault(TRUE);
$profile_profile_type_0_user1_2->save();
$this->assertTrue($profile_profile_type_0_user1_2->isDefault());
$this->assertFalse($profile_profile_type_0_user1_1->isDefault());
// Mark $profile_profile_type_1_user1 as default
// $profile_profile_type_1_user2 should stay not default.
$this->clickLink('Mark as default', 1);
$this->assertTrue(Profile::load($profile_profile_type_1_user1->id())->isDefault());
$this->assertFalse($profile_profile_type_1_user2->isDefault());
// Mark $profile_profile_type_1_user2 as default
// $profile_profile_type_1_user1 should stay default.
$profile_profile_type_1_user2->setDefault(TRUE);
$profile_profile_type_1_user2->save();
$this->assertTrue($profile_profile_type_1_user2->isDefault());
$this->assertTrue(Profile::load($profile_profile_type_1_user1->id())->isDefault());
// Mark $profile_profile_type_1_user1_inactive as default
// $profile_profile_type_1_user1_active should stay not default.
$this->clickLink('Mark as default', 2);
$this->assertTrue(Profile::load($profile_profile_type_1_user1_inactive->id())->isDefault());
$this->assertFalse($profile_profile_type_1_user1_active->isDefault());
// Mark $profile_profile_type_1_user1_active as default
// $profile_profile_type_1_user1_inactive should stay default.
$profile_profile_type_1_user1_active->setDefault(TRUE);
$profile_profile_type_1_user1_active->save();
$this->assertTrue($profile_profile_type_1_user1_active->isDefault());
$this->assertTrue(Profile::load($profile_profile_type_1_user1_inactive->id())->isDefault());
}
/**
* Tests whether profile default on edit is working.
*/
public function testProfileEdit() {
$types_data = [
'profile_type_0' => [
'label' => $this->randomMachineName(),
'multiple' => TRUE,
],
];
/** @var \Drupal\profile\Entity\ProfileTypeInterface[] $types */
$types = [];
foreach ($types_data as $id => $values) {
$types[$id] = $this->createProfileType($id, $values['label']);
}
$admin_user = $this->drupalCreateUser([
'administer profiles',
'administer users',
'edit any ' . $types['profile_type_0']->id() . ' profile',
]);
// Create new profiles.
$profile1 = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$profile1->save();
$profile2 = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$profile2->setDefault(TRUE);
$profile2->save();
$this->assertFalse($profile1->isDefault());
$this->assertTrue($profile2->isDefault());
$this->drupalLogin($admin_user);
$this->drupalPostForm("profile/{$profile1->id()}/edit", [], 'Save and make default');
\Drupal::entityTypeManager()->getStorage('profile')->resetCache([$profile1->id(), $profile2->id()]);
$this->assertTrue(Profile::load($profile1->id())->isDefault());
$this->assertFalse(Profile::load($profile2->id())->isDefault());
}
/**
* Tests administrative-only profiles.
*/
public function testAdminOnlyProfiles() {
$id = $this->type->id();
$field_name = $this->field->getName();
// Create a test user account.
$web_user = $this->drupalCreateUser(['access user profiles']);
$uid = $web_user->id();
$value = $this->randomMachineName();
// Administratively enter profile field values for the new account.
$this->drupalLogin($this->adminUser);
$edit = [
"{$this->field->getName()}[0][value]" => $value,
];
$this->drupalPostForm("user/$uid/$id", $edit, 'Save and make default');
/** @var \Drupal\profile\Entity\ProfileInterface $profile */
$profile = \Drupal::entityTypeManager()
->getStorage('profile')
->loadByUser($web_user, $this->type->id());
$profile_id = $profile->id();
$this->assertEqual($profile->getType(), $this->type->id());
/*
// Verify that the administrator can see the profile.
$this->drupalGet("user/$uid");
$this->assertText($this->type->label());
$this->assertText($value);
$this->drupalLogout();
// Verify that the user can not access, create 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/profile/$id/$profile_id");
$this->assertResponse(403);
// Check edit link isn't displayed.
$this->assertNoLinkByHref("user/$uid/edit/profile/$id/$profile_id");
// Check delete link isn't displayed.
$this->assertNoLinkByHref("user/$uid/delete/profile/$id/$profile_id");
// Allow users to edit own profiles.
user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ["edit own $id profile"]);
// Verify that the user is able to edit the own profile.
$value = $this->randomMachineName();
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm("user/$uid/edit/profile/$id/$profile_id", $edit, t('Save'));
$this->assertText(new FormattableMarkup('profile has been updated.', []));
// 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(AccountInterface::AUTHENTICATED_ROLE, ["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(AccountInterface::AUTHENTICATED_ROLE, ["delete own $id profile"]);
// Verify that the user can delete the own profile.
$this->drupalGet("user/$uid/edit/profile/$id/$profile_id");
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertRaw(new FormattableMarkup('@label profile deleted.', ['@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/profile/$id");
$this->assertNoText($value);
*/
}
}
@@ -0,0 +1,91 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Core\Cache\Cache;
/**
* Tests profile field access functionality.
*
* @group profile
*/
class ProfileFieldAccessTest extends ProfileTestBase {
private $webUser;
private $otherUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access user profiles',
'administer profile types',
'administer profile fields',
'administer profile display',
'bypass profile access',
]);
$user_permissions = [
'access user profiles',
"add own {$this->type->id()} profile",
"edit own {$this->type->id()} profile",
"view own {$this->type->id()} profile",
];
$this->webUser = $this->drupalCreateUser($user_permissions);
$this->otherUser = $this->drupalCreateUser($user_permissions);
}
/**
* Tests private profile field access.
*/
public function testPrivateField() {
$this->drupalLogin($this->adminUser);
// Create a private profile field.
$edit = [
'new_storage_type' => 'string',
'label' => 'Secret',
'field_name' => 'secret',
];
$this->drupalPostForm("admin/config/people/profiles/types/manage/{$this->type->id()}/fields/add-field", $edit, t('Save and continue'));
$this->drupalPostForm(NULL, [], t('Save field settings'));
$edit = [
'profile_private' => 1,
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// Fill in a field value.
$this->drupalLogin($this->webUser);
$uid = $this->webUser->id();
$secret = $this->randomMachineName();
$edit = [
'field_secret[0][value]' => $secret,
];
$this->drupalPostForm("user/$uid/{$this->type->id()}", $edit, t('Save'));
// User cache page need to be cleared to see new profile.
Cache::invalidateTags([
'user:' . $uid,
'user_view',
]);
// 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->adminUser);
$this->drupalGet("user/$uid");
$this->assertText($secret);
// Verify that the private field value does not appear for other users.
$this->drupalLogin($this->otherUser);
$this->drupalGet("user/$uid");
$this->assertNoText($secret);
}
}
@@ -0,0 +1,165 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Core\Session\AccountInterface;
/**
* Tests profile role access handling.
*
* @group profile
*/
class ProfileRoleAccessTest extends ProfileTestBase {
/**
* Randomly generated profile type entity.
*
* Requires some, but not all roles.
*
* @var \Drupal\profile\Entity\ProfileType
*/
protected $type2;
/**
* Randomly generated profile type entity.
*
* Requires all profile roles.
*
* @var \Drupal\profile\Entity\ProfileType
*/
protected $type3;
/**
* Randomly generated user role entity.
*
* @var \Drupal\user\Entity\Role
*/
protected $role1;
/**
* Randomly generated user role entity.
*
* @var \Drupal\user\Entity\Role
*/
protected $role2;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->role1 = $this->drupalCreateRole([]);
$this->role2 = $this->drupalCreateRole([]);
$this->type2 = $this->createProfileType(NULL, NULL, FALSE, [$this->role2]);
$this->type3 = $this->createProfileType(NULL, NULL, FALSE, [$this->role1, $this->role2]);
}
/**
* Tests add profile form access for a profile type that does not require
* users to have a role.
*/
public function testProfileWithNoRoles() {
// Create user with add own profile permissions.
$web_user1 = $this->drupalCreateUser(["add own {$this->type->id()} profile"]);
$this->drupalLogin($web_user1);
// Test user without role can access add profile form.
// Expected: User can access form.
$this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
$this->assertResponse(200);
}
public function testLockedRoles() {
$locked_role_type = $this->createProfileType(NULL, NULL, FALSE, [AccountInterface::AUTHENTICATED_ROLE]);
// Create user with add own profile permissions.
$web_user1 = $this->drupalCreateUser(["add own {$locked_role_type->id()} profile"]);
$this->drupalLogin($web_user1);
// Test user without role can access add profile form.
// Expected: User can access form.
$this->drupalGet("user/{$web_user1->id()}/{$locked_role_type->id()}");
$this->assertResponse(200);
}
/**
* Tests add profile form access for a profile type that requires users to
* have a single role.
*/
public function testProfileWithSingleRole() {
// Create user with add own profile permissions.
$web_user1 = $this->drupalCreateUser(["add own {$this->type2->id()} profile"]);
$this->drupalLogin($web_user1);
// Test user without role can access add profile form.
// Expected: User cannot access form.
$this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
$this->assertResponse(403);
// Test user with wrong role can access add profile form.
// Expected: User cannot access form.
$web_user1->addRole($this->role1);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
$this->assertResponse(403);
// Test user with correct role can access add profile form.
// Expected: User can access form.
$web_user1->removeRole($this->role1);
$web_user1->addRole($this->role2);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
$this->assertResponse(200);
}
/**
* Tests add profile form access for a profile type that requires users to
* have one of multiple roles.
*/
public function testProfileWithAllRoles() {
// Create user with add own profile permissions.
$web_user1 = $this->drupalCreateUser(["add own {$this->type3->id()} profile"]);
$this->drupalLogin($web_user1);
// Test user without role can access add profile form.
// Expected: User cannot access form.
$this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
$this->assertResponse(403);
// Test user with role 1 can access add profile form.
// Expected: User can access form.
$web_user1->addRole($this->role1);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
$this->assertResponse(200);
// Test user with both roles can access add profile form.
// Expected: User can access form.
$web_user1->addRole($this->role2);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
$this->assertResponse(200);
// Test user with role 2 can access add profile form.
// Expected: User can access form.
$web_user1->removeRole($this->role1);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
$this->assertResponse(200);
// Test user without role can access add profile form.
// Expected: User cannot access form.
$web_user1->removeRole($this->role2);
$web_user1->save();
$this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
$this->assertResponse(403);
}
}
@@ -0,0 +1,135 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Url;
use Drupal\profile\Entity\Profile;
use Drupal\profile\Entity\ProfileType;
use Drupal\user\Entity\User;
/**
* Tests tab functionality of profiles.
*
* @group profile
*/
class ProfileTabTest extends ProfileTestBase {
public static $modules = ['profile', 'field_ui', 'text', 'block'];
/**
* Testing demo user 1.
*
* @var \Drupal\user\UserInterface
*/
public $user1;
/**
* Testing demo user 2.
*
* @var \Drupal\user\UserInterface;
*/
public $user2;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access user profiles',
'administer profiles',
'administer profile types',
'bypass profile access',
'access administration pages',
]);
}
/**
* Tests tabs in profile UI.
*/
public function testProfileTabs() {
$types_data = [
'profile_type_0' => ['label' => $this->randomMachineName()],
'profile_type_1' => ['label' => $this->randomMachineName()],
];
/** @var ProfileType[] $types */
$types = [];
foreach ($types_data as $id => $values) {
$types[$id] = ProfileType::create(['id' => $id] + $values);
$types[$id]->save();
}
$this->container->get('router.builder')->rebuild();
$this->user1 = User::create([
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
]);
$this->user1->save();
$this->user2 = User::create([
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
]);
$this->user2->save();
// Create new profiles.
$profile1 = Profile::create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$profile1->save();
$profile2 = Profile::create($expected = [
'type' => $types['profile_type_1']->id(),
'uid' => $this->user2->id(),
]);
$profile2->save();
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config');
$this->clickLink('User profiles');
$this->assertResponse(200);
$this->assertUrl('admin/config/people/profiles');
$this->assertLink($profile1->label());
$this->assertLinkByHref($profile2->toUrl('canonical')->toString());
$tasks = [
['entity.profile.collection', []],
['entity.profile_type.collection', []],
];
$this->assertLocalTasks($tasks, 0);
}
/**
* Asserts local tasks in the page output.
*
* @param array $routes
* A list of expected local tasks, prepared as an array of route names and
* their associated route parameters, to assert on the page (in the given
* order).
* @param int $level
* (optional) The local tasks level to assert; 0 for primary, 1 for
* secondary. Defaults to 0.
*/
protected function assertLocalTasks(array $routes, $level = 0) {
$elements = $this->xpath('//*[contains(@class, :class)]//a', array(
':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
));
$this->assertTrue(count($elements), 'Local tasks found.');
foreach ($routes as $index => $route_info) {
list($route_name, $route_parameters) = $route_info;
$expected = Url::fromRoute($route_name, $route_parameters)->toString();
$method = ($elements[$index]['href'] == $expected ? 'pass' : 'fail');
$this->{$method}(new FormattableMarkup('Task @number href @value equals @expected.', [
'@number' => $index + 1,
'@value' => (string) $elements[$index]['href'],
'@expected' => $expected,
]));
}
}
}
@@ -0,0 +1,139 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\profile\ProfileTestTrait;
use Drupal\simpletest\WebTestBase;
/**
* Tests profile access handling.
*/
abstract class ProfileTestBase extends WebTestBase {
use ProfileTestTrait;
public static $modules = ['profile', 'field_ui', 'text', 'block'];
/**
* Testing profile type entity.
*
* @var \Drupal\profile\Entity\ProfileType
*/
protected $type;
/**
* Testing profile type entity view display.
*
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
*/
protected $display;
/**
* Testing profile type entity form display.
*
* @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form
*/
protected $form;
/**
* Testing field on profile type.
*
* @var \Drupal\Core\Field\FieldConfigInterface
*/
protected $field;
/**
* Testing admin user.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
$this->type = $this->createProfileType('test', 'Test profile', TRUE);
$id = $this->type->id();
$field_storage = FieldStorageConfig::create([
'field_name' => 'profile_fullname',
'entity_type' => 'profile',
'type' => 'text',
]);
$field_storage->save();
$this->field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->type->id(),
'label' => 'Full name',
]);
$this->field->save();
// Configure the default display.
$this->display = EntityViewDisplay::load("profile.{$this->type->id()}.default");
if (!$this->display) {
$this->display = EntityViewDisplay::create([
'targetEntityType' => 'profile',
'bundle' => $this->type->id(),
'mode' => 'default',
'status' => TRUE,
]);
$this->display->save();
}
$this->display
->setComponent($this->field->getName(), ['type' => 'string'])
->save();
// Configure rhe default form.
$this->form = EntityFormDisplay::load("profile.{$this->type->id()}.default");
if (!$this->form) {
$this->form = EntityFormDisplay::create([
'targetEntityType' => 'profile',
'bundle' => $this->type->id(),
'mode' => 'default',
'status' => TRUE,
]);
$this->form->save();
}
$this->form
->setComponent($this->field->getName(), [
'type' => 'string_textfield',
])->save();
$this->checkPermissions([
'administer profile types',
"view own $id profile",
"view any $id profile",
"add own $id profile",
"add any $id profile",
"edit own $id profile",
"edit any $id profile",
"delete own $id profile",
"delete any $id profile",
]);
user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ['access user profiles']);
$this->adminUser = $this->drupalCreateUser([
'administer profile types',
'administer profiles',
"view any $id profile",
"add any $id profile",
"edit any $id profile",
"delete any $id profile",
]);
}
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Unicode;
/**
* Tests basic CRUD functionality of profile types.
*
* @group profile
*/
class ProfileTypeCRUDTest extends ProfileTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access user profiles',
'administer profile types',
'administer profile fields',
'administer profile display',
'bypass profile access',
]);
}
/**
* Verify that routes are created for the profile type.
*/
public function testRoutes() {
$this->drupalLogin($this->adminUser);
$type = $this->createProfileType($this->randomMachineName());
\Drupal::service('router.builder')->rebuildIfNeeded();
$this->drupalGet("user/{$this->adminUser->id()}/{$type->id()}");
$this->assertResponse(200);
}
/**
* Tests CRUD operations for profile types through the UI.
*/
public function testCRUDUI() {
$this->drupalLogin($this->adminUser);
// Create a new profile type.
$this->drupalGet('admin/config/people/profiles/types');
$this->assertResponse(200);
$this->clickLink(t('Add profile type'));
$this->assertUrl('admin/config/people/profiles/types/add');
$id = Unicode::strtolower($this->randomMachineName());
$label = $this->randomString();
$edit = [
'id' => $id,
'label' => $label,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertUrl('admin/config/people/profiles/types');
$this->assertRaw(new FormattableMarkup('%label profile type has been created.', ['%label' => $label]));
$this->assertLinkByHref("admin/config/people/profiles/types/manage/$id");
$this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/fields");
$this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/display");
$this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/delete");
// Edit the new profile type.
$this->drupalGet("admin/config/people/profiles/types/manage/$id");
$this->assertRaw(new FormattableMarkup('Edit %label profile type', ['%label' => $label]));
$edit = [
'registration' => 1,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertUrl('admin/config/people/profiles/types');
$this->assertRaw(new FormattableMarkup('%label profile type has been updated.', ['%label' => $label]));
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
// Add a field to the profile type.
$this->drupalGet("admin/config/people/profiles/types/manage/$id/fields/add-field");
$field_name = Unicode::strtolower($this->randomMachineName());
$field_label = $this->randomString();
$edit = [
'new_storage_type' => 'string',
'label' => $field_label,
'field_name' => $field_name,
];
$this->drupalPostForm(NULL, $edit, t('Save and continue'));
$this->drupalPostForm(NULL, [], t('Save field settings'));
$this->drupalPostForm(NULL, [], t('Save settings'));
$this->assertRaw(new FormattableMarkup('Saved %label configuration.', ['%label' => $field_label]));
// Verify that the field is still associated with it.
$this->drupalGet("admin/config/people/profiles/types/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));
}
}
@@ -0,0 +1,95 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Cache\Cache;
/**
* Tests multiple enabled profile types.
*
* @group profile
*/
class ProfileTypeMultipleTest extends ProfileTestBase {
/**
* Tests the flow of a profile type that has multiple enabled.
*/
public function testMultipleProfileType() {
$this->drupalLogin($this->adminUser);
$edit = [
'multiple' => 1,
];
$this->drupalPostForm("admin/config/people/profiles/types/manage/{$this->type->id()}", $edit, t('Save'));
$this->assertRaw(new FormattableMarkup('%type profile type has been updated.', [
'%type' => $this->type->label(),
]));
$web_user1 = $this->drupalCreateUser(
[
"view own {$this->type->id()} profile",
"add own {$this->type->id()} profile",
"edit own {$this->type->id()} profile",
]
);
$this->drupalLogin($web_user1);
$value = $this->randomMachineName();
$edit = [
"{$this->field->getName()}[0][value]" => $value,
];
$this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, t('Save'));
$this->assertRaw(new FormattableMarkup('%type profile has been created.', [
'%type' => $this->type->label(),
]));
$this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
$this->assertLinkByHref("user/{$web_user1->id()}/{$this->type->id()}/add");
$this->assertText($value);
$value2 = $this->randomMachineName();
$edit = [
"{$this->field->getName()}[0][value]" => $value2,
];
$this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}/add", $edit, t('Save'));
$this->assertRaw(new FormattableMarkup('%type profile has been created.', [
'%type' => $this->type->label(),
]));
Cache::invalidateTags(['profile_view']);
$this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
$this->assertText($value2);
}
/**
* Tests the non-multiple profile type create and edit flow.
*/
public function testProfileNotMultipleFlow() {
$web_user1 = $this->createUser([
"add own {$this->type->id()} profile",
"edit own {$this->type->id()} profile",
]);
$this->drupalLogin($web_user1);
// Create the profile.
$edit = [
"{$this->field->getName()}[0][value]" => $this->randomString(),
];
$this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, 'Save and make default');
$this->assertRaw(new FormattableMarkup('%type profile has been created.', [
'%type' => $this->type->label(),
]));
// Update the profile.
$edit = [
"{$this->field->getName()}[0][value]" => $this->randomString(),
];
$this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, t('Save'));
$this->assertRaw(new FormattableMarkup('%type profile has been updated.', [
'%type' => $this->type->label(),
]));
}
}