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,9 @@
#!/bin/bash
set -e $DRUPAL_TI_DEBUG
# Ensure the right Drupal version is installed.
# Note: This function is re-entrant.
drupal_ti_ensure_drupal
phpcs --report=full --standard=Drupal "$DRUPAL_TI_DRUPAL_DIR/$DRUPAL_TI_MODULES_PATH/$DRUPAL_TI_MODULE_NAME" || true
@@ -0,0 +1,119 @@
# @file
# .travis.yml - Drupal for Travis CI Integration
#
# Template provided by https://github.com/LionsAd/drupal_ti.
#
# Based for simpletest upon:
# https://github.com/sonnym/travis-ci-drupal-module-example
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache
php:
- 5.5
- 5.6
- 7
- hhvm
matrix:
fast_finish: true
allow_failures:
- php: 7
- php: hhvm
branches:
only:
- "8.x-1.x"
env:
global:
# add composer's global bin directory to the path
# see: https://github.com/drush-ops/drush#install---composer
- PATH="$PATH:$HOME/.composer/vendor/bin"
# Configuration variables.
- DRUPAL_TI_MODULE_NAME="profile"
- DRUPAL_TI_SIMPLETEST_GROUP="profile"
# Define runners and environment vars to include before and after the
# main runners / environment vars.
#- DRUPAL_TI_SCRIPT_DIR_BEFORE="./drupal_ti/before"
#- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after"
# The environment to use, supported are: drupal-7, drupal-8
- DRUPAL_TI_ENVIRONMENT="drupal-8"
# Drupal specific variables.
- DRUPAL_TI_DB="drupal_travis_db"
- DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db"
# Note: Do not add a trailing slash here.
- DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
- DRUPAL_TI_WEBSERVER_PORT="8080"
# Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end.
- DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
# === Behat specific variables.
# This is relative to $TRAVIS_BUILD_DIR
- DRUPAL_TI_BEHAT_DIR="./tests/behat"
# These arguments are passed to the bin/behat command.
- DRUPAL_TI_BEHAT_ARGS=""
# Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables.
- DRUPAL_TI_BEHAT_YML="behat.yml.dist"
# This is used to setup Xvfb.
- DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16"
# The version of seleniumthat should be used.
- DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.44"
# PHPUnit specific commandline arguments.
- DRUPAL_TI_PHPUNIT_ARGS=""
# Code coverage via coveralls.io
- DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*"
# This needs to match your .coveralls.yml file.
- DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml"
# Debug options
#- DRUPAL_TI_DEBUG="-x -v"
# Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium",
# etc. to only output those channels.
#- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver"
matrix:
# [[[ SELECT ANY OR MORE OPTIONS ]]]
#- DRUPAL_TI_RUNNERS="phpunit"
- DRUPAL_TI_RUNNERS="simpletest"
#- DRUPAL_TI_RUNNERS="behat"
#- DRUPAL_TI_RUNNERS="phpunit simpletest behat"
# Uncomment once unit tests added.
#- DRUPAL_TI_RUNNERS="phpunit-core simpletest"
mysql:
database: drupal_travis_db
username: root
encoding: utf8
before_install:
- composer self-update
- composer global require "lionsad/drupal_ti:1.*"
- drupal-ti before_install
# Codesniffer and Coder
- composer global require "squizlabs/php_codesniffer:2.0.*@dev"
- composer global require drupal/coder:8.2.0-beta1
- ln -s ~/.composer/vendor/drupal/coder/coder_sniffer/Drupal ~/.composer/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/
install:
- drupal-ti install
before_script:
- drupal-ti before_script
script:
- drupal-ti --include .travis-phpcs.sh
- export SIMPLETEST_DB=mysql://root:@127.0.0.1/drupal_travis_db; drupal-ti script
after_script:
- drupal-ti after_script
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,23 @@
Profile
-------
The *Profile* module provides configurable user profiles.
## Requirements
* [Entity API](https://www.drupal.org/project/entity)
## Comparison to user account fields
Why use profiles instead of user account fields?
* With profile, user account settings and user profiles are conceptually different things, e.g. with the "Profile" module enabled users get two separate menu links "My account" and "My profile".
* Profile allows for creating multiple profile types, which may be assigned to roles via permissions (e.g. a general profile + a customer profile)
* Profile supports private profile fields, which are only shown to the user owning the profile and to administrators.
## Features
* Multiple profile types may be created via the UI (e.g. a general profile + a customer profile), whereas the module provides separated permissions for those.
* Optionally, profile forms are shown during user account registration.
* Fields may be configured to be private - thus visible only to the profile owner and administrators.
* Profile types are displayed on the user view page, and can be configured through "Manage Display" on account settings.
@@ -0,0 +1,10 @@
{
"name": "drupal/profile",
"type": "drupal-module",
"description": "Provides configurable user profiles.",
"homepage": "http://drupal.org/project/profile",
"license": "GPL-2.0+",
"require": {
"drupal/core": "~8.1"
}
}
@@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- profile
id: profile_delete_action
label: 'Delete selected profile'
type: profile
plugin: profile_delete_action
@@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- profile
id: profile_publish_action
label: 'Publish selected profile'
type: profile
plugin: profile_publish_action
@@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- profile
id: profile_unpublish_action
label: 'Unpublish selected profile'
type: profile
plugin: profile_unpublish_action
@@ -0,0 +1,379 @@
langcode: en
status: true
dependencies:
module:
- entity
- profile
id: profiles
label: Profiles
module: views
description: ''
tag: ''
base_table: profile
base_field: profile_id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
offset: 0
style:
type: grid
options:
grouping: { }
columns: 4
automatic_width: true
alignment: horizontal
col_class_default: true
col_class_custom: ''
row_class_default: true
row_class_custom: ''
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
rendered_entity:
id: rendered_entity
table: profile
field: rendered_entity
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
view_mode: default
entity_type: profile
plugin_id: rendered_entity
operations:
id: operations
table: profile
field: operations
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
entity_type: profile
plugin_id: entity_operations
filters: { }
sorts: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments:
uid:
id: uid
table: profile
field: uid
relationship: none
group_type: group
admin_label: ''
default_action: default
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: user
default_argument_options:
user: false
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
break_phrase: false
not: false
entity_type: profile
entity_field: uid
plugin_id: numeric
type:
id: type
table: profile
field: type
relationship: none
group_type: group
admin_label: ''
default_action: ignore
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: true
validate:
type: 'entity:profile_type'
fail: 'not found'
validate_options: { }
glossary: false
limit: 0
case: none
path_case: none
transform_dash: false
break_phrase: false
entity_type: profile
entity_field: type
plugin_id: string
status:
id: status
table: profile
field: status
relationship: none
group_type: group
admin_label: ''
default_action: ignore
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
break_phrase: false
not: false
entity_type: profile
entity_field: status
plugin_id: numeric
display_extenders: { }
title: Profiles
cache_metadata:
max-age: 0
contexts:
- 'languages:language_interface'
- url
profile_type_listing:
display_plugin: embed
id: profile_type_listing
display_title: 'Profile type listing'
position: 1
display_options:
display_extenders: { }
display_description: ''
cache_metadata:
max-age: 0
contexts:
- 'languages:language_interface'
- url
user_view:
display_plugin: embed
id: user_view
display_title: 'User view'
position: 2
display_options:
display_extenders: { }
fields:
rendered_entity:
id: rendered_entity
table: profile
field: rendered_entity
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
view_mode: default
entity_type: profile
plugin_id: rendered_entity
defaults:
fields: false
display_description: ''
cache_metadata:
max-age: 0
contexts:
- 'languages:language_interface'
- url
@@ -0,0 +1,53 @@
profile.type.*:
type: config_entity
label: 'Profile type settings'
mapping:
id:
type: string
label: 'Machine-readable name'
uuid:
type: string
label: 'UUID'
label:
type: label
label: 'Label'
registration:
type: boolean
label: 'Registration'
multiple:
type: boolean
label: 'Allow multiple profiles'
roles:
label: 'Allowed roles'
type: sequence
sequence:
type: string
weight:
type: integer
label: 'Weight'
status:
type: boolean
label: 'Status'
langcode:
type: string
label: 'Default language'
field.field.*.*.*.third_party.profile:
type: mapping
label: 'Profile private'
mapping:
profile_private:
type: boolean
label: 'Whether this field is private or not.'
action.configuration.profile_delete_action:
type: action_configuration_default
label: 'Delete profile configuration'
action.configuration.profile_publish_action:
type: action_configuration_default
label: 'Publish selected profile configuration'
action.configuration.profile_unpublish_action:
type: action_configuration_default
label: 'Unpublish selected profile configuration'
@@ -0,0 +1,11 @@
.profile-title {
font-weight: bold;
}
.profile-item {
margin-bottom: 20px;
}
.profile-item-action-links {
margin-bottom: 10px;
}
@@ -0,0 +1,51 @@
<?php
/**
* @file
* Hooks provided by profile module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Control access to a user profile.
*
* Modules may implement this hook to control whether a user has access to
* perform a certain operation on a profile.
*
* @param string $op
* The operation being performed. One of 'view', 'edit' (being the same as
* 'create' or 'update'), or 'delete'.
* @param Drupal\profile\Entity\Profile $profile
* A profile to check access for.
* @param Drupal\user\Entity\User $account
* The user performing the operation; the currently logged in user by default.
*
* @return bool
* Either a Boolean or NULL:
* - FALSE to explicitly deny access. If a module denies access, no other
* module is able to grant access and access is denied.
* - TRUE to grant access. Access is only granted if at least one module
* grants access and no module denies access.
* - NULL or nothing to not affect the operation. If no module explicitly
* grants access, access is denied.
*/
function hook_profile_access($op, Drupal\profile\Entity\Profile $profile, Drupal\user\Entity\User $account) {
// Explicitly deny access for a 'secret' profile type.
if ($profile->getType() == 'secret' && !\Drupal::currentUser()->hasPermission('custom permission')) {
return FALSE;
}
// For profiles other than the default profile grant access.
if ($profile->getType() != 'main' && \Drupal::currentUser()->hasPermission('custom permission')) {
return TRUE;
}
// In other cases do not alter access.
return NULL;
}
/**
* @}
*/
@@ -0,0 +1,17 @@
name: Profile
type: module
description: 'Provides configurable user profiles.'
# core: 8.x
configure: entity.profile_type.collection
dependencies:
- user
- entity
- field
- views
- system (>=8.1.0)
# Information added by Drupal.org packaging script on 2016-08-31
version: '8.x-1.0-alpha5+4-dev'
core: '8.x'
project: 'profile'
datestamp: 1472611474
@@ -0,0 +1,5 @@
drupal.profile-items:
version: VERSION
css:
component:
css/profile.base.css: {}
@@ -0,0 +1,5 @@
profile_type_add:
route_name: entity.profile_type.add_form
title: 'Add profile type'
appears_on:
- entity.profile_type.collection
@@ -0,0 +1,5 @@
entity.profile.collection:
title: 'User profiles'
description: 'Manage profile types, including fields, and profiles for users.'
route_name: entity.profile.collection
parent: user.admin_index
@@ -0,0 +1,14 @@
# Tabs on the profile admin page
entity.profile.collection_tab:
route_name: entity.profile.collection
base_route: entity.profile.collection
title: 'Profiles'
entity.profile_type.collection_tab:
route_name: entity.profile_type.collection
base_route: entity.profile.collection
title: 'Profile types'
# Add tabs for each profile type
entity.profile.user_profile_form:
deriver: Drupal\profile\Plugin\Derivative\ProfileLocalTask
@@ -0,0 +1,345 @@
<?php
/**
* @file
* Support for configurable user profiles.
*/
use Drupal\user\UserInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityInterface;
use Drupal\profile\Entity\Profile;
use Drupal\profile\Entity\ProfileType;
use Drupal\field\FieldConfigInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Denotes that the profile is not active.
*/
const PROFILE_NOT_ACTIVE = 0;
/**
* Denotes that the profile is active.
*/
const PROFILE_ACTIVE = 1;
/**
* Denotes that the profile is not default.
*/
const PROFILE_NOT_DEFAULT = 0;
/**
* Denotes that the profile is default.
*/
const PROFILE_DEFAULT = 1;
/**
* Implements hook_help().
*/
function profile_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.profile':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Profile module provides a fieldable entity, that allows administrators to define different sets of fields for user profiles, which are then displayed in the <a href="!user">My Account</a> section. This permits users of a site to share more information about themselves, and can help community-based sites organize users around specific information.', ['!user' => Url::fromRoute('user.page')]) . '</p>';
$output .= '<dl>';
$output .= '<dt>' . t('Types of profiles') . '</dt>';
$output .= '<dd>' . t('Profile types provide a way of grouping similar data for user profiles e.g. Personal information, Work etc. A default "Personal information type is provided. You may create more types and manage fields for each type from the <a href="!profile-types">Profile types</a> admin page. When creating a new profile type, you will be able to specify whether a user may create multiple profiles or make the profile form available when registering a new user.', ['!profile-types' => Url::fromRoute('entity.profile_type.collection')]) . '</dd>';
$output .= '<dt>' . t('Creating profiles') . '</dt>';
$output .= '<dd>' . t('A user will see tabs they have access to, when editing their main user account e.g. "Add personal information profile". The visibility of a tab depends on whether they can create multiple profiles or if they haven\'t created a profile of the type that doesn\'t allow multiple instances.') . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_theme().
*/
function profile_theme() {
return [
'profile' => [
'render element' => 'elements',
],
];
}
/**
* Prepares variables for profile templates.
*
* Default template: profile.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing rendered fields.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_profile(array &$variables) {
/** @var Drupal\profile\Entity\ProfileInterface $profile */
$profile = $variables['elements']['#profile'];
$variables['profile'] = $profile;
$variables['url'] = $profile->toUrl();
// Helpful $content variable for templates.
$variables['content'] = [];
foreach (\Drupal\Core\Render\Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Implements hook_user_view().
*/
function profile_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display, $view_mode) {
// Iterate through each bundle and see if it's component exists.
foreach (ProfileType::loadMultiple() as $bundle) {
$component_key = 'profile_' . $bundle->id();
if ($display->getComponent($component_key)) {
// Embed the view of active profiles for profile type.
$build[$component_key] = [
'#type' => 'view',
'#name' => 'profiles',
'#display_id' => 'user_view',
'#arguments' => [$account->id(), $bundle->id(), 1],
'#embed' => TRUE,
'#title' => $bundle->label(),
'#pre_render' => [
['\Drupal\views\Element\View', 'preRenderViewElement'],
'profile_views_add_title_pre_render',
],
];
}
}
}
/**
* Implements hook_entity_extra_field_info().
*/
function profile_entity_extra_field_info() {
$extra = [];
// Add each profile type as an extra field for display. Enabled by default.
foreach (ProfileType::loadMultiple() as $bundle) {
$extra['user']['user']['display']['profile_' . $bundle->id()] = array(
'label' => $bundle->label(),
'description' => t('Display @type profiles', ['@type' => $bundle->label()]),
'weight' => 10,
'visible' => TRUE,
);
}
return $extra;
}
/**
* Implements hook_user_delete().
*/
function profile_user_delete(EntityInterface $entity) {
$list = \Drupal::entityTypeManager()
->getStorage('profile')
->loadByProperties([
'uid' => $entity->id(),
]);
foreach ($list as $profile) {
$profile->delete();
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function profile_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$field = $form_state->getFormObject()->getEntity();
if ($field->getTargetEntityTypeId() != 'profile') {
return;
}
$form['field']['profile']['profile_private'] = [
'#type' => 'checkbox',
'#title' => t('This is a private field.'),
'#default_value' => $field->getThirdPartySetting('profile', 'profile_private', FALSE),
];
$form['actions']['submit']['#submit'][] = 'profile_form_field_config_edit_form_submit';
}
/**
* Form submission handler for profile_form_field_config_edit_form_alter.
*
* @param array $form
* The form array.
* @param FormStateInterface $form_state
* The form state.
*/
function profile_form_field_config_edit_form_submit(array $form, FormStateInterface $form_state) {
$field = $form_state->getFormObject()->getEntity();
$form_fields = &$form_state->getValues();
// If the private option is checked, update settings.
if ($form_fields['profile_private']) {
$field->setThirdPartySetting('profile', 'profile_private', TRUE);
$field->save();
}
else {
$field->unsetThirdPartySetting('profile', 'profile_private');
$field->save();
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add available profile forms to the user registration form.
*/
function profile_form_user_register_form_alter(&$form, FormStateInterface $form_state) {
$attached_profile_form = FALSE;
$weight = 90;
/** @var ProfileType[] $profile_types */
$profile_types = ProfileType::loadMultiple();
foreach ($profile_types as $profile_type) {
$instances = array_filter(\Drupal::service('entity_field.manager')->getFieldDefinitions('profile', $profile_type->id()), function ($field_definition) {
return $field_definition instanceof FieldConfigInterface;
});
if ($profile_type->getRegistration() === TRUE && count($instances)) {
$property = ['profiles', $profile_type->id()];
$profile = $form_state->get($property);
if (empty($profile)) {
$profile = Profile::create([
'type' => $profile_type->id(),
'langcode' => $profile_type->language() ?
$profile_type->language() : \Drupal::languageManager()->getDefaultLanguage()->getId(),
]);
// Attach profile entity form.
$form_state->set($property, $profile);
}
$form_state->set('form_display_' . $profile_type->id(), EntityFormDisplay::collectRenderDisplay($profile, 'default'));
$form['entity_' . $profile_type->id()] = [
'#type' => 'details',
'#title' => $profile_type->label(),
'#tree' => TRUE,
'#parents' => ['entity_' . $profile_type->id()],
'#weight' => ++$weight,
'#open' => TRUE,
];
$form_state
->get('form_display_' . $profile_type->id())
->buildForm($profile, $form['entity_' . $profile_type->id()], $form_state);
$attached_profile_form = TRUE;
}
}
if ($attached_profile_form) {
$form['actions']['submit']['#validate'][] = 'profile_form_user_register_form_validate';
$form['actions']['submit']['#submit'][] = 'profile_form_user_register_form_submit';
}
}
/**
* Extra form validation handler for the user registration form.
*/
function profile_form_user_register_form_validate(array &$form, FormStateInterface $form_state) {
$profiles = $form_state->get('profiles');
if (!empty($profiles)) {
foreach ($profiles as $bundle => $entity) {
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
$form_display = $form_state->get('form_display_' . $bundle);
$form_display->extractFormValues($entity, $form['entity_' . $bundle], $form_state);
$form_display->validateFormValues($entity, $form['entity_' . $bundle], $form_state);
}
}
// Entity was validated in entityFormValidate(). This will prevent validation
// exception from being thrown.
$form_state->getFormObject()->getEntity()->validate();
}
/**
* Extra form submission handler for the user registration form.
*/
function profile_form_user_register_form_submit(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = $form_state->getFormObject()->getEntity();
$profiles = $form_state->get('profiles');
if (!empty($profiles)) {
foreach ($profiles as $bundle => $entity) {
$entity->setOwnerId($account->id());
$entity->setActive(TRUE);
$entity->save();
}
}
}
/**
* Pre render callback for profile embedded views to ensure a title is set.
* @param $element
*
* @return mixed
*/
function profile_views_add_title_pre_render($element) {
/** @var \Drupal\views\ViewExecutable $view */
if (isset($element['#title'])) {
$view = $element['view_build']['#view'];
$view->setTitle($element['#title']);
}
return $element;
}
/**
* Implements hook_preprocess_HOOK().
*/
function profile_preprocess_views_view(&$variables) {
// We have to manually add back the title since it was removed by Views.
// @see template_preprocess_views_view()
/** @var \Drupal\views\ViewExecutable $view */
$view = $variables['view'];
if ($view->storage->id() == 'profiles' && !empty($view->result)) {
// Test access to the profile.
/** @var \Drupal\profile\Entity\profile $entity */
$entity = reset($view->result)->_entity;
if ($entity->access('view')) {
$variables['title'] = $view->getTitle();
}
}
}
/**
* Implements hook_views_data_alter().
*
* Adds a relationship from the user table to its' profile entity.
*/
function profile_views_data_alter(&$data) {
$data['users_field_data']['profile']['relationship'] = [
'title' => t('Profile'),
'label' => t('Profile'),
'group' => 'User',
'help' => t('Reference to the profile of a user.'),
'id' => 'standard',
'base' => 'profile',
'base field' => 'uid',
'field' => 'uid',
];
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function profile_theme_suggestions_profile(array $variables) {
$original = $variables['theme_hook_original'];
$entity = $variables['elements']['#profile'];
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$suggestions = [];
$suggestions[] = $original;
$suggestions[] = $original . '__' . $sanitized_view_mode;
$suggestions[] = $original . '__' . $entity->bundle();
$suggestions[] = $original . '__' . $entity->bundle() . '__' . $sanitized_view_mode;
$suggestions[] = $original . '__' . $entity->id();
$suggestions[] = $original . '__' . $entity->id() . '__' . $sanitized_view_mode;
return $suggestions;
}
@@ -0,0 +1,13 @@
administer profile types:
title: 'Administer profile types'
restrict access: true
administer profiles:
title: 'Administer profiles'
restrict access: true
bypass profile access:
title: 'Bypass profile access control'
description: 'View, edit and delete all user profiles, including private field values, regardless of permission restrictions.'
restrict access: true
permission_callbacks:
- \Drupal\profile\ProfilePermissions::profileTypePermissions
@@ -0,0 +1,60 @@
# edit_form, delete_form routes are generated by DefaultHtmlRouteProvider.
entity.profile.collection:
path: '/admin/config/people/profiles'
defaults:
_entity_list: 'profile'
_title: 'Profiles'
requirements:
_permission: 'administer profiles'
entity.profile_type.collection:
path: '/admin/config/people/profiles/types'
defaults:
_entity_list: 'profile_type'
_title: 'Profile types'
requirements:
_permission: 'administer profile types'
entity.profile_type.add_form:
path: '/admin/config/people/profiles/types/add'
defaults:
_entity_form: profile_type.add
_title: 'Add'
requirements:
_permission: 'administer profile types'
entity.profile.add_form:
path: '/user/{user}/profile/{profile_type}'
defaults:
_controller: '\Drupal\profile\Controller\ProfileController::addProfile'
_title: 'Add new profile'
operation: 'add'
requirements:
_profile_access_check: 'profile:{profile_type}'
entity.profile.canonical:
path: '/profile/{profile}'
defaults:
_controller: '\Drupal\profile\Controller\ProfileViewController::view'
_title_callback: '\Drupal\profile\Controller\ProfileViewController::title'
requirements:
_entity_access: 'profile.view'
entity.profile.multiple_delete_confirm:
path: '/admin/content/profile/delete'
defaults:
_form: '\Drupal\profile\Form\DeleteMultiple'
requirements:
_permission: 'administer profiles'
entity.profile.set_default:
path: '/profile/{profile}/set-default'
defaults:
_controller: '\Drupal\profile\Controller\ProfileController::setDefault'
options:
parameters:
profile:
type: entity:profile
requirements:
_entity_access: 'profile.edit'
_csrf_token: 'TRUE'
@@ -0,0 +1,7 @@
services:
access_check.profile_type.profile_access:
class: Drupal\profile\Access\ProfileAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check, applies_to: _profile_access_check }
@@ -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(),
]));
}
}
@@ -0,0 +1,24 @@
{#
/**
* @file
*
* Default theme implementation for profiles.
*
* Available variables:
* - content: Items for the content of the profile.
* Use 'content' to print them all, or print a subset such as
* 'content.title'. Use the following code to exclude the
* printing of a given child element:
* @code
* {{ content|without('title') }}
* @endcode
* - attributes: HTML attributes for the wrapper.
* - profile: The profile object.
* - url: The profile URL.
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{{ content }}
</div>
@@ -0,0 +1,12 @@
name: Profile
type: module
description: 'Test module for Profile.'
# core: 8.x
dependencies:
- profile
# Information added by Drupal.org packaging script on 2016-08-31
version: '8.x-1.0-alpha5+4-dev'
core: '8.x'
project: 'profile'
datestamp: 1472611474
@@ -0,0 +1,243 @@
langcode: en
status: true
dependencies:
module:
- profile
- user
id: users
label: Users
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access user profiles'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 0
offset: 0
id: 0
total_pages: null
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
quantity: 9
style:
type: default
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
fields:
name:
id: name
table: users_field_data
field: name
entity_type: user
entity_field: name
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: user_name
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
profile_id:
id: profile_id
table: profile
field: profile_id
relationship: profile
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: profile
entity_field: profile_id
plugin_id: field
filters:
status:
value: true
table: users_field_data
field: status
plugin_id: boolean
entity_type: user
entity_field: status
id: status
expose:
operator: ''
group: 1
sorts: { }
title: Users
header: { }
footer: { }
empty: { }
relationships:
profile:
id: profile
table: users_field_data
field: profile
relationship: none
group_type: group
admin_label: Profile
required: true
entity_type: user
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: users
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- user.permissions
tags: { }
@@ -0,0 +1,66 @@
<?php
namespace Drupal\Tests\profile\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Session\AccountInterface;
/**
* Tests attaching of profile entity forms to other forms.
*
* @group profile
*/
class ProfileRegisterFormTest extends ProfileTestBase {
/**
* Test user registration integration.
*/
public function testUserRegisterForm() {
$id = $this->type->id();
$field_name = $this->field->getName();
$this->field->setRequired(TRUE);
$this->field->save();
// Allow registration without administrative approval and log in user
// directly after registering.
\Drupal::configFactory()->getEditable('user.settings')
->set('register', USER_REGISTER_VISITORS)
->set('verify_mail', 0)
->save();
user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ['view own test profile']);
// Verify that the additional profile field is attached and required.
$name = $this->randomMachineName();
$pass_raw = $this->randomMachineName();
$edit = [
'name' => $name,
'mail' => $this->randomMachineName() . '@example.com',
'pass[pass1]' => $pass_raw,
'pass[pass2]' => $pass_raw,
];
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertSession()->pageTextContains(new FormattableMarkup('@name field is required.', ['@name' => $this->field->getLabel()]));
// Verify that we can register.
$edit["entity_" . $id . "[$field_name][0][value]"] = $this->randomMachineName();
$this->drupalPostForm(NULL, $edit, t('Create new account'));
$this->assertSession()->pageTextContains(new FormattableMarkup('Registration successful. You are now logged in.', []));
$new_user = user_load_by_name($name);
$this->assertTrue($new_user->isActive(), 'New account is active after registration.');
// Verify that a new profile was created for the new user ID.
$profile = $this->container->get('entity_type.manager')
->getStorage('profile')
->loadByUser($new_user, $this->type->id());
$this->assertEquals($profile->get($field_name)->value, $edit["entity_" . $id . "[$field_name][0][value]"], 'Field value found in loaded profile.');
// Verify that the profile field value appears on the user account page.
$this->drupalGet('user');
$this->assertSession()->pageTextContains($edit["entity_" . $id . "[$field_name][0][value]"]);
}
}
@@ -0,0 +1,140 @@
<?php
namespace Drupal\Tests\profile\Functional;
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;
use Drupal\Tests\BrowserTestBase;
/**
* Tests profile access handling.
*/
abstract class ProfileTestBase extends BrowserTestBase {
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,210 @@
<?php
namespace Drupal\profile\Tests;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\profile\Entity\Profile;
use Drupal\profile\ProfileTestTrait;
/**
* Tests profile access handling.
*
* @group profile
*/
class ProfileAccessTest extends EntityKernelTestBase {
use ProfileTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user', 'system', 'field', 'text', 'profile', 'views'];
/**
* The access control handler.
*
* @var \Drupal\profile\ProfileAccessControlHandler
*/
protected $accessControlHandler;
/**
* The access manager.
*
* @var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager;
/**
* Testing profile type entity.
*
* @var \Drupal\profile\Entity\ProfileType
*/
protected $type;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('profile');
$this->installEntitySchema('view');
$this->installConfig(['profile']);
$this->accessControlHandler = $this->container->get('entity_type.manager')->getAccessControlHandler('profile');
$this->accessManager = $this->container->get('access_manager');
$this->type = $this->createProfileType('test', 'Test profile', TRUE);
$this->createUser();
}
/**
* Tests profile create and permissions.
*/
public function testProfileCreateAccess() {
// Test user without any permissions.
$web_user1 = $this->createUser();
// Verify user does not have access to create.
$access = $this->accessControlHandler->createAccess($this->type->id(), $web_user1);
$this->assertFalse($access);
// Verify access through route.
$this->assertFalse($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user1->id(), 'profile_type' => $this->type->id()],
$web_user1
));
// Test user with permission to only add own profile.
$web_user2 = $this->createUser([], ["add own {$this->type->id()} profile"]);
$web_user1->addRole($web_user2->get('roles')->first()->target_id);
$web_user1->save();
// Verify user has access to add their own profile.
$access = $this->accessControlHandler->createAccess($this->type->id(), $web_user2);
$this->assertTrue($access);
// User 2 can create own profile of type.
$this->assertTrue($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
$web_user2
));
// User 1 cannot create a profile for User 2.
$this->assertFalse($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
$web_user1
));
// Create a new profile type.
$second_type = $this->createProfileType('test2', 'Test2 profile', TRUE);
$this->assertFalse($this->accessControlHandler->createAccess('test2', $web_user2));
$this->assertFalse($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user2->id(), 'profile_type' => $second_type->id()],
$web_user2
));
// Test user with permission to only add any profile.
$web_user3 = $this->createUser([], ["add any {$this->type->id()} profile"]);
$this->assertTrue($this->accessControlHandler->createAccess($this->type->id(), $web_user3));
// Verify access through route.
$this->assertTrue($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user3->id(), 'profile_type' => $this->type->id()],
$web_user3
));
$this->assertTrue($this->accessManager->checkNamedRoute(
'entity.profile.add_form',
['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
$web_user3
));
}
/**
* Tests profile view access.
*/
public function testProfileViewAccess() {
// Setup users.
$web_user1 = $this->createUser([], [
'access user profiles',
"view own {$this->type->id()} profile",
]);
$web_user2 = $this->createUser([], [
'access user profiles',
"view any {$this->type->id()} profile",
]);
// Setup profiles.
$profile1 = Profile::create([
'uid' => $web_user1->id(),
'type' => $this->type->id(),
]);
$profile1->save();
$profile2 = Profile::create([
'uid' => $web_user2->id(),
'type' => $this->type->id(),
]);
$profile2->save();
// Test user1 can only view own profiles.
$this->assertTrue($profile1->access('view', $web_user1));
$this->assertFalse($profile2->access('view', $web_user1));
// Test user2 can view any profiles.
$this->assertTrue($profile1->access('view', $web_user2));
$this->assertTrue($profile2->access('view', $web_user2));
$user_view_builder = $this->container->get('entity_type.manager')->getViewBuilder('user');
$this->container->get('current_user')->setAccount($web_user1);
$user2_view = $user_view_builder->view($web_user2);
$this->render($user2_view);
$this->assertNoText($this->type->label());
$this->container->get('current_user')->setAccount($web_user2);
$user1_view = $user_view_builder->view($web_user1);
$this->render($user1_view);
$this->assertText($this->type->label());
}
/**
* Tests profile edit flow and permissions.
*/
public function testProfileEditAccess() {
// Setup users.
$web_user1 = $this->createUser([], [
"add own {$this->type->id()} profile",
"edit own {$this->type->id()} profile",
]);
$web_user2 = $this->createUser([], [
"add any {$this->type->id()} profile",
"edit any {$this->type->id()} profile",
]);
// Setup profiles.
$profile1 = Profile::create([
'uid' => $web_user1->id(),
'type' => $this->type->id(),
]);
$profile1->save();
$profile2 = Profile::create([
'uid' => $web_user2->id(),
'type' => $this->type->id(),
]);
$profile2->save();
// Test user1 can only edit own profiles.
$this->assertTrue($profile1->access('edit', $web_user1));
$this->assertFalse($profile2->access('edit', $web_user1));
// Test user2 can edit any profiles.
$this->assertTrue($profile1->access('edit', $web_user2));
$this->assertTrue($profile2->access('edit', $web_user2));
}
}
@@ -0,0 +1,149 @@
<?php
namespace Drupal\Tests\profile\Kernel;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\profile\Entity\ProfileType;
use Drupal\profile\ProfileTestTrait;
/**
* Tests basic functionality of profiles.
*
* @group profile
*/
class ProfileTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'user',
'system',
'field',
'text',
'profile',
'views',
];
/**
* 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->installEntitySchema('profile');
$this->installEntitySchema('view');
$this->installSchema('user', ['users_data']);
$this->installConfig(['profile', 'user']);
$this->profileStorage = $this->container->get('entity_type.manager')
->getStorage('profile');
$this->user1 = $this->createUser();
$this->user2 = $this->createUser();
}
/**
* Tests the profile entity and its methods.
*/
public function testProfile() {
$types_data = [
'profile_type_0' => ['label' => $this->randomMachineName()],
'profile_type_1' => ['label' => $this->randomMachineName()],
];
/** @var \Drupal\profile\Entity\ProfileTypeInterface[] $types */
$types = [];
foreach ($types_data as $id => $values) {
$types[$id] = ProfileType::create(['id' => $id] + $values);
$types[$id]->save();
}
// Create a new profile.
/** @var \Drupal\profile\Entity\ProfileInterface $profile */
$profile = $this->profileStorage->create($expected = [
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$expected_label = new TranslatableMarkup('@type profile of @username (uid: @uid)', [
'@type' => $types['profile_type_0']->label(),
'@username' => $this->user1->getDisplayName(),
'@uid' => $this->user1->id(),
]);
$this->assertEquals($expected_label, $profile->label());
$this->assertEquals($profile->getOwnerId(), $this->user1->id());
$this->assertEquals($profile->getCreatedTime(), REQUEST_TIME);
$this->assertEquals($profile->getChangedTime(), REQUEST_TIME);
// Save the profile.
$profile->save();
$this->assertEquals(REQUEST_TIME, $profile->getChangedTime());
// List profiles for the user and verify that the new profile appears.
$list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
$list_ids = array_keys($list);
$this->assertEquals($list_ids, [$profile->id()]);
// Create a second profile.
$user1_profile1 = $profile;
$user1_profile = $this->profileStorage->create([
'type' => $types['profile_type_0']->id(),
'uid' => $this->user1->id(),
]);
$user1_profile->save();
// List profiles for the user and verify that both profiles appear.
$list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
$list_ids = array_keys($list);
$this->assertEquals($list_ids, [$user1_profile1->id(), $user1_profile->id()]);
// Delete the second profile and verify that the first still exists.
$user1_profile->delete();
$list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
$list_ids = array_keys($list);
$this->assertEquals($list_ids, [$user1_profile1->id()]);
// Create a profile for the second user.
$user2_profile1 = $this->profileStorage->create([
'type' => $types['profile_type_0']->id(),
'uid' => $this->user2->id(),
]);
$user2_profile1->save();
// Delete the first user and verify that all of its profiles are deleted.
$this->user1->delete();
$list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
$list_ids = array_keys($list);
$this->assertEquals($list_ids, []);
// List profiles for the second user and verify that they still exist.
$list = $this->profileStorage->loadByProperties(['uid' => $this->user2->id()]);
$list_ids = array_keys($list);
$this->assertEquals($list_ids, [$user2_profile1->id()]);
}
}
@@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\profile\Kernel;
use Drupal\Component\Utility\Unicode;
use Drupal\profile\ProfileTestTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\user\Entity\User;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestData;
/**
* Tests profile integration with Views.
*
* @group profile
*/
class ProfileViewTest extends ViewsKernelTestBase {
use ProfileTestTrait;
public static $modules = ['user', 'profile', 'profile_test'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = [
'users',
];
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('user');
$this->installEntitySchema('profile');
$this->installEntitySchema('profile_type');
ViewTestData::createTestViews(get_class($this), ['profile_test']);
}
/**
* Tests views relationship with multiple referenced entities.
*
* Relationship is required, so only users with profiles will be listed.
*/
public function testProfileRelationship() {
$profile_type = $this->createProfileType();
$user[0] = $user1 = User::create([
'name' => Unicode::strtolower($this->randomMachineName()),
'status' => TRUE,
]);
$user1->save();
$user2 = User::create([
'name' => Unicode::strtolower($this->randomMachineName()),
'status' => TRUE,
]);
$user2->save();
$user[1] = $user3 = User::create([
'name' => Unicode::strtolower($this->randomMachineName()),
'status' => TRUE,
]);
$user3->save();
$user4 = User::create([
'name' => Unicode::strtolower($this->randomMachineName()),
'status' => TRUE,
]);
$user4->save();
$profile[0] = $this->createProfile($profile_type, $user1);
$profile[1] = $this->createProfile($profile_type, $user3);
Views::viewsData()->clear();
// Check table relationship exists.
$views_data = Views::viewsData()->get('users_field_data');
$this->assertEquals($views_data['profile']['relationship']['base'], 'profile');
$this->assertEquals($views_data['profile']['relationship']['base field'], 'uid');
$view = Views::getView('users');
$this->executeView($view);
// Ensure values are populated for user and profiles.
foreach ($view->result as $index => $row) {
$this->assertEquals($row->uid, $user[$index]->id(), 'User ' . $user[$index]->id() . ' found on row: ' . $index);
$this->assertEquals($row->profile_users_field_data_profile_id, $profile[$index]->id(), 'Profile ' . $profile[$index]->id() . ' found on view: ' . $index);
}
}
/**
* Create a user, and optionally a profile.
*
* @return \Drupal\user\UserInterface
* A newly created user.
*/
protected function createUser() {
$user = User::create([
'name' => Unicode::strtolower($this->randomMachineName()),
'status' => TRUE,
]);
$user->save();
return $user;
}
}