a few more base modules
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
language: php
|
||||
sudo: false
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
# We cannot use hhvm-nightly since that does not work in Travis CI's old
|
||||
# Ubuntu 12.04.
|
||||
- php: hhvm
|
||||
# Don't wait for the allowed failures to build.
|
||||
fast_finish: true
|
||||
|
||||
mysql:
|
||||
database: entity
|
||||
username: root
|
||||
encoding: utf8
|
||||
|
||||
before_script:
|
||||
# Remove Xdebug as we don't need it and it causes
|
||||
# PHP Fatal error: Maximum function nesting level of '256' reached.
|
||||
# We also don't care if that file exists or not on PHP 7.
|
||||
- phpenv config-rm xdebug.ini || true
|
||||
|
||||
# Remember the current entity test directory for later use in the Drupal
|
||||
# installation.
|
||||
- TESTDIR=$(pwd)
|
||||
# Navigate out of module directory to prevent blown stack by recursive module
|
||||
# lookup.
|
||||
- cd ..
|
||||
|
||||
# Create database.
|
||||
- mysql -e 'create database entity'
|
||||
# Export database variable for kernel tests.
|
||||
- export SIMPLETEST_DB=mysql://root:@127.0.0.1/entity
|
||||
# Download Drupal 8 core.
|
||||
- travis_retry git clone --branch 8.1.x --depth 1 http://git.drupal.org/project/drupal.git
|
||||
- cd drupal
|
||||
- composer self-update
|
||||
- composer install -n
|
||||
|
||||
# Reference entity in build site.
|
||||
- ln -s $TESTDIR modules/entity
|
||||
|
||||
# Start a web server on port 8888, run in the background; wait for
|
||||
# initialization.
|
||||
- nohup php -S localhost:8888 > /dev/null 2>&1 &
|
||||
|
||||
# Export web server URL for browser tests.
|
||||
- export SIMPLETEST_BASE_URL=http://localhost:8888
|
||||
|
||||
script:
|
||||
# Run the PHPUnit tests which also include the kernel tests.
|
||||
- ./vendor/phpunit/phpunit/phpunit -c ./core/phpunit.xml.dist --verbose ./modules/entity
|
||||
@@ -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,11 @@
|
||||
Entity API module
|
||||
-----------------
|
||||
|
||||
This module contains improvements and extensions to the Drupal 8 Entity system.
|
||||
The goal is to bring useful improvements to upcoming Drupal 8 minor releases
|
||||
(e.g. 8.1, 8.2, ..) while maintaining backwards compatibility in future Entity
|
||||
API module versions (based on the improvements which went into core).
|
||||
|
||||
@todo: Explain version compatibility pattern.
|
||||
@todo: Add overview of all items and maintainers.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "drupal/entity",
|
||||
"type": "drupal-module",
|
||||
"description": "Provides expanded entity APIs, which will be moved to Drupal core one day.",
|
||||
"homepage": "http://drupal.org/project/entity",
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"drupal/core": "~8.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
action.configuration.entity_delete_action:*:
|
||||
type: action_configuration_default
|
||||
label: 'Delete entity configuration'
|
||||
|
||||
views.field.rendered_entity:
|
||||
type: views_field
|
||||
label: 'Rendered entity'
|
||||
mapping:
|
||||
view_mode:
|
||||
type: string
|
||||
label: 'View mode'
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Entity
|
||||
description: Provides expanded entity APIs, which will be moved to Drupal core one day.
|
||||
type: module
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- system (>=8.1.0)
|
||||
|
||||
# Information added by Drupal.org packaging script on 2016-06-23
|
||||
version: '8.x-1.0-alpha3+2-dev'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1466693538
|
||||
@@ -0,0 +1,3 @@
|
||||
entity.revisions_overview:
|
||||
deriver: 'Drupal\entity\Plugin\Derivative\RevisionsOverviewDeriver'
|
||||
weight: 100
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides expanded entity APIs.
|
||||
*/
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
access_checker.entity_revision:
|
||||
class: \Drupal\entity\Access\EntityRevisionRouteAccessChecker
|
||||
arguments: ['@entity_type.manager', '@request_stack']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _entity_access_revision }
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_views_data().
|
||||
*/
|
||||
function entity_views_data() {
|
||||
$entity_types = \Drupal::entityTypeManager()->getDefinitions();
|
||||
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type->isSubclassOf(ContentEntityInterface::class);
|
||||
});
|
||||
|
||||
$data = [];
|
||||
foreach ($entity_types as $entity_type) {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
|
||||
$base_table = $entity_type->getBaseTable() ?: $entity_type->id();
|
||||
|
||||
if ($entity_type->hasViewBuilderClass()) {
|
||||
$data[$base_table]['rendered_entity'] = [
|
||||
'field' => [
|
||||
'title' => t('Rendered entity'),
|
||||
'help' => t('Renders an entity in a view mode.'),
|
||||
'id' => 'rendered_entity',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Checks access to a entity revision.
|
||||
*/
|
||||
class EntityRevisionRouteAccessChecker implements AccessInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Stores calculated access check results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $accessCache = array();
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Creates a new EntityRevisionRouteAccessChecker instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(Route $route, AccountInterface $account, Request $request = NULL) {
|
||||
if (empty($request)) {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
}
|
||||
|
||||
$operation = $route->getRequirement('_entity_access_revision');
|
||||
list(, $operation) = explode('.', $operation, 2);
|
||||
|
||||
if ($operation === 'list') {
|
||||
$_entity = $request->attributes->get('_entity', $request->attributes->get($route->getOption('entity_type_id')));
|
||||
return AccessResult::allowedIf($this->checkAccess($_entity, $account, $operation))->cachePerPermissions();
|
||||
}
|
||||
else {
|
||||
$_entity_revision = $request->attributes->get('_entity_revision');
|
||||
return AccessResult::allowedIf($_entity_revision && $this->checkAccess($_entity_revision, $account, $operation))->cachePerPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkAccess(ContentEntityInterface $entity, AccountInterface $account, $operation = 'view') {
|
||||
$entity_type = $entity->getEntityType();
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$entity_access = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $entity_storage */
|
||||
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
|
||||
$map = [
|
||||
'view' => "view all $entity_type_id revisions",
|
||||
'list' => "view all $entity_type_id revisions",
|
||||
'update' => "revert all $entity_type_id revisions",
|
||||
'delete' => "delete all $entity_type_id revisions",
|
||||
];
|
||||
$bundle = $entity->bundle();
|
||||
$type_map = [
|
||||
'view' => "view $entity_type_id $bundle revisions",
|
||||
'list' => "view $entity_type_id $bundle revisions",
|
||||
'update' => "revert $entity_type_id $bundle revisions",
|
||||
'delete' => "delete $entity_type_id $bundle revisions",
|
||||
];
|
||||
|
||||
if (!$entity || !isset($map[$operation]) || !isset($type_map[$operation])) {
|
||||
// If there was no node to check against, or the $op was not one of the
|
||||
// supported ones, we return access denied.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Statically cache access by revision ID, language code, user account ID,
|
||||
// and operation.
|
||||
$langcode = $entity->language()->getId();
|
||||
$cid = $entity->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $operation;
|
||||
|
||||
if (!isset($this->accessCache[$cid])) {
|
||||
// Perform basic permission checks first.
|
||||
if (!$account->hasPermission($map[$operation]) && !$account->hasPermission($type_map[$operation]) && !$account->hasPermission('administer nodes')) {
|
||||
$this->accessCache[$cid] = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (($admin_permission = $entity_type->getAdminPermission()) && $account->hasPermission($admin_permission)) {
|
||||
$this->accessCache[$cid] = TRUE;
|
||||
}
|
||||
else {
|
||||
// First check the access to the default revision and finally, if the
|
||||
// node passed in is not the default revision then access to that, too.
|
||||
$this->accessCache[$cid] = $entity_access->access($entity_storage->load($entity->id()), $operation, $account) && ($entity->isDefaultRevision() || $entity_access->access($entity, $operation, $account));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->accessCache[$cid];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Counts the number of revisions in the default language.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
|
||||
* The entity storage.
|
||||
*
|
||||
* @return int
|
||||
* The number of revisions in the default language.
|
||||
*/
|
||||
protected function countDefaultLanguageRevisions(ContentEntityInterface $entity, EntityStorageInterface $entity_storage) {
|
||||
$entity_type = $entity->getEntityType();
|
||||
$count = $entity_storage->getQuery()
|
||||
->allRevisions()
|
||||
->condition($entity_type->getKey('id'), $entity->id())
|
||||
->condition($entity_type->getKey('default_langcode'), 1)
|
||||
->count()
|
||||
->execute();
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the access cache.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetAccessCache() {
|
||||
$this->accessCache = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Controller;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Defines a trait for common revision UI functionality.
|
||||
*/
|
||||
trait RevisionControllerTrait {
|
||||
|
||||
/**
|
||||
* Returns the entity type manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
abstract protected function entityTypeManager();
|
||||
|
||||
/**
|
||||
* Returns the langauge manager.
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
public abstract function languageManager();
|
||||
|
||||
/**
|
||||
* Determines if the user has permission to revert revisions.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to check revert access for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the user has revert access.
|
||||
*/
|
||||
abstract protected function hasRevertRevisionAccess(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Determines if the user has permission to delete revisions.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to check delete revision access for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the user has delete revision access.
|
||||
*/
|
||||
abstract protected function hasDeleteRevisionAccess(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Builds a link to revert an entity revision.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
|
||||
* The entity to build a revert revision link for.
|
||||
*
|
||||
* @return array
|
||||
* A link render array.
|
||||
*
|
||||
*/
|
||||
abstract protected function buildRevertRevisionLink(EntityInterface $entity_revision);
|
||||
|
||||
/**
|
||||
* Builds a link to delete an entity revision.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
|
||||
* The entity to build a delete revision link for.
|
||||
*
|
||||
* @return array
|
||||
* A link render array.
|
||||
*/
|
||||
abstract protected function buildDeleteRevisionLink(EntityInterface $entity_revision);
|
||||
|
||||
/**
|
||||
* Returns a string providing details of the revision.
|
||||
*
|
||||
* E.g. Node describes its revisions using {date} by {username}. For the
|
||||
* non-current revision, it also provides a link to view that revision.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $revision
|
||||
* The entity revision.
|
||||
* @param bool $is_current
|
||||
* TRUE if the revision is the current revision.
|
||||
*
|
||||
* @return string
|
||||
* Returns a string to provide the details of the revision.
|
||||
*/
|
||||
abstract protected function getRevisionDescription(ContentEntityInterface $revision, $is_current = FALSE);
|
||||
|
||||
/**
|
||||
* Loads all revision IDs of an entity sorted by revision ID descending.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function revisionIds(ContentEntityInterface $entity) {
|
||||
$entity_type = $entity->getEntityType();
|
||||
$result = $this->entityTypeManager()->getStorage($entity_type->id())->getQuery()
|
||||
->allRevisions()
|
||||
->condition($entity_type->getKey('id'), $entity->id())
|
||||
->sort($entity_type->getKey('revision'), 'DESC')
|
||||
->execute();
|
||||
return array_keys($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an overview table of older revisions of an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* An entity object.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
protected function revisionOverview(ContentEntityInterface $entity) {
|
||||
$langcode = $this->languageManager()
|
||||
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
|
||||
->getId();
|
||||
$entity_storage = $this->entityTypeManager()
|
||||
->getStorage($entity->getEntityTypeId());
|
||||
|
||||
$header = [$this->t('Revision'), $this->t('Operations')];
|
||||
$rows = [];
|
||||
|
||||
$revision_ids = $this->revisionIds($entity);
|
||||
// @todo Expand the entity storage to load multiple revisions.
|
||||
$entity_revisions = array_combine($revision_ids, array_map(function($vid) use ($entity_storage) {
|
||||
return $entity_storage->loadRevision($vid);
|
||||
}, $revision_ids));
|
||||
|
||||
foreach ($entity_revisions as $revision) {
|
||||
$row = [];
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)
|
||||
->isRevisionTranslationAffected()
|
||||
) {
|
||||
$row[] = $this->getRevisionDescription($revision, $revision->isDefaultRevision());
|
||||
|
||||
if ($revision->isDefaultRevision()) {
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#prefix' => '<em>',
|
||||
'#markup' => $this->t('Current revision'),
|
||||
'#suffix' => '</em>',
|
||||
],
|
||||
];
|
||||
foreach ($row as &$current) {
|
||||
$current['class'] = ['revision-current'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$links = $this->getOperationLinks($revision);
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$build[$entity->getEntityTypeId() . '_revisions_table'] = [
|
||||
'#theme' => 'table',
|
||||
'#rows' => $rows,
|
||||
'#header' => $header,
|
||||
];
|
||||
|
||||
// We have no clue about caching yet.
|
||||
$build['#cache']['max-age'] = 0;
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the links of the operations for an entity revision.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
|
||||
* The entity to build the revision links for.
|
||||
*
|
||||
* @return array
|
||||
* The operation links.
|
||||
*/
|
||||
protected function getOperationLinks(EntityInterface $entity_revision) {
|
||||
$links = [];
|
||||
if ($this->hasRevertRevisionAccess($entity_revision)) {
|
||||
$links['revert'] = $this->buildRevertRevisionLink($entity_revision);
|
||||
}
|
||||
|
||||
if ($this->hasDeleteRevisionAccess($entity_revision)) {
|
||||
$links['delete'] = $this->buildDeleteRevisionLink($entity_revision);
|
||||
}
|
||||
|
||||
return array_filter($links);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a controller which shows the revision history.
|
||||
*
|
||||
* This controller leverages the revision controller trait, which is agnostic to
|
||||
* any entity type, by using \Drupal\Core\Entity\RevisionLogInterface.
|
||||
*/
|
||||
class RevisionOverviewController extends ControllerBase {
|
||||
|
||||
use RevisionControllerTrait;
|
||||
|
||||
/**
|
||||
* The date formatter.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Creates a new RevisionOverviewController instance.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter.
|
||||
*/
|
||||
public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('date.formatter'), $container->get('renderer'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function hasDeleteRevisionAccess(EntityInterface $entity) {
|
||||
return $this->currentUser()->hasPermission("delete all {$entity->id()} revisions");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildRevertRevisionLink(EntityInterface $entity_revision) {
|
||||
if ($entity_revision->hasLinkTemplate('revision-revert-form')) {
|
||||
return [
|
||||
'title' => t('Revert'),
|
||||
'url' => $entity_revision->toUrl('revision-revert-form'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildDeleteRevisionLink(EntityInterface $entity_revision) {
|
||||
if ($entity_revision->hasLinkTemplate('revision-delete-form')) {
|
||||
return [
|
||||
'title' => t('Delete'),
|
||||
'url' => $entity_revision->toUrl('revision-delete-form'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an overview table of older revisions of an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
public function revisionOverviewController(RouteMatchInterface $route_match) {
|
||||
return $this->revisionOverview($route_match->getParameter($route_match->getRouteObject()->getOption('entity_type_id')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getRevisionDescription(ContentEntityInterface $revision, $is_default = FALSE) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\user\EntityOwnerInterface|\Drupal\Core\Entity\RevisionLogInterface $revision */
|
||||
if ($revision instanceof RevisionLogInterface) {
|
||||
// Use revision link to link to revisions that are not active.
|
||||
$date = $this->dateFormatter->format($revision->getRevisionCreationTime(), 'short');
|
||||
$link = $revision->toLink($date, 'revision');
|
||||
|
||||
// @todo: Simplify this when https://www.drupal.org/node/2334319 lands.
|
||||
$username = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $revision->getRevisionUser(),
|
||||
];
|
||||
$username = $this->renderer->render($username);
|
||||
}
|
||||
else {
|
||||
$link = $revision->toLink($revision->label(), 'revision');
|
||||
$username = '';
|
||||
|
||||
}
|
||||
|
||||
$markup = '';
|
||||
if ($revision instanceof RevisionLogInterface) {
|
||||
$markup = $revision->getRevisionLogMessage();
|
||||
}
|
||||
|
||||
if ($username) {
|
||||
$template = '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}';
|
||||
}
|
||||
else {
|
||||
$template = '{% trans %} {{ date }} {% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}';
|
||||
}
|
||||
|
||||
$column = [
|
||||
'data' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => $template,
|
||||
'#context' => [
|
||||
'date' => $link->toString(),
|
||||
'username' => $username,
|
||||
'message' => ['#markup' => $markup, '#allowed_tags' => Xss::getHtmlTagList()],
|
||||
],
|
||||
],
|
||||
];
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function hasRevertRevisionAccess(EntityInterface $entity) {
|
||||
return AccessResult::allowedIfHasPermission($this->currentUser(), "revert all {$entity->getEntityTypeId()} revisions")->orIf(
|
||||
AccessResult::allowedIfHasPermission($this->currentUser(), "revert {$entity->bundle()} {$entity->getEntityTypeId()} revisions")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
interface RevisionableEntityBundleInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns whether a new revision should be created by default.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a new revision should be created by default.
|
||||
*/
|
||||
public function shouldCreateNewRevision();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilder as CoreEntityViewBuilder;
|
||||
|
||||
/**
|
||||
* Provides a entity view builder with contextual links support
|
||||
*/
|
||||
class EntityViewBuilder extends CoreEntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
if (($entity instanceof ContentEntityInterface && $entity->isDefaultRevision()) || !$entity->getEntityType()->isRevisionable()) {
|
||||
$build['#contextual_links'][$entity_type_id] = [
|
||||
'route_parameters' => [
|
||||
$entity_type_id => $entity->id()
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$build['#contextual_links'][$entity_type_id . '_revision'] = [
|
||||
'route_parameters' => [
|
||||
$entity_type_id => $entity->id(),
|
||||
$entity_type_id . '_revision' => $entity->getRevisionId(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an entities deletion confirmation form.
|
||||
*/
|
||||
class DeleteMultiple extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore.
|
||||
*
|
||||
* @var \Drupal\user\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The entity type id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The selection, in the entity_id => langcodes format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selection = [];
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteMultiple object.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
*/
|
||||
public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('user.private_tempstore')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'entity_delete_multiple_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.' . $this->entityTypeId . '.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type id.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
|
||||
$this->entityTypeId = $entity_type_id;
|
||||
$this->selection = $this->tempStore->get($this->currentUser->id());
|
||||
if (empty($this->entityTypeId) || empty($this->selection)) {
|
||||
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
|
||||
}
|
||||
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
$items = [];
|
||||
foreach ($this->selection as $id => $langcodes) {
|
||||
foreach ($langcodes as $langcode) {
|
||||
$entity = $entities[$id]->getTranslation($langcode);
|
||||
$key = $id . ':' . $langcode;
|
||||
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
|
||||
|
||||
// If we have a translated entity we build a nested list of translations
|
||||
// that will be deleted.
|
||||
$languages = $entity->getTranslationLanguages();
|
||||
if (count($languages) > 1 && $entity->isDefaultTranslation()) {
|
||||
$names = [];
|
||||
foreach ($languages as $translation_langcode => $language) {
|
||||
$names[] = $language->getName();
|
||||
unset($items[$id . ':' . $translation_langcode]);
|
||||
}
|
||||
$items[$default_key] = [
|
||||
'label' => [
|
||||
'#markup' => $this->t('@label (Original translation) - <em>The following translations will be deleted:</em>', ['@label' => $entity->label()]),
|
||||
],
|
||||
'deleted_translations' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $names,
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif (!isset($items[$default_key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['entities'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
];
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$total_count = 0;
|
||||
$delete_entities = [];
|
||||
$delete_translations = [];
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
|
||||
foreach ($this->selection as $id => $langcodes) {
|
||||
foreach ($langcodes as $langcode) {
|
||||
$entity = $entities[$id]->getTranslation($langcode);
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
$delete_entities[$id] = $entity;
|
||||
unset($delete_translations[$id]);
|
||||
$total_count += count($entity->getTranslationLanguages());
|
||||
}
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_translations[$id][] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_entities) {
|
||||
$storage->delete($delete_entities);
|
||||
$this->logger('content')->notice('Deleted @count @entity_type items.', [
|
||||
'@count' => count($delete_entities),
|
||||
'@entity_type' => $this->entityTypeId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($delete_translations) {
|
||||
$count = 0;
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
|
||||
foreach ($delete_translations as $id => $translations) {
|
||||
$entity = $entities[$id]->getUntranslated();
|
||||
foreach ($translations as $translation) {
|
||||
$entity->removeTranslation($translation->language()->getId());
|
||||
}
|
||||
$entity->save();
|
||||
$count += count($translations);
|
||||
}
|
||||
if ($count) {
|
||||
$total_count += $count;
|
||||
$this->logger('content')->notice('Deleted @count @entity_type translations.', [
|
||||
'@count' => $count,
|
||||
'@entity_type' => $this->entityTypeId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_count) {
|
||||
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 item.', 'Deleted @count items.'));
|
||||
}
|
||||
$this->tempStore->delete($this->currentUser->id());
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class RevisionRevertForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The entity revision.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\RevisionLogInterface
|
||||
*/
|
||||
protected $revision;
|
||||
|
||||
/**
|
||||
* The date formatter.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The entity bundle information.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInformation;
|
||||
|
||||
/**
|
||||
* Creates a new RevisionRevertForm instance.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_information
|
||||
* The bundle information.
|
||||
*/
|
||||
public function __construct(DateFormatterInterface $date_formatter, EntityTypeBundleInfoInterface $bundle_information) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->bundleInformation = $bundle_information;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('date.formatter'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'entity_revision_revert_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
if ($this->revision instanceof RevisionLogInterface) {
|
||||
return $this->t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]);
|
||||
}
|
||||
return $this->t('Are you sure you want to revert the revision?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
if ($this->revision->getEntityType()->hasLinkTemplate('version-history')) {
|
||||
return $this->revision->toUrl('version-history');
|
||||
}
|
||||
return $this->revision->toUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return t('Revert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $_entity_revision = NULL, Request $request = NULL) {
|
||||
$this->revision = $_entity_revision;
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// The revision timestamp will be updated when the revision is saved. Keep
|
||||
// the original one for the confirmation message.
|
||||
$this->revision = $this->prepareRevision($this->revision);
|
||||
if ($this->revision instanceof RevisionLogInterface) {
|
||||
$original_revision_timestamp = $this->revision->getRevisionCreationTime();
|
||||
|
||||
$this->revision->setRevisionLogMessage($this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]));
|
||||
drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)]));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('@type %title has been reverted', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label()]));
|
||||
}
|
||||
|
||||
$this->revision->save();
|
||||
|
||||
$this->logger('content')->notice('@type: reverted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
|
||||
$form_state->setRedirect(
|
||||
"entity.{$this->revision->getEntityTypeId()}.version_history",
|
||||
[$this->revision->getEntityTypeId() => $this->revision->id()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a revision to be reverted.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\RevisionableInterface $revision
|
||||
* The revision to be reverted.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\RevisionableInterface
|
||||
* The prepared revision ready to be stored.
|
||||
*/
|
||||
protected function prepareRevision(RevisionableInterface $revision) {
|
||||
$revision->setNewRevision();
|
||||
$revision->isDefaultRevision(TRUE);
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bundle label.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\RevisionableInterface $revision
|
||||
* The entity revision.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBundleLabel(RevisionableInterface $revision) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $revision */
|
||||
$bundle_info = $this->bundleInformation->getBundleInfo($revision->getEntityTypeId());
|
||||
return $bundle_info[$revision->bundle()]['label'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\entity\Entity\RevisionableEntityBundleInterface;
|
||||
|
||||
/**
|
||||
* Extends the base entity form with revision support in the UI.
|
||||
*/
|
||||
class RevisionableContentEntityForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The entity being used by this form.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\entity\Revision\EntityRevisionLogInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareEntity() {
|
||||
parent::prepareEntity();
|
||||
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
|
||||
// Set up default values, if required.
|
||||
if (!$this->entity->isNew()) {
|
||||
$this->entity->setRevisionLogMessage(NULL);
|
||||
}
|
||||
|
||||
if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
|
||||
// Always use the default revision setting.
|
||||
$this->entity->setNewRevision($bundle_entity && $bundle_entity->shouldCreateNewRevision());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle entity of the entity, or NULL if there is none.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
*/
|
||||
protected function getBundleEntity() {
|
||||
if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) {
|
||||
return $this->entity->{$bundle_key}->referencedEntities()[0];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$entity_type = $this->entity->getEntityType();
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
$account = $this->currentUser();
|
||||
|
||||
if ($this->operation == 'edit') {
|
||||
$form['#title'] = $this->t('Edit %bundle_label @label', [
|
||||
'%bundle_label' => $bundle_entity ? $bundle_entity->label() : '',
|
||||
'@label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
$form['advanced'] = [
|
||||
'#type' => 'vertical_tabs',
|
||||
'#weight' => 99,
|
||||
];
|
||||
|
||||
// Add a log field if the "Create new revision" option is checked, or if the
|
||||
// current user has the ability to check that option.
|
||||
// @todo Could we autogenerate this form by using some widget on the
|
||||
// revision info field.
|
||||
$form['revision_information'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Revision information'),
|
||||
// Open by default when "Create new revision" is checked.
|
||||
'#open' => $this->entity->isNewRevision(),
|
||||
'#group' => 'advanced',
|
||||
'#weight' => 20,
|
||||
'#access' => $this->entity->isNewRevision() || $account->hasPermission($entity_type->get('admin_permission')),
|
||||
];
|
||||
|
||||
$form['revision_information']['revision'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Create new revision'),
|
||||
'#default_value' => $this->entity->isNewRevision(),
|
||||
'#access' => $account->hasPermission($entity_type->get('admin_permission')),
|
||||
];
|
||||
|
||||
// Check the revision log checkbox when the log textarea is filled in.
|
||||
// This must not happen if "Create new revision" is enabled by default,
|
||||
// since the state would auto-disable the checkbox otherwise.
|
||||
if (!$this->entity->isNewRevision()) {
|
||||
$form['revision_information']['revision']['#states'] = [
|
||||
'checked' => [
|
||||
'textarea[name="revision_log"]' => ['empty' => FALSE],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$form['revision_information']['revision_log'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Revision log message'),
|
||||
'#rows' => 4,
|
||||
'#default_value' => $this->entity->getRevisionLogMessage(),
|
||||
'#description' => $this->t('Briefly describe the changes you have made.'),
|
||||
];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
// Save as a new revision if requested to do so.
|
||||
if (!$form_state->isValueEmpty('revision')) {
|
||||
$this->entity->setNewRevision();
|
||||
}
|
||||
|
||||
$insert = $this->entity->isNew();
|
||||
$this->entity->save();
|
||||
$context = ['@type' => $this->entity->bundle(), '%info' => $this->entity->label()];
|
||||
$logger = $this->logger($this->entity->id());
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
$t_args = ['@type' => $bundle_entity ? $bundle_entity->label() : 'None', '%info' => $this->entity->label()];
|
||||
|
||||
if ($insert) {
|
||||
$logger->notice('@type: added %info.', $context);
|
||||
drupal_set_message($this->t('@type %info has been created.', $t_args));
|
||||
}
|
||||
else {
|
||||
$logger->notice('@type: updated %info.', $context);
|
||||
drupal_set_message($this->t('@type %info has been updated.', $t_args));
|
||||
}
|
||||
|
||||
if ($this->entity->id()) {
|
||||
$form_state->setValue('id', $this->entity->id());
|
||||
$form_state->set('id', $this->entity->id());
|
||||
|
||||
if ($this->entity->getEntityType()->hasLinkTemplate('collection')) {
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('canonical'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// In the unlikely case something went wrong on save, the entity will be
|
||||
// rebuilt and entity form redisplayed.
|
||||
drupal_set_message($this->t('The entity could not be saved.'), 'error');
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\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 an entity deletion form.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity_delete_action",
|
||||
* label = @Translation("Delete entity"),
|
||||
* deriver = "Drupal\entity\Plugin\Action\Derivative\DeleteActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The tempstore object.
|
||||
*
|
||||
* @var \Drupal\user\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteAction 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 $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
$selection = [];
|
||||
foreach ($entities as $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
$this->tempStore->set($this->currentUser->id(), $selection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$this->executeMultiple([$object]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a delete action for each content entity type.
|
||||
*/
|
||||
class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteActionDeriver object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static($container->get('entity_type.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (empty($this->derivatives)) {
|
||||
$definitions = [];
|
||||
foreach ($this->getParticipatingEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]);
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['confirm_form_route_name'] = 'entity.' . $entity_type_id . '.delete_multiple_form';
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
}
|
||||
$this->derivatives = $definitions;
|
||||
}
|
||||
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of participating entity types.
|
||||
*
|
||||
* The list consists of all content entity types with a delete-multiple-form
|
||||
* link template.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
* The participating entity types, keyed by entity type id.
|
||||
*/
|
||||
protected function getParticipatingEntityTypes() {
|
||||
$entity_types = $this->entityTypeManager->getDefinitions();
|
||||
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type->isSubclassOf(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
|
||||
});
|
||||
|
||||
return $entity_types;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides local tasks for the revision overview.
|
||||
*/
|
||||
class RevisionsOverviewDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new RevisionsOverviewDeriver instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(\Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager) {
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$exclude = ['node'];
|
||||
|
||||
$this->derivatives = [];
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
if (in_array($entity_type_id, $exclude)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity_type->hasLinkTemplate('version-history')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->derivatives[$entity_type_id] = [
|
||||
'route_name' => "entity.$entity_type_id.version_history",
|
||||
'title' => 'Revisions',
|
||||
'base_route' => "entity.$entity_type_id.canonical",
|
||||
'weight' => 20,
|
||||
] + $base_plugin_definition;
|
||||
}
|
||||
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Revision;
|
||||
|
||||
use Drupal\Core\Entity\RevisionableContentEntityBase as BaseRevisionableContentEntityBase;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
|
||||
/**
|
||||
* Improves the url route handling of core's revisionable content entity base.
|
||||
*/
|
||||
abstract class RevisionableContentEntityBase extends BaseRevisionableContentEntityBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function urlRouteParameters($rel) {
|
||||
$uri_route_parameters = [];
|
||||
|
||||
if ($rel != 'collection') {
|
||||
// The entity ID is needed as a route parameter.
|
||||
$uri_route_parameters[$this->getEntityTypeId()] = $this->id();
|
||||
}
|
||||
if (strpos($this->getEntityType()->getLinkTemplate($rel), $this->getEntityTypeId() . '_revision') !== FALSE) {
|
||||
$uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
|
||||
}
|
||||
|
||||
return $uri_route_parameters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides the HTML route for deleting multiple entities.
|
||||
*/
|
||||
class DeleteMultipleRouteProvider implements EntityRouteProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$routes = new RouteCollection();
|
||||
if ($route = $this->deleteMultipleFormRoute($entity_type)) {
|
||||
$routes->add('entity.' . $entity_type->id() . '.delete_multiple_form', $route);
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delete multiple form route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function deleteMultipleFormRoute(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('delete-multiple-form')) {
|
||||
$route = new Route($entity_type->getLinkTemplate('delete-multiple-form'));
|
||||
$route->setDefault('_form', '\Drupal\entity\Form\DeleteMultiple');
|
||||
$route->setDefault('entity_type_id', $entity_type->id());
|
||||
$route->setRequirement('_permission', $entity_type->getAdminPermission());
|
||||
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides revision routes.
|
||||
*/
|
||||
class RevisionRouteProvider implements EntityRouteProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$collection = new RouteCollection();
|
||||
$entity_type_id = $entity_type->id();
|
||||
if ($view_route = $this->getRevisionViewRoute($entity_type)) {
|
||||
$collection->add("entity.$entity_type_id.revision", $view_route);
|
||||
}
|
||||
if ($view_route = $this->getRevisionRevertRoute($entity_type)) {
|
||||
$collection->add("entity.$entity_type_id.revision_revert_form", $view_route);
|
||||
}
|
||||
|
||||
if ($view_route = $this->getRevisionHistoryRoute($entity_type)) {
|
||||
$collection->add("entity.$entity_type_id.version_history", $view_route);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity revision view route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getRevisionViewRoute(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('revision')) {
|
||||
$entity_type_id = $entity_type->id();
|
||||
$route = new Route($entity_type->getLinkTemplate('revision'));
|
||||
$route->addDefaults([
|
||||
'_controller' => '\Drupal\Core\Entity\Controller\EntityViewController::viewRevision',
|
||||
'_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
|
||||
]);
|
||||
$route->addRequirements([
|
||||
'_entity_access_revision' => "$entity_type_id.view",
|
||||
]);
|
||||
$route->setOption('parameters', [
|
||||
$entity_type->id() => [
|
||||
'type' => 'entity:' . $entity_type->id(),
|
||||
],
|
||||
$entity_type->id() . '_revision' => [
|
||||
'type' => 'entity_revision:' . $entity_type->id(),
|
||||
],
|
||||
]);
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity revision revert route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getRevisionRevertRoute(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('revision-revert-form')) {
|
||||
$entity_type_id = $entity_type->id();
|
||||
$route = new Route($entity_type->getLinkTemplate('revision-revert-form'));
|
||||
$route->addDefaults([
|
||||
'_form' => '\Drupal\entity\Form\RevisionRevertForm',
|
||||
'title' => 'Revert to earlier revision',
|
||||
]);
|
||||
$route->addRequirements([
|
||||
'_entity_access_revision' => "$entity_type_id.update",
|
||||
]);
|
||||
$route->setOption('parameters', [
|
||||
$entity_type->id() => [
|
||||
'type' => 'entity:' . $entity_type->id(),
|
||||
],
|
||||
$entity_type->id() . '_revision' => [
|
||||
'type' => 'entity_revision:' . $entity_type->id(),
|
||||
],
|
||||
]);
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity revision version history route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getRevisionHistoryRoute($entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('version-history')) {
|
||||
$entity_type_id = $entity_type->id();
|
||||
$route = new Route($entity_type->getLinkTemplate('version-history'));
|
||||
$route->addDefaults([
|
||||
'_controller' => '\Drupal\entity\Controller\RevisionOverviewController::revisionOverviewController',
|
||||
'_title' => 'Revisions',
|
||||
]);
|
||||
$route->setRequirement('_entity_access_revision', "$entity_type_id.list");
|
||||
$route->setOption('entity_type_id', $entity_type->id());
|
||||
$route->setOption('parameters', [
|
||||
$entity_type->id() => [
|
||||
'type' => 'entity:' . $entity_type->id(),
|
||||
],
|
||||
]);
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests some integration of the revision overview:
|
||||
*
|
||||
* - Are the routes added properly.
|
||||
* - Are the local tasks added properly.
|
||||
*/
|
||||
class RevisionOverviewIntegrationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['node', 'entity_module_test', 'entity', 'user', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('system', 'router');
|
||||
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
public function testIntegration() {
|
||||
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $local_tasks_manager */
|
||||
$local_tasks_manager = \Drupal::service('plugin.manager.menu.local_task');
|
||||
|
||||
$tasks = $local_tasks_manager->getDefinitions();
|
||||
$this->assertArrayHasKey('entity.revisions_overview:entity_test_enhanced', $tasks);
|
||||
$this->assertArrayNotHasKey('entity.revisions_overview:node', $tasks, 'Node should have been excluded because it provides their own');
|
||||
|
||||
$this->assertEquals('entity.entity_test_enhanced.version_history', $tasks['entity.revisions_overview:entity_test_enhanced']['route_name']);
|
||||
$this->assertEquals('entity.entity_test_enhanced.canonical', $tasks['entity.revisions_overview:entity_test_enhanced']['base_route']);
|
||||
|
||||
/** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider */
|
||||
$route_provider = \Drupal::service('router.route_provider');
|
||||
|
||||
$route = $route_provider->getRouteByName('entity.entity_test_enhanced.version_history');
|
||||
$this->assertInstanceOf(Route::class, $route);
|
||||
$this->assertEquals('\Drupal\entity\Controller\RevisionOverviewController::revisionOverviewController', $route->getDefault('_controller'));
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
entity_module_test.entity_test_enhanced_bundle.*:
|
||||
type: config_entity
|
||||
label: 'Entity test with enhancements - Bundle'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'Type'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
new_revision:
|
||||
type: boolean
|
||||
label: 'New revision'
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
name: Entity test
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2016-06-23
|
||||
version: '8.x-1.0-alpha3+2-dev'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1466693538
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
entity.entity_test_enhanced.canonical:
|
||||
title: View
|
||||
route_name: entity.entity_test_enhanced.canonical
|
||||
base_route: entity.entity_test_enhanced.canonical
|
||||
|
||||
entity.entity_module_test.edit_form:
|
||||
title: Edit
|
||||
route_name: entity.entity_test_enhanced.edit_form
|
||||
base_route: entity.entity_test_enhanced.canonical
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
'administer entity_test_enhanced':
|
||||
title: 'Administer entity_test_enhanced'
|
||||
'restrict access': TRUE
|
||||
|
||||
'view all entity_test_enhanced revisions':
|
||||
title: 'View all entity_test_enhanced revisions'
|
||||
'restrict access': TRUE
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
entity.entity_test_enhanced.collection:
|
||||
path: '/entity_test_enhanced'
|
||||
defaults:
|
||||
_entity_list: 'entity_test_enhanced'
|
||||
_title: 'Entity test with enhancements'
|
||||
requirements:
|
||||
_permission: 'administer entity_test_enhanced'
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity_module_test\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\entity\Revision\RevisionableContentEntityBase;
|
||||
|
||||
/**
|
||||
* Provides a test entity which uses all the capabilities of entity module.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "entity_test_enhanced",
|
||||
* label = @Translation("Entity test with enhancements"),
|
||||
* handlers = {
|
||||
* "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage",
|
||||
* "form" = {
|
||||
* "add" = "\Drupal\entity\Form\RevisionableContentEntityForm",
|
||||
* "edit" = "\Drupal\entity\Form\RevisionableContentEntityForm",
|
||||
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
|
||||
* "delete-multiple" = "\Drupal\entity\Routing\DeleteMultipleRouteProvider",
|
||||
* },
|
||||
* "list_builder" = "\Drupal\Core\Entity\EntityListBuilder",
|
||||
* },
|
||||
* base_table = "entity_test_enhanced",
|
||||
* data_table = "entity_test_enhanced_field_data",
|
||||
* revision_table = "entity_test_enhanced_revision",
|
||||
* revision_data_table = "entity_test_enhanced_field_revision",
|
||||
* translatable = TRUE,
|
||||
* revisionable = TRUE,
|
||||
* admin_permission = "administer entity_test_enhanced",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "bundle" = "type",
|
||||
* "revision" = "vid",
|
||||
* "langcode" = "langcode",
|
||||
* },
|
||||
* links = {
|
||||
* "add-page" = "/entity_test_enhanced/add",
|
||||
* "add-form" = "/entity_test_enhanced/add/{type}",
|
||||
* "edit-form" = "/entity_test_enhanced/{entity_test_enhanced}/edit",
|
||||
* "canonical" = "/entity_test_enhanced/{entity_test_enhanced}",
|
||||
* "collection" = "/entity_test_enhanced",
|
||||
* "delete-multiple-form" = "/entity_test_enhanced/delete",
|
||||
* "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view",
|
||||
* "revision-revert-form" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/revert",
|
||||
* "version-history" = "/entity_test_enhanced/{entity_test_enhanced}/revisions",
|
||||
* },
|
||||
* bundle_entity_type = "entity_test_enhanced_bundle",
|
||||
* )
|
||||
*/
|
||||
class EnhancedEntity extends RevisionableContentEntityBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel('Name')
|
||||
->setRevisionable(TRUE)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
]);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity_module_test\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\Core\Entity\EntityDescriptionInterface;
|
||||
use Drupal\entity\Entity\RevisionableEntityBundleInterface;
|
||||
|
||||
/**
|
||||
* Provides bundles for the test entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "entity_test_enhanced_bundle",
|
||||
* label = @Translation("Entity test with enhancments - Bundle"),
|
||||
* admin_permission = "administer entity_test_enhanced",
|
||||
* config_prefix = "entity_test_enhanced_bundle",
|
||||
* bundle_of = "entity_test_enhanced",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label"
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "description"
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class EnhancedEntityBundle extends ConfigEntityBundleBase implements EntityDescriptionInterface, RevisionableEntityBundleInterface {
|
||||
|
||||
/**
|
||||
* The bundle ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The bundle label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The bundle description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* Should new entities of this bundle have a new revision by default.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $new_revision = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldCreateNewRevision() {
|
||||
return $this->new_revision;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Functional;
|
||||
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\simpletest\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the delete multiple confirmation form.
|
||||
*
|
||||
* @group entity
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class DeleteMultipleFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface;
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['entity_module_test', 'user', 'entity'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
EnhancedEntityBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
])->save();
|
||||
$this->account = $this->drupalCreateUser(['administer entity_test_enhanced']);
|
||||
$this->drupalLogin($this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the add page.
|
||||
*/
|
||||
public function testForm() {
|
||||
$entities = [];
|
||||
$selection = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$entity = EnhancedEntity::create([
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
$entities[$entity->id()] = $entity;
|
||||
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
// Add the selection to the tempstore just like DeleteAction would.
|
||||
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
|
||||
$tempstore->set($this->account->id(), $selection);
|
||||
|
||||
$this->drupalGet('/entity_test_enhanced/delete');
|
||||
$assert = $this->assertSession();
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these items?');
|
||||
$delete_button = $this->getSession()->getPage()->findButton('Delete');
|
||||
$delete_button->click();
|
||||
$assert = $this->assertSession();
|
||||
$assert->addressEquals('/entity_test_enhanced');
|
||||
$assert->responseContains('Deleted 2 items.');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('entity_test_enhanced')->resetCache();
|
||||
$remaining_entities = EnhancedEntity::loadMultiple(array_keys($selection));
|
||||
$this->assertEmpty($remaining_entities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Functional;
|
||||
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\simpletest\BlockCreationTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the revision route access check.
|
||||
*
|
||||
* @group entity
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class RevisionRouteAccessTest extends BrowserTestBase {
|
||||
|
||||
use BlockCreationTrait;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface;
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['entity_module_test', 'user', 'entity', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
EnhancedEntityBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
])->save();
|
||||
|
||||
$this->placeBlock('local_tasks_block');
|
||||
$this->placeBlock('system_breadcrumb_block');
|
||||
|
||||
$this->account = $this->drupalCreateUser([
|
||||
'administer entity_test_enhanced',
|
||||
'view all entity_test_enhanced revisions',
|
||||
]);
|
||||
|
||||
$this->drupalLogin($this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enhanced entity revision routes access.
|
||||
*/
|
||||
public function testRevisionRouteAccess() {
|
||||
$entity = EnhancedEntity::create([
|
||||
'name' => 'rev 1',
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$revision = clone $entity;
|
||||
$revision->name->value = 'rev 2';
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->save();
|
||||
|
||||
$this->drupalGet('/entity_test_enhanced/1/revisions');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Revisions');
|
||||
$collection_link = $this->getSession()->getPage()->findLink('Entity test with enhancements');
|
||||
$collection_link->click();
|
||||
$this->assertSession()->addressEquals('/entity_test_enhanced');
|
||||
$this->assertSession()->responseContains('Edit');
|
||||
$edit_link = $this->getSession()->getPage()->findLink('Edit');
|
||||
$edit_link->click();
|
||||
$this->assertSession()->addressEquals('/entity_test_enhanced/1/edit');
|
||||
// Check if we have revision tab link on edit page.
|
||||
$this->getSession()->getPage()->findLink('Revisions')->click();
|
||||
$this->assertSession()->addressEquals('/entity_test_enhanced/1/revisions');
|
||||
$this->drupalGet('/entity_test_enhanced/1/revisions/2/view');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('rev 2');
|
||||
$revisions_link = $this->getSession()->getPage()->findLink('Revisions');
|
||||
$revisions_link->click();
|
||||
$this->assertSession()->addressEquals('/entity_test_enhanced/1/revisions');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Kernel;
|
||||
|
||||
use Drupal\entity\Plugin\Action\DeleteAction;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the delete entity action.
|
||||
* @group entity
|
||||
*/
|
||||
class DeleteActionTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['action', 'node', 'entity_module_test', 'entity',
|
||||
'user', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('entity_test_enhanced');
|
||||
$this->installSchema('system', ['key_value_expire', 'sequences']);
|
||||
|
||||
$bundle = EnhancedEntityBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
]);
|
||||
$bundle->save();
|
||||
|
||||
$this->user = User::create([
|
||||
'name' => 'username',
|
||||
'status' => 1,
|
||||
]);
|
||||
$this->user->save();
|
||||
\Drupal::service('current_user')->setAccount($this->user);
|
||||
}
|
||||
|
||||
public function testAction() {
|
||||
/** @var \Drupal\system\ActionConfigEntityInterface $action */
|
||||
$action = Action::create([
|
||||
'id' => 'enhanced_entity_delete_action',
|
||||
'label' => 'Delete enhanced entity',
|
||||
'plugin' => 'entity_delete_action:entity_test_enhanced',
|
||||
]);
|
||||
$status = $action->save();
|
||||
$this->assertEquals(SAVED_NEW, $status);
|
||||
$this->assertInstanceOf(DeleteAction::class, $action->getPlugin());
|
||||
|
||||
$entities = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$entity = EnhancedEntity::create([
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
$entities[$entity->id()] = $entity;
|
||||
}
|
||||
|
||||
$action->execute($entities);
|
||||
// Confirm that the entity ids and langcodes are now in the tempstore.
|
||||
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
|
||||
$selection = $tempstore->get($this->user->id());
|
||||
$this->assertEquals(array_keys($entities), array_keys($selection));
|
||||
$this->assertEquals([['en' => 'en'], ['en' => 'en']], array_values($selection));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Kernel;
|
||||
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @group entity
|
||||
*/
|
||||
class RevisionBasicUITest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['entity_module_test', 'system', 'user', 'entity'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('entity_test_enhanced');
|
||||
$this->installSchema('system', 'router');
|
||||
$this->installConfig(['system']);
|
||||
|
||||
$bundle = EnhancedEntityBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
]);
|
||||
$bundle->save();
|
||||
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the revision history controller.
|
||||
*/
|
||||
public function testRevisionHistory() {
|
||||
$entity = EnhancedEntity::create([
|
||||
'name' => 'rev 1',
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$revision = clone $entity;
|
||||
$revision->name->value = 'rev 2';
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->save();
|
||||
|
||||
/** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
|
||||
$http_kernel = \Drupal::service('http_kernel');
|
||||
$request = Request::create($revision->url('version-history'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
|
||||
$role = Role::create(['id' => 'test_role']);
|
||||
$role->grantPermission('view all entity_test_enhanced revisions');
|
||||
$role->grantPermission('administer entity_test_enhanced');
|
||||
$role->save();
|
||||
|
||||
$user = User::create([
|
||||
'name' => 'Test user',
|
||||
]);
|
||||
$user->addRole($role->id());
|
||||
\Drupal::service('account_switcher')->switchTo($user);
|
||||
|
||||
$request = Request::create($revision->url('version-history'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
// This ensures that the default revision is still the first revision.
|
||||
$this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1/revisions/2/view') !== FALSE);
|
||||
$this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1') !== FALSE);
|
||||
|
||||
// Publish a new revision.
|
||||
$revision = clone $entity;
|
||||
$revision->name->value = 'rev 3';
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(TRUE);
|
||||
$revision->save();
|
||||
|
||||
$request = Request::create($revision->url('version-history'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
// The first revision row should now include a revert link.
|
||||
$this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1/revisions/1/revert') !== FALSE);
|
||||
}
|
||||
|
||||
public function testRevisionView() {
|
||||
$entity = EnhancedEntity::create([
|
||||
'name' => 'rev 1',
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$revision = clone $entity;
|
||||
$revision->name->value = 'rev 2';
|
||||
$revision->setNewRevision(TRUE);
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$revision->save();
|
||||
|
||||
/** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
|
||||
$http_kernel = \Drupal::service('http_kernel');
|
||||
$request = Request::create($revision->url('revision'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
|
||||
$role = Role::create(['id' => 'test_role']);
|
||||
$role->grantPermission('view all entity_test_enhanced revisions');
|
||||
$role->grantPermission('administer entity_test_enhanced');
|
||||
$role->save();
|
||||
|
||||
$user = User::create([
|
||||
'name' => 'Test user',
|
||||
]);
|
||||
$user->addRole($role->id());
|
||||
\Drupal::service('account_switcher')->switchTo($user);
|
||||
|
||||
$request = Request::create($revision->url('revision'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertNotContains('rev 1', $response->getContent());
|
||||
$this->assertContains('rev 2', $response->getContent());
|
||||
}
|
||||
|
||||
public function testRevisionRevert() {
|
||||
$entity = EnhancedEntity::create([
|
||||
'name' => 'rev 1',
|
||||
'type' => 'entity_test_enhance',
|
||||
]);
|
||||
$entity->save();
|
||||
$entity->name->value = 'rev 2';
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->save();
|
||||
|
||||
$role = Role::create(['id' => 'test_role']);
|
||||
$role->grantPermission('administer entity_test_enhanced');
|
||||
$role->grantPermission('revert all entity_test_enhanced revisions');
|
||||
$role->save();
|
||||
|
||||
$user = User::create([
|
||||
'name' => 'Test user',
|
||||
]);
|
||||
$user->addRole($role->id());
|
||||
\Drupal::service('account_switcher')->switchTo($user);
|
||||
|
||||
/** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
|
||||
$http_kernel = \Drupal::service('http_kernel');
|
||||
$request = Request::create($entity->url('revision-revert-form'));
|
||||
$response = $http_kernel->handle($request);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user