a few more base modules
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
+9
@@ -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
|
||||
+9
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -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
|
||||
+243
@@ -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: { }
|
||||
+66
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user