added features
This commit is contained in:
@@ -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,8 @@
|
||||
{
|
||||
"name": "drupal/features",
|
||||
"description": "Enables administrators to package configuration into modules.",
|
||||
"type": "drupal-module",
|
||||
"license": "GPL-2.0+",
|
||||
"minimum-stability": "dev",
|
||||
"require": { }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
name: Default
|
||||
machine_name: default
|
||||
description: ''
|
||||
assignments:
|
||||
base:
|
||||
types:
|
||||
config:
|
||||
comment_type: comment_type
|
||||
node_type: node_type
|
||||
content:
|
||||
user: user
|
||||
enabled: true
|
||||
weight: -2
|
||||
core:
|
||||
types:
|
||||
config:
|
||||
date_format: date_format
|
||||
field_storage_config: field_storage_config
|
||||
entity_form_mode: entity_form_mode
|
||||
image_style: image_style
|
||||
menu: menu
|
||||
responsive_image_style: responsive_image_style
|
||||
user_role: user_role
|
||||
entity_view_mode: entity_view_mode
|
||||
enabled: true
|
||||
weight: 5
|
||||
dependency:
|
||||
enabled: true
|
||||
weight: 15
|
||||
exclude:
|
||||
types:
|
||||
config:
|
||||
features_bundle: features_bundle
|
||||
curated: true
|
||||
module:
|
||||
installed: true
|
||||
profile: true
|
||||
namespace: true
|
||||
namespace_any: false
|
||||
enabled: true
|
||||
weight: -5
|
||||
existing:
|
||||
enabled: true
|
||||
weight: 12
|
||||
forward_dependency:
|
||||
enabled: true
|
||||
weight: 4
|
||||
namespace:
|
||||
enabled: true
|
||||
weight: 0
|
||||
optional:
|
||||
types:
|
||||
config: {}
|
||||
enabled: true
|
||||
weight: 0
|
||||
packages:
|
||||
enabled: true
|
||||
weight: -20
|
||||
profile:
|
||||
curated: true
|
||||
standard:
|
||||
files: true
|
||||
dependencies: true
|
||||
types:
|
||||
config:
|
||||
block: block
|
||||
language_content_settings: language_content_settings
|
||||
configurable_language: configurable_language
|
||||
migration: migration
|
||||
shortcut_set: shortcut_set
|
||||
tour: tour
|
||||
enabled: true
|
||||
weight: 10
|
||||
site:
|
||||
types:
|
||||
config:
|
||||
action: action
|
||||
contact_form: contact_form
|
||||
block_content_type: block_content_type
|
||||
rdf_mapping: rdf_mapping
|
||||
search_page: search_page
|
||||
taxonomy_vocabulary: taxonomy_vocabulary
|
||||
editor: editor
|
||||
filter_format: filter_format
|
||||
enabled: true
|
||||
weight: 7
|
||||
profile_name: ''
|
||||
is_profile: false
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export:
|
||||
folder: 'custom'
|
||||
langcode: en
|
||||
@@ -0,0 +1,207 @@
|
||||
features.settings:
|
||||
type: config_entity
|
||||
label: 'Features settings'
|
||||
mapping:
|
||||
export:
|
||||
type: mapping
|
||||
label: "Export settings"
|
||||
mapping:
|
||||
folder:
|
||||
type: string
|
||||
label: "Folder"
|
||||
langcode:
|
||||
type: string
|
||||
label: "Language Code"
|
||||
|
||||
features.bundle.*:
|
||||
type: config_entity
|
||||
label: 'Features bundle'
|
||||
mapping:
|
||||
machine_name:
|
||||
type: string
|
||||
label: "Machine name"
|
||||
name:
|
||||
type: string
|
||||
label: "Name"
|
||||
description:
|
||||
type: string
|
||||
label: "Description"
|
||||
assignments:
|
||||
type: sequence
|
||||
label: "Assignment"
|
||||
sequence:
|
||||
type: features.assignment.[%key]
|
||||
profile_name:
|
||||
type: string
|
||||
label: "Profile name"
|
||||
is_profile:
|
||||
type: boolean
|
||||
label: "Is install profile"
|
||||
|
||||
features.assignment.*:
|
||||
type: mapping
|
||||
label: "Assignment settings"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
|
||||
features.assignment.base:
|
||||
type: mapping
|
||||
label: "Base type"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
content:
|
||||
type: sequence
|
||||
label: "Content entity types"
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
features.assignment.core:
|
||||
type: mapping
|
||||
label: "Core type"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
features.assignment.exclude:
|
||||
type: mapping
|
||||
label: "Exclude"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
curated:
|
||||
type: boolean
|
||||
label: "Exclude designated site-specific configuration"
|
||||
module:
|
||||
type: mapping
|
||||
label: "Module"
|
||||
mapping:
|
||||
installed:
|
||||
type: boolean
|
||||
label: "Exclude installed module-provided entity configuration"
|
||||
profile:
|
||||
type: boolean
|
||||
label: "Don't exclude install profile's configuration"
|
||||
namespace:
|
||||
type: boolean
|
||||
label: "Don't exclude non-installed configuration by namespace"
|
||||
namespace_any:
|
||||
type: boolean
|
||||
label: "Don't exclude ANY configuration by namespace"
|
||||
|
||||
features.assignment.optional:
|
||||
type: mapping
|
||||
label: "Optional"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
features.assignment.profile:
|
||||
type: mapping
|
||||
label: "Profile"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
curated:
|
||||
type: boolean
|
||||
label: "Add commonly-needed configuration"
|
||||
standard:
|
||||
type: mapping
|
||||
label: "Standard"
|
||||
mapping:
|
||||
files:
|
||||
type: boolean
|
||||
label: "Add configuration and files from Standard profile"
|
||||
dependencies:
|
||||
type: boolean
|
||||
label: "Add module and theme dependencies from Standard profile"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
features.assignment.site:
|
||||
type: mapping
|
||||
label: "Site"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
mapping:
|
||||
config:
|
||||
type: sequence
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
@@ -0,0 +1,936 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Features module drush integration.
|
||||
*/
|
||||
|
||||
use Drupal\features\ConfigurationItem;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite;
|
||||
use Drupal\Component\Diff\DiffFormatter;
|
||||
|
||||
/**
|
||||
* Implements hook_drush_command().
|
||||
*/
|
||||
function features_drush_command() {
|
||||
$items = array();
|
||||
|
||||
$items['features-status'] = array(
|
||||
'description' => 'Display current Features settings.',
|
||||
'aliases' => array('fs'),
|
||||
);
|
||||
|
||||
$items['features-list-packages'] = array(
|
||||
'description' => 'Display a list of all existing features and packages available to be generated. If a package name is provided as an argument, then all of the configuration objects assigned to that package will be listed.',
|
||||
'examples' => array(
|
||||
"drush features-list-packages" => 'Display a list of all existing featurea and packages available to be generated.',
|
||||
"drush features-list-packages 'example_article'" => "Display a list of all configuration objects assigned to the 'example_article' package.",
|
||||
),
|
||||
'arguments' => array(
|
||||
'package' => 'The package to list. Optional; if specified, lists all configuration objects assigned to that package. If no package is specified, lists all of the features.',
|
||||
),
|
||||
'outputformat' => array(
|
||||
'default' => 'table',
|
||||
'pipe-format' => 'list',
|
||||
'field-labels' => array(
|
||||
'name' => 'Name',
|
||||
'machine_name' => 'Machine name',
|
||||
'status' => 'Status',
|
||||
'version' => 'Version',
|
||||
'state' => 'State',
|
||||
'object' => 'Configuration object',
|
||||
),
|
||||
'output-data-type' => 'format-table',
|
||||
),
|
||||
'aliases' => array('fl'),
|
||||
);
|
||||
|
||||
$items['features-import-all'] = array(
|
||||
'description' => 'Import module config from all installed features.',
|
||||
'examples' => array(
|
||||
"drush features-import-all" => 'Import module config from all installed features.',
|
||||
),
|
||||
'aliases' => array('fra', 'fia', 'fim-all'),
|
||||
);
|
||||
|
||||
$items['features-export'] = array(
|
||||
'description' => "Export the configuration on your site into a custom module.",
|
||||
'arguments' => array(
|
||||
'package' => 'A space delimited list of features to export.',
|
||||
),
|
||||
'options' => array(
|
||||
'add-profile' => 'Package features into an install profile.',
|
||||
),
|
||||
'examples' => array(
|
||||
"drush features-export" => 'Export all available packages.',
|
||||
"drush features-export example_article example_page" => "Export the example_article and example_page packages.",
|
||||
"drush features-export --add-profile" => "Export all available packages and add them to an install profile.",
|
||||
),
|
||||
// Add previous "fu" alias for compatibility.
|
||||
'aliases' => array('fex', 'fu', 'fua', 'fu-all'),
|
||||
);
|
||||
|
||||
$items['features-add'] = array(
|
||||
'description' => "Add a config item to a feature package.",
|
||||
'arguments' => array(
|
||||
'feature' => 'Feature package to export and add config to.',
|
||||
'components' => 'Patterns of config to add, see features-components for the format of patterns.',
|
||||
),
|
||||
'aliases' => array('fa', 'fe'),
|
||||
);
|
||||
|
||||
$items['features-components'] = array(
|
||||
'description' => 'List features components.',
|
||||
'arguments' => array(
|
||||
'patterns' => 'The features components type to list. Omit this argument to list all components.',
|
||||
),
|
||||
'options' => array(
|
||||
'exported' => array(
|
||||
'description' => 'Show only components that have been exported.',
|
||||
),
|
||||
'not-exported' => array(
|
||||
'description' => 'Show only components that have not been exported.',
|
||||
),
|
||||
),
|
||||
'aliases' => array('fc'),
|
||||
);
|
||||
|
||||
$items['features-diff'] = array(
|
||||
'description' => "Show the difference between the active config and the default config stored in a feature package.",
|
||||
'arguments' => array(
|
||||
'feature' => 'The feature in question.',
|
||||
),
|
||||
'options' => array(
|
||||
'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
|
||||
'lines' => 'Generate diffs with <n> lines of context instead of the usual two.',
|
||||
),
|
||||
'aliases' => array('fd'),
|
||||
);
|
||||
|
||||
$items['features-import'] = array(
|
||||
'description' => "Import a module config into your site.",
|
||||
'arguments' => array(
|
||||
'feature' => 'A space delimited list of features or feature:component pairs to import.',
|
||||
),
|
||||
'options' => array(
|
||||
'force' => "Force import even if config is not overridden.",
|
||||
),
|
||||
'examples' => array(
|
||||
'drush features-import foo:node.type.page foo:taxonomy.vocabulary.tags bar' => 'Import node and taxonomy config of feature "foo". Import all config of feature "bar".',
|
||||
),
|
||||
'aliases' => array('fim', 'fr'),
|
||||
);
|
||||
|
||||
foreach ($items as $name => &$item) {
|
||||
$item['options']['bundle'] = array(
|
||||
'description' => 'Use a specific bundle namespace.',
|
||||
);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies global options for Features drush commands.
|
||||
*
|
||||
* The option --name="bundle_name" sets the bundle namespace.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
function _drush_features_options() {
|
||||
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
|
||||
$assigner = \Drupal::service('features_assigner');
|
||||
$bundle_name = drush_get_option('bundle');
|
||||
if (!empty($bundle_name)) {
|
||||
$bundle = $assigner->applyBundle($bundle_name);
|
||||
if ($bundle->getMachineName() != $bundle_name) {
|
||||
drush_log(dt('Bundle @name not found. Using default.', array('@name' => $bundle_name)), 'warning');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$assigner->assignConfigPackages();
|
||||
}
|
||||
return $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Drush command callback for features-status.
|
||||
*/
|
||||
function drush_features_status() {
|
||||
$args = func_get_args();
|
||||
$assigner = _drush_features_options();
|
||||
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$current_bundle = $assigner->getBundle();
|
||||
$export_settings = $manager->getExportSettings();
|
||||
$methods = $assigner->getEnabledAssigners();
|
||||
if ($current_bundle->isDefault()) {
|
||||
drush_print(dt('Current bundle: none'));
|
||||
}
|
||||
else {
|
||||
drush_print(dt('Current bundle: @name (@machine_name)',
|
||||
array(
|
||||
'@name' => $current_bundle->getName(),
|
||||
'@machine_name' => $current_bundle->getMachineName(),
|
||||
)));
|
||||
}
|
||||
drush_print(dt('Export folder: @folder', array('@folder' => $export_settings['folder'])));
|
||||
$dt_args = array('@methods' => implode(', ', array_keys($methods)));
|
||||
drush_print(dt('The following assignment methods are enabled:'));
|
||||
drush_print(dt(' @methods', $dt_args));
|
||||
|
||||
if (!empty($args)) {
|
||||
$config = $manager->getConfigCollection();
|
||||
if (count($args) > 1) {
|
||||
print_r(array_keys($config));
|
||||
}
|
||||
else {
|
||||
print_r($config[$args[0]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drush command callback for features-list-packages.
|
||||
*
|
||||
* @param string $package_name
|
||||
* (optional) The package name.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
function drush_features_list_packages($package_name = '') {
|
||||
$assigner = _drush_features_options();
|
||||
$current_bundle = $assigner->getBundle();
|
||||
$namespace = $current_bundle->isDefault() ? '' : $current_bundle->getMachineName();
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$packages = $manager->getPackages();
|
||||
|
||||
$packages = $manager->filterPackages($packages, $namespace);
|
||||
$result = array();
|
||||
|
||||
// If no package was specified, list all packages.
|
||||
if (empty($package_name)) {
|
||||
drush_hide_output_fields(array('object'));
|
||||
foreach ($packages as $package) {
|
||||
$overrides = $manager->detectOverrides($package);
|
||||
$state = $package->getState();
|
||||
if (!empty($overrides) && ($package->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) {
|
||||
$state = FeaturesManagerInterface::STATE_OVERRIDDEN;
|
||||
}
|
||||
|
||||
$result[$package->getMachineName()] = array(
|
||||
'name' => $package->getName(),
|
||||
'machine_name' => $package->getMachineName(),
|
||||
'status' => $manager->statusLabel($package->getStatus()),
|
||||
'version' => $package->getVersion(),
|
||||
'state' => ($state != FeaturesManagerInterface::STATE_DEFAULT)
|
||||
? $manager->stateLabel($state)
|
||||
: '',
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
// If a valid package was listed, list its configuration.
|
||||
else {
|
||||
foreach ($packages as $package) {
|
||||
if ($package->getMachineName() == $package_name) {
|
||||
drush_hide_output_fields(array(
|
||||
'machine_name',
|
||||
'name',
|
||||
'status',
|
||||
'version',
|
||||
'state',
|
||||
));
|
||||
foreach ($package->getConfig() as $item_name) {
|
||||
$result[$item_name] = array(
|
||||
'object' => $item_name,
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If no matching package found, return an error.
|
||||
drush_log(dt('Package "@package" not found.', array('@package' => $package_name)), 'warning');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drush command callback for features-import-all.
|
||||
*
|
||||
*/
|
||||
function drush_features_import_all() {
|
||||
_drush_features_options();
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$packages = $manager->getPackages();
|
||||
$packages = $manager->filterPackages($packages);
|
||||
$overridden = array();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$overrides = $manager->detectOverrides($package);
|
||||
$missing = $manager->detectMissing($package);
|
||||
if ((!empty($missing) || !empty($overrides)) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) {
|
||||
$overridden[] = $package->getMachineName();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($overridden)) {
|
||||
call_user_func_array('drush_features_import', $overridden);
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Current state already matches active config, aborting.'), 'ok');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Drush command callback for features-export.
|
||||
*/
|
||||
function drush_features_export($packages = NULL) {
|
||||
$packages = func_get_args();
|
||||
$assigner = _drush_features_options();
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
/** @var \Drupal\features\FeaturesGeneratorInterface $generator */
|
||||
$generator = \Drupal::service('features_generator');
|
||||
|
||||
$current_bundle = $assigner->getBundle();
|
||||
|
||||
if (drush_get_option('add-profile')) {
|
||||
if ($current_bundle->isDefault) {
|
||||
return drush_set_error('', dt("Must specify a profile name with --name"));
|
||||
}
|
||||
$current_bundle->setIsProfile(TRUE);
|
||||
}
|
||||
|
||||
$all_packages = $manager->getPackages();
|
||||
foreach ($packages as $name) {
|
||||
if (!isset($all_packages[$name])) {
|
||||
return drush_set_error('', dt("The package @name does not exist.", array('@name' => $name)));
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($packages)) {
|
||||
$packages = $all_packages;
|
||||
$dt_args = array('@modules' => implode(', ', array_keys($packages)));
|
||||
drush_print(dt('The following extensions will be exported: @modules', $dt_args));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
return drush_user_abort('Aborting.');
|
||||
}
|
||||
}
|
||||
|
||||
// If any packages exist, confirm before overwriting.
|
||||
if ($existing_packages = $manager->listPackageDirectories($packages, $current_bundle)) {
|
||||
foreach ($existing_packages as $name => $directory) {
|
||||
drush_print(dt("The extension @name already exists at @directory.", array('@name' => $name, '@directory' => $directory)));
|
||||
}
|
||||
// Apparently, format_plural is not always available.
|
||||
if (count($existing_packages) == 1) {
|
||||
$message = dt('Would you like to overwrite it?');
|
||||
}
|
||||
else {
|
||||
$message = dt('Would you like to overwrite them?');
|
||||
}
|
||||
if (!drush_confirm($message)) {
|
||||
return drush_user_abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Use the write generation method.
|
||||
$method_id = FeaturesGenerationWrite::METHOD_ID;
|
||||
$result = $generator->generatePackages($method_id, $current_bundle, $packages);
|
||||
|
||||
foreach ($result as $message) {
|
||||
$type = $message['success'] ? 'success' : 'error';
|
||||
drush_log($message['message'], $message['variables'], $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a component to a features module.
|
||||
*
|
||||
* @param
|
||||
* The selected components.
|
||||
*/
|
||||
function drush_features_add() {
|
||||
if ($args = func_get_args()) {
|
||||
$assigner = _drush_features_options();
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
/** @var \Drupal\features\FeaturesGeneratorInterface $generator */
|
||||
$generator = \Drupal::service('features_generator');
|
||||
|
||||
$current_bundle = $assigner->getBundle();
|
||||
|
||||
$module = array_shift($args);
|
||||
if (empty($args)) {
|
||||
return drush_set_error('', 'No components supplied.');
|
||||
}
|
||||
$components = _drush_features_component_list();
|
||||
$options = array(
|
||||
'exported' => FALSE,
|
||||
);
|
||||
|
||||
$filtered_components = _drush_features_component_filter($components, $args, $options);
|
||||
$items = $filtered_components['components'];
|
||||
|
||||
if (empty($items)) {
|
||||
return drush_set_error('', 'No components to add.');
|
||||
}
|
||||
|
||||
$packages = array($module);
|
||||
// If any packages exist, confirm before overwriting.
|
||||
if ($existing_packages = $manager->listPackageDirectories($packages)) {
|
||||
foreach ($existing_packages as $name => $directory) {
|
||||
drush_print(dt("The extension @name already exists at @directory.", array('@name' => $name, '@directory' => $directory)));
|
||||
}
|
||||
// Apparently, format_plural is not always available.
|
||||
if (count($existing_packages) == 1) {
|
||||
$message = dt('Would you like to overwrite it?');
|
||||
}
|
||||
else {
|
||||
$message = dt('Would you like to overwrite them?');
|
||||
}
|
||||
if (!drush_confirm($message)) {
|
||||
return drush_user_abort();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$package = $manager->initPackage($module, NULL, '', 'module', $current_bundle);
|
||||
list($full_name, $path) = $manager->getExportInfo($package, $current_bundle);
|
||||
drush_print(dt('Will create a new extension @name in @directory', array('@name' => $full_name, '@directory' => $path)));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
drush_die('Aborting.');
|
||||
}
|
||||
}
|
||||
|
||||
$config = _drush_features_build_config($items);
|
||||
|
||||
$manager->assignConfigPackage($module, $config);
|
||||
|
||||
// Use the write generation method.
|
||||
$method_id = FeaturesGenerationWrite::METHOD_ID;
|
||||
$result = $generator->generatePackages($method_id, $current_bundle, $packages);
|
||||
|
||||
foreach ($result as $message) {
|
||||
$type = $message['success'] ? 'success' : 'error';
|
||||
drush_log($message['message'], $message['variables'], $type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return drush_set_error('', 'No feature name given.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists components, with pattern matching.
|
||||
*/
|
||||
function drush_features_components() {
|
||||
$args = func_get_args();
|
||||
_drush_features_options();
|
||||
|
||||
$components = _drush_features_component_list();
|
||||
ksort($components);
|
||||
// If no args supplied, prompt with a list.
|
||||
if (empty($args)) {
|
||||
$types = array_keys($components);
|
||||
array_unshift($types, 'all');
|
||||
$choice = drush_choice($types, 'Enter a number to choose which component type to list.');
|
||||
if ($choice === FALSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = ($choice == 0) ? array('*') : array($types[$choice]);
|
||||
}
|
||||
$options = array(
|
||||
'provided by' => TRUE,
|
||||
);
|
||||
if (drush_get_option(array('exported', 'e'), NULL)) {
|
||||
$options['not exported'] = FALSE;
|
||||
}
|
||||
elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
|
||||
$options['exported'] = FALSE;
|
||||
}
|
||||
|
||||
$filtered_components = _drush_features_component_filter($components, $args, $options);
|
||||
if ($filtered_components) {
|
||||
_drush_features_component_print($filtered_components);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the differences in the package config vs the active store.
|
||||
*
|
||||
* @param string $package
|
||||
* The machine name of a package.
|
||||
*/
|
||||
function drush_features_diff() {
|
||||
if (!$args = func_get_args()) {
|
||||
drush_print_table(drush_features_list_packages());
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
|
||||
$assigner = \Drupal::service('features_assigner');
|
||||
$assigner->assignConfigPackages();
|
||||
|
||||
$module = $args[0];
|
||||
$filter_ctypes = drush_get_option("ctypes");
|
||||
if ($filter_ctypes) {
|
||||
$filter_ctypes = explode(',', $filter_ctypes);
|
||||
}
|
||||
|
||||
$feature = _drush_features_load_feature($module, TRUE);
|
||||
if (empty($feature)) {
|
||||
drush_log(dt('No such feature is available: @module', array('@module' => $module)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = drush_get_option('lines');
|
||||
$lines = isset($lines) ? $lines : 2;
|
||||
|
||||
$formatter = new DiffFormatter();
|
||||
$formatter->leading_context_lines = $lines;
|
||||
$formatter->trailing_context_lines = $lines;
|
||||
$formatter->show_header = FALSE;
|
||||
|
||||
if (drush_get_context('DRUSH_NOCOLOR')) {
|
||||
$red = $green = "%s";
|
||||
}
|
||||
else {
|
||||
$red = "\033[31;40m\033[1m%s\033[0m";
|
||||
$green = "\033[0;32;40m\033[1m%s\033[0m";
|
||||
}
|
||||
|
||||
$overrides = $manager->detectOverrides($feature);
|
||||
$missing = $manager->reorderMissing($manager->detectMissing($feature));
|
||||
$overrides = array_merge($overrides, $missing);
|
||||
|
||||
if (empty($overrides)) {
|
||||
drush_print(dt('Active config matches stored config for @module.', array('@module' => $module)));
|
||||
}
|
||||
else {
|
||||
/** @var \Drupal\config_update\ConfigDiffInterface $config_diff */
|
||||
$config_diff = \Drupal::service('config_update.config_diff');
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
|
||||
// Print key for colors.
|
||||
drush_print(dt('Legend: '));
|
||||
drush_print(sprintf($red, dt('Code: drush features-import will replace the active config with the displayed code.')));
|
||||
drush_print(sprintf($green, dt('Active: drush features-export will update the exported feature with the displayed active config')));
|
||||
|
||||
foreach ($overrides as $name) {
|
||||
$message = '';
|
||||
if (in_array($name, $missing)) {
|
||||
$message = sprintf($red, t('(missing from active)'));
|
||||
$extension = array();
|
||||
}
|
||||
else {
|
||||
$active = $manager->getActiveStorage()->read($name);
|
||||
$extension = $manager->getExtensionStorages()->read($name);
|
||||
if (empty($extension)) {
|
||||
$extension = array();
|
||||
$message = sprintf($green, t('(not exported)'));
|
||||
}
|
||||
$diff = $config_diff->diff($extension, $active);
|
||||
$rows = explode("\n", $formatter->format($diff));
|
||||
}
|
||||
drush_print();
|
||||
drush_print(dt("Config @name @message", array('@name' => $name, '@message' => $message)));
|
||||
if (!empty($extension)) {
|
||||
foreach ($rows as $row) {
|
||||
if (strpos($row, '>') === 0) {
|
||||
drush_print(sprintf($green, $row));
|
||||
}
|
||||
elseif (strpos($row, '<') === 0) {
|
||||
drush_print(sprintf($red, $row));
|
||||
}
|
||||
else {
|
||||
drush_print($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports module config into the active store.
|
||||
*
|
||||
* Same as the old "revert" functionality.
|
||||
*/
|
||||
function drush_features_import() {
|
||||
if ($args = func_get_args()) {
|
||||
_drush_features_options();
|
||||
|
||||
// Determine if revert should be forced.
|
||||
$force = drush_get_option('force');
|
||||
// Determine if -y was supplied. If so, we can filter out needless output
|
||||
// from this command.
|
||||
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
|
||||
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
/** @var \Drupal\config_update\ConfigRevertInterface $config_revert */
|
||||
$config_revert = \Drupal::service('features.config_update');
|
||||
|
||||
// Parse list of arguments.
|
||||
$modules = array();
|
||||
foreach ($args as $arg) {
|
||||
$arg = explode(':', $arg);
|
||||
$module = array_shift($arg);
|
||||
$component = array_shift($arg);
|
||||
|
||||
if (isset($module)) {
|
||||
if (empty($component)) {
|
||||
// If we received just a feature name, this means that we need all of
|
||||
// its components.
|
||||
$modules[$module] = TRUE;
|
||||
}
|
||||
elseif ($modules[$module] !== TRUE) {
|
||||
if (!isset($modules[$module])) {
|
||||
$modules[$module] = array();
|
||||
}
|
||||
$modules[$module][] = $component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process modules.
|
||||
foreach ($modules as $module => $components_needed) {
|
||||
|
||||
$dt_args['@module'] = $module;
|
||||
/** @var \Drupal\features\Package $feature */
|
||||
$feature = _drush_features_load_feature($module, TRUE);
|
||||
if (empty($feature)) {
|
||||
drush_log(dt('No such feature is available: @module', $dt_args), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) {
|
||||
drush_log(dt('No such feature is installed: @module', $dt_args), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Forcefully revert all components of a feature.
|
||||
if ($force) {
|
||||
$components = $feature->getConfigOrig();
|
||||
}
|
||||
// Only revert components that are detected to be Overridden.
|
||||
else {
|
||||
$components = $manager->detectOverrides($feature);
|
||||
$missing = $manager->reorderMissing($manager->detectMissing($feature));
|
||||
// Be sure to import missing components first.
|
||||
$components = array_merge($missing, $components);
|
||||
}
|
||||
|
||||
if (!empty($components_needed) && is_array($components_needed)) {
|
||||
$components = array_intersect($components, $components_needed);
|
||||
}
|
||||
|
||||
if (empty($components)) {
|
||||
drush_log(dt('Current state already matches active config, aborting.'), 'ok');
|
||||
}
|
||||
else {
|
||||
$config = $manager->getConfigCollection();
|
||||
foreach ($components as $component) {
|
||||
$dt_args['@component'] = $component;
|
||||
$confirmation_message = 'Do you really want to import @module : @component?';
|
||||
if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
|
||||
if (!isset($config[$component])) {
|
||||
// Import missing component.
|
||||
/** @var array $item */
|
||||
$item = $manager->getConfigType($component);
|
||||
$type = ConfigurationItem::fromConfigStringToConfigType($item['type']);
|
||||
$config_revert->import($type, $item['name_short']);
|
||||
drush_log(dt('Import @module : @component.', $dt_args), 'ok');
|
||||
}
|
||||
else {
|
||||
// Revert existing component.
|
||||
/** @var \Drupal\features\ConfigurationItem $item */
|
||||
$item = $config[$component];
|
||||
$type = ConfigurationItem::fromConfigStringToConfigType($item->getType());
|
||||
$config_revert->revert($type, $item->getShortName());
|
||||
drush_log(dt('Reverted @module : @component.', $dt_args), 'ok');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Skipping @module : @component.', $dt_args), 'ok');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_print_table(drush_features_list_packages());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a Features package.
|
||||
*
|
||||
* @param string $module
|
||||
* The machine name of a module.
|
||||
* @param bool $any
|
||||
* If TRUE then check for any module, not just a Features module.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function _drush_features_load_feature($module, $any = FALSE) {
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$feature = $manager->getPackage($module);
|
||||
if ($any && !isset($feature)) {
|
||||
// See if this is a non-features module.
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
$modules = $module_handler->getModuleList();
|
||||
if (!empty($modules[$module])) {
|
||||
$extension = $modules[$module];
|
||||
$feature = $manager->initPackageFromExtension($extension);
|
||||
$config = $manager->listExtensionConfig($extension);
|
||||
$feature->setConfig($config);
|
||||
$feature->setStatus(FeaturesManagerInterface::STATUS_INSTALLED);
|
||||
}
|
||||
}
|
||||
return $feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of full config names given a array[$type][$component].
|
||||
*
|
||||
* @param array $items
|
||||
* The items to return data for.
|
||||
*/
|
||||
function _drush_features_build_config(array $items) {
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$result = array();
|
||||
foreach ($items as $config_type => $item) {
|
||||
foreach ($item as $item_name => $title) {
|
||||
$result[] = $manager->getFullName($config_type, $item_name);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a listing of all known components, indexed by source.
|
||||
*/
|
||||
function _drush_features_component_list() {
|
||||
$result = array();
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$config = $manager->getConfigCollection();
|
||||
foreach ($config as $item_name => $item) {
|
||||
$result[$item->getType()][$item->getShortName()] = $item->getLabel();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters components by patterns.
|
||||
*/
|
||||
function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) {
|
||||
$options += array(
|
||||
'exported' => TRUE,
|
||||
'not exported' => TRUE,
|
||||
'provided by' => FALSE,
|
||||
);
|
||||
$pool = array();
|
||||
// Maps exported components to feature modules.
|
||||
$components_map = _drush_features_get_component_map();
|
||||
// First filter on exported state.
|
||||
foreach ($all_components as $source => $components) {
|
||||
foreach ($components as $name => $title) {
|
||||
$exported = count($components_map[$source][$name]) > 0;
|
||||
if ($exported) {
|
||||
if ($options['exported']) {
|
||||
$pool[$source][$name] = $title;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($options['not exported']) {
|
||||
$pool[$source][$name] = $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$state_string = '';
|
||||
|
||||
if (!$options['exported']) {
|
||||
$state_string = 'unexported';
|
||||
}
|
||||
elseif (!$options['not exported']) {
|
||||
$state_string = 'exported';
|
||||
}
|
||||
|
||||
$selected = array();
|
||||
foreach ($patterns as $pattern) {
|
||||
// Rewrite * to %. Let users use both as wildcard.
|
||||
$pattern = strtr($pattern, array('*' => '%'));
|
||||
$sources = array();
|
||||
list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
|
||||
// If source is empty, use a pattern.
|
||||
if ($source_pattern == '') {
|
||||
$source_pattern = '%';
|
||||
}
|
||||
if ($component_pattern == '') {
|
||||
$component_pattern = '%';
|
||||
}
|
||||
|
||||
$preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*'));
|
||||
$preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*'));
|
||||
// If it isn't a pattern, but a simple string, we don't anchor the
|
||||
// pattern. This allows for abbreviating. Otherwise, we do, as this seems
|
||||
// more natural for patterns.
|
||||
if (strpos($source_pattern, '%') !== FALSE) {
|
||||
$preg_source_pattern = '^' . $preg_source_pattern . '$';
|
||||
}
|
||||
if (strpos($component_pattern, '%') !== FALSE) {
|
||||
$preg_component_pattern = '^' . $preg_component_pattern . '$';
|
||||
}
|
||||
$matches = array();
|
||||
|
||||
// Find the sources.
|
||||
$all_sources = array_keys($pool);
|
||||
$matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
|
||||
if (count($matches) > 0) {
|
||||
// If we have multiple matches and the source string wasn't a
|
||||
// pattern, check if one of the matches is equal to the pattern, and
|
||||
// use that, or error out.
|
||||
if (count($matches) > 1 and $preg_source_pattern[0] != '^') {
|
||||
if (in_array($source_pattern, $matches)) {
|
||||
$matches = array($source_pattern);
|
||||
}
|
||||
else {
|
||||
return drush_set_error('', dt('Ambiguous source "@source", matches @matches', array(
|
||||
'@source' => $source_pattern,
|
||||
'@matches' => implode(', ', $matches),
|
||||
)));
|
||||
}
|
||||
}
|
||||
// Loose the indexes preg_grep preserved.
|
||||
$sources = array_values($matches);
|
||||
}
|
||||
else {
|
||||
return drush_set_error('', dt('No @state sources match "@source"', array('@state' => $state_string, '@source' => $source_pattern)));
|
||||
}
|
||||
|
||||
// Now find the components.
|
||||
foreach ($sources as $source) {
|
||||
// Find the components.
|
||||
$all_components = array_keys($pool[$source]);
|
||||
// See if there's any matches.
|
||||
$matches = preg_grep('/' . $preg_component_pattern . '/', $all_components);
|
||||
if (count($matches) > 0) {
|
||||
// If we have multiple matches and the components string wasn't a
|
||||
// pattern, check if one of the matches is equal to the pattern, and
|
||||
// use that, or error out.
|
||||
if (count($matches) > 1 and $preg_component_pattern[0] != '^') {
|
||||
if (in_array($component_pattern, $matches)) {
|
||||
$matches = array($component_pattern);
|
||||
}
|
||||
else {
|
||||
return drush_set_error('', dt('Ambiguous component "@component", matches @matches', array(
|
||||
'@component' => $component_pattern,
|
||||
'@matches' => implode(', ', $matches),
|
||||
)));
|
||||
}
|
||||
}
|
||||
if (!is_array($selected[$source])) {
|
||||
$selected[$source] = array();
|
||||
}
|
||||
$selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
|
||||
}
|
||||
else {
|
||||
// No matches. If the source was a pattern, just carry on, else
|
||||
// error out. Allows for patterns like :*field*
|
||||
if ($preg_source_pattern[0] != '^') {
|
||||
return drush_set_error('', dt('No @state @source components match "@component"', array(
|
||||
'@state' => $state_string,
|
||||
'@component' => $component_pattern,
|
||||
'@source' => $source,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, provide feature module information on the selected components, if
|
||||
// requested.
|
||||
$provided_by = array();
|
||||
if ($options['provided by'] && $options['exported']) {
|
||||
foreach ($selected as $source => $components) {
|
||||
foreach ($components as $name => $title) {
|
||||
$exported = count($components_map[$source][$name]) > 0;
|
||||
if ($exported) {
|
||||
$provided_by[$source . ':' . $name] = implode(', ', $components_map[$source][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'components' => $selected,
|
||||
'sources' => $provided_by,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a component to feature map (port of features_get_component_map).
|
||||
*/
|
||||
function _drush_features_get_component_map() {
|
||||
$result = array();
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
// Recalc full config list without running assignments.
|
||||
$config = $manager->getConfigCollection();
|
||||
$packages = $manager->getPackages();
|
||||
|
||||
foreach ($config as $item_name => $item) {
|
||||
$type = $item->getType();
|
||||
$short_name = $item->getShortName();
|
||||
$name = $item->getName();
|
||||
if (!isset($result[$type][$short_name])) {
|
||||
$result[$type][$short_name] = array();
|
||||
}
|
||||
if (!empty($item->getPackage())) {
|
||||
$package = $packages[$item->getPackage()];
|
||||
$result[$type][$short_name][] = $package->getMachineName();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a list of filtered components.
|
||||
*/
|
||||
function _drush_features_component_print($filtered_components) {
|
||||
$rows = array(array(dt('Available sources')));
|
||||
foreach ($filtered_components['components'] as $source => $components) {
|
||||
foreach ($components as $name => $value) {
|
||||
$row = array($source . ':' . $name);
|
||||
if (isset($filtered_components['sources'][$source . ':' . $name])) {
|
||||
$row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source . ':' . $name];
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
drush_print_table($rows, TRUE);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
name: 'Features'
|
||||
type: module
|
||||
description: 'Enables administrators to package configuration into modules.'
|
||||
package: Development
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- config
|
||||
- config_update
|
||||
|
||||
# Information added by Drupal.org packaging script on 2016-09-02
|
||||
version: '8.x-3.0-beta8'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1472847281
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Main hooks for Features module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function features_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.features':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Features module provides a user interface for exporting bundles of configuration into modules. For more information, see the online documentation for <a href=":url">Features module</a>', array(
|
||||
':url' => 'http://drupal.org/node/2404427',
|
||||
)) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_download().
|
||||
*/
|
||||
function features_file_download($uri) {
|
||||
$scheme = file_uri_scheme($uri);
|
||||
$target = file_uri_target($uri);
|
||||
if ($scheme == 'temporary' && $target) {
|
||||
return array(
|
||||
'Content-disposition' => 'attachment; filename="' . $target . '"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_modules_installed().
|
||||
*/
|
||||
function features_modules_installed($modules) {
|
||||
if (!in_array('features', $modules)) {
|
||||
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
|
||||
$assigner = \Drupal::service('features_assigner');
|
||||
$assigner->purgeConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
features.export_download:
|
||||
path: '/admin/config/development/features/download/{uri}'
|
||||
defaults:
|
||||
_controller: 'Drupal\features\Controller\FeaturesController::downloadExport'
|
||||
requirements:
|
||||
_permission: 'export configuration'
|
||||
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
plugin.manager.features_assignment_method:
|
||||
class: Drupal\features\FeaturesAssignmentMethodManager
|
||||
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.features_generation_method:
|
||||
class: Drupal\features\FeaturesGenerationMethodManager
|
||||
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
features_assigner:
|
||||
class: Drupal\features\FeaturesAssigner
|
||||
arguments: ['@features.manager', '@plugin.manager.features_assignment_method', '@entity.manager', '@config.factory', '@config.storage']
|
||||
calls:
|
||||
- [initFeaturesManager]
|
||||
features_generator:
|
||||
class: Drupal\features\FeaturesGenerator
|
||||
arguments: ['@features.manager', '@plugin.manager.features_generation_method', '@features_assigner']
|
||||
calls:
|
||||
- [initFeaturesManager]
|
||||
features.manager:
|
||||
class: Drupal\features\FeaturesManager
|
||||
arguments: ['@app.root', '@entity.manager', '@config.factory', '@config.storage', '@config.manager', '@module_handler']
|
||||
|
||||
features.config_update:
|
||||
class: Drupal\config_update\ConfigReverter
|
||||
arguments: ['@entity.manager', '@config.storage', '@features.extension_storage', '@features.extension_optional_storage', '@config.factory', '@event_dispatcher']
|
||||
|
||||
features.extension_storage:
|
||||
class: Drupal\features\FeaturesInstallStorage
|
||||
arguments: ['@config.storage']
|
||||
|
||||
features.extension_optional_storage:
|
||||
class: Drupal\features\FeaturesInstallStorage
|
||||
arguments: ['@config.storage', 'config/optional']
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
span.features-item-list span {
|
||||
background: #eee;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 5px;
|
||||
white-space: nowrap;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
.features-listing span.features-override,
|
||||
.features-listing a.features-override {
|
||||
background: #FFCE6F;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.features-listing span.features-detected,
|
||||
.features-listing a.features-detected {
|
||||
color:#68a;
|
||||
background:#def;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.features-listing span.features-conflict,
|
||||
.features-listing a.features-conflict {
|
||||
color: #fff;
|
||||
background-color: #c30 !important;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.features-listing span.features-missing,
|
||||
.features-listing a.features-missing {
|
||||
color: #fff;
|
||||
background-color: #0E5EA9 !important;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.features-listing .column-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr.features-export-header-row td {
|
||||
border-top: 30px solid white;
|
||||
background: #eee;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#features-assignment-methods tr.draggable > td:nth-child(1) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/** Styles for Features Export/Listing page **/
|
||||
.features-listing td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.features-listing td.feature-name {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.features-listing td details {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.features-listing details[open] {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.features-listing td summary {
|
||||
padding: 0;
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.features-listing td .features-item-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.features-header > div,
|
||||
.features-header > input {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/** Styles for Features Edit page **/
|
||||
|
||||
.fieldset-legend {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#features-export-info {
|
||||
width: 49%;
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
||||
#features-export-wrapper {
|
||||
width: 49%;
|
||||
float: right;
|
||||
clear: both;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-type-checkbox.form-item-conflicts {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
div.features-export-list {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
border: 1px solid #CCC;
|
||||
border-top-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 10px 0 30px;
|
||||
}
|
||||
|
||||
span.features-component-list span {
|
||||
white-space:nowrap;
|
||||
margin-right:5px;
|
||||
padding:2px 5px;
|
||||
background:#eee;
|
||||
-moz-border-radius:5px;
|
||||
-webkit-border-radius:5px;
|
||||
}
|
||||
|
||||
div.features-export-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.features-component-list span.features-conflict {
|
||||
background-color: #c30 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
span.features-component-list .features-detected {
|
||||
color:#68a;
|
||||
background:#def;
|
||||
}
|
||||
|
||||
span.features-component-list .features-dependency {
|
||||
color:#999;
|
||||
background:#f8f8f8;
|
||||
}
|
||||
|
||||
#features-legend .fieldset-wrapper span {
|
||||
font-style: normal;
|
||||
color: black;
|
||||
display: inline-block;
|
||||
background: transparent;
|
||||
border: 1px solid #DDD;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
#features-export-wrapper .config-name {
|
||||
color: #777;
|
||||
}
|
||||
#features-export-wrapper .component-detected .form-type-checkbox,
|
||||
#features-legend .fieldset-wrapper .component-detected {
|
||||
font-style: italic;
|
||||
color:#68a;
|
||||
background:#def;
|
||||
border-width: 0;
|
||||
}
|
||||
#features-export-wrapper .component-added .form-type-checkbox,
|
||||
#features-legend .fieldset-wrapper .component-added {
|
||||
font-weight: bold;
|
||||
background: #EEE;
|
||||
border-width: 0;
|
||||
}
|
||||
#features-export-wrapper .component-conflict.form-type-checkbox,
|
||||
#features-legend .fieldset-wrapper .component-conflict {
|
||||
color: #c30 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
#features-export-wrapper .component-conflict.form-type-checkbox label,
|
||||
#features-export-wrapper .component-added .form-type-checkbox label {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
#features-filter input[size="60"].form-text {
|
||||
width: 200px;
|
||||
}
|
||||
#features-filter .fieldset-content,
|
||||
#features-filter .fieldset-wrapper,
|
||||
#features-filter fieldset {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#features-filter fieldset legend {
|
||||
display: none;
|
||||
}
|
||||
#features-filter label,
|
||||
#features-filter input {
|
||||
display: inline;
|
||||
width: auto;
|
||||
}
|
||||
#features-filter .form-item {
|
||||
float: left;
|
||||
margin: 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
#features-filter .form-item.form-type-checkbox {
|
||||
margin: 5px 0;
|
||||
}
|
||||
#features-filter span {
|
||||
float: left;
|
||||
white-space: normal;
|
||||
margin: 5px 5px;
|
||||
padding: 0 5px;
|
||||
background: transparent;
|
||||
background: #EEE;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#features-filter span:hover {
|
||||
background:#def;
|
||||
}
|
||||
|
||||
#features-export-wrapper div.features-export-parent {
|
||||
clear: both;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
details.features-export-component {
|
||||
background: #F3F8FB;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
}
|
||||
details.features-export-component summary {
|
||||
white-space: nowrap;
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
details.features-export-component summary .component-count {
|
||||
font-size: 11px;
|
||||
}
|
||||
details.features-export-component .details-wrapper {
|
||||
padding-left: 25px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/** Styles for Bundle assignment config form **/
|
||||
#edit-bundles-wrapper .form-item-bundle-bundle-select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#edit-bundles-wrapper #edit-bundle-remove {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Administration functions for features.module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Render\Element;
|
||||
Use \Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
|
||||
/**
|
||||
* Returns HTML for the features listing form.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - form: A render element representing the form.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_features_listing(array $variables) {
|
||||
$form = $variables['form'];
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
// Individual table headers.
|
||||
$rows = array();
|
||||
// Iterate through all the features, which are children of this element.
|
||||
foreach (Element::children($form) as $key) {
|
||||
// Stick the key into $module for easier access.
|
||||
$element = $form[$key];
|
||||
// Create the row for the table.
|
||||
$row = array();
|
||||
// Add the checkbox into the first cell.
|
||||
unset($element['enable']['#title']);
|
||||
|
||||
$row[] = array('class' => array('checkbox'), 'data' => $renderer->render($element['enable']));
|
||||
|
||||
// Add the module label and expand/collapse functionalty.
|
||||
$id = Html::getUniqueId('feature-' . $key);
|
||||
$col2 = new FormattableMarkup('<label id="@id" for="@for" class="module-name table-filter-text-source">@name</label>',
|
||||
array(
|
||||
'@id' => $id,
|
||||
'@for' => $element['enable']['#id'],
|
||||
'@name' => $renderer->render($element['name']),
|
||||
)
|
||||
);
|
||||
$row[] = array('class' => array('module'), 'data' => $col2);
|
||||
|
||||
$row[] = array('class' => array('machine_name'), 'data' => $renderer->render($element['machine_name']));
|
||||
|
||||
$description = t('@details', array('@details' => $renderer->render($element['details'])));
|
||||
$details = array(
|
||||
'#type' => 'details',
|
||||
'#title' => new FormattableMarkup('<span class="text">@desc</span>', array( '@desc' => $renderer->render($element['description']))),
|
||||
'#attributes' => array('id' => $element['enable']['#id'] . '-description'),
|
||||
'#description' => $description,
|
||||
);
|
||||
$row[] = array(
|
||||
'class' => array('description', 'expand'),
|
||||
'data' => $renderer->render($details),
|
||||
);
|
||||
$row[] = array(
|
||||
'class' => array('feature-version'),
|
||||
'data' => $renderer->render($element['version']),
|
||||
);
|
||||
$row[] = array(
|
||||
'class' => array('feature-state'),
|
||||
'data' => $renderer->render($element['state']),
|
||||
);
|
||||
|
||||
$rows[] = array('data' => $row);
|
||||
}
|
||||
|
||||
$table = array(
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $form['#header'],
|
||||
'#options' => $rows,
|
||||
'#empty' => t('No Features packages available.'),
|
||||
);
|
||||
return $renderer->render($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for package assignment configuration form.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - form: A render element representing the form.
|
||||
*/
|
||||
function template_preprocess_features_assignment_configure_form(&$variables) {
|
||||
$form =& $variables['form'];
|
||||
|
||||
$header = array(
|
||||
t('Assignment method'),
|
||||
t('Description'),
|
||||
t('Enabled'),
|
||||
t('Weight'),
|
||||
);
|
||||
|
||||
// If there is at least one operation enabled, show the operation column.
|
||||
if ($form['#show_operations']) {
|
||||
$header[] = t('Operations');
|
||||
}
|
||||
|
||||
$table = array(
|
||||
'#type' => 'table',
|
||||
'#weight' => 5,
|
||||
'#header' => $header,
|
||||
'#attributes' => array('id' => 'features-assignment-methods'),
|
||||
'#tabledrag' => array(
|
||||
array(
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'assignment-method-weight',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($form['title'] as $id => $element) {
|
||||
// Do not take form control structures.
|
||||
if (is_array($element) && Element::child($id)) {
|
||||
$table[$id]['#attributes']['class'][] = 'draggable';
|
||||
$table[$id]['#weight'] = $element['#weight'];
|
||||
|
||||
$table[$id]['title'] = array(
|
||||
'#prefix' => '<strong>',
|
||||
$form['title'][$id],
|
||||
'#suffix' => '</strong>',
|
||||
);
|
||||
$table[$id]['description'] = $form['description'][$id];
|
||||
$table[$id]['enabled'] = $form['enabled'][$id];
|
||||
$table[$id]['weight'] = $form['weight'][$id];
|
||||
if ($form['#show_operations']) {
|
||||
$table[$id]['operation'] = $form['operation'][$id];
|
||||
}
|
||||
// Unset to prevent rendering along with children.
|
||||
unset($form['title'][$id]);
|
||||
unset($form['description'][$id]);
|
||||
unset($form['enabled'][$id]);
|
||||
unset($form['weight'][$id]);
|
||||
unset($form['operation'][$id]);
|
||||
}
|
||||
}
|
||||
|
||||
// For some reason, the #weight is not being handled by drupal_render.
|
||||
// So we remove the actions and then put them back into the form after the
|
||||
// table.
|
||||
$actions = $form['actions'];
|
||||
unset($form['actions']);
|
||||
$form['table'] = $table;
|
||||
$form['actions'] = $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes individual items in an item list.
|
||||
*/
|
||||
function theme_features_items(array $variables) {
|
||||
$items = $variables['items'];
|
||||
|
||||
$list = array();
|
||||
foreach ($items as $item) {
|
||||
$class = !empty($item['class']) ? $item['class'] : '';
|
||||
$list[] = '<span class="features-item ' . $class . '" title="' . $item['name'] . '">' . $item['label'] . '</span>';
|
||||
}
|
||||
|
||||
return '<span class="features-item-list">' . implode(' ', $list) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes the assignment form.
|
||||
*/
|
||||
function theme_assignment_form(array $variables) {
|
||||
$renderer = \Drupal::service('renderer');
|
||||
return $renderer->render($variables['form']);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Features UI
|
||||
type: module
|
||||
description: 'Provides the user interface for Features.'
|
||||
package: Development
|
||||
# core: 8.x
|
||||
configure: features.assignment
|
||||
dependencies:
|
||||
- features
|
||||
|
||||
# Information added by Drupal.org packaging script on 2016-09-02
|
||||
version: '8.x-3.0-beta8'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1472847281
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
drupal.features_ui.admin:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/features_ui.admin.css: {}
|
||||
js:
|
||||
js/features_ui.admin.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/drupalSettings
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
features_ui.feature_add:
|
||||
route_name: features.edit
|
||||
title: 'Create new feature'
|
||||
appears_on:
|
||||
- features.export
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
features.export:
|
||||
title: 'Features'
|
||||
description: 'Package your configuration into feature modules.'
|
||||
route_name: features.export
|
||||
parent: system.admin_config_development
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
features.export:
|
||||
route_name: features.export
|
||||
title: 'Features'
|
||||
base_route: features.export
|
||||
|
||||
features.assignment:
|
||||
route_name: features.assignment
|
||||
title: 'Configure Bundles'
|
||||
base_route: features.export
|
||||
|
||||
features.diff:
|
||||
route_name: features.diff
|
||||
title: 'Differences'
|
||||
base_route: features.export
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Allows site administrators to modify configuration.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function features_ui_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'features.assignment':
|
||||
$output = '';
|
||||
$output .= '<p>' . t('Bundles are used to collect together groups of features. A bundle provides a shared <a href=":namespace">namespace</a> for all features included in it, which prevents conflicts and helps distinguish your features from those produced for other purposes. Common uses of bundles include:', array(':namespace' => 'http://en.wikipedia.org/wiki/Namespace'));
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Custom features for use on a particular site.') . '</li>';
|
||||
$output .= '<li>' . t('The features of a given <a href=":distributions">distribution</a>.', array(':distributions' => 'https://www.drupal.org/documentation/build/distributions')) . '</li>';
|
||||
$output .= '</ul></p>';
|
||||
$output .= '<p>' . t('Use the form below to manage bundles. Each bundle comes with a set of assignment methods. By configuring and ordering the assignment methods, you can set the defaults for what does and doesn\'t get packaged into features for your bundle. Use the <em>Bundle</em> select to choose which bundle to edit, or chose <em>--New--</em> to create a new bundle. The <em>Default</em> bundle does not include a namespace and cannot be deleted.') . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'features.export':
|
||||
$output = '';
|
||||
$output .= '<p>' . t('Export packages of configuration into modules.') . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function features_ui_theme() {
|
||||
return array(
|
||||
'features_listing' => array(
|
||||
'render element' => 'form',
|
||||
'file' => 'features_ui.admin.inc',
|
||||
'function' => 'theme_features_listing',
|
||||
),
|
||||
'features_assignment_configure_form' => array(
|
||||
'render element' => 'form',
|
||||
'file' => 'features_ui.admin.inc',
|
||||
'function' => 'theme_assignment_form',
|
||||
),
|
||||
'features_items' => array(
|
||||
'variables' => array(
|
||||
'items' => array(),
|
||||
),
|
||||
'file' => 'features_ui.admin.inc',
|
||||
'function' => 'theme_features_items',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
features.export:
|
||||
path: '/admin/config/development/features'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\FeaturesExportForm'
|
||||
_title: 'Features'
|
||||
requirements:
|
||||
_permission: 'export configuration'
|
||||
|
||||
features.assignment:
|
||||
path: '/admin/config/development/features/bundle/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentConfigureForm'
|
||||
_title: 'Bundle assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_base:
|
||||
path: '/admin/config/development/features/bundle/_base/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentBaseForm'
|
||||
_title: 'Configure base package assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_core:
|
||||
path: '/admin/config/development/features/bundle/_core/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentCoreForm'
|
||||
_title: 'Configure core package assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_exclude:
|
||||
path: '/admin/config/development/features/bundle/_exclude/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentExcludeForm'
|
||||
_title: 'Configure package exclusion'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_optional:
|
||||
path: '/admin/config/development/features/bundle/_optional/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentOptionalForm'
|
||||
_title: 'Configure optional package assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_profile:
|
||||
path: '/admin/config/development/features/bundle/_profile/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentProfileForm'
|
||||
_title: 'Configure profile package assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.assignment_site:
|
||||
path: '/admin/config/development/features/bundle/_site/{bundle_name}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\AssignmentSiteForm'
|
||||
_title: 'Configure site package assignment'
|
||||
bundle_name: NULL
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.edit:
|
||||
path: '/admin/config/development/features/edit/{featurename}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\FeaturesEditForm'
|
||||
_title: 'Edit'
|
||||
featurename: ''
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.diff:
|
||||
path: '/admin/config/development/features/diff/{featurename}'
|
||||
defaults:
|
||||
_form: '\Drupal\features_ui\Form\FeaturesDiffForm'
|
||||
_title: 'Differences'
|
||||
featurename: ''
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
features.detect:
|
||||
path: '/features/api/detect/{name}'
|
||||
defaults:
|
||||
_controller: '\Drupal\features_ui\Controller\FeaturesUIController::detect'
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* jQuery.fn.sortElements
|
||||
* --------------
|
||||
* @param Function comparator:
|
||||
* Exactly the same behaviour as [1,2,3].sort(comparator)
|
||||
*
|
||||
* @param Function getSortable
|
||||
* A function that should return the element that is
|
||||
* to be sorted. The comparator will run on the
|
||||
* current collection, but you may want the actual
|
||||
* resulting sort to occur on a parent or another
|
||||
* associated element.
|
||||
*
|
||||
* E.g. $('td').sortElements(comparator, function(){
|
||||
* return this.parentNode;
|
||||
* })
|
||||
*
|
||||
* The <td>'s parent (<tr>) will be sorted instead
|
||||
* of the <td> itself.
|
||||
*
|
||||
* Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/
|
||||
*
|
||||
*/
|
||||
jQuery.fn.sortElements = (function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
var sort = [].sort;
|
||||
|
||||
return function (comparator, getSortable) {
|
||||
|
||||
getSortable = getSortable || function () {return this;};
|
||||
|
||||
var placements = this.map(function () {
|
||||
|
||||
var sortElement = getSortable.call(this);
|
||||
var parentNode = sortElement.parentNode;
|
||||
|
||||
// Since the element itself will change position, we have
|
||||
// to have some way of storing its original position in
|
||||
// the DOM. The easiest way is to have a 'flag' node:
|
||||
var nextSibling = parentNode.insertBefore(
|
||||
document.createTextNode(''),
|
||||
sortElement.nextSibling
|
||||
);
|
||||
|
||||
return function () {
|
||||
|
||||
if (parentNode === this) {
|
||||
throw new Error(
|
||||
"You can't sort elements if any one is a descendant of another."
|
||||
);
|
||||
}
|
||||
|
||||
// Insert before flag:
|
||||
parentNode.insertBefore(this, nextSibling);
|
||||
// Remove flag:
|
||||
parentNode.removeChild(nextSibling);
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
return sort.call(this, comparator).each(function (i) {
|
||||
placements[i].call(getSortable.call(this));
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
(function ($) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Drupal.behaviors.features = {
|
||||
attach: function (context) {
|
||||
|
||||
// mark any conflicts with a class
|
||||
if ((typeof drupalSettings.features !== 'undefined') && (typeof drupalSettings.features.conflicts !== 'undefined')) {
|
||||
// for (var configType in drupalSettings.features.conflicts) {
|
||||
if (drupalSettings.features.conflicts) {
|
||||
var configConflicts = drupalSettings.features.conflicts;
|
||||
$('#features-export-wrapper input[type=checkbox]', context).each(function () {
|
||||
if (!$(this).hasClass('features-checkall')) {
|
||||
var key = $(this).attr('name');
|
||||
var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
|
||||
var component = matches[1];
|
||||
var item = matches[4];
|
||||
if ((component in configConflicts) && (item in configConflicts[component])) {
|
||||
$(this).parent().addClass('component-conflict');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
function _checkAll(value) {
|
||||
if (value) {
|
||||
$('#features-export-wrapper .component-select input[type=checkbox]:visible', context).each(function () {
|
||||
var move_id = $(this).attr('id');
|
||||
$(this).click();
|
||||
$('#'+ move_id).prop('checked', true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#features-export-wrapper .component-added input[type=checkbox]:visible', context).each(function () {
|
||||
var move_id = $(this).attr('id');
|
||||
$(this).click();
|
||||
$('#'+ move_id).prop('checked', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateComponentCountInfo(item, section) {
|
||||
var parent;
|
||||
|
||||
switch (section) {
|
||||
case 'select':
|
||||
parent = $(item).closest('.features-export-list').siblings('.features-export-component');
|
||||
$('.component-count', parent).text(function (index, text) {
|
||||
return +text + 1;
|
||||
}
|
||||
);
|
||||
break;
|
||||
case 'added':
|
||||
case 'detected':
|
||||
parent = $(item).closest('.features-export-component');
|
||||
$('.component-count', parent).text(function (index, text) {
|
||||
return text - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function moveCheckbox(item, section, value) {
|
||||
updateComponentCountInfo(item, section);
|
||||
var curParent = item;
|
||||
if ($(item).hasClass('form-type-checkbox')) {
|
||||
item = $(item).children('input[type=checkbox]');
|
||||
}
|
||||
else {
|
||||
curParent = $(item).parents('.form-type-checkbox');
|
||||
}
|
||||
var newParent = $(curParent).parents('.features-export-parent').find('.component-'+section+' .form-checkboxes');
|
||||
$(curParent).detach();
|
||||
$(curParent).appendTo(newParent);
|
||||
var list = ['select', 'added', 'detected', 'included'];
|
||||
for (var i in list) {
|
||||
if (list[i]) {
|
||||
$(curParent).removeClass('component-' + list[i]);
|
||||
$(item).removeClass('component-' + list[i]);
|
||||
}
|
||||
}
|
||||
$(curParent).addClass('component-'+section);
|
||||
$(item).addClass('component-'+section);
|
||||
if (value) {
|
||||
$(item).attr('checked', 'checked');
|
||||
}
|
||||
else {
|
||||
$(item).removeAttr('checked');
|
||||
}
|
||||
$(newParent).parents('.component-list').removeClass('features-export-empty');
|
||||
|
||||
// re-sort new list of checkboxes based on labels
|
||||
$(newParent).find('label').sortElements(
|
||||
function (a, b) {
|
||||
return $(a).text() > $(b).text() ? 1 : -1;
|
||||
},
|
||||
function () {
|
||||
return this.parentNode;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// provide timer for auto-refresh trigger
|
||||
var timeoutID = 0;
|
||||
var inTimeout = 0;
|
||||
function _triggerTimeout() {
|
||||
timeoutID = 0;
|
||||
_updateDetected();
|
||||
}
|
||||
function _resetTimeout() {
|
||||
inTimeout++;
|
||||
// if timeout is already active, reset it
|
||||
if (timeoutID !== 0) {
|
||||
window.clearTimeout(timeoutID);
|
||||
if (inTimeout > 0) { inTimeout--; }
|
||||
}
|
||||
timeoutID = window.setTimeout(_triggerTimeout, 500);
|
||||
}
|
||||
|
||||
function _updateDetected() {
|
||||
if (!drupalSettings.features.autodetect) { return; }
|
||||
// query the server for a list of components/items in the feature and update
|
||||
// the auto-detected items
|
||||
var items = []; // will contain a list of selected items exported to feature
|
||||
var components = {}; // contains object of component names that have checked items
|
||||
$('#features-export-wrapper input[type=checkbox]:checked', context).each(function () {
|
||||
if (!$(this).hasClass('features-checkall')) {
|
||||
var key = $(this).attr('name');
|
||||
var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
|
||||
components[matches[1]] = matches[1];
|
||||
if (!$(this).hasClass('component-detected')) {
|
||||
items.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
var featureName = $('#edit-machine-name').val();
|
||||
if (featureName === '') {
|
||||
featureName = '*';
|
||||
}
|
||||
|
||||
var url = Drupal.url('features/api/detect/' + featureName);
|
||||
var excluded = drupalSettings.features.excluded;
|
||||
var required = drupalSettings.features.required;
|
||||
var postData = {'items': items, 'excluded': excluded, 'required': required};
|
||||
jQuery.post(url, postData, function (data) {
|
||||
if (inTimeout > 0) { inTimeout--; }
|
||||
// if we have triggered another timeout then don't update with old results
|
||||
if (inTimeout === 0) {
|
||||
// data is an object keyed by component listing the exports of the feature
|
||||
for (var component in data) {
|
||||
if (data[component]) {
|
||||
var itemList = data[component];
|
||||
$('#features-export-wrapper .component-' + component + ' input[type=checkbox]', context).each(function () {
|
||||
var key = $(this).attr('value');
|
||||
// first remove any auto-detected items that are no longer in component
|
||||
if ($(this).hasClass('component-detected')) {
|
||||
if (!(key in itemList)) {
|
||||
moveCheckbox(this, 'select', false);
|
||||
}
|
||||
}
|
||||
// next, add any new auto-detected items
|
||||
else if ($(this).hasClass('component-select')) {
|
||||
if (key in itemList) {
|
||||
moveCheckbox(this, 'detected', itemList[key]);
|
||||
$(this).prop('checked', true);
|
||||
$(this).parent().show(); // make sure it's not hidden from filter
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// loop over all selected components and check for any that have been completely removed
|
||||
for (var selectedComponent in components) {
|
||||
if ((data == null) || !(selectedComponent in data)) {
|
||||
$('#features-export-wrapper .component-' + selectedComponent + ' input[type=checkbox].component-detected', context).each(moveCheckbox(this, 'select', false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
|
||||
// Handle component selection UI
|
||||
$('#features-export-wrapper input[type=checkbox]', context).click(function () {
|
||||
_resetTimeout();
|
||||
if ($(this).hasClass('component-select')) {
|
||||
moveCheckbox(this, 'added', true);
|
||||
}
|
||||
else if ($(this).hasClass('component-included')) {
|
||||
moveCheckbox(this, 'added', false);
|
||||
}
|
||||
else if ($(this).hasClass('component-added')) {
|
||||
if ($(this).is(':checked')) {
|
||||
moveCheckbox(this, 'included', true);
|
||||
}
|
||||
else {
|
||||
moveCheckbox(this, 'select', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle select/unselect all
|
||||
$('#features-filter .features-checkall.form-checkbox', context).click(function () {
|
||||
if ($(this).prop('checked')) {
|
||||
_checkAll(true);
|
||||
$(this).next().html(Drupal.t('Deselect all'));
|
||||
}
|
||||
else {
|
||||
_checkAll(false);
|
||||
$(this).next().html(Drupal.t('Select all'));
|
||||
}
|
||||
_resetTimeout();
|
||||
});
|
||||
|
||||
// Handle filtering
|
||||
|
||||
// provide timer for auto-refresh trigger
|
||||
var filterTimeoutID = 0;
|
||||
function _triggerFilterTimeout() {
|
||||
filterTimeoutID = 0;
|
||||
_updateFilter();
|
||||
}
|
||||
function _resetFilterTimeout() {
|
||||
// if timeout is already active, reset it
|
||||
if (filterTimeoutID !== 0) {
|
||||
window.clearTimeout(filterTimeoutID);
|
||||
filterTimeoutID = null;
|
||||
}
|
||||
filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200);
|
||||
}
|
||||
function _updateFilter() {
|
||||
var filter = $('#features-filter input').val();
|
||||
var regex = new RegExp(filter, 'i');
|
||||
// collapse fieldsets
|
||||
var newState = {};
|
||||
var currentState = {};
|
||||
$('#features-export-wrapper details.features-export-component', context).each(function () {
|
||||
// expand parent fieldset
|
||||
var section = $(this).attr('id');
|
||||
var details = $(this);
|
||||
|
||||
currentState[section] = details.prop('open');
|
||||
if (!(section in newState)) {
|
||||
newState[section] = false;
|
||||
}
|
||||
|
||||
details.find('.form-checkboxes label').each(function () {
|
||||
if (filter === '') {
|
||||
// collapse the section, but make checkbox visible
|
||||
if (currentState[section]) {
|
||||
details.prop('open', false);
|
||||
currentState[section] = false;
|
||||
}
|
||||
$(this).parent().show();
|
||||
}
|
||||
else if ($(this).text().match(regex)) {
|
||||
$(this).parent().show();
|
||||
newState[section] = true;
|
||||
}
|
||||
else {
|
||||
$(this).parent().hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
for (var section in newState) {
|
||||
if (currentState[section] !== newState[section]) {
|
||||
if (newState[section]) {
|
||||
$('#'+section).prop('open', true);
|
||||
}
|
||||
else {
|
||||
$('#'+section).prop('open', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#features-filter input', context).bind("input", function () {
|
||||
_resetFilterTimeout();
|
||||
});
|
||||
$('#features-filter .features-filter-clear', context).click(function () {
|
||||
$('#features-filter input').val('');
|
||||
_updateFilter();
|
||||
});
|
||||
|
||||
// show the filter bar
|
||||
$('#features-filter', context).removeClass('element-invisible');
|
||||
|
||||
// handle Package selection checkboxes in the Differences page
|
||||
$('.features-diff-listing .features-diff-header input.form-checkbox', context).click(function () {
|
||||
var value = $(this).prop('checked');
|
||||
$('.features-diff-listing .diff-'+$(this).prop('value')+' input.form-checkbox', context).each(function () {
|
||||
$(this).prop('checked', value);
|
||||
if (value) {
|
||||
$(this).parents('tr').addClass('selected');
|
||||
}
|
||||
else {
|
||||
$(this).parents('tr').removeClass('selected');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// handle special theming of headers in tableselect
|
||||
$('td.features-export-header-row', context).each(function () {
|
||||
var row = $(this).parent('tr');
|
||||
row.addClass('features-export-header-row');
|
||||
var checkbox = row.find('td input:checkbox');
|
||||
if (checkbox.length) {
|
||||
checkbox.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// handle clicking anywhere in row on Differences page
|
||||
$('.features-diff-listing tr td:nth-child(2)', context).click(function () {
|
||||
var checkbox = $(this).parent().find('td input:checkbox');
|
||||
checkbox.prop('checked', !checkbox.prop('checked')).triggerHandler('click');
|
||||
if (checkbox.prop('checked')) {
|
||||
$(this).parents('tr').addClass('selected');
|
||||
}
|
||||
else {
|
||||
$(this).parents('tr').removeClass('selected');
|
||||
}
|
||||
});
|
||||
$('.features-diff-listing thead th:nth-child(2)', context).click(function () {
|
||||
var checkbox = $(this).parent().find('th input:checkbox');
|
||||
checkbox.click();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Controller;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
|
||||
/**
|
||||
* Returns ajax responses for the Features UI.
|
||||
*/
|
||||
class FeaturesUIController implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The package assigner.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesUIController object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('features.manager'),
|
||||
$container->get('features_assigner')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of auto-detected config items for a feature.
|
||||
*
|
||||
* @param string $name
|
||||
* Short machine name of feature to process.
|
||||
*
|
||||
* @return array
|
||||
* List of auto-detected config items, keyed by type and short name.
|
||||
*/
|
||||
public function detect($name) {
|
||||
$detected = array();
|
||||
$this->assigner->assignConfigPackages();
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
$items = $_POST['items'];
|
||||
if (!empty($items)) {
|
||||
$excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array();
|
||||
$selected = array();
|
||||
foreach ($items as $key) {
|
||||
preg_match('/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/', $key, $matches);
|
||||
if (!empty($matches[1]) && !empty($matches[4])) {
|
||||
$component = $matches[1];
|
||||
$item = $this->domDecode($matches[4]);
|
||||
if (!isset($excluded[$component][$item])) {
|
||||
$selected[] = $this->featuresManager->getFullName($component, $item);
|
||||
}
|
||||
}
|
||||
}
|
||||
$detected = !empty($selected) ? $this->getConfigDependents($selected, $name) : array();
|
||||
$detected = array_merge($detected, $selected);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($detected as $config_name) {
|
||||
$item = $config_collection[$config_name];
|
||||
$result[$item->getType()][$item->getShortName()] = $item->getName();
|
||||
}
|
||||
return new JsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration dependent on given items.
|
||||
*
|
||||
* @param array $item_names
|
||||
* An array of item names.
|
||||
* @param string $package_name
|
||||
* Short machine name of feature to process.
|
||||
*
|
||||
* @return array
|
||||
* An array of config items.
|
||||
*/
|
||||
protected function getConfigDependents(array $item_names, $package_name) {
|
||||
$result = [];
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
$settings = $this->featuresManager->getSettings();
|
||||
$allow_conflicts = $settings->get('conflicts');
|
||||
|
||||
if (empty($item_names)) {
|
||||
$item_names = array_keys($config_collection);
|
||||
}
|
||||
|
||||
// Add any existing auto-detected items already in the package config
|
||||
$this->package = $packages[$package_name];
|
||||
$package_config = isset($this->package) ? $this->package->getConfig() : array();
|
||||
$package_config = !empty($package_config) ? array_unique(array_merge($package_config, $item_names)) : $item_names;
|
||||
foreach ($package_config as $config_name) {
|
||||
if (!$config_collection[$config_name]->getPackageExcluded()) {
|
||||
$result[] = $config_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Now add dependents of the items selected
|
||||
foreach ($item_names as $item_name) {
|
||||
if ($config_collection[$item_name]->getPackage()) {
|
||||
foreach ($config_collection[$item_name]->getDependents() as $dependent_item_name) {
|
||||
if (isset($config_collection[$dependent_item_name])) {
|
||||
$allow = TRUE;
|
||||
if (!$allow_conflicts && $config_collection[$dependent_item_name]->getPackage()) {
|
||||
if ($packages[$config_collection[$dependent_item_name]->getPackage()]) {
|
||||
$allow = ($packages[$config_collection[$dependent_item_name]->getPackage()]->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT)
|
||||
|| ($config_collection[$item_name]->getPackage() == $config_collection[$dependent_item_name]->getPackage());
|
||||
}
|
||||
}
|
||||
if ($allow) {
|
||||
$result[] = $dependent_item_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a given key.
|
||||
*
|
||||
* @param string $key
|
||||
* The key to encode.
|
||||
*
|
||||
* @return string
|
||||
* The encoded key.
|
||||
*/
|
||||
protected function domEncode($key) {
|
||||
$replacements = $this->domEncodeMap();
|
||||
return strtr($key, $replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a given key.
|
||||
*
|
||||
* @param string $key
|
||||
* The key to decode.
|
||||
*
|
||||
* @return string
|
||||
* The decoded key.
|
||||
*/
|
||||
protected function domDecode($key) {
|
||||
$replacements = array_flip($this->domEncodeMap());
|
||||
return strtr($key, $replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encoding map for decode and encode options.
|
||||
*
|
||||
* @return array
|
||||
* An encoding map.
|
||||
*/
|
||||
protected function domEncodeMap() {
|
||||
return array(
|
||||
':' => '__' . ord(':') . '__',
|
||||
'/' => '__' . ord('/') . '__',
|
||||
',' => '__' . ord(',') . '__',
|
||||
'.' => '__' . ord('.') . '__',
|
||||
'<' => '__' . ord('<') . '__',
|
||||
'>' => '__' . ord('>') . '__',
|
||||
'%' => '__' . ord('%') . '__',
|
||||
')' => '__' . ord(')') . '__',
|
||||
'(' => '__' . ord('(') . '__',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
class AssignmentBaseForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'base';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_base_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
// Pass the last argument to limit the select to config entity types that
|
||||
// provide bundles for other entity types.
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('base'), TRUE);
|
||||
// Pass the last argument to limit the select to content entity types do
|
||||
// not have config entity provided bundles, thus avoiding duplication with
|
||||
// the config type select options.
|
||||
$this->setContentTypeSelect($form, $settings['types']['content'], $this->t('base'), TRUE);
|
||||
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in types selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$settings['types'] = $form_state->getValue('types');
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
$this->setRedirect($form_state);
|
||||
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configures the configuration assignment methods for this site.
|
||||
*/
|
||||
class AssignmentConfigureForm extends FormBase {
|
||||
|
||||
/**
|
||||
* Bundle select value that should trigger a new bundle to be created.
|
||||
*/
|
||||
const NEW_BUNDLE_SELECT_VALUE = 'new';
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The package assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* Constructs a AssignmentConfigureForm object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The configuration assignment methods manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('features.manager'),
|
||||
$container->get('features_assigner')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_configure_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the values from the bundle into the user input.
|
||||
* Used during Ajax callback since updating #default_values is ignored.
|
||||
* @param $bundle_name
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
*/
|
||||
protected function loadBundleValues($bundle_name, FormStateInterface &$form_state, $current_bundle, $enabled_methods, $methods_weight) {
|
||||
$input = $form_state->getUserInput();
|
||||
if ($bundle_name == self::NEW_BUNDLE_SELECT_VALUE) {
|
||||
$input['bundle']['name'] = '';
|
||||
$input['bundle']['machine_name'] = '';
|
||||
$input['bundle']['description'] = '';
|
||||
$input['bundle']['is_profile'] = NULL;
|
||||
$input['bundle']['profile_name'] = '';
|
||||
}
|
||||
else {
|
||||
$input['bundle']['name'] = $current_bundle->getName();
|
||||
$input['bundle']['machine_name'] = $current_bundle->getMachineName();
|
||||
$input['bundle']['description'] = $current_bundle->getDescription();
|
||||
$input['bundle']['is_profile'] = $current_bundle->isProfile() ? 1 : null;
|
||||
$input['bundle']['profile_name'] = $current_bundle->isProfile() ? $current_bundle->getProfileName() : '';
|
||||
}
|
||||
|
||||
foreach ($methods_weight as $method_id => $weight) {
|
||||
$enabled = isset($enabled_methods[$method_id]);
|
||||
$input['weight'][$method_id] = $weight;
|
||||
$input['enabled'][$method_id] = $enabled ? 1 : null;
|
||||
}
|
||||
|
||||
$form_state->setUserInput($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if an element triggered the form submission via Ajax.
|
||||
* TODO: SHOULDN'T NEED THIS! BUT DRUPAL IS CALLING buildForm AFTER THE
|
||||
* BUNDLE AJAX IS SELECTED AND DOESN'T HAVE getTriggeringElement() SET YET.
|
||||
*/
|
||||
protected function elementTriggeredScriptedSubmission(FormStateInterface &$form_state) {
|
||||
$input = $form_state->getUserInput();
|
||||
if (!empty($input['_triggering_element_name'])) {
|
||||
return $input['_triggering_element_name'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$load_values = FALSE;
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
// TODO: See if there is a Drupal Core issue for this.
|
||||
// Sometimes the first ajax call on the page causes buildForm to be called
|
||||
// twice! First time form_state->getTriggeringElement is NOT SET, but
|
||||
// the form_state['input'] shows the _triggering_element_name. Then the
|
||||
// SECOND time it is called the getTriggeringElement is fine.
|
||||
$real_trigger = $this->elementTriggeredScriptedSubmission($form_state);
|
||||
if (!isset($trigger) && ($real_trigger == 'bundle[bundle_select]')) {
|
||||
$input = $form_state->getUserInput();
|
||||
$bundle_name = $input['bundle']['bundle_select'];
|
||||
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE) {
|
||||
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
|
||||
}
|
||||
$load_values = TRUE;
|
||||
}
|
||||
elseif ($trigger['#name'] == 'bundle[bundle_select]') {
|
||||
$bundle_name = $form_state->getValue(array('bundle', 'bundle_select'));
|
||||
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE) {
|
||||
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
|
||||
}
|
||||
$load_values = TRUE;
|
||||
}
|
||||
elseif ($trigger['#name'] == 'removebundle') {
|
||||
$current_bundle = $this->assigner->loadBundle($bundle_name);
|
||||
$bundle_name = $current_bundle->getMachineName();
|
||||
$this->assigner->removeBundle($bundle_name);
|
||||
return $this->redirect('features.assignment', array(''));
|
||||
}
|
||||
if (!isset($current_bundle)) {
|
||||
switch ($bundle_name) {
|
||||
// If no bundle is selected, use the current one.
|
||||
case NULL:
|
||||
$current_bundle = $this->assigner->loadBundle();
|
||||
$bundle_name = $current_bundle->getMachineName();
|
||||
break;
|
||||
case self::NEW_BUNDLE_SELECT_VALUE:
|
||||
$current_bundle = $this->assigner->loadBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
break;
|
||||
default:
|
||||
$current_bundle = $this->assigner->loadBundle($bundle_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$enabled_methods = $current_bundle->getEnabledAssignments();
|
||||
$methods_weight = $current_bundle->getAssignmentWeights();
|
||||
|
||||
// Add missing data to the methods lists.
|
||||
$assignment_info = $this->assigner->getAssignmentMethods();
|
||||
foreach ($assignment_info as $method_id => $method) {
|
||||
if (!isset($methods_weight[$method_id])) {
|
||||
$methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0;
|
||||
}
|
||||
}
|
||||
// Order methods list by weight.
|
||||
asort($methods_weight);
|
||||
|
||||
if ($load_values) {
|
||||
$this->loadBundleValues($bundle_name, $form_state, $current_bundle, $enabled_methods, $methods_weight);
|
||||
}
|
||||
|
||||
$form = array(
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
// Provides the copyFieldValue behavior invoked below.
|
||||
'system/drupal.system',
|
||||
'features_ui/drupal.features_ui.admin',
|
||||
),
|
||||
),
|
||||
// '#attributes' => array('class' => 'edit-bundles-wrapper'),
|
||||
'#tree' => TRUE,
|
||||
'#show_operations' => FALSE,
|
||||
'weight' => array('#tree' => TRUE),
|
||||
'#prefix' => '<div id="edit-bundles-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
|
||||
$form['bundle'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Bundle'),
|
||||
'#tree' => TRUE,
|
||||
'#weight' => -9,
|
||||
);
|
||||
|
||||
if ($bundle_name == self::NEW_BUNDLE_SELECT_VALUE) {
|
||||
$default_values = [
|
||||
'bundle_select' => self::NEW_BUNDLE_SELECT_VALUE,
|
||||
'name' => '',
|
||||
'machine_name' => '',
|
||||
'description' => '',
|
||||
'is_profile' => FALSE,
|
||||
'profile_name' => '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$default_values = [
|
||||
'bundle_select' => $current_bundle->getMachineName(),
|
||||
'name' => $current_bundle->getName(),
|
||||
'machine_name' => $current_bundle->getMachineName(),
|
||||
'description' => $current_bundle->getDescription(),
|
||||
'is_profile' => $current_bundle->isProfile(),
|
||||
'profile_name' => $current_bundle->getProfileName(),
|
||||
];
|
||||
}
|
||||
$form['bundle']['bundle_select'] = array(
|
||||
'#title' => $this->t('Bundle'),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'select',
|
||||
'#options' => [self::NEW_BUNDLE_SELECT_VALUE => $this->t('--New--')] + $this->assigner->getBundleOptions(),
|
||||
'#default_value' => $default_values['bundle_select'],
|
||||
'#ajax' => array(
|
||||
'callback' => '::updateForm',
|
||||
'wrapper' => 'edit-bundles-wrapper',
|
||||
),
|
||||
);
|
||||
|
||||
// Don't show the remove button for the default bundle or when adding a new
|
||||
// bundle.
|
||||
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE && !$current_bundle->isDefault()) {
|
||||
$form['bundle']['remove'] = array(
|
||||
'#type' => 'button',
|
||||
'#name' => 'removebundle',
|
||||
'#value' => $this->t('Remove bundle'),
|
||||
);
|
||||
}
|
||||
|
||||
$form['bundle']['name'] = array(
|
||||
'#title' => $this->t('Bundle name'),
|
||||
'#type' => 'textfield',
|
||||
'#description' => $this->t('A unique human-readable name of this bundle.'),
|
||||
'#default_value' => $default_values['name'],
|
||||
'#required' => TRUE,
|
||||
'#disabled' => $bundle_name == FeaturesBundleInterface::DEFAULT_BUNDLE,
|
||||
);
|
||||
|
||||
// Don't allow changing the default bundle machine name.
|
||||
if ($bundle_name == FeaturesBundleInterface::DEFAULT_BUNDLE) {
|
||||
$form['bundle']['machine_name'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $default_values['machine_name'],
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form['bundle']['machine_name'] = array(
|
||||
'#title' => $this->t('Machine name'),
|
||||
'#type' => 'machine_name',
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $default_values['machine_name'],
|
||||
'#description' => $this->t('A unique machine-readable name of this bundle. Used to prefix exported packages. It must only contain lowercase letters, numbers, and underscores.'),
|
||||
'#machine_name' => array(
|
||||
'source' => array('bundle', 'name'),
|
||||
'exists' => array($this, 'bundleExists'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$form['bundle']['description'] = array(
|
||||
'#title' => $this->t('Distribution description'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $default_values['description'],
|
||||
'#description' => $this->t('A description of the bundle.'),
|
||||
'#size' => 80,
|
||||
);
|
||||
|
||||
$form['bundle']['is_profile'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include install profile'),
|
||||
'#default_value' => $default_values['is_profile'],
|
||||
'#description' => $this->t('Select this option to have your features packaged into an install profile.'),
|
||||
'#attributes' => array(
|
||||
'data-add-profile' => 'status',
|
||||
),
|
||||
);
|
||||
|
||||
$show_and_require_if_profile_checked = array(
|
||||
'visible' => array(
|
||||
':input[data-add-profile="status"]' => array('checked' => TRUE),
|
||||
),
|
||||
'required' => array(
|
||||
':input[data-add-profile="status"]' => array('checked' => TRUE),
|
||||
),
|
||||
);
|
||||
|
||||
$form['bundle']['profile_name'] = array(
|
||||
'#title' => $this->t('Profile name'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $default_values['profile_name'],
|
||||
'#description' => $this->t('The machine name (directory name) of your profile.'),
|
||||
'#size' => 30,
|
||||
// Show and require only if the profile.add option is selected.
|
||||
'#states' => $show_and_require_if_profile_checked,
|
||||
);
|
||||
|
||||
// Attach the copyFieldValue behavior to the profile_name field. In
|
||||
// practice this only works if a user tabs through the bundle machine name
|
||||
// field or manually edits it.
|
||||
$form['#attached']['drupalSettings']['copyFieldValue']['edit-bundle-machine-name'] = ['edit-bundle-profile-name'];
|
||||
|
||||
foreach ($methods_weight as $method_id => $weight) {
|
||||
|
||||
// A packaging method might no longer be available if the defining module
|
||||
// has been uninstalled after the last configuration saving.
|
||||
if (!isset($assignment_info[$method_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$enabled = isset($enabled_methods[$method_id]);
|
||||
$method = $assignment_info[$method_id];
|
||||
|
||||
$method_name = Html::escape($method['name']);
|
||||
|
||||
$form['weight'][$method_id] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for @title package assignment method', array('@title' => Unicode::strtolower($method_name))),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $weight,
|
||||
'#attributes' => array('class' => array('assignment-method-weight')),
|
||||
'#delta' => 20,
|
||||
);
|
||||
|
||||
$form['title'][$method_id] = array('#markup' => $method_name);
|
||||
|
||||
$form['enabled'][$method_id] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enable @title package assignment method', array('@title' => Unicode::strtolower($method_name))),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $enabled,
|
||||
);
|
||||
|
||||
$form['description'][$method_id] = array('#markup' => $method['description']);
|
||||
|
||||
$config_op = array();
|
||||
if (isset($method['config_route_name'])) {
|
||||
$config_op['configure'] = array(
|
||||
'title' => $this->t('Configure'),
|
||||
'url' => Url::fromRoute($method['config_route_name'], array('bundle_name' => $current_bundle->getMachineName())),
|
||||
);
|
||||
// If there is at least one operation enabled, show the operation
|
||||
// column.
|
||||
$form['#show_operations'] = TRUE;
|
||||
}
|
||||
$form['operation'][$method_id] = array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $config_op,
|
||||
);
|
||||
}
|
||||
|
||||
$form['actions'] = array('#type' => 'actions', '#weight' => 9);
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#button_type' => 'primary',
|
||||
'#value' => $this->t('Save settings'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for handling switching the bundle selector.
|
||||
*/
|
||||
public function updateForm($form, FormStateInterface $form_state) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->getValue(array('bundle', 'is_profile')) && empty($form_state->getValue(array('bundle', 'profile_name')))) {
|
||||
$form_state->setErrorByName('bundle][profile_name', $this->t('To create a profile, please enter a profile name.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$enabled_methods = array_filter($form_state->getValue('enabled'));
|
||||
ksort($enabled_methods);
|
||||
$method_weights = $form_state->getValue('weight');
|
||||
ksort($method_weights);
|
||||
|
||||
$machine_name = $form_state->getValue(array('bundle', 'machine_name'));
|
||||
|
||||
// If this is a new bundle, create it.
|
||||
if ($form_state->getValue(array('bundle', 'bundle_select')) == self::NEW_BUNDLE_SELECT_VALUE) {
|
||||
$bundle = $this->assigner->createBundleFromDefault($machine_name);
|
||||
}
|
||||
// Otherwise, load the current bundle and rename if needed.
|
||||
else {
|
||||
$bundle = $this->assigner->getBundle();
|
||||
$old_name = $bundle->getMachineName();
|
||||
$new_name = $form_state->getValue(array('bundle', 'machine_name'));
|
||||
if ($old_name != $new_name) {
|
||||
$bundle = $this->assigner->renameBundle($old_name, $new_name);
|
||||
}
|
||||
}
|
||||
|
||||
$bundle->setName($form_state->getValue(array('bundle', 'name')));
|
||||
$bundle->setDescription($form_state->getValue(array('bundle', 'description')));
|
||||
$bundle->setEnabledAssignments(array_keys($enabled_methods));
|
||||
$bundle->setAssignmentWeights($method_weights);
|
||||
$bundle->setIsProfile($form_state->getValue(array('bundle', 'is_profile')));
|
||||
$bundle->setProfileName($form_state->getValue(array('bundle', 'profile_name')));
|
||||
$bundle->save();
|
||||
$this->assigner->setBundle($bundle);
|
||||
$this->assigner->setCurrent($bundle);
|
||||
|
||||
$form_state->setRedirect('features.assignment');
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for machine_name exists()
|
||||
* @param $value
|
||||
* @param $element
|
||||
* @param $form_state
|
||||
* @return bool
|
||||
*/
|
||||
public function bundleExists($value, $element, $form_state) {
|
||||
$bundle = $this->assigner->getBundle($value);
|
||||
return isset($bundle);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
class AssignmentCoreForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'core';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_core_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('core'));
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in types selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$settings['types'] = $form_state->getValue('types');
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
$this->setRedirect($form_state);
|
||||
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
class AssignmentExcludeForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'exclude';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_exclude_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('exclude'));
|
||||
|
||||
$module_settings = $settings['module'];
|
||||
$curated_settings = $settings['curated'];
|
||||
|
||||
$form['curated'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Exclude designated site-specific configuration'),
|
||||
'#default_value' => $curated_settings,
|
||||
'#description' => $this->t('Select this option to exclude from packaging items on a curated list of site-specific configuration.'),
|
||||
);
|
||||
|
||||
$form['module'] = array(
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
$form['module']['installed'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Exclude installed module-provided entity configuration'),
|
||||
'#default_value' => $module_settings['installed'],
|
||||
'#description' => $this->t('Select this option to exclude from packaging any configuration that is provided by already installed modules.'),
|
||||
'#attributes' => array(
|
||||
'data-module-installed' => 'status',
|
||||
),
|
||||
);
|
||||
|
||||
$show_if_module_installed_checked = array(
|
||||
'visible' => array(
|
||||
':input[data-module-installed="status"]' => array('checked' => TRUE),
|
||||
),
|
||||
);
|
||||
|
||||
$info = system_get_info('module', drupal_get_profile());
|
||||
$form['module']['profile'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Don't exclude install profile's configuration"),
|
||||
'#default_value' => $module_settings['profile'],
|
||||
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by this site's install profile, %profile.", array('%profile' => $info['name'])),
|
||||
'#states' => $show_if_module_installed_checked,
|
||||
);
|
||||
|
||||
$machine_name = $this->currentBundle->getMachineName();
|
||||
$machine_name = !empty($machine_name) ? $machine_name : $this->t('none');
|
||||
$form['module']['namespace'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Don't exclude non-installed configuration by namespace"),
|
||||
'#default_value' => $module_settings['namespace'],
|
||||
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by non-installed modules with the package namespace (currently %namespace).", array('%namespace' => $machine_name)),
|
||||
'#states' => $show_if_module_installed_checked,
|
||||
'#attributes' => array(
|
||||
'data-namespace' => 'status',
|
||||
),
|
||||
);
|
||||
|
||||
$show_if_namespace_checked = array(
|
||||
'visible' => array(
|
||||
':input[data-namespace="status"]' => array('checked' => TRUE),
|
||||
':input[data-module-installed="status"]' => array('checked' => TRUE),
|
||||
),
|
||||
);
|
||||
|
||||
$form['module']['namespace_any'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Don't exclude ANY configuration by namespace"),
|
||||
'#default_value' => $module_settings['namespace_any'],
|
||||
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by ANY modules with the package namespace (currently %namespace).
|
||||
Warning: Can cause installed configuration to be reassigned to different packages.", array('%namespace' => $machine_name)),
|
||||
'#states' => $show_if_namespace_checked,
|
||||
);
|
||||
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$settings = array_merge($settings, [
|
||||
'types' => $form_state->getValue('types'),
|
||||
'curated' => $form_state->getValue('curated'),
|
||||
'module' => $form_state->getValue('module'),
|
||||
]);
|
||||
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
|
||||
$this->setRedirect($form_state);
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
abstract class AssignmentFormBase extends FormBase {
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The package assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The current bundle.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesBundleInterface
|
||||
*/
|
||||
protected $currentBundle;
|
||||
|
||||
/**
|
||||
* Constructs a AssignmentBaseForm object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The assigner.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner, EntityManagerInterface $entity_manager) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assigner = $assigner;
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('features.manager'),
|
||||
$container->get('features_assigner'),
|
||||
$container->get('entity.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds configuration types checkboxes.
|
||||
*/
|
||||
protected function setConfigTypeSelect(&$form, $defaults, $type, $bundles_only = FALSE) {
|
||||
$options = $this->featuresManager->listConfigTypes($bundles_only);
|
||||
|
||||
if (!isset($form['types'])) {
|
||||
$form['types'] = array(
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
$form['types']['config'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Configuration types'),
|
||||
'#description' => $this->t('Select types of configuration that should be considered @type types.', array('@type' => $type)),
|
||||
'#options' => $options,
|
||||
'#default_value' => $defaults,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content entity types checkboxes.
|
||||
*/
|
||||
protected function setContentTypeSelect(&$form, $defaults, $type, $exclude_has_config_bundles = TRUE) {
|
||||
$entity_types = $this->entityManager->getDefinitions();
|
||||
|
||||
$has_config_bundle = array();
|
||||
foreach ($entity_types as $definition) {
|
||||
if ($entity_type_id = $definition->getBundleOf()) {
|
||||
$has_config_bundle[] = $entity_type_id;
|
||||
}
|
||||
}
|
||||
$options = array();
|
||||
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
if (!$entity_type instanceof ContentEntityTypeInterface) {
|
||||
continue;
|
||||
}
|
||||
if ($exclude_has_config_bundles && in_array($entity_type_id, $has_config_bundle)) {
|
||||
continue;
|
||||
}
|
||||
$options[$entity_type_id] = $entity_type->getLabel() ?: $entity_type_id;
|
||||
}
|
||||
|
||||
// Sort the entity types by label.
|
||||
uasort($options, 'strnatcasecmp');
|
||||
|
||||
if (!isset($form['types'])) {
|
||||
$form['types'] = array(
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
$form['types']['content'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Content entity types'),
|
||||
'#description' => $this->t('Select content entity types that should be considered @type types.', array('@type' => $type)),
|
||||
'#options' => $options,
|
||||
'#default_value' => $defaults,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "Save settings" submit action.
|
||||
*/
|
||||
protected function setActions(&$form) {
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#button_type' => 'primary',
|
||||
'#value' => $this->t('Save settings'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects back to the Bundle config form.
|
||||
*
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
protected function setRedirect(FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('features.assignment', array('bundle_name' => $this->currentBundle->getMachineName()));
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
class AssignmentOptionalForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'optional';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_optional_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('optional'));
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in types selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$settings['types'] = $form_state->getValue('types');
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
$this->setRedirect($form_state);
|
||||
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this profile.
|
||||
*/
|
||||
class AssignmentProfileForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'profile';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_profile_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('profile'));
|
||||
|
||||
$form['curated'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Add commonly-needed configuration'),
|
||||
'#default_value' => $settings['curated'],
|
||||
'#description' => $this->t('Select this option to add a curated list of commonly-needed configuration including cron- and theme-related settings to the install profile.'),
|
||||
);
|
||||
|
||||
$standard_settings = $settings['standard'];
|
||||
|
||||
$form['standard'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Crib from the Standard profile'),
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
$form['standard']['files'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Crib code'),
|
||||
'#default_value' => $standard_settings['files'],
|
||||
'#description' => $this->t('Select this option to add configuration and other files to the optional install profile from the Drupal core Standard install profile. Without these additions, a generated install profile will be missing some important initial setup.'),
|
||||
);
|
||||
$form['standard']['dependencies'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Crib dependencies'),
|
||||
'#default_value' => $standard_settings['dependencies'],
|
||||
'#description' => $this->t('Select this option to add module and theme dependencies from the Standard install profile.'),
|
||||
);
|
||||
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
$settings = array_merge($settings, [
|
||||
'curated' => $form_state->getValue('curated'),
|
||||
'standard' => $form_state->getValue('standard'),
|
||||
'types' => $form_state->getValue('types'),
|
||||
]);
|
||||
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
$this->setRedirect($form_state);
|
||||
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configures the selected configuration assignment method for this site.
|
||||
*/
|
||||
class AssignmentSiteForm extends AssignmentFormBase {
|
||||
|
||||
const METHOD_ID = 'site';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_assignment_site_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
|
||||
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
|
||||
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('site'));
|
||||
$this->setActions($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setValue('types', array_map('array_filter', $form_state->getValue('types')));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Merge in types selections.
|
||||
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
|
||||
$settings['types'] = $form_state->getValue('types');
|
||||
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
|
||||
$this->setRedirect($form_state);
|
||||
|
||||
drupal_set_message($this->t('Package assignment configuration saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\features\ConfigurationItem;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Component\Diff\DiffFormatter;
|
||||
use Drupal\config_update\ConfigRevertInterface;
|
||||
use Drupal\config_update\ConfigDiffInterface;
|
||||
|
||||
/**
|
||||
* Defines the features differences form.
|
||||
*/
|
||||
class FeaturesDiffForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The package assigner.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* The config differ.
|
||||
*
|
||||
* @var \Drupal\config_update\ConfigDiffInterface
|
||||
*/
|
||||
protected $configDiff;
|
||||
|
||||
/**
|
||||
* The diff formatter.
|
||||
*
|
||||
* @var \Drupal\Core\Diff\DiffFormatter
|
||||
*/
|
||||
protected $diffFormatter;
|
||||
|
||||
/**
|
||||
* The config reverter.
|
||||
*
|
||||
* @var \Drupal\config_update\ConfigRevertInterface
|
||||
*/
|
||||
protected $configRevert;
|
||||
|
||||
/**
|
||||
* Constructs a FeaturesDiffForm object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner,
|
||||
ConfigDiffInterface $config_diff, DiffFormatter $diff_formatter,
|
||||
ConfigRevertInterface $config_revert) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assigner = $assigner;
|
||||
$this->configDiff = $config_diff;
|
||||
$this->diffFormatter = $diff_formatter;
|
||||
$this->configRevert = $config_revert;
|
||||
$this->diffFormatter->show_header = FALSE;
|
||||
$this->diffFormatter->leading_context_lines = 0;
|
||||
$this->diffFormatter->trailing_context_lines = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('features.manager'),
|
||||
$container->get('features_assigner'),
|
||||
$container->get('config_update.config_diff'),
|
||||
$container->get('diff.formatter'),
|
||||
$container->get('features.config_update')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_diff_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $featurename = '') {
|
||||
$current_bundle = $this->assigner->applyBundle();
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
$form = array();
|
||||
|
||||
$machine_name = '';
|
||||
if (!empty($featurename) && empty($packages[$featurename])) {
|
||||
drupal_set_message($this->t('Feature @name does not exist.', array('@name' => $featurename)), 'error');
|
||||
return array();
|
||||
}
|
||||
elseif (!empty($featurename)) {
|
||||
$machine_name = $packages[$featurename]->getMachineName();
|
||||
$packages = array($packages[$featurename]);
|
||||
}
|
||||
else {
|
||||
$packages = $this->featuresManager->filterPackages($packages, $current_bundle->getMachineName());
|
||||
}
|
||||
|
||||
$header = array(
|
||||
'row' => array(
|
||||
'data' => !empty($machine_name)
|
||||
? $this->t('Differences in @name', array('@name' => $machine_name))
|
||||
: ($current_bundle->isDefault() ? $this->t('All differences') : $this->t('All differences in bundle: @bundle', array('@bundle' => $current_bundle->getName()))),
|
||||
),
|
||||
);
|
||||
|
||||
$options = array();
|
||||
foreach ($packages as $package) {
|
||||
if ($package->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT) {
|
||||
$missing = $this->featuresManager->reorderMissing($this->featuresManager->detectMissing($package));
|
||||
$overrides = $this->featuresManager->detectOverrides($package, TRUE);
|
||||
if (!empty($overrides) || !empty($missing)) {
|
||||
$options += array(
|
||||
$package->getMachineName() => array(
|
||||
'row' => array(
|
||||
'data' => array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'h2',
|
||||
'#value' => Html::escape($package->getName()),
|
||||
),
|
||||
),
|
||||
'#attributes' => array(
|
||||
'class' => 'features-diff-header',
|
||||
),
|
||||
),
|
||||
);
|
||||
$options += $this->diffOutput($package, $overrides, $missing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['diff'] = array(
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $header,
|
||||
'#options' => $options,
|
||||
'#attributes' => array('class' => array('features-diff-listing')),
|
||||
'#empty' => $this->t('No differences exist in exported features.'),
|
||||
);
|
||||
|
||||
$form['actions'] = array('#type' => 'actions', '#tree' => TRUE);
|
||||
$form['actions']['revert'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import changes'),
|
||||
);
|
||||
$form['actions']['help'] = array(
|
||||
'#markup' => $this->t('Import the selected changes above into the active configuration.'),
|
||||
);
|
||||
|
||||
$form['#attached']['library'][] = 'system/diff';
|
||||
$form['#attached']['library'][] = 'features_ui/drupal.features_ui.admin';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->assigner->assignConfigPackages();
|
||||
$config = $this->featuresManager->getConfigCollection();
|
||||
$items = array_filter($form_state->getValue('diff'));
|
||||
if (empty($items)) {
|
||||
drupal_set_message($this->t('No configuration was selected for import.'), 'warning');
|
||||
return;
|
||||
}
|
||||
foreach ($items as $config_name) {
|
||||
if (isset($config[$config_name])) {
|
||||
$item = $config[$config_name];
|
||||
$type = ConfigurationItem::fromConfigStringToConfigType($item->getType());
|
||||
$this->configRevert->revert($type, $item->getShortName());
|
||||
}
|
||||
else {
|
||||
$item = $this->featuresManager->getConfigType($config_name);
|
||||
$type = ConfigurationItem::fromConfigStringToConfigType($item['type']);
|
||||
$this->configRevert->import($type, $item['name_short']);
|
||||
}
|
||||
drupal_set_message($this->t('Imported @name', array('@name' => $config_name)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form element for the given overrides.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* A package.
|
||||
* @param array $overrides
|
||||
* An array of overrides.
|
||||
* @param array $missing
|
||||
* An array of missing config.
|
||||
*
|
||||
* @return array
|
||||
* A form element.
|
||||
*/
|
||||
protected function diffOutput(Package $package, $overrides, $missing = array()) {
|
||||
$element = array();
|
||||
$config = $this->featuresManager->getConfigCollection();
|
||||
$components = array_merge($missing, $overrides);
|
||||
|
||||
$header = array(
|
||||
array('data' => '', 'class' => 'diff-marker'),
|
||||
array('data' => $this->t('Active site config'), 'class' => 'diff-context'),
|
||||
array('data' => '', 'class' => 'diff-marker'),
|
||||
array('data' => $this->t('Feature code config'), 'class' => 'diff-context'),
|
||||
);
|
||||
|
||||
foreach ($components as $name) {
|
||||
$rows[] = array(array('data' => $name, 'colspan' => 4, 'header' => TRUE));
|
||||
|
||||
if (!isset($config[$name])) {
|
||||
$details = array(
|
||||
'#markup' => $this->t('Component in feature missing from active config.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$active = $this->featuresManager->getActiveStorage()->read($name);
|
||||
$extension = $this->featuresManager->getExtensionStorages()->read($name);
|
||||
if (empty($extension)) {
|
||||
$details = array(
|
||||
'#markup' => $this->t('Dependency detected in active config but not exported to the feature.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$diff = $this->configDiff->diff($active, $extension);
|
||||
$details = array(
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $this->diffFormatter->format($diff),
|
||||
'#attributes' => array('class' => array('diff', 'features-diff')),
|
||||
);
|
||||
}
|
||||
}
|
||||
$element[$name] = array(
|
||||
'row' => array(
|
||||
'data' => array(
|
||||
'#type' => 'details',
|
||||
'#title' => Html::escape($name),
|
||||
'#open' => TRUE,
|
||||
'#description' => array(
|
||||
'data' => $details,
|
||||
),
|
||||
),
|
||||
),
|
||||
'#attributes' => array(
|
||||
'class' => 'diff-' . $package->getMachineName(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
+1085
File diff suppressed because it is too large
Load Diff
+539
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
use Drupal\features\FeaturesGeneratorInterface;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines the configuration export form.
|
||||
*/
|
||||
class FeaturesExportForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The package assigner.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* The package generator.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a FeaturesExportForm object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $features_assigner
|
||||
* The features assigner.
|
||||
* @param \Drupal\features\FeaturesGeneratorInterface $features_generator
|
||||
* The features generator.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The features generator.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner, FeaturesGeneratorInterface $generator, ModuleHandlerInterface $module_handler) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assigner = $assigner;
|
||||
$this->generator = $generator;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('features.manager'),
|
||||
$container->get('features_assigner'),
|
||||
$container->get('features_generator'),
|
||||
$container->get('module_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'features_export_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if an element triggered the form submission via Ajax.
|
||||
* TODO: SHOULDN'T NEED THIS! BUT DRUPAL IS CALLING buildForm AFTER THE
|
||||
* BUNDLE AJAX IS SELECTED AND DOESN'T HAVE getTriggeringElement() SET YET.
|
||||
*/
|
||||
protected function elementTriggeredScriptedSubmission(FormStateInterface &$form_state) {
|
||||
$input = $form_state->getUserInput();
|
||||
if (!empty($input['_triggering_element_name'])) {
|
||||
return $input['_triggering_element_name'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
// TODO: See if there is a Drupal Core issue for this.
|
||||
// Sometimes the first ajax call on the page causes buildForm to be called
|
||||
// twice! First time form_state->getTriggeringElement is NOT SET, but
|
||||
// the form_state['input'] shows the _triggering_element_name. Then the
|
||||
// SECOND time it is called the getTriggeringElement is fine.
|
||||
$real_trigger = $this->elementTriggeredScriptedSubmission($form_state);
|
||||
if (!isset($trigger) && ($real_trigger == 'bundle')) {
|
||||
$input = $form_state->getUserInput();
|
||||
$bundle_name = $input['bundle'];
|
||||
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
|
||||
}
|
||||
elseif ($trigger['#name'] == 'bundle') {
|
||||
$bundle_name = $form_state->getValue('bundle', '');
|
||||
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
|
||||
}
|
||||
else {
|
||||
$this->assigner->loadBundle();
|
||||
}
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$this->assigner->assignConfigPackages();
|
||||
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
// Add in un-packaged configuration items.
|
||||
$this->addUnpackaged($packages, $config_collection);
|
||||
|
||||
// Filter packages on bundle if selected.
|
||||
if (!$current_bundle->isDefault()) {
|
||||
$packages = $this->featuresManager->filterPackages($packages, $current_bundle->getMachineName(), TRUE);
|
||||
}
|
||||
|
||||
// Pass the packages and bundle data for use in the form pre_render
|
||||
// callback.
|
||||
$form['#packages'] = $packages;
|
||||
$form['#profile_package'] = $current_bundle->getProfileName();
|
||||
$form['header'] = array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array('class' => 'features-header'),
|
||||
);
|
||||
|
||||
$bundle_options = $this->assigner->getBundleOptions();
|
||||
|
||||
// If there are no custom bundles, provide message.
|
||||
if (count($bundle_options) < 2) {
|
||||
drupal_set_message($this->t('You have not yet created any bundles. Before generating features, you may wish to <a href=":create">create a bundle</a> to group your features within.', [':create' => Url::fromRoute('features.assignment')->toString()]));
|
||||
}
|
||||
|
||||
$form['#prefix'] = '<div id="edit-features-wrapper">';
|
||||
$form['#suffix'] = '</div>';
|
||||
$form['header']['bundle'] = array(
|
||||
'#title' => $this->t('Bundle'),
|
||||
'#type' => 'select',
|
||||
'#options' => $bundle_options,
|
||||
'#default_value' => $current_bundle->getMachineName(),
|
||||
'#prefix' => '<div id="edit-package-set-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
'#ajax' => array(
|
||||
'callback' => '::updatePreview',
|
||||
'wrapper' => 'edit-features-preview-wrapper',
|
||||
),
|
||||
'#attributes' => array(
|
||||
'data-new-package-set' => 'status',
|
||||
),
|
||||
);
|
||||
|
||||
$form['preview'] = $this->buildListing($packages);
|
||||
|
||||
$form['#attached'] = array(
|
||||
'library' => array(
|
||||
'features_ui/drupal.features_ui.admin',
|
||||
),
|
||||
);
|
||||
|
||||
if (\Drupal::currentUser()->hasPermission('export configuration')) {
|
||||
// Offer available generation methods.
|
||||
$generation_info = $this->generator->getGenerationMethods();
|
||||
// Sort generation methods by weight.
|
||||
uasort($generation_info, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
|
||||
$form['description'] = array(
|
||||
'#markup' => '<p>' . $this->t('Use an export method button below to generate the selected features.') . '</p>',
|
||||
);
|
||||
|
||||
$form['actions'] = array('#type' => 'actions', '#tree' => TRUE);
|
||||
foreach ($generation_info as $method_id => $method) {
|
||||
$form['actions'][$method_id] = array(
|
||||
'#type' => 'submit',
|
||||
'#name' => $method_id,
|
||||
'#value' => $this->t('@name', array('@name' => $method['name'])),
|
||||
'#attributes' => array(
|
||||
'title' => Html::escape($method['description']),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$form['#pre_render'][] = array(get_class($this), 'preRenderRemoveInvalidCheckboxes');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles switching the configuration type selector.
|
||||
*/
|
||||
public function updatePreview($form, FormStateInterface $form_state) {
|
||||
// We should really be able to add this pre_render callback to the
|
||||
// 'preview' element. However, since doing so leads to an error (no rows
|
||||
// are displayed), we need to instead explicitly invoke it here for the
|
||||
// processing to apply to the Ajax-rendered form element.
|
||||
$form = $this->preRenderRemoveInvalidCheckboxes($form);
|
||||
return $form['preview'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the portion of the form showing a listing of features.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* The packages.
|
||||
*
|
||||
* @return array
|
||||
* A render array of a form element.
|
||||
*/
|
||||
protected function buildListing(array $packages) {
|
||||
|
||||
$header = array(
|
||||
'name' => array('data' => $this->t('Feature')),
|
||||
'machine_name' => array('data' => $this->t('')),
|
||||
'details' => array('data' => $this->t('Description'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
|
||||
'version' => array('data' => $this->t('Version'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
|
||||
'status' => array('data' => $this->t('Status'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
|
||||
'state' => array('data' => $this->t('State'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
|
||||
);
|
||||
|
||||
$options = array();
|
||||
$first = TRUE;
|
||||
foreach ($packages as $package) {
|
||||
if ($first && $package->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT) {
|
||||
// Don't offer new non-profile packages that are empty.
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT &&
|
||||
!$this->assigner->getBundle()->isProfilePackage($package->getMachineName()) &&
|
||||
empty($package->getConfig())) {
|
||||
continue;
|
||||
}
|
||||
$first = FALSE;
|
||||
$options[] = array(
|
||||
'name' => array(
|
||||
'data' => $this->t('The following packages are not exported.'),
|
||||
'class' => 'features-export-header-row',
|
||||
'colspan' => 6,
|
||||
),
|
||||
);
|
||||
}
|
||||
$options[$package->getMachineName()] = $this->buildPackageDetail($package);
|
||||
}
|
||||
|
||||
$element = array(
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $header,
|
||||
'#options' => $options,
|
||||
'#attributes' => array('class' => array('features-listing')),
|
||||
'#prefix' => '<div id="edit-features-preview-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the details of a package.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package.
|
||||
*
|
||||
* @return array
|
||||
* A render array of a form element.
|
||||
*/
|
||||
protected function buildPackageDetail(Package $package) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
$url = Url::fromRoute('features.edit', array('featurename' => $package->getMachineName()));
|
||||
|
||||
$element['name'] = array(
|
||||
'data' => \Drupal::l($package->getName(), $url),
|
||||
'class' => array('feature-name'),
|
||||
);
|
||||
$machine_name = $package->getMachineName();
|
||||
// Except for the 'unpackaged' pseudo-package, display the full name, since
|
||||
// that's what will be generated.
|
||||
if ($machine_name !== 'unpackaged') {
|
||||
$machine_name = $package->getFullName($machine_name);
|
||||
}
|
||||
$element['machine_name'] = $machine_name;
|
||||
$element['status'] = array(
|
||||
'data' => $this->featuresManager->statusLabel($package->getStatus()),
|
||||
'class' => array('column-nowrap'),
|
||||
);
|
||||
// Use 'data' instead of plain string value so a blank version doesn't
|
||||
// remove column from table.
|
||||
$element['version'] = array(
|
||||
'data' => Html::escape($package->getVersion()),
|
||||
'class' => array('column-nowrap'),
|
||||
);
|
||||
$overrides = $this->featuresManager->detectOverrides($package);
|
||||
$new_config = $this->featuresManager->detectNew($package);
|
||||
$conflicts = array();
|
||||
$missing = array();
|
||||
|
||||
if ($package->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT) {
|
||||
$overrides = array();
|
||||
$new_config = array();
|
||||
}
|
||||
// Bundle package configuration by type.
|
||||
$package_config = array();
|
||||
foreach ($package->getConfig() as $item_name) {
|
||||
if (isset($config_collection[$item_name])) {
|
||||
$item = $config_collection[$item_name];
|
||||
$package_config[$item->getType()][] = array(
|
||||
'name' => Html::escape($item_name),
|
||||
'label' => Html::escape($item->getLabel()),
|
||||
'class' => in_array($item_name, $overrides) ? 'features-override' :
|
||||
(in_array($item_name, $new_config) ? 'features-detected' : ''),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Conflict config from other modules.
|
||||
foreach ($package->getConfigOrig() as $item_name) {
|
||||
if (!isset($config_collection[$item_name])) {
|
||||
$missing[] = $item_name;
|
||||
$package_config['missing'][] = array(
|
||||
'name' => Html::escape($item_name),
|
||||
'label' => Html::escape($item_name),
|
||||
'class' => 'features-missing',
|
||||
);
|
||||
}
|
||||
elseif (!in_array($item_name, $package->getConfig())) {
|
||||
$item = $config_collection[$item_name];
|
||||
$conflicts[] = $item_name;
|
||||
$package_name = !empty($item->getPackage()) ? $item->getPackage() : $this->t('PACKAGE NOT ASSIGNED');
|
||||
$package_config[$item->getType()][] = array(
|
||||
'name' => Html::escape($package_name),
|
||||
'label' => Html::escape($item->getLabel()),
|
||||
'class' => 'features-conflict',
|
||||
);
|
||||
}
|
||||
}
|
||||
// Add dependencies.
|
||||
$package_config['dependencies'] = array();
|
||||
foreach ($package->getDependencies() as $dependency) {
|
||||
$package_config['dependencies'][] = array(
|
||||
'name' => $dependency,
|
||||
'label' => $this->moduleHandler->getName($dependency),
|
||||
'class' => '',
|
||||
);
|
||||
}
|
||||
|
||||
$class = '';
|
||||
$state_links = [];
|
||||
if (!empty($conflicts)) {
|
||||
$state_links[] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Conflicts'),
|
||||
'#url' => Url::fromRoute('features.edit', array('featurename' => $package->getMachineName())),
|
||||
'#attributes' => array('class' => array('features-conflict')),
|
||||
);
|
||||
}
|
||||
if (!empty($overrides)) {
|
||||
$state_links[] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->featuresManager->stateLabel(FeaturesManagerInterface::STATE_OVERRIDDEN),
|
||||
'#url' => Url::fromRoute('features.diff', array('featurename' => $package->getMachineName())),
|
||||
'#attributes' => array('class' => array('features-override')),
|
||||
);
|
||||
}
|
||||
if (!empty($new_config)) {
|
||||
$state_links[] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('New detected'),
|
||||
'#url' => Url::fromRoute('features.diff', array('featurename' => $package->getMachineName())),
|
||||
'#attributes' => array('class' => array('features-detected')),
|
||||
);
|
||||
}
|
||||
if (!empty($missing) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) {
|
||||
$state_links[] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Missing'),
|
||||
'#url' => Url::fromRoute('features.edit', array('featurename' => $package->getMachineName())),
|
||||
'#attributes' => array('class' => array('features-missing')),
|
||||
);
|
||||
}
|
||||
if (!empty($state_links)) {
|
||||
$element['state'] = array(
|
||||
'data' => $state_links,
|
||||
'class' => array('column-nowrap'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$element['state'] = '';
|
||||
}
|
||||
|
||||
$config_types = $this->featuresManager->listConfigTypes();
|
||||
// Add dependencies.
|
||||
$config_types['dependencies'] = $this->t('Dependencies');
|
||||
$config_types['missing'] = $this->t('Missing');
|
||||
uasort($config_types, 'strnatcasecmp');
|
||||
|
||||
$rows = array();
|
||||
// Use sorted array for order.
|
||||
foreach ($config_types as $type => $label) {
|
||||
// For each component type, offer alternating rows.
|
||||
$row = array();
|
||||
if (isset($package_config[$type])) {
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'span',
|
||||
'#value' => Html::escape($label),
|
||||
'#attributes' => array(
|
||||
'title' => Html::escape($type),
|
||||
'class' => 'features-item-label',
|
||||
),
|
||||
),
|
||||
);
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
'#theme' => 'features_items',
|
||||
'#items' => $package_config[$type],
|
||||
'#value' => Html::escape($label),
|
||||
'#title' => Html::escape($type),
|
||||
),
|
||||
'class' => 'item',
|
||||
);
|
||||
$rows[] = $row;
|
||||
}
|
||||
}
|
||||
$element['table'] = array(
|
||||
'#type' => 'table',
|
||||
'#rows' => $rows,
|
||||
);
|
||||
|
||||
$details = array();
|
||||
$details['description'] = array(
|
||||
'#markup' => Xss::filterAdmin($package->getDescription()),
|
||||
);
|
||||
$details['table'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => array('#markup' => $this->t('Included configuration')),
|
||||
'#description' => array('data' => $element['table']),
|
||||
);
|
||||
$element['details'] = array(
|
||||
'class' => array('description', 'expand'),
|
||||
'data' => $details,
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pseudo-package to display unpackaged configuration.
|
||||
*
|
||||
* @param array $packages
|
||||
* An array of package names.
|
||||
* @param \Drupal\features\ConfigurationItem[] $config_collection
|
||||
* A collection of configuration.
|
||||
*/
|
||||
protected function addUnpackaged(array &$packages, array $config_collection) {
|
||||
$packages['unpackaged'] = new Package('unpackaged', [
|
||||
'name' => $this->t('Unpackaged'),
|
||||
'description' => $this->t('Configuration that has not been added to any package.'),
|
||||
'config' => [],
|
||||
'status' => FeaturesManagerInterface::STATUS_NO_EXPORT,
|
||||
'version' => '',
|
||||
]);
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
if (!$item->getPackage() && !$item->isExcluded() && !$item->isProviderExcluded()) {
|
||||
$packages['unpackaged']->appendConfig($item_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Denies access to the checkboxes for uninstalled or empty packages and the
|
||||
* "unpackaged" pseudo-package.
|
||||
*
|
||||
* @param array $form
|
||||
* The form build array to alter.
|
||||
*
|
||||
* @return array
|
||||
* The form build array.
|
||||
*/
|
||||
public static function preRenderRemoveInvalidCheckboxes(array $form) {
|
||||
/** @var \Drupal\features\Package $package */
|
||||
foreach ($form['#packages'] as $package) {
|
||||
// Remove checkboxes for packages that:
|
||||
// - have no configuration assigned and are not the profile, or
|
||||
// - are the "unpackaged" pseudo-package.
|
||||
if ((empty($package->getConfig()) && !($package->getMachineName() == $form['#profile_package'])) ||
|
||||
$package->getMachineName() == 'unpackaged') {
|
||||
$form['preview'][$package->getMachineName()]['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$current_bundle = $this->assigner->loadBundle();
|
||||
$this->assigner->assignConfigPackages();
|
||||
|
||||
$package_names = array_filter($form_state->getValue('preview'));
|
||||
|
||||
if (empty($package_names)) {
|
||||
drupal_set_message($this->t('Please select one or more packages to export.'), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
$method_id = NULL;
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
$op = $form_state->getValue('op');
|
||||
if (!empty($trigger) && empty($op)) {
|
||||
$method_id = $trigger['#name'];
|
||||
}
|
||||
|
||||
if (!empty($method_id)) {
|
||||
$this->generator->generatePackages($method_id, $current_bundle, $package_names);
|
||||
$this->generator->applyExportFormSubmit($method_id, $form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Tests;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Tests configuring bundles.
|
||||
*
|
||||
* @group features_ui
|
||||
*/
|
||||
class FeaturesBundleUITest extends WebTestBase {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* @todo Remove the disabled strict config schema checking.
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'features', 'features_ui'];
|
||||
|
||||
/**
|
||||
* The features bundle storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface $bundleStorage
|
||||
*/
|
||||
protected $bundleStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->bundleStorage = \Drupal::entityTypeManager()->getStorage('features_bundle');
|
||||
|
||||
$admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default features bundle.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* The features bundle.
|
||||
*/
|
||||
protected function defaultBundle() {
|
||||
return $this->bundleStorage->load('default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely remove a features assignment method from the bundle.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The assignment method ID.
|
||||
*/
|
||||
protected function removeAssignment($method_id) {
|
||||
$bundle = $this->defaultBundle();
|
||||
$assignments = $bundle->get('assignments');
|
||||
unset($assignments[$method_id]);
|
||||
$bundle->set('assignments', $assignments);
|
||||
$bundle->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring an assignment.
|
||||
*/
|
||||
public function testAssignmentConfigure() {
|
||||
// Check initial values.
|
||||
$settings = $this->defaultBundle()->getAssignmentSettings('exclude');
|
||||
$this->assertTrue(isset($settings['types']['config']['features_bundle']), 'Excluding features_bundle');
|
||||
$this->assertFalse(isset($settings['types']['config']['system_simple']), 'Not excluding system_simple');
|
||||
$this->assertFalse(isset($settings['types']['config']['user_role']), 'Not excluding user_role');
|
||||
$this->assertTrue($settings['curated'], 'Excluding curated items');
|
||||
$this->assertTrue($settings['module']['namespace'], 'Excluding by namespace');
|
||||
|
||||
// Check initial form.
|
||||
$this->drupalGet('admin/config/development/features/bundle/_exclude/default');
|
||||
$this->assertFieldChecked('edit-types-config-features-bundle', 'features_bundle is checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-system-simple', 'system_simple is not checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-user-role', 'user_role is not checked');
|
||||
$this->assertFieldChecked('edit-curated', 'curated is checked');
|
||||
$this->assertFieldChecked('edit-module-namespace', 'namespace is checked');
|
||||
|
||||
// Configure the form.
|
||||
$this->drupalPostForm(NULL, [
|
||||
'types[config][system_simple]' => TRUE,
|
||||
'types[config][user_role]' => FALSE,
|
||||
'curated' => TRUE,
|
||||
'module[namespace]' => FALSE,
|
||||
], $this->t('Save settings'));
|
||||
|
||||
// Check form results.
|
||||
$this->drupalGet('admin/config/development/features/bundle/_exclude/default');
|
||||
$this->assertFieldChecked('edit-types-config-features-bundle', 'Saved, features_bundle is checked');
|
||||
$this->assertFieldChecked('edit-types-config-system-simple', 'Saved, system_simple is checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-user-role', 'Saved, user_role is not checked');
|
||||
$this->assertFieldChecked('edit-curated', 'Saved, curated is checked');
|
||||
$this->assertNoFieldChecked('edit-module-namespace', 'Saved, namespace is not checked');
|
||||
|
||||
// Check final values.
|
||||
$settings = $this->defaultBundle()->getAssignmentSettings('exclude');
|
||||
$this->assertTrue(isset($settings['types']['config']['features_bundle']), 'Saved, excluding features_bundle');
|
||||
$this->assertTrue(isset($settings['types']['config']['system_simple']), 'Saved, excluding system_simple');
|
||||
$this->assertFalse(isset($settings['types']['config']['user_role']), 'Saved, not excluding user_role');
|
||||
$this->assertTrue($settings['curated'], 'Saved, excluding curated items');
|
||||
$this->assertFalse($settings['module']['namespace'], 'Saved, not excluding by namespace');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring an assignment that didn't exist before.
|
||||
*/
|
||||
public function testNewAssignmentConfigure() {
|
||||
$this->removeAssignment('exclude');
|
||||
|
||||
// Is it really removed?
|
||||
$all_settings = $this->defaultBundle()->getAssignmentSettings();
|
||||
$this->assertFalse(isset($all_settings['exclude']), 'Exclude plugin is unknown');
|
||||
|
||||
// Can still get settings.
|
||||
$settings = $this->defaultBundle()->getAssignmentSettings('exclude');
|
||||
$this->assertFalse($settings['enabled'], 'Disabled exclude plugin');
|
||||
$this->assertFalse(isset($settings['types']['config']['features_bundle']), 'Not excluding features_bundle');
|
||||
$this->assertFalse(isset($settings['types']['config']['system_simple']), 'Not excluding system_simple');
|
||||
$this->assertFalse(isset($settings['types']['config']['user_role']), 'Not excluding user_role');
|
||||
$this->assertFalse($settings['curated'], 'Not excluding curated items');
|
||||
$this->assertFalse($settings['module']['namespace'], 'Not excluding by namespace');
|
||||
|
||||
// Can we visit the config page with no settings?
|
||||
$this->drupalGet('admin/config/development/features/bundle/_exclude/default');
|
||||
$this->assertNoFieldChecked('edit-types-config-features-bundle', 'features_bundle is not checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-system-simple', 'system_simple is not checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-user-role', 'user_role is not checked');
|
||||
$this->assertNoFieldChecked('edit-curated', 'curated is not checked');
|
||||
$this->assertNoFieldChecked('edit-module-namespace', 'namespace is not checked');
|
||||
|
||||
// Can we enable the method?
|
||||
$this->drupalGet('admin/config/development/features/bundle');
|
||||
$this->assertNoFieldChecked('edit-enabled-exclude', 'Exclude disabled');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'enabled[exclude]' => TRUE,
|
||||
], $this->t('Save settings'));
|
||||
$this->assertFieldChecked('edit-enabled-exclude', 'Exclude enabled');
|
||||
|
||||
// Check new settings.
|
||||
$settings = $this->defaultBundle()->getAssignmentSettings('exclude');
|
||||
$this->assertTrue($settings['enabled'], 'Enabled exclude plugin');
|
||||
$this->assertFalse(isset($settings['types']['config']['features_bundle']), 'Not excluding features_bundle');
|
||||
$this->assertFalse(isset($settings['types']['config']['system_simple']), 'Not excluding system_simple');
|
||||
$this->assertFalse(isset($settings['types']['config']['user_role']), 'Not excluding user_role');
|
||||
$this->assertFalse($settings['curated'], 'Not excluding curated items');
|
||||
$this->assertFalse($settings['module']['namespace'], 'Not excluding by namespace');
|
||||
|
||||
// Can we run assignment with no settings?
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
|
||||
// Can we configure the method?
|
||||
$this->drupalPostForm('admin/config/development/features/bundle/_exclude/default', [
|
||||
'types[config][system_simple]' => TRUE,
|
||||
'types[config][user_role]' => FALSE,
|
||||
'curated' => TRUE,
|
||||
'module[namespace]' => FALSE,
|
||||
], $this->t('Save settings'));
|
||||
|
||||
// Check form results.
|
||||
$this->drupalGet('admin/config/development/features/bundle/_exclude/default');
|
||||
$this->assertNoFieldChecked('edit-types-config-features-bundle', 'Saved, features_bundle is not checked');
|
||||
$this->assertFieldChecked('edit-types-config-system-simple', 'Saved, system_simple is checked');
|
||||
$this->assertNoFieldChecked('edit-types-config-user-role', 'Saved, user_role is not checked');
|
||||
$this->assertFieldChecked('edit-curated', 'Saved, curated is checked');
|
||||
$this->assertNoFieldChecked('edit-module-namespace', 'Saved, namespace is not checked');
|
||||
|
||||
// Check final values.
|
||||
$settings = $this->defaultBundle()->getAssignmentSettings('exclude');
|
||||
$this->assertFalse(isset($settings['types']['config']['features_bundle']), 'Saved, not excluding features_bundle');
|
||||
$this->assertTrue(isset($settings['types']['config']['system_simple']), 'Saved, excluding system_simple');
|
||||
$this->assertFalse(isset($settings['types']['config']['user_role']), 'Saved, not excluding user_role');
|
||||
$this->assertTrue($settings['curated'], 'Saved, excluding curated items');
|
||||
$this->assertFalse($settings['module']['namespace'], 'Saved, not excluding by namespace');
|
||||
}
|
||||
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Core\Archiver\ArchiveTar;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Tests the creation of a feature.
|
||||
*
|
||||
* @group features_ui
|
||||
*/
|
||||
class FeaturesCreateUITest extends WebTestBase {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* @todo Remove the disabled strict config schema checking.
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'features', 'features_ui'];
|
||||
|
||||
/**
|
||||
* Tests creating a feature via UI and download it.
|
||||
*/
|
||||
public function testCreateFeaturesUI() {
|
||||
$feature_name = 'test_feature2';
|
||||
$admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
$this->clickLink('Create new feature');
|
||||
$this->assertResponse(200);
|
||||
|
||||
$edit = [
|
||||
'name' => 'Test feature',
|
||||
'machine_name' => $feature_name,
|
||||
'description' => 'Test description: <strong>giraffe</strong>',
|
||||
'version' => '8.x-1.0',
|
||||
'system_simple[sources][selected][system.theme]' => TRUE,
|
||||
'system_simple[sources][selected][user.settings]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, $this->t('Download Archive'));
|
||||
|
||||
$this->assertResponse(200);
|
||||
$archive = $this->getRawContent();
|
||||
$filename = tempnam($this->tempFilesDirectory, 'feature');
|
||||
file_put_contents($filename, $archive);
|
||||
|
||||
$archive = new ArchiveTar($filename);
|
||||
$files = $archive->listContent();
|
||||
|
||||
$this->assertEqual(4, count($files));
|
||||
$this->assertEqual($feature_name . '/' . $feature_name . '.info.yml', $files[0]['filename']);
|
||||
$this->assertEqual($feature_name . '/' . $feature_name . '.features.yml', $files[1]['filename']);
|
||||
$this->assertEqual($feature_name . '/config/install/system.theme.yml', $files[2]['filename']);
|
||||
$this->assertEqual($feature_name . '/config/install/user.settings.yml', $files[3]['filename']);
|
||||
|
||||
// Ensure that the archive contains the expected values.
|
||||
$info_filename = tempnam($this->tempFilesDirectory, 'feature');
|
||||
file_put_contents($info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.info.yml'));
|
||||
$features_info_filename = tempnam($this->tempFilesDirectory, 'feature');
|
||||
file_put_contents($features_info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.features.yml'));
|
||||
/** @var \Drupal\Core\Extension\InfoParser $info_parser */
|
||||
$info_parser = \Drupal::service('info_parser');
|
||||
$parsed_info = $info_parser->parse($info_filename);
|
||||
$this->assertEqual('Test feature', $parsed_info['name']);
|
||||
$parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
|
||||
$this->assertEqual([
|
||||
'required' => ['system.theme', 'user.settings'],
|
||||
], $parsed_features_info);
|
||||
|
||||
$archive->extract(\Drupal::service('kernel')->getSitePath() . '/modules');
|
||||
$module_path = \Drupal::service('kernel')->getSitePath() . '/modules/' . $feature_name;
|
||||
|
||||
// Ensure that the features listing renders the right content.
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertLink('Test feature');
|
||||
$this->assertEqual($feature_name, (string) $tr->children()[2]);
|
||||
$description_column = (string) $tr->children()[3]->asXml();
|
||||
$this->assertTrue(strpos($description_column, 'system.theme') !== FALSE);
|
||||
$this->assertTrue(strpos($description_column, 'user.settings') !== FALSE);
|
||||
$this->assertRaw('Test description: <strong>giraffe</strong>');
|
||||
$this->assertEqual('Uninstalled', (string) $tr->children()[5]);
|
||||
$this->assertEqual('', (string) $tr->children()[6]);
|
||||
|
||||
// Remove one and add new configuration.
|
||||
$this->clickLink('Test feature');
|
||||
$edit = [
|
||||
'system_simple[included][system.theme]' => FALSE,
|
||||
'user_role[sources][selected][authenticated]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, $this->t('Write'));
|
||||
$info_filename = $module_path . '/' . $feature_name . '.info.yml';
|
||||
|
||||
$parsed_info = $info_parser->parse($info_filename);
|
||||
$this->assertEqual('Test feature', $parsed_info['name']);
|
||||
|
||||
$features_info_filename = $module_path . '/' . $feature_name . '.features.yml';
|
||||
$parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
|
||||
$this->assertEqual([
|
||||
'excluded' => ['system.theme'],
|
||||
'required' => true,
|
||||
], $parsed_features_info);
|
||||
|
||||
$this->drupalGet('admin/modules');
|
||||
$edit = [
|
||||
'modules[Other][' . $feature_name . '][enable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, $this->t('Install'));
|
||||
|
||||
// Check that the feature is listed as installed.
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertEqual('Installed', (string) $tr->children()[5]);
|
||||
|
||||
// Check that a config change results in a feature marked as changed.
|
||||
\Drupal::configFactory()->getEditable('user.settings')
|
||||
->set('anonymous', 'Anonymous giraffe')
|
||||
->save();
|
||||
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
|
||||
|
||||
// Uninstall module.
|
||||
$this->drupalPostForm('admin/modules/uninstall', [
|
||||
'uninstall[' . $feature_name . ']' => TRUE,
|
||||
], $this->t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, [], $this->t('Uninstall'));
|
||||
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
|
||||
|
||||
$this->clickLink($this->t('Changed'));
|
||||
$this->assertRaw('<td class="diff-context diff-deletedline">anonymous : Anonymous <span class="diffchange">giraffe</span></td>');
|
||||
$this->assertRaw('<td class="diff-context diff-addedline">anonymous : Anonymous</td>');
|
||||
|
||||
$this->drupalGet('admin/modules');
|
||||
$edit = [
|
||||
'modules[Other][' . $feature_name . '][enable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, $this->t('Install'));
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertEqual('Installed', (string) $tr->children()[5]);
|
||||
|
||||
// Ensure that the changed config got overridden.
|
||||
$this->assertEqual('Anonymous', \Drupal::config('user.settings')->get('anonymous'));
|
||||
|
||||
// Change the value, export and ensure that its not shown as changed.
|
||||
\Drupal::configFactory()->getEditable('user.settings')
|
||||
->set('anonymous', 'Anonymous giraffe')
|
||||
->save();
|
||||
|
||||
// Ensure that exporting this change will result in an unchanged feature.
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
|
||||
|
||||
$this->clickLink('Test feature');
|
||||
$this->drupalPostForm(NULL, [], $this->t('Write'));
|
||||
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
$tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
|
||||
$this->assertEqual('Installed', (string) $tr->children()[5]);
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features_ui\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Tests the creation of a feature.
|
||||
*
|
||||
* @group features_ui
|
||||
*/
|
||||
class FeaturesUITest extends WebTestBase {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* @todo Remove the disabled strict config schema checking.
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['features', 'features_ui'];
|
||||
|
||||
/**
|
||||
* Tests creating a feature via UI and download it.
|
||||
*/
|
||||
public function testFeaturesUI() {
|
||||
$admin_user = $this->drupalCreateUser(['administer site configuration', 'export configuration', 'administer modules']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
// Check the message is displaying if there are no custom bundles.
|
||||
$this->assertText($this->t('You have not yet created any bundles. Before generating features, you may wish to create a bundle to group your features within.'));
|
||||
// Creating custom bundle.
|
||||
$this->drupalGet('admin/config/development/features/bundle');
|
||||
$this->drupalPostAjaxForm(NULL, array('bundle[bundle_select]' => 'new'), 'bundle[bundle_select]');
|
||||
$edit = [
|
||||
'bundle[name]' => 'foo',
|
||||
'bundle[machine_name]' => 'foo',
|
||||
'bundle[description]' => $this->randomString(),
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, $this->t('Save settings'));
|
||||
$this->drupalGet('admin/config/development/features');
|
||||
// Check the message is not displaying if there are custom bundles.
|
||||
$this->assertNoText($this->t('You have not yet created any bundles. Before generating features, you may wish to create a bundle to group your features within.'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Contains some configuration together with metadata like the name + package.
|
||||
*
|
||||
* @todo Should the object be immutable?
|
||||
* @todo Should this object have an interface?
|
||||
*/
|
||||
class ConfigurationItem {
|
||||
|
||||
/**
|
||||
* Prefixed configuration item name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Configuration item name without prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $shortName;
|
||||
|
||||
/**
|
||||
* Human readable name of configuration item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Type of configuration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The contents of the configuration item in exported format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Array of names of dependent configuration items.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dependents = [];
|
||||
|
||||
/**
|
||||
* Feature subdirectory to export item to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $subdirectory;
|
||||
|
||||
/**
|
||||
* Machine name of a package the configuration is assigned to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $package;
|
||||
|
||||
/**
|
||||
* Whether the configuration is marked as excluded.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $excluded = FALSE;
|
||||
|
||||
/**
|
||||
* Whether the configuration provider is excluded.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $providerExcluded = FALSE;
|
||||
|
||||
/**
|
||||
* The provider of the config item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $provider;
|
||||
|
||||
/**
|
||||
* Array of package names that this item should be excluded from.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $packageExcluded = [];
|
||||
|
||||
/**
|
||||
* Creates a new ConfigurationItem instance.
|
||||
*
|
||||
* @param string $name
|
||||
* The config name.
|
||||
* @param array $data
|
||||
* The config data.
|
||||
* @param array $additional_properties
|
||||
* (optional) Additional properties set on the object.
|
||||
*/
|
||||
public function __construct($name, array $data, array $additional_properties = []) {
|
||||
$this->name = $name;
|
||||
$this->data = $data;
|
||||
|
||||
$properties = get_object_vars($this);
|
||||
foreach ($additional_properties as $property => $value) {
|
||||
if (!array_key_exists($property, $properties)) {
|
||||
throw new \InvalidArgumentException('Invalid property: ' . $property);
|
||||
}
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config type usable in configuration.
|
||||
*
|
||||
* By default Drupal uses system.simple as config type, which cannot be used
|
||||
* inside configuration itself. Therefore convert it to system_simple.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type provided by core.
|
||||
*
|
||||
* @return string
|
||||
* The config type as string without dots.
|
||||
*/
|
||||
public static function fromConfigTypeToConfigString($type) {
|
||||
return $type == 'system.simple' ? FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a config type string in configuration back to the config type.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type as string without dots.
|
||||
*
|
||||
* @return string
|
||||
* The config type provided by core.
|
||||
*/
|
||||
public static function fromConfigStringToConfigType($type) {
|
||||
return $type == FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG ? 'system.simple' : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $name
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getShortName() {
|
||||
return $this->shortName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $shortName
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setShortName($shortName) {
|
||||
$this->shortName = $shortName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $label
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setLabel($label) {
|
||||
$this->label = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $type
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed array
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getDependents() {
|
||||
return $this->dependents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $dependents
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setDependents($dependents) {
|
||||
$this->dependents = $dependents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSubdirectory() {
|
||||
return $this->subdirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $subdirectory
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setSubdirectory($subdirectory) {
|
||||
$this->subdirectory = $subdirectory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPackage() {
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $package
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setPackage($package) {
|
||||
$this->package = $package;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isExcluded() {
|
||||
return $this->excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $excluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setExcluded($excluded) {
|
||||
$this->excluded = $excluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isProviderExcluded() {
|
||||
return $this->providerExcluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $providerExcluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setProviderExcluded($providerExcluded) {
|
||||
$this->providerExcluded = $providerExcluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getProvider() {
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $provider
|
||||
*/
|
||||
public function setProvider($provider) {
|
||||
$this->provider = $provider;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPackageExcluded() {
|
||||
return $this->packageExcluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $packageExcluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setPackageExcluded($packageExcluded) {
|
||||
$this->packageExcluded = $packageExcluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Controller;
|
||||
|
||||
use Drupal\Core\Access\CsrfTokenGenerator;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\system\FileDownloadController;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* Returns responses for config module routes.
|
||||
*/
|
||||
class FeaturesController implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The file download controller.
|
||||
*
|
||||
* @var \Drupal\system\FileDownloadController
|
||||
*/
|
||||
protected $fileDownloadController;
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
new FileDownloadController(),
|
||||
$container->get('csrf_token')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a FeaturesController object.
|
||||
*
|
||||
* @param \Drupal\system\FileDownloadController $file_download_controller
|
||||
* The file download controller.
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(FileDownloadController $file_download_controller, CsrfTokenGenerator $csrf_token) {
|
||||
$this->fileDownloadController = $file_download_controller;
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a tarball of the site configuration.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI to download.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
* The downloaded file.
|
||||
*/
|
||||
public function downloadExport($uri, Request $request) {
|
||||
if ($uri) {
|
||||
// @todo Simplify once https://www.drupal.org/node/2630920 is solved.
|
||||
if (!$this->csrfToken->validate($request->query->get('token'), $uri)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$request = new Request(array('file' => $uri));
|
||||
return $this->fileDownloadController->download($request, 'temporary');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\features\FeaturesAssignmentMethodInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
|
||||
/**
|
||||
* Defines a features bundle.
|
||||
* @todo Better description
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "features_bundle",
|
||||
* label = @Translation("Features bundle"),
|
||||
* handlers = {
|
||||
* },
|
||||
* admin_permission = "administer site configuration",
|
||||
* config_prefix = "bundle",
|
||||
* entity_keys = {
|
||||
* "id" = "machine_name",
|
||||
* "label" = "name"
|
||||
* },
|
||||
* links = {
|
||||
* },
|
||||
* config_export = {
|
||||
* "name",
|
||||
* "machine_name",
|
||||
* "description",
|
||||
* "assignments",
|
||||
* "profile_name",
|
||||
* "is_profile",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesBundle extends ConfigEntityBase implements FeaturesBundleInterface {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $machine_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $assignments = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $profile_name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_profile;
|
||||
|
||||
public function id() {
|
||||
// @todo Convert it to $this->id in the long run.
|
||||
return $this->getMachineName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDefault() {
|
||||
return $this->machine_name == static::DEFAULT_BUNDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMachineName() {
|
||||
return $this->machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMachineName($machine_name) {
|
||||
$this->machine_name = $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFullName($short_name) {
|
||||
if ($this->isDefault() || $this->inBundle($short_name)) {
|
||||
return $short_name;
|
||||
}
|
||||
else {
|
||||
return $this->machine_name . '_' . $short_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getShortName($machine_name) {
|
||||
if (!$this->isProfilePackage($machine_name) && $this->inBundle($machine_name)) {
|
||||
return substr($machine_name, strlen($this->getMachineName()) + 1, strlen($machine_name) - strlen($this->getMachineName()) - 1);
|
||||
}
|
||||
return $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function inBundle($machine_name) {
|
||||
return ($this->isProfilePackage($machine_name) || strpos($machine_name, $this->machine_name . '_') === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProfilePackage($machine_name) {
|
||||
return ($this->isProfile() && $machine_name == $this->getProfileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProfile() {
|
||||
return $this->is_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setIsProfile($value) {
|
||||
$this->is_profile = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProfileName() {
|
||||
$name = $this->isProfile() ? $this->profile_name : '';
|
||||
return !empty($name) ? $name : drupal_get_profile();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProfileName($machine_name) {
|
||||
$this->profile_name = $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEnabledAssignments() {
|
||||
$list = array();
|
||||
foreach ($this->assignments as $method_id => $method) {
|
||||
if ($method['enabled']) {
|
||||
$list[$method_id] = $method_id;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEnabledAssignments(array $assignments) {
|
||||
// Add any new assignments that we don't yet know about.
|
||||
$new_assignments = array_diff($assignments, array_keys($this->assignments));
|
||||
foreach ($new_assignments as $method_id) {
|
||||
$this->assignments[$method_id] = $this->getAssignmentSettings($method_id);
|
||||
}
|
||||
|
||||
foreach ($this->assignments as $method_id => &$method) {
|
||||
$method['enabled'] = in_array($method_id, $assignments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAssignmentWeights() {
|
||||
$list = array();
|
||||
foreach ($this->assignments as $method_id => $method) {
|
||||
$list[$method_id] = $method['weight'];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssignmentWeights(array $assignments) {
|
||||
foreach ($this->assignments as $method_id => &$method) {
|
||||
if (isset($assignments[$method_id])) {
|
||||
$method['weight'] = $assignments[$method_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of default settings for the given plugin method
|
||||
*
|
||||
* @param $method_id
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultSettings($method_id) {
|
||||
$settings = ['enabled' => FALSE, 'weight' => 0];
|
||||
|
||||
$manager = \Drupal::service('plugin.manager.features_assignment_method');
|
||||
$definition = $manager->getDefinition($method_id);
|
||||
|
||||
if (isset($definition['weight'])) {
|
||||
$settings['weight'] = $definition['weight'];
|
||||
}
|
||||
if (isset($definition['default_settings'])) {
|
||||
$settings += $definition['default_settings'];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAssignmentSettings($method_id = NULL) {
|
||||
if (isset($method_id)) {
|
||||
if (isset($this->assignments[$method_id])) {
|
||||
return $this->assignments[$method_id];
|
||||
}
|
||||
else {
|
||||
// Use defaults.
|
||||
return $this->getDefaultSettings($method_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$list = array();
|
||||
foreach (array_keys($this->assignments) as $method_id) {
|
||||
$list[$method_id] = $this->getAssignmentSettings($method_id);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssignmentSettings($method_id, array $settings) {
|
||||
if (isset($method_id)) {
|
||||
$this->assignments[$method_id] = $settings;
|
||||
}
|
||||
else {
|
||||
foreach ($settings as $method_id => $method_settings) {
|
||||
if (!empty($method_settings)) {
|
||||
$this->setAssignmentSettings($method_id, $method_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove() {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ExtensionInstallStorage;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
|
||||
/**
|
||||
* Class responsible for performing package assignment.
|
||||
*/
|
||||
class FeaturesAssigner implements FeaturesAssignerInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The package assignment method plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $assignerManager;
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The configuration storage.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Local cache for package assignment method instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $methods;
|
||||
|
||||
/**
|
||||
* Bundles.
|
||||
*
|
||||
* @var array of \Drupal\features\FeaturesBundleInterface
|
||||
*/
|
||||
protected $bundles;
|
||||
|
||||
/**
|
||||
* Currently active bundle.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesBundleInterface
|
||||
*/
|
||||
protected $currentBundle;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesAssigner object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $assigner_manager
|
||||
* The package assignment methods plugin manager.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assignerManager = $assigner_manager;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->configStorage = $config_storage;
|
||||
$this->bundles = $this->getBundleList();
|
||||
$this->currentBundle = $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
// Ensure bundle information is fresh.
|
||||
$this->createBundlesFromPackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the injected features manager with the assigner.
|
||||
*
|
||||
* This should be called right after instantiating the assigner to make it
|
||||
* available to the features manager without introducing a circular
|
||||
* dependency.
|
||||
*/
|
||||
public function initFeaturesManager() {
|
||||
$this->featuresManager->setAssigner($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->methods = array();
|
||||
$this->featuresManager->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets enabled assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of enabled assignment methods, sorted by weight.
|
||||
*/
|
||||
public function getEnabledAssigners() {
|
||||
$enabled = $this->currentBundle->getEnabledAssignments();
|
||||
$weights = $this->currentBundle->getAssignmentWeights();
|
||||
foreach ($enabled as $key => $value) {
|
||||
$enabled[$key] = $weights[$key];
|
||||
}
|
||||
asort($enabled);
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the package list after all config has been assigned
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
foreach ($packages as $index => $package) {
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT && empty($package->getConfig()) && empty($package->getConfigOrig())) {
|
||||
unset($packages[$index]);
|
||||
}
|
||||
}
|
||||
$this->featuresManager->setPackages($packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignConfigPackages($force = FALSE) {
|
||||
foreach ($this->getEnabledAssigners() as $method_id => $info) {
|
||||
$this->applyAssignmentMethod($method_id, $force);
|
||||
}
|
||||
$this->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyAssignmentMethod($method_id, $force = FALSE) {
|
||||
$this->getAssignmentMethodInstance($method_id)->assignPackages($force);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAssignmentMethods() {
|
||||
return $this->assignerManager->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the specified package assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package assignment method to use to package
|
||||
* configuration.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesAssignmentMethodInterface
|
||||
*/
|
||||
protected function getAssignmentMethodInstance($method_id) {
|
||||
if (!isset($this->methods[$method_id])) {
|
||||
$instance = $this->assignerManager->createInstance($method_id, array());
|
||||
$instance->setFeaturesManager($this->featuresManager);
|
||||
$instance->setAssigner($this);
|
||||
$instance->setEntityManager($this->entityManager);
|
||||
$instance->setConfigFactory($this->configFactory);
|
||||
$this->methods[$method_id] = $instance;
|
||||
}
|
||||
return $this->methods[$method_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function purgeConfiguration() {
|
||||
// Ensure that we are getting the defined package assignment information.
|
||||
// An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
|
||||
// \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
|
||||
// cached information.
|
||||
$this->assignerManager->clearCachedDefinitions();
|
||||
$this->featuresManager->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundle($name = NULL) {
|
||||
if (empty($name)) {
|
||||
return $this->currentBundle;
|
||||
}
|
||||
elseif (isset($this->bundles[$name])) {
|
||||
return $this->bundles[$name];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setBundle(FeaturesBundleInterface $bundle, $current = TRUE) {
|
||||
$this->bundles[$bundle->getMachineName()] = $bundle;
|
||||
if (isset($this->currentBundle) && ($current || ($bundle->getMachineName() == $this->currentBundle->getMachineName()))) {
|
||||
$this->currentBundle = $bundle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findBundle(array $info, $features_info = NULL) {
|
||||
$bundle = NULL;
|
||||
if (!empty($features_info['bundle'])) {
|
||||
$bundle = $this->getBundle($features_info['bundle']);
|
||||
}
|
||||
elseif (!empty($info['package'])) {
|
||||
$bundle = $this->findBundleByName($info['package']);
|
||||
}
|
||||
if (!isset($bundle)) {
|
||||
// Return the default bundle.
|
||||
return $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
}
|
||||
return $bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setCurrent(FeaturesBundleInterface $bundle) {
|
||||
$this->currentBundle = $bundle;
|
||||
$session = \Drupal::request()->getSession();
|
||||
if (isset($session)) {
|
||||
$session->set('features_current_bundle', $bundle->getMachineName());
|
||||
}
|
||||
return $bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleList() {
|
||||
if (empty($this->bundles)) {
|
||||
$this->bundles = array();
|
||||
foreach ($this->entityManager->getStorage('features_bundle')->loadMultiple() as $machine_name => $bundle) {
|
||||
$this->bundles[$machine_name] = $bundle;
|
||||
}
|
||||
}
|
||||
return $this->bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findBundleByName($name, $create = FALSE) {
|
||||
$bundles = $this->getBundleList();
|
||||
foreach ($bundles as $machine_name => $bundle) {
|
||||
if ($name == $bundle->getName()) {
|
||||
return $bundle;
|
||||
}
|
||||
}
|
||||
$machine_name = strtolower(str_replace(array(' ', '-'), '_', $name));
|
||||
if (isset($bundles[$machine_name])) {
|
||||
return $bundles[$machine_name];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createBundleFromDefault($machine_name, $name = NULL, $description = NULL, $is_profile = FALSE, $profile_name = NULL) {
|
||||
// Duplicate the default bundle to get its default configuration.
|
||||
$default = $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
if (!$default) {
|
||||
// If we don't have the default installed, generate it from the install
|
||||
// config file.
|
||||
$ext_storage = new ExtensionInstallStorage($this->configStorage);
|
||||
$record = $ext_storage->read('features.bundle.default');
|
||||
$bundle_storage = $this->entityManager->getStorage('features_bundle');
|
||||
$default = $bundle_storage->createFromStorageRecord($record);
|
||||
}
|
||||
|
||||
/** @var \Drupal\features\Entity\FeaturesBundle $bundle */
|
||||
$bundle = $default->createDuplicate();
|
||||
|
||||
$bundle->setMachineName($machine_name);
|
||||
$bundle->setName($name);
|
||||
if (isset($description)) {
|
||||
$bundle->setDescription($description);
|
||||
}
|
||||
else {
|
||||
$bundle->setDescription(t('Auto-generated bundle from package @name', array('@name' => $name)));
|
||||
}
|
||||
$bundle->setIsProfile($is_profile);
|
||||
if (isset($profile_name)) {
|
||||
$bundle->setProfileName($profile_name);
|
||||
}
|
||||
$bundle->save();
|
||||
$this->setBundle($bundle);
|
||||
|
||||
return $bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createBundlesFromPackages() {
|
||||
$existing_bundles = $this->getBundleList();
|
||||
$new_bundles = [];
|
||||
// Only parse from installed features.
|
||||
$modules = $this->featuresManager->getFeaturesModules(NULL, TRUE);
|
||||
|
||||
foreach ($modules as $module) {
|
||||
$info = $this->featuresManager->getExtensionInfo($module);
|
||||
// @todo This entire function could be simplified a lot using packages.
|
||||
$features_info = $this->featuresManager->getFeaturesInfo($module);
|
||||
// Create a new bundle if:
|
||||
// - the feature specifies a bundle and
|
||||
// - that bundle doesn't yet exist locally.
|
||||
// Allow profiles to override previous values.
|
||||
if (!empty($features_info['bundle']) &&
|
||||
!isset($existing_bundles[$features_info['bundle']]) &&
|
||||
(!in_array($features_info['bundle'], $new_bundles) || $info['type'] == 'profile')) {
|
||||
if ($info['type'] == 'profile') {
|
||||
$new_bundle = [
|
||||
'name' => $info['name'],
|
||||
'description' => $info['description'],
|
||||
'is_profile' => TRUE,
|
||||
'profile_name' => $module->getName(),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$new_bundle = [
|
||||
'name' => isset($info['package']) ? $info['package'] : ucwords(str_replace('_', ' ', $features_info['bundle'])),
|
||||
'description' => NULL,
|
||||
'is_profile' => FALSE,
|
||||
'profile_name' => NULL,
|
||||
];
|
||||
}
|
||||
$new_bundle['machine_name'] = $features_info['bundle'];
|
||||
$new_bundles[$new_bundle['machine_name']] = $new_bundle;
|
||||
}
|
||||
}
|
||||
foreach ($new_bundles as $new_bundle) {
|
||||
$new_bundle = $this->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
|
||||
drupal_set_message($this->t('Features bundle @name automatically created.', ['@name' => $new_bundle->getName()]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleOptions() {
|
||||
$list = $this->getBundleList();
|
||||
$result = array();
|
||||
foreach ($list as $machine_name => $bundle) {
|
||||
$result[$machine_name] = $bundle->getName();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyBundle($machine_name = NULL) {
|
||||
$this->reset();
|
||||
$bundle = $this->loadBundle($machine_name);
|
||||
if (isset($bundle)) {
|
||||
$this->assignConfigPackages();
|
||||
return $this->currentBundle;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renameBundle($old_machine, $new_machine) {
|
||||
$is_current = (isset($this->currentBundle) && ($old_machine == $this->currentBundle->getMachineName()));
|
||||
$bundle = $this->getBundle($old_machine);
|
||||
if ($bundle->getMachineName() != '') {
|
||||
// Remove old bundle from the list if it's not the Default bundle.
|
||||
unset($this->bundles[$old_machine]);
|
||||
}
|
||||
$bundle->setMachineName($new_machine);
|
||||
$this->setBundle($bundle);
|
||||
// Put the bundle into the list with the correct name.
|
||||
$this->bundles[$bundle->getMachineName()] = $bundle;
|
||||
if ($is_current) {
|
||||
$this->setCurrent($bundle);
|
||||
}
|
||||
return $bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadBundle($machine_name = NULL) {
|
||||
if (!isset($machine_name)) {
|
||||
$session = \Drupal::request()->getSession();
|
||||
if (isset($session)) {
|
||||
$machine_name = isset($session) ? $session->get('features_current_bundle', FeaturesBundleInterface::DEFAULT_BUNDLE) : FeaturesBundleInterface::DEFAULT_BUNDLE;
|
||||
}
|
||||
}
|
||||
$bundle = $this->getBundle($machine_name);
|
||||
if (!isset($bundle)) {
|
||||
// If bundle no longer exists then return default.
|
||||
$bundle = $this->bundles[FeaturesBundleInterface::DEFAULT_BUNDLE];
|
||||
}
|
||||
return $this->setCurrent($bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeBundle($machine_name) {
|
||||
$bundle = $this->getBundle($machine_name);
|
||||
if (isset($bundle) && !$bundle->isDefault()) {
|
||||
unset($this->bundles[$machine_name]);
|
||||
$bundle->remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Common interface for features assignment services.
|
||||
*
|
||||
* The feature API is based on two major concepts:
|
||||
* - Packages: modules into which configuration is packaged.
|
||||
* - Package assignment methods: responsible for `determining
|
||||
* which package to assign a given piece of configuration to.
|
||||
* Assignment methods are customizable.
|
||||
*
|
||||
* Features defines several package assignment methods, which are simple plugin
|
||||
* classes that implement a particular logic to assign pieces of configuration
|
||||
* to a given package (module).
|
||||
*
|
||||
* Modules can define additional package assignment methods by simply providing
|
||||
* the related plugins, and alter existing methods through
|
||||
* hook_features_assignment_method_info_alter(). Here is an example
|
||||
* snippet:
|
||||
* @code
|
||||
* function mymodule_features_assignment_method_info_alter(&$assignment_info) {
|
||||
* // Replace the original plugin with our own implementation.
|
||||
* $method_id = \Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentBaseType::METHOD_ID;
|
||||
* $assignment_info[$method_id]['class'] = 'Drupal\my_module\Plugin\FeaturesAssignment\MyFeaturesAssignmentBaseType';
|
||||
* }
|
||||
*
|
||||
* class MyFeaturesAssignmentBaseType extends FeaturesAssignmentBaseType {
|
||||
* public function assignPackages($force = FALSE) {
|
||||
* // Insert customization here.
|
||||
* }
|
||||
* }
|
||||
* ?>
|
||||
* @endcode
|
||||
*
|
||||
* For more information, see
|
||||
* @link http://drupal.org/node/2404473 Developing for Features 3.x @endlink
|
||||
*/
|
||||
interface FeaturesAssignerInterface {
|
||||
|
||||
/**
|
||||
* The package assignment method id for the package assigner itself.
|
||||
*/
|
||||
const METHOD_ID = 'assigner-default';
|
||||
|
||||
/**
|
||||
* Resets the assigned packages and the method instances.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Apply all enabled package assignment methods.
|
||||
*
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function assignConfigPackages($force = FALSE);
|
||||
|
||||
/**
|
||||
* Applies a given package assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package assignment method to use to package
|
||||
* configuration.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function applyAssignmentMethod($method_id, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the enabled package assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of package assignment method IDs.
|
||||
*/
|
||||
public function getAssignmentMethods();
|
||||
|
||||
/**
|
||||
* Resaves the configuration to purge missing assignment methods.
|
||||
*/
|
||||
public function purgeConfiguration();
|
||||
|
||||
/**
|
||||
* Returns a FeaturesBundle object.
|
||||
*
|
||||
* @param string $name
|
||||
* machine name of package set. If omitted, returns the current bundle.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function getBundle($name = NULL);
|
||||
|
||||
/**
|
||||
* Stores a features bundle.
|
||||
*
|
||||
* Added to list if machine_name is new.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle.
|
||||
* @param bool $current
|
||||
* Determine if the current bundle is set to $bundle.
|
||||
* If False, the current bundle is only updated if it already has the same
|
||||
* machine name as the $bundle.
|
||||
*/
|
||||
public function setBundle(FeaturesBundleInterface $bundle, $current = TRUE);
|
||||
|
||||
/**
|
||||
* Searches for a bundle that matches the $info.yml or $features.yml export.
|
||||
*
|
||||
* Creates a new bundle as needed.
|
||||
*
|
||||
* @param array $info
|
||||
* The bundle info.
|
||||
* @param mixed $features_info
|
||||
* The features info.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A bundle.
|
||||
*/
|
||||
public function findBundle(array $info, $features_info = NULL);
|
||||
|
||||
/**
|
||||
* Sets the currently active bundle.
|
||||
*
|
||||
* Updates value in current SESSION.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function setCurrent(FeaturesBundleInterface $bundle);
|
||||
|
||||
/**
|
||||
* Returns an array of all existing features bundles.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface[]
|
||||
* Keyed by machine_name with value of
|
||||
* \Drupal\features\FeaturesBundleInterface.
|
||||
*/
|
||||
public function getBundleList();
|
||||
|
||||
/**
|
||||
* Returns a named bundle.
|
||||
*
|
||||
* First searches by Human name, then by machine_name.
|
||||
*
|
||||
* @param string $name
|
||||
* The bundle name to search by.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function findBundleByName($name);
|
||||
|
||||
/**
|
||||
* Creates a new bundle by duplicating the default bundle and customizing.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name.
|
||||
* @param string $name
|
||||
* (optional) Human readable name of the bundle.
|
||||
* @param string $description
|
||||
* (optional) Description of the bundle.
|
||||
* @param bool $is_profile
|
||||
* (optional) TRUE if a profile is used with this bundle.
|
||||
* @param string $profile_name
|
||||
* (optional) The machine name of the profile.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function createBundleFromDefault($name, $machine_name = '', $description = '', $is_profile = FALSE, $profile_name = NULL);
|
||||
|
||||
/**
|
||||
* Creates bundles by parsing information from installed packages.
|
||||
*/
|
||||
public function createBundlesFromPackages();
|
||||
|
||||
/**
|
||||
* Returns an array of bundle names suitable for a select option list.
|
||||
*
|
||||
* @return array
|
||||
* An array of bundles, keyed by machine_name, with values being human
|
||||
* readable names.
|
||||
*/
|
||||
public function getBundleOptions();
|
||||
|
||||
/**
|
||||
* Makes the named bundle the current bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The name of a features bundle. If omitted, gets the last bundle from the
|
||||
* Session.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function applyBundle($machine_name = NULL);
|
||||
|
||||
/**
|
||||
* Renames a bundle.
|
||||
*
|
||||
* @param string $old_machine
|
||||
* The old machine name of a bundle.
|
||||
* @param string $new_machine
|
||||
* The new machine name of a bundle.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function renameBundle($old_machine, $new_machine);
|
||||
|
||||
/**
|
||||
* Loads a named bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* (optional) The name of a features bundle.
|
||||
* Defaults to NULL, gets the last bundle from the session.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function loadBundle($machine_name = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
|
||||
/**
|
||||
* Base class for package assignment methods.
|
||||
*/
|
||||
abstract class FeaturesAssignmentMethodBase extends PluginBase implements FeaturesAssignmentMethodInterface {
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setfeaturesManager(FeaturesManagerInterface $features_manager) {
|
||||
$this->featuresManager = $features_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner) {
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEntityManager(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfigFactory(ConfigFactoryInterface $config_factory) {
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns configuration of the types specified in a setting to a package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of the package.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
protected function assignPackageByConfigTypes($machine_name, $force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$types = $settings['types']['config'];
|
||||
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
// Don't assign configuration that's provided by an extension.
|
||||
if (in_array($item->getType(), $types) && !($item->isProviderExcluded())) {
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($machine_name, [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a given subdirectory to configuration of specified types.
|
||||
*
|
||||
* @param string $subdirectory
|
||||
* The subdirectory that designated configuration should be exported to.
|
||||
*/
|
||||
protected function assignSubdirectoryByConfigTypes($subdirectory) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$types = $settings['types']['config'];
|
||||
|
||||
if (!empty($types)) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as &$item) {
|
||||
if (in_array($item->getType(), $types)) {
|
||||
$item->setSubdirectory($subdirectory);
|
||||
}
|
||||
}
|
||||
// Clean up the $item pass by reference.
|
||||
unset($item);
|
||||
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Interface for package assignment classes.
|
||||
*/
|
||||
interface FeaturesAssignmentMethodInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Injects the features manager.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager to be used to retrieve the configuration list and
|
||||
* the already assigned packages.
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager);
|
||||
|
||||
/**
|
||||
* Injects the features assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The features assigner to be used to retrieve the bundle configuration.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Injects the entity manager.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager to be used to retrieve entity information.
|
||||
*/
|
||||
public function setEntityManager(EntityManagerInterface $entity_manager);
|
||||
|
||||
/**
|
||||
* Injects the configuration factory.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory to be used to retrieve configuration values.
|
||||
*/
|
||||
public function setConfigFactory(ConfigFactoryInterface $config_factory);
|
||||
|
||||
/**
|
||||
* Performs package assignment.
|
||||
*
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function assignPackages($force = FALSE);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages configuration packaging methods.
|
||||
*/
|
||||
class FeaturesAssignmentMethodManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesAssignmentMethodManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* An object that implements CacheBackendInterface.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* An object that implements ModuleHandlerInterface.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/FeaturesAssignment', $namespaces, $module_handler,
|
||||
'Drupal\features\FeaturesAssignmentMethodInterface');
|
||||
$this->alterInfo('features_assignment_info');
|
||||
$this->setCacheBackend($cache_backend, 'features_assignment_methods');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Provides an interface for the FeaturesBundle object.
|
||||
*/
|
||||
interface FeaturesBundleInterface {
|
||||
|
||||
const DEFAULT_BUNDLE = 'default';
|
||||
|
||||
/**
|
||||
* Determines whether the current bundle is the default one.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if this is the default bundle.
|
||||
*/
|
||||
public function isDefault();
|
||||
|
||||
/**
|
||||
* Returns the machine name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The machine name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setMachineName()
|
||||
*/
|
||||
public function getMachineName();
|
||||
|
||||
/**
|
||||
* Sets the machine name of a bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getMachineName()
|
||||
*/
|
||||
public function setMachineName($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the human readable name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The human readable name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setName()
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the human readable name of a bundle.
|
||||
*
|
||||
* @param string $name
|
||||
* The human readable name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getName()
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Returns a full machine name prefixed with the bundle name.
|
||||
*
|
||||
* @param string $short_name
|
||||
* The short machine_name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The full machine_name of a bundle.
|
||||
*/
|
||||
public function getFullName($short_name);
|
||||
|
||||
/**
|
||||
* Returns a short machine name not prefixed with the bundle name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The full machine_name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The short machine_name of a bundle.
|
||||
*/
|
||||
public function getShortName($machine_name);
|
||||
|
||||
/**
|
||||
* Determines if the $machine_name is prefixed by the bundle machine name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the machine name is prefixed by the bundle machine name.
|
||||
*/
|
||||
public function inBundle($machine_name);
|
||||
|
||||
/**
|
||||
* Determines if the package with $machine_name is the bundle profile.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package with $machine_name is the bundle profile.
|
||||
*/
|
||||
public function isProfilePackage($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the description of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The description of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setDescription()
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the description of a bundle.
|
||||
*
|
||||
* @param string $description
|
||||
* The description of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getDescription()
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Gets option for using a profile with this bundle.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a profile is used with this profile.
|
||||
*/
|
||||
public function isProfile();
|
||||
|
||||
/**
|
||||
* Sets option for using a profile with this bundle.
|
||||
*
|
||||
* @param bool $value
|
||||
* TRUE if a profile is used with this bundle.
|
||||
*/
|
||||
public function setIsProfile($value);
|
||||
|
||||
/**
|
||||
* Returns the machine name of the profile.
|
||||
*
|
||||
* If the bundle doesn't use a profile, return the current site profile.
|
||||
*
|
||||
* @return string
|
||||
* THe machie name of a profile.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setProfileName()
|
||||
*/
|
||||
public function getProfileName();
|
||||
|
||||
/**
|
||||
* Sets the name of the profile associated with this bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a profile.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getProfileName()
|
||||
*/
|
||||
public function setProfileName($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the list of enabled assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of method IDs keyed by assignment method IDs.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setEnabledAssignments()
|
||||
*/
|
||||
public function getEnabledAssignments();
|
||||
|
||||
/**
|
||||
* Sets the list of enabled assignment methods.
|
||||
*
|
||||
* @param array $assignments
|
||||
* An array of values keyed by assignment method IDs. Non-empty value is
|
||||
* enabled.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getEnabledAssignments()
|
||||
*/
|
||||
public function setEnabledAssignments(array $assignments);
|
||||
|
||||
/**
|
||||
* Gets the weights of the assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array keyed by assignment method_id with a numeric weight.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setAssignmentWeights()
|
||||
*/
|
||||
public function getAssignmentWeights();
|
||||
|
||||
/**
|
||||
* Sets the weights of the assignment methods.
|
||||
*
|
||||
* @param array $assignments
|
||||
* An array keyed by assignment method_id with a numeric weight value.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getAssignmentWeights()
|
||||
*/
|
||||
public function setAssignmentWeights(array $assignments);
|
||||
|
||||
/**
|
||||
* Gets settings specific to an assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of an assignment method. If NULL, return all assignment settings
|
||||
* keyed by method_id.
|
||||
*
|
||||
* @return array
|
||||
* An array of settings. Format specific to assignment method.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setAssignmentSettings()
|
||||
*/
|
||||
public function getAssignmentSettings($method_id = NULL);
|
||||
|
||||
/**
|
||||
* Sets settings specific to an assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of an assignment method. If NULL, all $settings are given keyed
|
||||
* by method_ID.
|
||||
* @param array $settings
|
||||
* An array of setting values.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getAssignmentSettings()
|
||||
*/
|
||||
public function setAssignmentSettings($method_id, array $settings);
|
||||
|
||||
/**
|
||||
* Saves the bundle to the active config.
|
||||
*/
|
||||
public function save();
|
||||
|
||||
/**
|
||||
* Removes the bundle from the active config.
|
||||
*/
|
||||
public function remove();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
|
||||
|
||||
/**
|
||||
* Class FeaturesConfigDependencyManager
|
||||
* @package Drupal\features
|
||||
*/
|
||||
class FeaturesConfigDependencyManager extends ConfigDependencyManager{
|
||||
|
||||
protected $sorted_graph;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependentEntities($type, $name) {
|
||||
$dependent_entities = array();
|
||||
|
||||
$entities_to_check = array();
|
||||
if ($type == 'config') {
|
||||
$entities_to_check[] = $name;
|
||||
}
|
||||
else {
|
||||
if ($type == 'module' || $type == 'theme' || $type == 'content') {
|
||||
$dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
|
||||
return $entity->hasDependency($type, $name);
|
||||
});
|
||||
}
|
||||
// If checking content, module, or theme dependencies, discover which
|
||||
// entities are dependent on the entities that have a direct dependency.
|
||||
foreach ($dependent_entities as $entity) {
|
||||
$entities_to_check[] = $entity->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
$dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
|
||||
if (!$this->sorted_graph) {
|
||||
// Sort dependencies in the reverse order of the graph. So the least
|
||||
// dependent is at the top. For example, this ensures that fields are
|
||||
// always after field storages. This is because field storages need to be
|
||||
// created before a field.
|
||||
$this->sorted_graph = $this->getGraph();
|
||||
uasort($this->sorted_graph, array($this, 'sortGraph'));
|
||||
}
|
||||
return array_replace(array_intersect_key($this->sorted_graph, $dependencies), $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
parent::setData($data);
|
||||
$this->sorted_graph = NULL;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\ConfigInstaller;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Class for customizing the test for pre existing configuration.
|
||||
*
|
||||
* Copy of ConfigInstaller with findPreExistingConfiguration() modified to
|
||||
* allow Feature modules to be installed.
|
||||
*/
|
||||
class FeaturesConfigInstaller extends ConfigInstaller {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function findPreExistingConfiguration(StorageInterface $storage) {
|
||||
// CHANGE START
|
||||
// Override
|
||||
// Drupal\Core\Config\ConfigInstaller::findPreExistingConfiguration().
|
||||
// Allow config that already exists coming from Features.
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$features_config = array_keys($manager->listExistingConfig());
|
||||
// Map array so we can use isset instead of in_array for faster access.
|
||||
$features_config = array_combine($features_config, $features_config);
|
||||
// CHANGE END.
|
||||
$existing_configuration = array();
|
||||
// Gather information about all the supported collections.
|
||||
$collection_info = $this->configManager->getConfigCollectionInfo();
|
||||
|
||||
foreach ($collection_info->getCollectionNames() as $collection) {
|
||||
$config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
|
||||
$active_storage = $this->getActiveStorages($collection);
|
||||
foreach ($config_to_create as $config_name) {
|
||||
if ($active_storage->exists($config_name)) {
|
||||
// CHANGE START
|
||||
// Test if config is part of a Feature package.
|
||||
if (!isset($features_config[$config_name])) {
|
||||
// CHANGE END.
|
||||
$existing_configuration[$collection][] = $config_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $existing_configuration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Wraps FeaturesInstallStorage to support multiple configuration
|
||||
* directories.
|
||||
*/
|
||||
class FeaturesExtensionStorages implements FeaturesExtensionStoragesInterface {
|
||||
|
||||
/**
|
||||
* The target storage.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* The extension storages.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $extensionStorages;
|
||||
|
||||
/**
|
||||
* Configuration provided by extension storages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configurationLists;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesExtensionStorages object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The configuration storage.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage) {
|
||||
$this->configStorage = $config_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExtensionStorages() {
|
||||
return $this->extensionStorages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addStorage($directory = InstallStorage::CONFIG_INSTALL_DIRECTORY) {
|
||||
$this->extensionStorages[$directory] = new FeaturesInstallStorage($this->configStorage, $directory);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($name) {
|
||||
$list = $this->listAllByDirectory('');
|
||||
if (isset($list[$name])) {
|
||||
$directory = $list[$name];
|
||||
return $this->extensionStorages[$directory]->read($name);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
return array_keys($this->listAllByDirectory($prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension) {
|
||||
$extension_config = [];
|
||||
foreach ($this->extensionStorages as $directory => $extension_storage) {
|
||||
$extension_config = array_merge($extension_config, array_keys($extension_storage->getComponentNames([
|
||||
$extension->getName() => $extension,
|
||||
])));
|
||||
}
|
||||
return $extension_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets packages and configuration assignment.
|
||||
*/
|
||||
protected function reset() {
|
||||
$this->configurationLists = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all configuration available from extensions.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array with configuration item names as keys and configuration
|
||||
* directories as values.
|
||||
*/
|
||||
protected function listAllByDirectory($prefix = '') {
|
||||
if (!isset($this->configurationLists[$prefix])) {
|
||||
$this->configurationLists[$prefix] = [];
|
||||
foreach ($this->extensionStorages as $directory => $extension_storage) {
|
||||
$this->configurationLists[$prefix] += array_fill_keys($extension_storage->listAll($prefix), $directory);
|
||||
}
|
||||
}
|
||||
return $this->configurationLists[$prefix];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* The FeaturesExtensionStorages provides a collection of extension storages,
|
||||
* one for each supported configuration directory.
|
||||
*
|
||||
* Typically this will include the install and optional directories defined by
|
||||
* Drupal core, but may also include any extension configuration directories
|
||||
* added by contributed modules.
|
||||
*
|
||||
* This class serves as a partial wrapper to
|
||||
* Drupal\Core\Config\StorageInterface, providing a subset of methods that can
|
||||
* be called to apply to all available extension storages. For example,
|
||||
* FeaturesExtensionStoragesInterface::read() will read an extension-provided
|
||||
* configuration item regardless of which extension storage directory it is
|
||||
* provided in.
|
||||
*/
|
||||
interface FeaturesExtensionStoragesInterface {
|
||||
|
||||
/**
|
||||
* Returns all registered extension storages.
|
||||
*
|
||||
* @return FeaturesInstallStorage[]
|
||||
* Array of install storages keyed by configuration directory.
|
||||
*/
|
||||
public function getExtensionStorages();
|
||||
|
||||
/**
|
||||
* Adds a storage.
|
||||
*
|
||||
* @param string $directory
|
||||
* (optional) The configuration directory. If omitted,
|
||||
* InstallStorage::CONFIG_INSTALL_DIRECTORY will be used.
|
||||
*/
|
||||
public function addStorage($directory = InstallStorage::CONFIG_INSTALL_DIRECTORY);
|
||||
|
||||
/**
|
||||
* Reads configuration data from the storages.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to load.
|
||||
*
|
||||
* @return array|bool
|
||||
* The configuration data stored for the configuration object name. If no
|
||||
* configuration data exists for the given name, FALSE is returned.
|
||||
*/
|
||||
public function read($name);
|
||||
|
||||
/**
|
||||
* Gets configuration object names starting with a given prefix.
|
||||
*
|
||||
* Given the following configuration objects:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type.' will return an array containing the above
|
||||
* names.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
public function listAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Lists names of configuration objects provided by a given extension.
|
||||
*
|
||||
* If a $name and/or $namespace is specified, only matching modules will be
|
||||
* returned. Otherwise, all install are returned.
|
||||
*
|
||||
* @param mixed $extension
|
||||
* A string name of an extension or a full Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration object names.
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Base class for package assignment methods.
|
||||
*/
|
||||
abstract class FeaturesGenerationMethodBase implements FeaturesGenerationMethodInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager) {
|
||||
$this->featuresManager = $features_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner) {
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges an info file into a package's info file.
|
||||
*
|
||||
* @param string $package_info
|
||||
* The Yaml encoded package info.
|
||||
* @param string $info_file_uri
|
||||
* The info file's URI.
|
||||
*/
|
||||
protected function mergeInfoFile($package_info, $info_file_uri) {
|
||||
$package_info = Yaml::decode($package_info);
|
||||
/** @var \Drupal\Core\Extension\InfoParserInterface $existing_info */
|
||||
$existing_info = \Drupal::service('info_parser')->parse($info_file_uri);
|
||||
return Yaml::encode($this->featuresManager->mergeInfoArray($existing_info, $package_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepare(array &$packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
// If any packages exist, read in their files.
|
||||
$existing_packages = $this->featuresManager->listPackageDirectories(array_keys($packages), $bundle);
|
||||
|
||||
foreach ($packages as &$package) {
|
||||
list($full_name, $path) = $this->featuresManager->getExportInfo($package, $bundle);
|
||||
if (empty($package->getDirectory())) {
|
||||
$package->setDirectory($path);
|
||||
}
|
||||
|
||||
// If this is the profile, its directory is already assigned.
|
||||
if (!isset($bundle) || !$bundle->isProfilePackage($package->getMachineName())) {
|
||||
$package->setDirectory($package->getDirectory() . '/' . $full_name);
|
||||
}
|
||||
|
||||
$this->preparePackage($package, $existing_packages, $bundle);
|
||||
}
|
||||
// Clean up the $package pass by reference.
|
||||
unset($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs any required changes on a package prior to generation.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package to be prepared.
|
||||
* @param array $existing_packages
|
||||
* An array of existing packages with machine names as keys and paths as
|
||||
* values.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle used for export
|
||||
*/
|
||||
abstract protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Interface for package assignment classes.
|
||||
*/
|
||||
interface FeaturesGenerationMethodInterface {
|
||||
|
||||
/**
|
||||
* Injects the features manager.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager to be used to retrieve the configuration
|
||||
* list and the assigned packages.
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager);
|
||||
|
||||
/**
|
||||
* Injects the features assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The features assigner to be used to retrieve the bundle configuration.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Prepares packages for generation.
|
||||
*
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* An array of packages data.
|
||||
*/
|
||||
public function prepare(array &$packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Performs package generation.
|
||||
*
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Responds to the submission of
|
||||
* \Drupal\features_ui\Form\FeaturesExportForm.
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages configuration packaging methods.
|
||||
*/
|
||||
class FeaturesGenerationMethodManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesGenerationMethodManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* An object that implements CacheBackendInterface.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* An object that implements ModuleHandlerInterface.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/FeaturesGeneration', $namespaces, $module_handler, 'Drupal\features\FeaturesGenerationMethodInterface');
|
||||
$this->cacheBackend = $cache_backend;
|
||||
$this->cacheKeyPrefix = 'features_generation_methods';
|
||||
$this->cacheKey = 'features_generation_methods';
|
||||
$this->alterInfo('features_generation_info');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Class responsible for performing package generation.
|
||||
*/
|
||||
class FeaturesGenerator implements FeaturesGeneratorInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The package generation method plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $generatorManager;
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* Local cache for package generation method instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $methods;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesGenerator object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $generator_manager
|
||||
* The package generation methods plugin manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $generator_manager, FeaturesAssignerInterface $assigner) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->generatorManager = $generator_manager;
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the injected features manager with the generator.
|
||||
*
|
||||
* This should be called right after instantiating the generator to make it
|
||||
* available to the features manager without introducing a circular
|
||||
* dependency.
|
||||
*/
|
||||
public function initFeaturesManager() {
|
||||
$this->featuresManager->setGenerator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->methods = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyGenerationMethod($method_id, array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
$method = $this->getGenerationMethodInstance($method_id);
|
||||
$method->prepare($packages, $bundle);
|
||||
return $method->generate($packages, $bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyExportFormSubmit($method_id, &$form, FormStateInterface $form_state) {
|
||||
$method = $this->getGenerationMethodInstance($method_id);
|
||||
$method->exportFormSubmit($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGenerationMethods() {
|
||||
return $this->generatorManager->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the specified package generation method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package generation method to use to package
|
||||
* configuration.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesGenerationMethodInterface
|
||||
*/
|
||||
protected function getGenerationMethodInstance($method_id) {
|
||||
if (!isset($this->methods[$method_id])) {
|
||||
$instance = $this->generatorManager->createInstance($method_id, array());
|
||||
$instance->setFeaturesManager($this->featuresManager);
|
||||
$instance->setAssigner($this->assigner);
|
||||
$this->methods[$method_id] = $instance;
|
||||
}
|
||||
return $this->methods[$method_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePackages($method_id, FeaturesBundleInterface $bundle, array $package_names = array()) {
|
||||
$this->featuresManager->setPackageBundleNames($bundle, $package_names);
|
||||
return $this->generate($method_id, $bundle, $package_names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a file representation of configuration packages and, optionally,
|
||||
* an install profile.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of the generation method to use.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param string[] $package_names
|
||||
* Names of packages to be generated. If none are specified, all
|
||||
* available packages will be added.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
protected function generate($method_id, FeaturesBundleInterface $bundle, array $package_names = array()) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
// Filter out the packages that weren't requested.
|
||||
if (!empty($package_names)) {
|
||||
$packages = array_intersect_key($packages, array_fill_keys($package_names, NULL));
|
||||
}
|
||||
|
||||
$this->featuresManager->assignInterPackageDependencies($bundle, $packages);
|
||||
|
||||
// Prepare the files.
|
||||
$this->featuresManager->prepareFiles($packages);
|
||||
|
||||
$return = $this->applyGenerationMethod($method_id, $packages, $bundle);
|
||||
|
||||
foreach ($return as $message) {
|
||||
if ($message['display']) {
|
||||
$type = $message['success'] ? 'status' : 'error';
|
||||
drupal_set_message($this->t($message['message'], $message['variables']), $type);
|
||||
}
|
||||
$type = $message['success'] ? 'notice' : 'error';
|
||||
\Drupal::logger('features')->{$type}($message['message'], $message['variables']);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Common interface for features generation services.
|
||||
*
|
||||
* The configuration packaging API is based on two major concepts:
|
||||
* - Packages: modules into which configuration is packaged.
|
||||
* - Package generation methods: responsible for `determining
|
||||
* which package to assign a given piece of configuration to.
|
||||
* Generation methods are customizable.
|
||||
*
|
||||
* Features defines two package generation methods, which are simple plugin
|
||||
* classes that implement a particular logic to assign pieces of configuration
|
||||
* to a given package (module).
|
||||
*
|
||||
* Modules can define additional package generation methods by simply providing
|
||||
* the related plugins, and alter existing methods through
|
||||
* hook_features_generation_method_info_alter(). Here is an example
|
||||
* snippet:
|
||||
* @code
|
||||
* function mymodule_features_generation_method_info_alter(&$generation_info) {
|
||||
* // Replace the original plugin with our own implementation.
|
||||
* $method_id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive::METHOD_ID;
|
||||
* $generation_info[$method_id]['class'] = 'Drupal\my_module\Plugin\FeaturesGeneration\MyFeaturesGenerationArchive';
|
||||
* }
|
||||
*
|
||||
* class MyFeaturesGenerationArchive extends FeaturesGenerationArchive {
|
||||
* public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
* // Insert customization here.
|
||||
* }
|
||||
* }
|
||||
* ?>
|
||||
* @endcode
|
||||
*
|
||||
* For more information, see
|
||||
* @link http://drupal.org/node/2404473 Developing for Features 3.x @endlink
|
||||
*/
|
||||
interface FeaturesGeneratorInterface {
|
||||
|
||||
/**
|
||||
* The package generation method id for the package generator itself.
|
||||
*/
|
||||
const METHOD_ID = 'generator-default';
|
||||
|
||||
/**
|
||||
* Resets the assigned packages and the method instances.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Apply a given package generation method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package generation method to use to package
|
||||
* configuration.
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
public function applyGenerationMethod($method_id, array $packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Responds to the submission of
|
||||
* \Drupal\features_ui\Form\FeaturesExportForm.
|
||||
*/
|
||||
public function applyExportFormSubmit($method_id, &$form, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* Returns the enabled package generation methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of package generation method definitions keyed by method id.
|
||||
*/
|
||||
public function getGenerationMethods();
|
||||
|
||||
/**
|
||||
* Generates file representations of configuration packages.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of the generation method to use.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param array $package_names
|
||||
* Array of names of packages to be generated. If none are specified, all
|
||||
* available packages will be added.
|
||||
*/
|
||||
public function generatePackages($method_id, FeaturesBundleInterface $bundle, array $package_names = array());
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Config\ExtensionInstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Storage to access configuration and schema in installed extensions.
|
||||
*
|
||||
* Overrides the normal ExtensionInstallStorage to prevent profile from
|
||||
* overriding.
|
||||
*
|
||||
* Also supports modules that are not installed yet.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ExtensionInstallStorage
|
||||
*/
|
||||
class FeaturesInstallStorage extends ExtensionInstallStorage {
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\ExtensionInstallStorage::__construct().
|
||||
*
|
||||
* Sets includeProfile to FALSE.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The active configuration store where the list of installed modules and
|
||||
* themes is stored.
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
parent::__construct($config_storage, $directory, $collection, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all config object names and their folders.
|
||||
*
|
||||
* The list is based on installed modules and themes. The active
|
||||
* configuration storage is used rather than
|
||||
* \Drupal\Core\Extension\ModuleHandler and
|
||||
* \Drupal\Core\Extension\ThemeHandler in order to resolve circular
|
||||
* dependencies between these services and
|
||||
* \Drupal\Core\Config\ConfigInstaller and
|
||||
* \Drupal\Core\Config\TypedConfigManager.
|
||||
*
|
||||
* NOTE: This code is copied from ExtensionInstallStorage::getAllFolders() with
|
||||
* the following changes (Notes in CHANGED below)
|
||||
* - Load all modules whether installed or not
|
||||
*
|
||||
* @return array
|
||||
* An array mapping config object names with directories.
|
||||
*/
|
||||
public function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = array();
|
||||
$this->folders += $this->getCoreNames();
|
||||
|
||||
$install_profile = Settings::get('install_profile');
|
||||
$profile = drupal_get_profile();
|
||||
$extensions = $this->configStorage->read('core.extension');
|
||||
// @todo Remove this scan as part of https://www.drupal.org/node/2186491
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
|
||||
// CHANGED START: Add profile directories for any bundles that use a profile.
|
||||
$profile_directories = [];
|
||||
if ($profile) {
|
||||
$profile_directories[] = drupal_get_path('profile', $profile);
|
||||
}
|
||||
if ($this->includeProfile) {
|
||||
// Add any profiles used in bundles.
|
||||
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
|
||||
$assigner = \Drupal::service('features_assigner');
|
||||
$bundles = $assigner->getBundleList();
|
||||
foreach ($bundles as $bundle_name => $bundle) {
|
||||
if ($bundle->isProfile()) {
|
||||
// Register the profile directory.
|
||||
$profile_directory = 'profiles/' . $bundle->getProfileName();
|
||||
if (is_dir($profile_directory)) {
|
||||
$profile_directories[] = $profile_directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$listing->setProfileDirectories($profile_directories);
|
||||
// CHANGED END
|
||||
|
||||
if (!empty($extensions['module'])) {
|
||||
|
||||
// CHANGED START: Find ANY modules, not just installed ones.
|
||||
//$modules = $extensions['module'];
|
||||
$module_list_scan = $listing->scan('module');
|
||||
$modules = $module_list_scan;
|
||||
// CHANGED END
|
||||
|
||||
// Remove the install profile as this is handled later.
|
||||
unset($modules[$install_profile]);
|
||||
$profile_list = $listing->scan('profile');
|
||||
if ($profile && isset($profile_list[$profile])) {
|
||||
// Prime the drupal_get_filename() static cache with the profile info
|
||||
// file location so we can use drupal_get_path() on the active profile
|
||||
// during the module scan.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
|
||||
}
|
||||
// CHANGED START: Put Features modules first in list returned.
|
||||
// to allow features to override config provided by other extensions.
|
||||
$featuresManager = \Drupal::service('features.manager');
|
||||
$features_list = array();
|
||||
$module_list = array();
|
||||
foreach (array_keys($module_list_scan) as $module) {
|
||||
if ($featuresManager->isFeatureModule($module_list_scan[$module])) {
|
||||
$features_list[$module] = $module_list_scan[$module];
|
||||
}
|
||||
else {
|
||||
$module_list[$module] = $module_list_scan[$module];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($features_list);
|
||||
$this->folders += $this->getComponentNames($module_list);
|
||||
// CHANGED END
|
||||
}
|
||||
if (!empty($extensions['theme'])) {
|
||||
$theme_list_scan = $listing->scan('theme');
|
||||
foreach (array_keys($extensions['theme']) as $theme) {
|
||||
if (isset($theme_list_scan[$theme])) {
|
||||
$theme_list[$theme] = $theme_list_scan[$theme];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($theme_list);
|
||||
}
|
||||
|
||||
if ($this->includeProfile) {
|
||||
// The install profile can override module default configuration. We do
|
||||
// this by replacing the config file path from the module/theme with the
|
||||
// install profile version if there are any duplicates.
|
||||
if (isset($profile)) {
|
||||
if (!isset($profile_list)) {
|
||||
$profile_list = $listing->scan('profile');
|
||||
}
|
||||
if (isset($profile_list[$profile])) {
|
||||
$profile_folders = $this->getComponentNames(array($profile_list[$profile]));
|
||||
$this->folders = $profile_folders + $this->folders;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->folders;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,601 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Provides an interface for the FeaturesManager.
|
||||
*/
|
||||
interface FeaturesManagerInterface {
|
||||
|
||||
/**
|
||||
* Simple configuration.
|
||||
*
|
||||
* Core uses system.simple, but since we're using this key in configuration
|
||||
* arrays we can't include a period.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2297311
|
||||
*/
|
||||
const SYSTEM_SIMPLE_CONFIG = 'system_simple';
|
||||
|
||||
/**
|
||||
* Constants for package/module status.
|
||||
*/
|
||||
const STATUS_NO_EXPORT = 0;
|
||||
const STATUS_UNINSTALLED = 1;
|
||||
const STATUS_INSTALLED = 2;
|
||||
const STATUS_DEFAULT = self::STATUS_NO_EXPORT;
|
||||
|
||||
/**
|
||||
* Constants for package/module state.
|
||||
*/
|
||||
const STATE_DEFAULT = 0;
|
||||
const STATE_OVERRIDDEN = 1;
|
||||
|
||||
/**
|
||||
* Returns the active config store.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
public function getActiveStorage();
|
||||
|
||||
/**
|
||||
* Returns a set of config storages.
|
||||
*
|
||||
* This method is used for support of multiple extension configuration
|
||||
* directories, including the core-provided install and optional directories.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
public function getExtensionStorages();
|
||||
|
||||
/**
|
||||
* Resets packages and configuration assignment.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Gets an array of site configuration.
|
||||
*
|
||||
* @param bool $reset
|
||||
* If TRUE, recalculate the configuration (undo all assignment methods).
|
||||
*
|
||||
* @return \Drupal\features\ConfigurationItem[]
|
||||
* An array of items, each with the following keys:
|
||||
* - 'name': prefixed configuration item name.
|
||||
* - 'name_short': configuration item name without prefix.
|
||||
* - 'label': human readable name of configuration item.
|
||||
* - 'type': type of configuration.
|
||||
* - 'data': the contents of the configuration item in exported format.
|
||||
* - 'dependents': array of names of dependent configuration items.
|
||||
* - 'subdirectory': feature subdirectory to export item to.
|
||||
* - 'package': machine name of a package the configuration is assigned to.
|
||||
* - 'extension_provided': whether the configuration is provided by an
|
||||
* extension.
|
||||
* - 'package_excluded': array of package names that this item should be
|
||||
* excluded from.
|
||||
*/
|
||||
public function getConfigCollection($reset = FALSE);
|
||||
|
||||
/**
|
||||
* Sets an array of site configuration.
|
||||
*
|
||||
* @param \Drupal\features\ConfigurationItem[] $config_collection
|
||||
* An array of items.
|
||||
*/
|
||||
public function setConfigCollection(array $config_collection);
|
||||
|
||||
/**
|
||||
* Gets an array of packages.
|
||||
*
|
||||
* @return \Drupal\features\Package[]
|
||||
* An array of items, each with the following keys:
|
||||
* - 'machine_name': machine name of the package such as 'example_article'.
|
||||
* 'article'.
|
||||
* - 'name': human readable name of the package such as 'Example Article'.
|
||||
* - 'description': description of the package.
|
||||
* - 'type': type of Drupal project ('module').
|
||||
* - 'core': Drupal core compatibility ('8.x').
|
||||
* - 'dependencies': array of module dependencies.
|
||||
* - 'themes': array of names of themes to install.
|
||||
* - 'config': array of names of configuration items.
|
||||
* - 'status': status of the package. Valid values are:
|
||||
* - FeaturesManagerInterface::STATUS_NO_EXPORT
|
||||
* - FeaturesManagerInterface::STATUS_INSTALLED
|
||||
* - FeaturesManagerInterface::STATUS_UNINSTALLED
|
||||
* - 'version': version of the extension.
|
||||
* - 'state': state of the extension. Valid values are:
|
||||
* - FeaturesManagerInterface::STATE_DEFAULT
|
||||
* - FeaturesManagerInterface::STATE_OVERRIDDEN
|
||||
* - 'directory': the extension's directory.
|
||||
* - 'files' array of files, each having the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
* - 'bundle': name of the features bundle this package belongs to.
|
||||
* - 'extension': \Drupal\Core\Extension\Extension object.
|
||||
* - 'info': the original info array from an existing package.
|
||||
* - 'config_info': the original config of the module.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::setPackages()
|
||||
*/
|
||||
public function getPackages();
|
||||
|
||||
/**
|
||||
* Sets an array of packages.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function setPackages(array $packages);
|
||||
|
||||
/**
|
||||
* Gets a specific package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Full machine name of package.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* Package data.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::getPackages()
|
||||
*/
|
||||
public function getPackage($machine_name);
|
||||
|
||||
/**
|
||||
* Gets a specific package.
|
||||
* Similar to getPackage but will also match package FullName
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Full machine name of package.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* Package data.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::getPackages()
|
||||
*/
|
||||
public function findPackage($machine_name);
|
||||
|
||||
/**
|
||||
* Updates a package definition in the package list.
|
||||
*
|
||||
* NOTE: This does not "export" the package; it simply updates the internal
|
||||
* data.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package.
|
||||
*/
|
||||
public function setPackage(Package $package);
|
||||
|
||||
/**
|
||||
* Filters the supplied package list by the given namespace.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
* @param string $namespace
|
||||
* The namespace to use.
|
||||
* @param bool $only_exported
|
||||
* If true, only filter out packages that are exported
|
||||
*
|
||||
* @return \Drupal\features\Package[]
|
||||
* An array of packages.
|
||||
*/
|
||||
public function filterPackages(array $packages, $namespace = '', $only_exported = FALSE);
|
||||
|
||||
/**
|
||||
* Gets a reference to a package assigner.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesAssignerInterface
|
||||
* The package assigner.
|
||||
*/
|
||||
public function getAssigner();
|
||||
|
||||
/**
|
||||
* Injects the package assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The package assigner.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Gets a reference to a package generator.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesGeneratorInterface
|
||||
* The package generator.
|
||||
*/
|
||||
public function getGenerator();
|
||||
|
||||
/**
|
||||
* Injects the package generator.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesGeneratorInterface $generator
|
||||
* The package generator.
|
||||
*/
|
||||
public function setGenerator(FeaturesGeneratorInterface $generator);
|
||||
|
||||
/**
|
||||
* Returns the current export settings.
|
||||
*
|
||||
* @return array
|
||||
* An array with the following keys:
|
||||
* - 'folder' - subdirectory to export packages to.
|
||||
* - 'namespace' - module namespace being exported.
|
||||
*/
|
||||
public function getExportSettings();
|
||||
|
||||
/**
|
||||
* Returns the current general features settings.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* A config object containing settings.
|
||||
*/
|
||||
public function getSettings();
|
||||
|
||||
/**
|
||||
* Returns the contents of an extensions info.yml file.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array representing data in an info.yml file.
|
||||
*/
|
||||
public function getExtensionInfo(Extension $extension);
|
||||
|
||||
/**
|
||||
* Determine if extension is enabled
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* @return bool
|
||||
*/
|
||||
public function extensionEnabled(Extension $extension);
|
||||
|
||||
/**
|
||||
* Initializes a configuration package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of the package.
|
||||
* @param string $name
|
||||
* (optional) Human readable name of the package.
|
||||
* @param string $description
|
||||
* (optional) Description of the package.
|
||||
* @param string $type
|
||||
* (optional) Type of project.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to use to add profile directories to the scan.
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* (optional) An Extension object.
|
||||
* @return array
|
||||
* The created package array.
|
||||
*/
|
||||
public function initPackage($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL);
|
||||
|
||||
/**
|
||||
* Initializes a configuration package using module info data.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* The created package array.
|
||||
*/
|
||||
public function initPackageFromExtension(Extension $extension);
|
||||
|
||||
/**
|
||||
* Lists directories in which packages are present.
|
||||
*
|
||||
* This method scans to find package modules whether or not they are
|
||||
* currently active (installed). As well as the directories that are
|
||||
* usually scanned for modules and profiles, a profile directory for the
|
||||
* current profile is scanned if it exists. For example, if the value
|
||||
* for $bundle->getProfileName() is 'example', a
|
||||
* directory profiles/example will be scanned if it exists. Therefore, when
|
||||
* regenerating package modules, existing ones from a prior export will be
|
||||
* recognized.
|
||||
*
|
||||
* @param string[] $machine_names
|
||||
* Package machine names to return directories for. If omitted, return all
|
||||
* directories.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle to use to add profile directories to the scan.
|
||||
*
|
||||
* @return array
|
||||
* Array of package directories keyed by package machine name.
|
||||
*/
|
||||
public function listPackageDirectories(array $machine_names = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Assigns a set of configuration items to a given package or profile.
|
||||
*
|
||||
* @param string $package_name
|
||||
* Machine name of a package or the profile.
|
||||
* @param string[] $item_names
|
||||
* Configuration item names.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function assignConfigPackage($package_name, array $item_names, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Assigns configuration items with names matching given strings to given
|
||||
* packages.
|
||||
*
|
||||
* @param array $patterns
|
||||
* Array with string patterns as keys and package machine names as values.
|
||||
*/
|
||||
public function assignConfigByPattern(array $patterns);
|
||||
|
||||
/**
|
||||
* For given configuration items, assigns any dependent configuration to the
|
||||
* same package.
|
||||
*
|
||||
* @param string[] $item_names
|
||||
* Configuration item names.
|
||||
* @param string $package
|
||||
* Short machine name of package to assign dependent config to. If NULL,
|
||||
* use the current package of the parent config items.
|
||||
*/
|
||||
public function assignConfigDependents(array $item_names = NULL, $package = NULL);
|
||||
|
||||
/**
|
||||
* Adds the optional bundle prefix to package machine names.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param string[] &$package_names
|
||||
* (optional) Array of package names, passed by reference.
|
||||
*/
|
||||
public function setPackageBundleNames(FeaturesBundleInterface $bundle, array &$package_names = []);
|
||||
|
||||
/**
|
||||
* Assigns dependencies from config items into the package.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages. NULL for all packages
|
||||
*/
|
||||
public function assignPackageDependencies(Package $package = NULL);
|
||||
|
||||
/**
|
||||
* Assigns dependencies between packages based on configuration dependencies.
|
||||
*
|
||||
* \Drupal\features\FeaturesBundleInterface::setPackageBundleNames() must be
|
||||
* called prior to calling this method.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle.
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function assignInterPackageDependencies(FeaturesBundleInterface $bundle, array &$packages);
|
||||
|
||||
/**
|
||||
* Merges two info arrays and processes the resulting array.
|
||||
*
|
||||
* Ensures values are unique and sorted.
|
||||
*
|
||||
* @param array $info1
|
||||
* The first array.
|
||||
* @param array $info2
|
||||
* The second array.
|
||||
* @param string[] $keys
|
||||
* Keys to merge. If not specified, all keys present will be merged.
|
||||
*
|
||||
* @return array
|
||||
* An array with the merged and processed results.
|
||||
*
|
||||
* @fixme Should this be moved to the package object or a related helper?
|
||||
*/
|
||||
public function mergeInfoArray(array $info1, array $info2, array $keys = array());
|
||||
|
||||
/**
|
||||
* Lists the types of configuration available on the site.
|
||||
*
|
||||
* @param boolean $bundles_only
|
||||
* Whether to list only configuration types that provide bundles.
|
||||
*
|
||||
* @return array
|
||||
* An array with machine name keys and human readable values.
|
||||
*/
|
||||
public function listConfigTypes($bundles_only = FALSE);
|
||||
|
||||
/**
|
||||
* Lists stored configuration for a given configuration type.
|
||||
*
|
||||
* @param string $config_type
|
||||
* The type of configuration.
|
||||
*/
|
||||
public function listConfigByType($config_type);
|
||||
|
||||
/**
|
||||
* Returns a list of all modules present on the site's file system.
|
||||
*
|
||||
* @return Drupal\Core\Extension\Extension[]
|
||||
* An array of extension objects.
|
||||
*/
|
||||
public function getAllModules();
|
||||
|
||||
/**
|
||||
* Returns a list of Features modules regardless of if they are installed.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle to filter module list.
|
||||
* If given, only modules matching the bundle namespace will be returned.
|
||||
* If the bundle uses a profile, only modules in the profile will be
|
||||
* returned.
|
||||
* @param bool $installed
|
||||
* List only installed modules.
|
||||
*
|
||||
* @return Drupal\Core\Extension\Extension[]
|
||||
* An array of extension objects.
|
||||
*/
|
||||
public function getFeaturesModules(FeaturesBundleInterface $bundle = NULL, $installed = FALSE);
|
||||
|
||||
/**
|
||||
* Lists names of configuration objects provided by a given extension.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration object names.
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension);
|
||||
|
||||
/**
|
||||
* Lists names of configuration items provided by existing Features modules.
|
||||
*
|
||||
* @param bool $installed
|
||||
* List only installed Features.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to find existing configuration for.
|
||||
*
|
||||
* @return array
|
||||
* An array with config names as keys and providing module names as values.
|
||||
*/
|
||||
public function listExistingConfig($installed = FALSE, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Iterates through packages and prepares file names and contents.
|
||||
*
|
||||
* @param array $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function prepareFiles(array $packages);
|
||||
|
||||
/**
|
||||
* Returns the full name of a config item.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type, or '' to indicate $name is already prefixed.
|
||||
* @param string $name
|
||||
* The config name, without prefix.
|
||||
*
|
||||
* @return string
|
||||
* The config item's full name.
|
||||
*/
|
||||
public function getFullName($type, $name);
|
||||
|
||||
/**
|
||||
* Returns the short name and type of a full config name.
|
||||
*
|
||||
* @param string $fullname
|
||||
* The full configuration name
|
||||
* @return array
|
||||
* 'type' => string the config type
|
||||
* 'name_short' => string the short config name, without prefix.
|
||||
*/
|
||||
public function getConfigType($fullname);
|
||||
|
||||
/**
|
||||
* Returns the full machine name and directory for exporting a package.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle being used for export.
|
||||
*
|
||||
* @return array
|
||||
* An array with the full name as the first item and directory as second
|
||||
* item.
|
||||
*/
|
||||
public function getExportInfo(Package $package, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Determines if the module is a Features package, optinally testing by
|
||||
* bundle.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $module
|
||||
* An extension object.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to filter by.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the given module is a Features package of the given bundle (if any).
|
||||
*/
|
||||
public function isFeatureModule(Extension $module, FeaturesBundleInterface $bundle);
|
||||
|
||||
/**
|
||||
* Determines which config is overridden in a package.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
* The 'state' property is updated if overrides are detected.
|
||||
* @param bool $include_new
|
||||
* If set, include newly detected config not yet exported.
|
||||
*
|
||||
* @result array $different
|
||||
* The array of config items that are overridden.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::detectNew()
|
||||
*/
|
||||
public function detectOverrides(Package $feature, $include_new = FALSE);
|
||||
|
||||
/**
|
||||
* Determines which config has not been exported to the feature.
|
||||
*
|
||||
* Typically added as an auto-detected dependency.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
*
|
||||
* @return array
|
||||
* The array of config items that are overridden.
|
||||
*/
|
||||
public function detectNew(Package $feature);
|
||||
|
||||
/**
|
||||
* Determines which config is exported in the feature but not in the active.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
*
|
||||
* @return array
|
||||
* The array of config items that are missing from active store.
|
||||
*/
|
||||
public function detectMissing(Package $feature);
|
||||
|
||||
/**
|
||||
* Sort the Missing config into order by dependencies.
|
||||
* @param array $missing config items
|
||||
* @return array of config items in dependency order
|
||||
*/
|
||||
public function reorderMissing(array $missing);
|
||||
|
||||
/**
|
||||
* Helper function that returns a translatable label for the different status
|
||||
* constants.
|
||||
*
|
||||
* @param int $status
|
||||
* A status constant.
|
||||
*
|
||||
* @return string
|
||||
* A translatable label.
|
||||
*/
|
||||
public function statusLabel($status);
|
||||
|
||||
/**
|
||||
* Helper function that returns a translatable label for the different state
|
||||
* constants.
|
||||
*
|
||||
* @param int $state
|
||||
* A state constant.
|
||||
*
|
||||
* @return string
|
||||
* A translatable label.
|
||||
*/
|
||||
public function stateLabel($state);
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeaturesInfo(Extension $extension);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
||||
|
||||
/**
|
||||
* Service provider implementation for Features to override config.installer.
|
||||
*
|
||||
* @ingroup container
|
||||
*/
|
||||
class FeaturesServiceProvider extends ServiceProviderBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
// Override the config.installer class with a new class.
|
||||
$definition = $container->getDefinition('config.installer');
|
||||
$definition->setClass('Drupal\features\FeaturesConfigInstaller');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Defines a value object for storing package related data.
|
||||
*
|
||||
* A package contains of a name, version number, containing config etc.
|
||||
*/
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $machineName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $version = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $core = '8.x';
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'module';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $themes = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $excluded = [];
|
||||
|
||||
/**
|
||||
* @var string[]|bool
|
||||
*/
|
||||
protected $required = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $info = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Extension\Extension
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $configOrig = [];
|
||||
|
||||
/**
|
||||
* Creates a new Package instance.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name.
|
||||
* @param array $additional_properties
|
||||
* (optional) Additional properties of the object.
|
||||
*/
|
||||
public function __construct($machine_name, array $additional_properties = []) {
|
||||
$this->machineName = $machine_name;
|
||||
|
||||
$properties = get_object_vars($this);
|
||||
foreach ($additional_properties as $property => $value) {
|
||||
if (!array_key_exists($property, $properties)) {
|
||||
throw new \InvalidArgumentException('Invalid property: ' . $property);
|
||||
}
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMachineName() {
|
||||
return $this->machineName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return TRUE if the machine_name already has the bundle prefix.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* @param string $bundle_name
|
||||
* @return bool
|
||||
*/
|
||||
protected function inBundle($machine_name, $bundle_name) {
|
||||
return strpos($machine_name, $bundle_name . '_') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFullName() {
|
||||
if (empty($this->bundle) || $this->inBundle($this->machineName, $this->bundle)) {
|
||||
return $this->machineName;
|
||||
}
|
||||
else {
|
||||
return $this->bundle . '_' . $this->machineName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion() {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStatus() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getConfig() {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a new filename.
|
||||
*
|
||||
* @param string $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function appendConfig($config) {
|
||||
$this->config[] = $config;
|
||||
$this->config = array_unique($this->config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeConfig($name) {
|
||||
$this->config = array_diff($this->config, [$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBundle() {
|
||||
return $this->bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExcluded() {
|
||||
return $this->excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRequired() {
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequiredAll() {
|
||||
$config_orig = $this->getConfigOrig();
|
||||
$info = is_array($this->required) ? $this->required : array();
|
||||
$diff = array_diff($config_orig, $info);
|
||||
// Mark all as required if required:true, or required is empty, or
|
||||
// if required contains all the exported config
|
||||
return empty($diff) || empty($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getConfigOrig() {
|
||||
return $this->configOrig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCore() {
|
||||
return $this->core;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \string[]
|
||||
*/
|
||||
public function getThemes() {
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo() {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getState() {
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDirectory() {
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFiles() {
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\Core\Extension\Extension
|
||||
*/
|
||||
public function getExtension() {
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
public function removeDependency($name) {
|
||||
$this->dependencies = array_diff($this->dependencies, [$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDependencyInfo() {
|
||||
return isset($this->info['dependencies']) ? $this->info['dependencies'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the features info.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeaturesInfo() {
|
||||
$info = [];
|
||||
if (!empty($this->bundle)) {
|
||||
$info['bundle'] = $this->bundle;
|
||||
}
|
||||
if (!empty($this->excluded)) {
|
||||
$info['excluded'] = $this->excluded;
|
||||
}
|
||||
if ($this->required !== FALSE) {
|
||||
$info['required'] = $this->required;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new machine name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMachineName($machine_name) {
|
||||
$this->machineName = $machine_name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVersion($version) {
|
||||
$this->version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bundle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBundle($bundle) {
|
||||
$this->bundle = $bundle;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $info
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInfo($info) {
|
||||
$this->info = $info;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $features_info
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFeaturesInfo($features_info) {
|
||||
if (isset($features_info['bundle'])) {
|
||||
$this->setBundle($features_info['bundle']);
|
||||
}
|
||||
$this->setRequired(isset($features_info['required']) ? $features_info['required'] : false);
|
||||
$this->setExcluded(isset($features_info['excluded']) ? $features_info['excluded'] : array());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $dependencies
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDependencies($dependencies) {
|
||||
$this->dependencies = $dependencies;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dependency
|
||||
*
|
||||
* return $this
|
||||
*/
|
||||
public function appendDependency($dependency) {
|
||||
$this->dependencies[] = $dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatus($status) {
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setConfig($config) {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $excluded
|
||||
*/
|
||||
public function setExcluded($excluded) {
|
||||
$this->excluded = $excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $required
|
||||
*/
|
||||
public function setRequired($required) {
|
||||
$this->required = $required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $core
|
||||
*/
|
||||
public function setCore($core) {
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $themes
|
||||
*/
|
||||
public function setThemes($themes) {
|
||||
$this->themes = $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $state
|
||||
*/
|
||||
public function setState($state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $directory
|
||||
*/
|
||||
public function setDirectory($directory) {
|
||||
$this->directory = $directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $files
|
||||
*/
|
||||
public function setFiles($files) {
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $file_array
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function appendFile(array $file_array, $key = NULL) {
|
||||
if (!isset($key)) {
|
||||
$this->files[] = $file_array;
|
||||
}
|
||||
else {
|
||||
$this->files[$key] = $file_array;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
*/
|
||||
public function setExtension($extension) {
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $configOrig
|
||||
*/
|
||||
public function setConfigOrig($configOrig) {
|
||||
$this->configOrig = $configOrig;
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\component\Utility\Unicode;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "base",
|
||||
* weight = -2,
|
||||
* name = @Translation("Base type"),
|
||||
* description = @Translation("Use designated types of configuration as the base for configuration package modules. For example, if content types are selected as a base type, a package will be generated for each content type and will include all configuration dependent on that content type."),
|
||||
* config_route_name = "features.assignment_base",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* "content" = {}
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentBaseType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$config_base_types = $settings['types']['config'];
|
||||
|
||||
$config_types = $this->featuresManager->listConfigTypes();
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
if (in_array($item->getType(), $config_base_types)) {
|
||||
if (is_null($this->featuresManager->findPackage($item->getShortName())) && !$item->getPackage()) {
|
||||
$description = $this->t('Provides @label @type and related configuration.', array('@label' => $item->getLabel(), '@type' => Unicode::strtolower($config_types[$item->getType()])));
|
||||
if (isset($item->getData()['description'])) {
|
||||
$description .= ' ' . $item->getData()['description'];
|
||||
}
|
||||
$this->featuresManager->initPackage($item->getShortName(), $item->getLabel(), $description, 'module', $current_bundle);
|
||||
// Update list with the package we just added.
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($item->getShortName(), [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
$this->featuresManager->assignConfigDependents([$item_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entity_types = $this->entityManager->getDefinitions();
|
||||
|
||||
$content_base_types = $settings['types']['content'];
|
||||
foreach ($content_base_types as $entity_type_id) {
|
||||
if (!isset($packages[$entity_type_id]) && isset($entity_types[$entity_type_id])) {
|
||||
$label = $entity_types[$entity_type_id]->getLabel();
|
||||
$description = $this->t('Provide @label related configuration.', array('@label' => $label));
|
||||
$this->featuresManager->initPackage($entity_type_id, $label, $description, 'module', $current_bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to a core package based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "core",
|
||||
* weight = 5,
|
||||
* name = @Translation("Core type"),
|
||||
* description = @Translation("Assign designated types of configuration to a core configuration package module. For example, if image styles are selected as a core type, a core package will be generated and image styles will be assigned to it."),
|
||||
* config_route_name = "features.assignment_core",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentCoreType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$machine_name = 'core';
|
||||
$name = $this->t('Core');
|
||||
$description = $this->t('Provides core components required by other features.');
|
||||
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
|
||||
$this->assignPackageByConfigTypes($machine_name, $force);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on configuration
|
||||
* dependencies.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "dependency",
|
||||
* weight = 15,
|
||||
* name = @Translation("Dependency"),
|
||||
* description = @Translation("Add to packages configuration dependent on items already in that package."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentDependency extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$this->featuresManager->assignConfigDependents();
|
||||
}
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for excluding configuration from packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "exclude",
|
||||
* weight = -5,
|
||||
* name = @Translation("Exclude"),
|
||||
* description = @Translation("Exclude configuration items from packaging by various methods including by configuration type."),
|
||||
* config_route_name = "features.assignment_exclude",
|
||||
* default_settings = {
|
||||
* "curated" = FALSE,
|
||||
* "module" = {
|
||||
* "installed" = FALSE,
|
||||
* "profile" = FALSE,
|
||||
* "namespace" = FALSE,
|
||||
* "namespace_any" = FALSE,
|
||||
* },
|
||||
* "types" = { "config" = {} }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentExclude extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
// Exclude by configuration type.
|
||||
$exclude_types = $settings['types']['config'];
|
||||
if (!empty($exclude_types)) {
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
// Don't exclude already-assigned items.
|
||||
if (empty($item->getPackage()) && in_array($item->getType(), $exclude_types)) {
|
||||
$item->setExcluded(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude configuration already provided by modules.
|
||||
$exclude_module = $settings['module'];
|
||||
if (!empty($exclude_module['installed'])) {
|
||||
$install_list = $this->featuresManager->getExtensionStorages()->listAll();
|
||||
|
||||
// There are two settings that can limit what's included.
|
||||
// First, we can skip configuration provided by the install profile.
|
||||
$module_profile = !empty($exclude_module['profile']);
|
||||
// Second, we can skip configuration provided by namespaced modules.
|
||||
$module_namespace = !empty($exclude_module['namespace']);
|
||||
if ($module_profile || $module_namespace) {
|
||||
$profile_list = [];
|
||||
$extension_list = [];
|
||||
// Load the names of any configuration objects provided by the install
|
||||
// profile.
|
||||
if ($module_profile) {
|
||||
$all_modules = $this->featuresManager->getAllModules();
|
||||
// FeaturesBundleInterface::getProfileName() would return the profile
|
||||
// for the current bundle, if any. We want the profile that was
|
||||
// installed.
|
||||
$profile_name = drupal_get_profile();
|
||||
if (isset($all_modules[$profile_name])) {
|
||||
$profile_list = $this->featuresManager->listExtensionConfig($all_modules[$profile_name]);
|
||||
// If the configuration has been assigned to a feature that's
|
||||
// present on the file system, don't make an exception for it.
|
||||
foreach ($all_modules as $name => $extension) {
|
||||
if ($name != $profile_name && $this->featuresManager->isFeatureModule($extension)) {
|
||||
$profile_list = array_diff($profile_list, $this->featuresManager->listExtensionConfig($extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load the names of any configuration objects provided by modules
|
||||
// having the namespace of the current package set.
|
||||
if ($module_namespace) {
|
||||
$modules = $this->featuresManager->getFeaturesModules($current_bundle);
|
||||
foreach ($modules as $extension) {
|
||||
// Only make exception for non-exported modules
|
||||
if (!empty($exclude_module['namespace_any']) || !isset($all_modules[$extension->getName()])) {
|
||||
$extension_list = array_merge($extension_list, $this->featuresManager->listExtensionConfig($extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If any configuration was found, remove it from the list.
|
||||
$install_list = array_diff($install_list, $profile_list, $extension_list);
|
||||
}
|
||||
foreach ($install_list as $item_name) {
|
||||
if (isset($config_collection[$item_name])) {
|
||||
// Flag extension-provided configuration, which should not be added
|
||||
// to regular features but can be added to an install profile.
|
||||
$config_collection[$item_name]->setProviderExcluded(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude configuration items on a curated list of site-specific
|
||||
// configuration.
|
||||
if ($settings['curated']) {
|
||||
$item_names = [
|
||||
'core.extension',
|
||||
'field.settings',
|
||||
'field_ui.settings',
|
||||
'filter.settings',
|
||||
'forum.settings',
|
||||
'image.settings',
|
||||
'node.settings',
|
||||
'system.authorize',
|
||||
'system.date',
|
||||
'system.file',
|
||||
'system.diff',
|
||||
'system.logging',
|
||||
'system.maintenance',
|
||||
'system.performance',
|
||||
'system.site',
|
||||
'update.settings',
|
||||
];
|
||||
foreach ($item_names as $item_name) {
|
||||
unset($config_collection[$item_name]);
|
||||
}
|
||||
// Unset role-related actions that are automatically created by the
|
||||
// User module.
|
||||
// @see user_user_role_insert()
|
||||
$prefixes = [
|
||||
'system.action.user_add_role_action.',
|
||||
'system.action.user_remove_role_action.',
|
||||
];
|
||||
foreach (array_keys($config_collection) as $item_name) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (strpos($item_name, $prefix) === 0) {
|
||||
unset($config_collection[$item_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the updated data.
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
|
||||
/**
|
||||
* Class for assigning existing modules to packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "existing",
|
||||
* weight = 12,
|
||||
* name = @Translation("Existing"),
|
||||
* description = @Translation("Add exported config to existing packages."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentExisting extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* Calls assignConfigPackage without allowing exceptions to abort us.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of package.
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*/
|
||||
protected function safeAssignConfig($machine_name, $extension) {
|
||||
$config = $this->featuresManager->listExtensionConfig($extension);
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($machine_name, $config);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
// Assign config to installed modules first.
|
||||
foreach ($packages as $name => $package) {
|
||||
// @todo Introduce $package->isInstalled() and / or $package->isUninstalled().
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_INSTALLED) {
|
||||
$this->safeAssignConfig($package->getMachineName(), $package->getExtension());
|
||||
}
|
||||
}
|
||||
// Now assign to uninstalled modules.
|
||||
foreach ($packages as $name => $package) {
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_UNINSTALLED) {
|
||||
$this->safeAssignConfig($package->getMachineName(), $package->getExtension());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on forward dependencies.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "forward_dependency",
|
||||
* weight = 20,
|
||||
* name = @Translation("Forward dependency"),
|
||||
* description = @Translation("Add to packages configuration on which items in the package depend."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentForwardDependency extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$ordered = $this->dependencyOrder($config_collection);
|
||||
|
||||
foreach ($ordered as $name) {
|
||||
$item = $config_collection[$name];
|
||||
if ($item->getPackage()) {
|
||||
// Already has a package, not our business.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find packages of dependent items.
|
||||
$dependent_packages = [];
|
||||
foreach ($item->getDependents() as $dependent) {
|
||||
if (isset($config_collection[$dependent])) {
|
||||
if ($package = $config_collection[$dependent]->getPackage()) {
|
||||
$dependent_packages[$package] = $package;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If zero or multiple packages, we don't know what to do.
|
||||
if (count($dependent_packages) == 1) {
|
||||
$package = key($dependent_packages);
|
||||
$this->featuresManager->assignConfigPackage($package, [$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config items such that each item comes before anything it depends on.
|
||||
*
|
||||
* @param \Drupal\features\ConfigurationItem[] $config_collection
|
||||
* A collection of configuration items.
|
||||
*
|
||||
* @return string[]
|
||||
* The names of configuration items, in dependency order.
|
||||
*/
|
||||
protected function dependencyOrder($config_collection) {
|
||||
// Populate a graph.
|
||||
$graph = [];
|
||||
foreach ($config_collection as $config) {
|
||||
$graph[$config->getName()] = [];
|
||||
foreach ($config->getDependents() as $dependent) {
|
||||
$graph[$config->getName()]['edges'][$dependent] = 1;
|
||||
}
|
||||
}
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
|
||||
// Order by inverse weight.
|
||||
$weights = array_column($graph, 'weight');
|
||||
array_multisort($weights, SORT_DESC, $graph);
|
||||
return array_keys($graph);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on namespaces.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "namespace",
|
||||
* weight = 0,
|
||||
* name = @Translation("Namespace"),
|
||||
* description = @Translation("Add to packages configuration with a machine name containing that package's machine name."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentNamespace extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$packages = array_keys($this->featuresManager->getPackages());
|
||||
$this->featuresManager->assignConfigByPattern(array_combine($packages, $packages));
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to the
|
||||
* InstallStorage::CONFIG_OPTIONAL_DIRECTORY based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "optional",
|
||||
* weight = 0,
|
||||
* name = @Translation("Optional type"),
|
||||
* description = @Translation("Assign designated types of configuration to the 'config/optional' install directory. For example, if views are selected as optional, views assigned to any feature will be exported to the 'config/optional' directory and will not create a dependency on the Views module."),
|
||||
* config_route_name = "features.assignment_optional",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentOptionalType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$this->assignSubdirectoryByConfigTypes(InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning existing modules to packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "packages",
|
||||
* weight = -20,
|
||||
* name = @Translation("Packages"),
|
||||
* description = @Translation("Detect and add existing package modules."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentPackages extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$bundle = $this->assigner->getBundle();
|
||||
$existing = $this->featuresManager->getFeaturesModules();
|
||||
foreach ($existing as $extension) {
|
||||
$package = $this->featuresManager->initPackageFromExtension($extension);
|
||||
$short_name = $package->getMachineName();
|
||||
|
||||
// Copy over package excluded settings, if any.
|
||||
if (!$package->getExcluded()) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
foreach ($package->getExcluded() as $config_name) {
|
||||
if (isset($config_collection[$config_name])) {
|
||||
$package_excluded = $config_collection[$config_name]->getPackageExcluded();
|
||||
$package_excluded[] = $short_name;
|
||||
$config_collection[$config_name]->setPackageExcluded($package_excluded);
|
||||
}
|
||||
}
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
|
||||
// Assign required components, if any.
|
||||
if ($package->getRequired() !== FALSE) {
|
||||
$config = $package->getRequired();
|
||||
if (empty($config) || !is_array($config)) {
|
||||
// if required is "true" or empty, add all config as required
|
||||
$config = $this->featuresManager->listExtensionConfig($extension);
|
||||
}
|
||||
$this->featuresManager->assignConfigPackage($short_name, $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
|
||||
/**
|
||||
* Class for adding configuration for the optional install profile.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "profile",
|
||||
* weight = 10,
|
||||
* name = @Translation("Profile"),
|
||||
* description = @Translation("Add configuration and other files to the optional install profile from the Drupal core Standard install profile. Without these additions, a generated install profile will be missing some important initial setup."),
|
||||
* config_route_name = "features.assignment_profile",
|
||||
* default_settings = {
|
||||
* "curated" = FALSE,
|
||||
* "standard" = {
|
||||
* "files" = FALSE,
|
||||
* "dependencies" = FALSE,
|
||||
* },
|
||||
* "types" = { "config" = {} }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentProfile extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
|
||||
if ($current_bundle->isProfile()) {
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
|
||||
// Ensure the profile package exists.
|
||||
$profile_name = $current_bundle->getProfileName();
|
||||
|
||||
$profile_package = $this->featuresManager->getPackage($profile_name);
|
||||
if (empty($profile_package)) {
|
||||
$profile_package = $this->featuresManager->initPackage($profile_name, $current_bundle->getName(), $current_bundle->getDescription(), 'profile', $current_bundle);
|
||||
}
|
||||
|
||||
// Assign configuration by type.
|
||||
$this->assignPackageByConfigTypes($profile_name, $force);
|
||||
|
||||
// Include a curated list of configuration.
|
||||
if ($settings['curated']) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$item_names = [
|
||||
'automated_cron.settings',
|
||||
'system.cron',
|
||||
'system.theme',
|
||||
];
|
||||
$theme_settings = $this->configFactory->get('system.theme');
|
||||
foreach (['default', 'admin'] as $key) {
|
||||
$item_names[] = $theme_settings->get($key) . '.settings';
|
||||
}
|
||||
foreach ($item_names as $item_name) {
|
||||
if (isset($config_collection[$item_name])) {
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($profile_name, [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only read in from the Standard profile if this profile doesn't already
|
||||
// exist.
|
||||
$package_directories = $this->featuresManager->listPackageDirectories(array(), $current_bundle);
|
||||
if (!isset($package_directories[$profile_name])) {
|
||||
$standard_directory = 'core/profiles/standard';
|
||||
// Conditionally add files from the 'standard' install profile.
|
||||
if ($settings['standard']['files']) {
|
||||
// Add configuration from the Standard profile.
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$subdirectory = InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
||||
$item_names = $this->listRequiredStandardConfig();
|
||||
foreach ($item_names as $item_name) {
|
||||
// If the configuration is present on the site, assign it.
|
||||
if (isset($config_collection[$item_name])) {
|
||||
// Only assign it if it's not already assigned to a package.
|
||||
// @todo: if it's provided by a module, add a dependency.
|
||||
if (!$config_collection[$item_name]->getPackage()) {
|
||||
$this->featuresManager->assignConfigPackage($profile_name, [$item_name], $force);
|
||||
// Reload the profile to refresh the config array after the addition.
|
||||
$profile_package = $this->featuresManager->getPackage($profile_name);
|
||||
}
|
||||
// If it's already assigned to a package in the current bundle,
|
||||
// add a dependency.
|
||||
else {
|
||||
$machine_name = $current_bundle->getFullName($config_collection[$item_name]->getPackage());
|
||||
if (!in_array($machine_name, $profile_package->getDependencies())) {
|
||||
$profile_package->appendDependency($machine_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, copy it over from Standard.
|
||||
else {
|
||||
$filename = $item_name . '.yml';
|
||||
$profile_package->appendFile([
|
||||
'filename' => $filename,
|
||||
'subdirectory' => $subdirectory,
|
||||
'string' => file_get_contents($standard_directory . '/' . $subdirectory . '/' . $filename)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add .profile and .install files from Standard.
|
||||
$files = [
|
||||
'install',
|
||||
'profile',
|
||||
];
|
||||
// Iterate through the files.
|
||||
foreach ($files as $extension) {
|
||||
$filename = $standard_directory . '/standard.' . $extension;
|
||||
if (file_exists($filename)) {
|
||||
// Read the file contents.
|
||||
$string = file_get_contents($filename);
|
||||
// Substitute the profile's machine name and name for the Standard
|
||||
// profile's equivalents.
|
||||
$string = str_replace(
|
||||
['standard', 'Standard'],
|
||||
[$profile_name, $current_bundle->getName()],
|
||||
$string
|
||||
);
|
||||
// Add the files to those to be output.
|
||||
$profile_package->appendFile([
|
||||
'filename' => $profile_name . '.' . $extension,
|
||||
'subdirectory' => NULL,
|
||||
'string' => $string
|
||||
], $extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Conditionally merge in module and theme dependencies from the
|
||||
// 'standard' install profile.
|
||||
if ($settings['standard']['dependencies']) {
|
||||
$info_file_uri = $standard_directory . '/standard.info.yml';
|
||||
if (file_exists($info_file_uri)) {
|
||||
$profile_info = \Drupal::service('info_parser')->parse($info_file_uri);
|
||||
$info = [
|
||||
'dependencies' => $profile_package->getDependencies(),
|
||||
'themes' => $profile_package->getThemes(),
|
||||
];
|
||||
$info = $this->featuresManager->mergeInfoArray($info, $profile_info);
|
||||
$profile_package->setDependencies($info['dependencies']);
|
||||
$profile_package->setThemes($info['themes']);
|
||||
}
|
||||
}
|
||||
$this->featuresManager->setPackage($profile_package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of configuration items required by the Standard install
|
||||
* profile.
|
||||
*
|
||||
* If install code is adapted from the Standard profile, these configuration
|
||||
* items will be required.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration item names.
|
||||
*/
|
||||
protected function listRequiredStandardConfig() {
|
||||
return [
|
||||
'contact.form.feedback',
|
||||
'user.role.administrator'
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to a site package based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "site",
|
||||
* weight = 7,
|
||||
* name = @Translation("Site type"),
|
||||
* description = @Translation("Assign designated types of configuration to a site configuration package module. For example, if image styles are selected as a site type, a site package will be generated and image styles will be assigned to it."),
|
||||
* config_route_name = "features.assignment_site",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentSiteType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$machine_name = 'site';
|
||||
$name = $this->t('Site');
|
||||
$description = $this->t('Provides site components.');
|
||||
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
|
||||
$this->assignPackageByConfigTypes($machine_name, $force);
|
||||
}
|
||||
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesGeneration;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\features\FeaturesGenerationMethodBase;
|
||||
use Drupal\Core\Archiver\ArchiveTar;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class for generating a compressed archive of packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive::METHOD_ID,
|
||||
* weight = -2,
|
||||
* name = @Translation("Download Archive"),
|
||||
* description = @Translation("Generate packages and optional profile as a compressed archive for download."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Creates a new FeaturesGenerationArchive instance.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(\Drupal\Core\Access\CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('csrf_token')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The package generation method id.
|
||||
*/
|
||||
const METHOD_ID = 'archive';
|
||||
|
||||
/**
|
||||
* The filename being written.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $archiveName;
|
||||
|
||||
/**
|
||||
* Reads and merges in existing files for a given package or profile.
|
||||
*/
|
||||
protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
|
||||
if (isset($existing_packages[$package->getMachineName()])) {
|
||||
$existing_directory = $existing_packages[$package->getMachineName()];
|
||||
// Scan for all files.
|
||||
$files = file_scan_directory($existing_directory, '/.*/');
|
||||
foreach ($files as $file) {
|
||||
// Skip files in the any existing configuration directory, as these
|
||||
// will be replaced.
|
||||
foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
|
||||
if (strpos($file->uri, $directory) !== FALSE) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// Merge in the info file.
|
||||
if ($file->name == $package->getMachineName() . '.info') {
|
||||
$files = $package->getFiles();
|
||||
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $file->uri);
|
||||
$package->setFiles($files);
|
||||
}
|
||||
// Read in remaining files.
|
||||
else {
|
||||
// Determine if the file is within a subdirectory of the
|
||||
// extension's directory.
|
||||
$file_directory = dirname($file->uri);
|
||||
if ($file_directory !== $existing_directory) {
|
||||
$subdirectory = substr($file_directory, strlen($existing_directory) + 1);
|
||||
}
|
||||
else {
|
||||
$subdirectory = NULL;
|
||||
}
|
||||
$package->appendFile([
|
||||
'filename' => $file->filename,
|
||||
'subdirectory' => $subdirectory,
|
||||
'string' => file_get_contents($file->uri)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
// Determine the best name for the tar archive.
|
||||
// Single package export, so name by package name.
|
||||
if (count($packages) == 1) {
|
||||
$filename = current($packages)->getMachineName();
|
||||
}
|
||||
// Profile export, so name by profile.
|
||||
elseif (isset($bundle) && $bundle->isProfile()) {
|
||||
$filename = $bundle->getProfileName();
|
||||
}
|
||||
// Non-default bundle, so name by bundle.
|
||||
elseif (isset($bundle) && !$bundle->isDefault()) {
|
||||
$filename = $bundle->getMachineName();
|
||||
}
|
||||
// Set a fallback name.
|
||||
else {
|
||||
$filename = 'generated_features';
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
$this->archiveName = $filename . '.tar.gz';
|
||||
$archive_name = file_directory_temp() . '/' . $this->archiveName;
|
||||
if (file_exists($archive_name)) {
|
||||
file_unmanaged_delete($archive_name);
|
||||
}
|
||||
|
||||
$archiver = new ArchiveTar($archive_name);
|
||||
|
||||
// Add package files.
|
||||
foreach ($packages as $package) {
|
||||
if (count($packages) == 1) {
|
||||
// Single module export, so don't generate entire modules dir structure.
|
||||
$package->setDirectory($package->getMachineName());
|
||||
}
|
||||
$this->generatePackage($return, $package, $archiver);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a package or profile's files to an archive.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param ArchiveTar $archiver
|
||||
* The archiver.
|
||||
*/
|
||||
protected function generatePackage(array &$return, Package $package, ArchiveTar $archiver) {
|
||||
$success = TRUE;
|
||||
foreach ($package->getFiles() as $file) {
|
||||
try {
|
||||
$this->generateFile($package->getDirectory(), $file, $archiver);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
$this->failure($return, $package, $exception);
|
||||
$success = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$this->success($return, $package);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a successful package or profile archive operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function success(array &$return, Package $package) {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => TRUE,
|
||||
// Archive writing doesn't merit a message, and if done through the UI
|
||||
// would appear on the subsequent page load.
|
||||
'display' => FALSE,
|
||||
'message' => '@type @package written to archive.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a failed package or profile archive operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param \Exception $exception
|
||||
* The exception object.
|
||||
* @param string $message
|
||||
* Error message when there isn't an Exception object.
|
||||
*/
|
||||
protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => FALSE,
|
||||
// Archive writing doesn't merit a message, and if done through the UI
|
||||
// would appear on the subsequent page load.
|
||||
'display' => FALSE,
|
||||
'message' => '@type @package not written to archive. Error: @error.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@error' => isset($exception) ? $exception->getMessage() : $message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to the file system, creating its directory as needed.
|
||||
*
|
||||
* @param string $directory
|
||||
* The extension's directory.
|
||||
* @param array $file
|
||||
* Array with the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
* @param ArchiveTar $archiver
|
||||
* The archiver.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function generateFile($directory, array $file, ArchiveTar $archiver) {
|
||||
$filename = $directory;
|
||||
if (!empty($file['subdirectory'])) {
|
||||
$filename .= '/' . $file['subdirectory'];
|
||||
}
|
||||
$filename .= '/' . $file['filename'];
|
||||
// Set the mode to 0644 rather than the default of 0600.
|
||||
if ($archiver->addString($filename, $file['string'], FALSE, ['mode' => 0644]) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to archive file @filename.', ['@filename' => $file['filename']]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state) {
|
||||
// Redirect to the archive file download.
|
||||
$form_state->setRedirect('features.export_download', ['uri' => $this->archiveName, 'token' => $this->csrfToken->get($this->archiveName)]);
|
||||
}
|
||||
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesGeneration;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\features\FeaturesGenerationMethodBase;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class for writing packages to the local file system.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite::METHOD_ID,
|
||||
* weight = 2,
|
||||
* name = @Translation("Write"),
|
||||
* description = @Translation("Write packages and optional profile to the file system."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The package generation method id.
|
||||
*/
|
||||
const METHOD_ID = 'write';
|
||||
|
||||
/**
|
||||
* The app root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Creates a new FeaturesGenerationWrite instance.
|
||||
*
|
||||
* @param string $root
|
||||
* The app root.
|
||||
*/
|
||||
public function __construct($root) {
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('app.root')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and merges in existing files for a given package or profile.
|
||||
*
|
||||
* @param \Drupal\features\Package &$package
|
||||
* The package.
|
||||
* @param array $existing_packages
|
||||
* An array of existing packages.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle the package belongs to.
|
||||
*/
|
||||
protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
|
||||
// If this package is already present, prepare files.
|
||||
if (isset($existing_packages[$package->getMachineName()])) {
|
||||
$existing_directory = $existing_packages[$package->getMachineName()];
|
||||
|
||||
$package->setDirectory($existing_directory);
|
||||
|
||||
// Merge in the info file.
|
||||
$info_file_uri = $this->root . '/' . $existing_directory . '/' . $package->getMachineName() . '.info.yml';
|
||||
if (file_exists($info_file_uri)) {
|
||||
$files = $package->getFiles();
|
||||
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $info_file_uri);
|
||||
$package->setFiles($files);
|
||||
}
|
||||
|
||||
// Remove the config directories, as they will be replaced.
|
||||
foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
|
||||
$config_directory = $this->root . '/' . $existing_directory . '/' . $directory;
|
||||
if (is_dir($config_directory)) {
|
||||
file_unmanaged_delete_recursive($config_directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
// Add package files.
|
||||
// We need to update the system.module.files state because it's cached.
|
||||
// Cannot just call system_rebuild_module_data() because $listing->scan() has
|
||||
// it's own internal static cache that we cannot clear at this point.
|
||||
$files = \Drupal::state()->get('system.module.files');
|
||||
foreach ($packages as $package) {
|
||||
$this->generatePackage($return, $package);
|
||||
if (!isset($files[$package->getMachineName()]) && isset($package->getFiles()['info'])) {
|
||||
$files[$package->getMachineName()] = $package->getDirectory() . '/' . $package->getFiles()['info']['filename'];
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild system module cache
|
||||
\Drupal::state()->set('system.module.files', $files);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a package or profile's files to the file system.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function generatePackage(array &$return, Package $package) {
|
||||
if (!$package->getFiles()) {
|
||||
$this->failure($return, $package, NULL, $this->t('No configuration was selected to be exported.'));
|
||||
return;
|
||||
}
|
||||
$success = TRUE;
|
||||
foreach ($package->getFiles() as $file) {
|
||||
try {
|
||||
$this->generateFile($package->getDirectory(), $file);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
$this->failure($return, $package, $exception);
|
||||
$success = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$this->success($return, $package);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a successful package or profile write operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function success(array &$return, Package $package) {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => TRUE,
|
||||
'display' => TRUE,
|
||||
'message' => '@type @package written to @directory.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@directory' => $package->getDirectory(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a failed package or profile write operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param \Exception $exception
|
||||
* The exception object.
|
||||
* @param string $message
|
||||
* Error message when there isn't an Exception object.
|
||||
*/
|
||||
protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => FALSE,
|
||||
'display' => TRUE,
|
||||
'message' => '@type @package not written to @directory. Error: @error.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@directory' => $package->getDirectory(),
|
||||
'@error' => isset($exception) ? $exception->getMessage() : $message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to the file system, creating its directory as needed.
|
||||
*
|
||||
* @param string $directory
|
||||
* The extension's directory.
|
||||
* @param array $file
|
||||
* Array with the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function generateFile($directory, array $file) {
|
||||
if (!empty($file['subdirectory'])) {
|
||||
$directory .= '/' . $file['subdirectory'];
|
||||
}
|
||||
$directory = $this->root . '/' . $directory;
|
||||
if (!is_dir($directory)) {
|
||||
if (drupal_mkdir($directory, NULL, TRUE) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to create directory @directory.', ['@directory' => $directory]));
|
||||
}
|
||||
}
|
||||
if (file_put_contents($directory . '/' . $file['filename'], $file['string']) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to write file @filename.', ['@filename' => $file['filename']]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\features\FeaturesConfigInstaller' "modules/contrib/features/src".
|
||||
*/
|
||||
|
||||
namespace Drupal\features\ProxyClass {
|
||||
|
||||
/**
|
||||
* Provides a proxy class for \Drupal\features\FeaturesConfigInstaller.
|
||||
*
|
||||
* @see \Drupal\Component\ProxyBuilder
|
||||
*/
|
||||
class FeaturesConfigInstaller implements \Drupal\Core\Config\ConfigInstallerInterface
|
||||
{
|
||||
|
||||
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The id of the original proxied service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $drupalProxyOriginalServiceId;
|
||||
|
||||
/**
|
||||
* The real proxied service, after it was lazy loaded.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesConfigInstaller
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* The service container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructs a ProxyClass Drupal proxy object.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The container.
|
||||
* @param string $drupal_proxy_original_service_id
|
||||
* The service ID of the original service.
|
||||
*/
|
||||
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy loads the real service from the container.
|
||||
*
|
||||
* @return object
|
||||
* Returns the constructed real service.
|
||||
*/
|
||||
protected function lazyLoadItself()
|
||||
{
|
||||
if (!isset($this->service)) {
|
||||
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installDefaultConfig($type, $name)
|
||||
{
|
||||
return $this->lazyLoadItself()->installDefaultConfig($type, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installOptionalConfig(\Drupal\Core\Config\StorageInterface $storage = NULL, $dependency = array (
|
||||
))
|
||||
{
|
||||
return $this->lazyLoadItself()->installOptionalConfig($storage, $dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection)
|
||||
{
|
||||
return $this->lazyLoadItself()->installCollectionDefaultConfig($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSourceStorage(\Drupal\Core\Config\StorageInterface $storage)
|
||||
{
|
||||
return $this->lazyLoadItself()->setSourceStorage($storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceStorage()
|
||||
{
|
||||
return $this->lazyLoadItself()->getSourceStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSyncing($status)
|
||||
{
|
||||
return $this->lazyLoadItself()->setSyncing($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSyncing()
|
||||
{
|
||||
return $this->lazyLoadItself()->isSyncing();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkConfigurationToInstall($type, $name)
|
||||
{
|
||||
return $this->lazyLoadItself()->checkConfigurationToInstall($type, $name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
threshold:
|
||||
requirements_warning: 172800
|
||||
requirements_error: 1209600
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
bundle: test
|
||||
excluded:
|
||||
- system.theme
|
||||
required: true
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
name: feature
|
||||
type: module
|
||||
# core: 8.x
|
||||
package: Test
|
||||
# Force this to install after features.
|
||||
dependencies:
|
||||
- features
|
||||
|
||||
# Information added by Drupal.org packaging script on 2016-09-02
|
||||
version: '8.x-3.0-beta8'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1472847281
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Kernel\Entity;
|
||||
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\features\Entity\FeaturesBundle
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesBundleIntegrationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['features'];
|
||||
|
||||
public function testCrud() {
|
||||
$bundle = FeaturesBundle::create([
|
||||
'machine_name' => 'test',
|
||||
'name' => 'Test',
|
||||
]);
|
||||
$bundle->save();
|
||||
|
||||
/** @var \Drupal\features\Entity\FeaturesBundle $bundle */
|
||||
$bundle = FeaturesBundle::load('test');
|
||||
$this->assertEquals('Test', $bundle->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::isDefault
|
||||
*/
|
||||
public function testIsDefaultWithDefaultBundle() {
|
||||
$bundle = FeaturesBundle::create([
|
||||
'machine_name' => FeaturesBundleInterface::DEFAULT_BUNDLE,
|
||||
]);
|
||||
|
||||
$this->assertTrue($bundle->isDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::isDefault
|
||||
*/
|
||||
public function testIsDefaultWithNonDefaultBundle() {
|
||||
$bundle = FeaturesBundle::create([
|
||||
'machine_name' => 'other',
|
||||
]);
|
||||
|
||||
$this->assertFalse($bundle->isDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFullName
|
||||
*/
|
||||
public function testGetFullName() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getShortName
|
||||
*/
|
||||
public function testGetShortName() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getProfileName
|
||||
* @covers ::setProfileName
|
||||
*/
|
||||
public function testGetProfile() {
|
||||
$bundle = FeaturesBundle::create([
|
||||
'machine_name' => 'other',
|
||||
'profile_name' => 'example',
|
||||
'is_profile' => TRUE,
|
||||
]);
|
||||
$this->assertEquals('example', $bundle->getProfileName());
|
||||
|
||||
$bundle->setProfileName('example2');
|
||||
$this->assertEquals('example2', $bundle->getProfileName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\features\ConfigurationItem;
|
||||
|
||||
/**
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesAssignTest extends KernelTestBase {
|
||||
|
||||
const PACKAGE_NAME = 'my_test_package';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['features', 'node', 'system', 'user'];
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesManager
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesAssigner
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* @todo Remove the disabled strict config schema checking.
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig('features');
|
||||
$this->installConfig('system');
|
||||
\Drupal::configFactory()->getEditable('features.settings')
|
||||
->set('assignment.enabled', [])
|
||||
->set('bundle.settings', [])
|
||||
->save();
|
||||
|
||||
$this->featuresManager = \Drupal::service('features.manager');
|
||||
$this->assigner = \Drupal::service('features_assigner');
|
||||
|
||||
// Start with an empty configuration collection.
|
||||
$this->featuresManager->setConfigCollection([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentBaseType
|
||||
*/
|
||||
public function testAssignBase() {
|
||||
$method_id = 'base';
|
||||
|
||||
// Enable the method.
|
||||
$this->enableAssignmentMethod($method_id);
|
||||
|
||||
// Test the default options for the base assignment method.
|
||||
|
||||
// Test node type assignments.
|
||||
// Declare the node_type entity 'article'.
|
||||
$this->addConfigurationItem('node.type.article', [], [
|
||||
'shortName' => 'article',
|
||||
'label' => 'Article',
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['field.field.node.article.body'],
|
||||
]);
|
||||
|
||||
// Add a piece of dependent configuration.
|
||||
$this->addConfigurationItem('field.field.node.article.body', [], [
|
||||
'shortName' => 'node.article.body',
|
||||
'label' => 'Body',
|
||||
'type' => 'field_config',
|
||||
'dependents' => [],
|
||||
]);
|
||||
|
||||
$this->assigner->applyAssignmentMethod($method_id);
|
||||
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
$expected_package_names = ['article', 'user'];
|
||||
|
||||
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
|
||||
|
||||
$expected_config_items = [
|
||||
'node.type.article',
|
||||
'field.field.node.article.body',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected_config_items, $packages['article']->getConfig(), 'Expected configuration items not present in article package.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentCoreType
|
||||
*/
|
||||
public function testAssignCore() {
|
||||
$method_id = 'core';
|
||||
|
||||
// Enable the method.
|
||||
$this->enableAssignmentMethod($method_id);
|
||||
|
||||
// Test the default options for the core assignment method.
|
||||
|
||||
// Add a piece of configuration of a core type.
|
||||
$this->addConfigurationItem('field.storage.node.body', [], [
|
||||
'shortName' => 'node.body',
|
||||
'label' => 'node.body',
|
||||
'type' => 'field_storage_config',
|
||||
'dependents' => ['field.field.node.article.body'],
|
||||
]);
|
||||
|
||||
// Add a piece of configuration of a non-core type.
|
||||
$this->addConfigurationItem('field.field.node.article.body', [], [
|
||||
'shortName' => 'node.article.body',
|
||||
'label' => 'Body',
|
||||
'type' => 'field_config',
|
||||
'dependents' => [],
|
||||
]);
|
||||
|
||||
$this->assigner->applyAssignmentMethod($method_id);
|
||||
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
$expected_package_names = ['core'];
|
||||
|
||||
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
|
||||
|
||||
$this->assertTrue(in_array('field.storage.node.body', $packages['core']->getConfig(), 'Expected configuration item not present in core package.'));
|
||||
$this->assertFalse(in_array('field.field.node.article.body', $packages['core']->getConfig(), 'Unexpected configuration item present in core package.'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentDependency
|
||||
*/
|
||||
public function testAssignDependency() {
|
||||
$method_id = 'dependency';
|
||||
|
||||
// Enable the method.
|
||||
$this->enableAssignmentMethod($method_id);
|
||||
|
||||
// Test the default options for the base assignment method.
|
||||
|
||||
// Test node type assignments.
|
||||
// Declare the node_type entity 'article'.
|
||||
$this->addConfigurationItem('node.type.article', [], [
|
||||
'shortName' => 'article',
|
||||
'label' => 'Article',
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['field.field.node.article.body'],
|
||||
]);
|
||||
|
||||
// Add a piece of dependent configuration.
|
||||
$this->addConfigurationItem('field.field.node.article.body', [], [
|
||||
'shortName' => 'node.article.body',
|
||||
'label' => 'Body',
|
||||
'type' => 'field_config',
|
||||
'dependents' => [],
|
||||
]);
|
||||
|
||||
$this->featuresManager->initPackage(self::PACKAGE_NAME, 'My test package');
|
||||
$this->featuresManager->assignConfigPackage(self::PACKAGE_NAME, ['node.type.article']);
|
||||
|
||||
$this->assigner->applyAssignmentMethod($method_id);
|
||||
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
$expected_package_names = [self::PACKAGE_NAME];
|
||||
|
||||
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
|
||||
|
||||
$expected_config_items = [
|
||||
'node.type.article',
|
||||
'field.field.node.article.body',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected_config_items, $packages[self::PACKAGE_NAME]->getConfig(), 'Expected configuration items not present in article package.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentForwardDependency
|
||||
*/
|
||||
public function testAssignForwardDependency() {
|
||||
$method_id = 'forward_dependency';
|
||||
|
||||
// Enable the method.
|
||||
$this->enableAssignmentMethod($method_id);
|
||||
|
||||
// Add some configuration.
|
||||
// Two parent items.
|
||||
$this->addConfigurationItem('parent1', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['grandparent'],
|
||||
]);
|
||||
$this->addConfigurationItem('parent2', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => [],
|
||||
]);
|
||||
// Something that belongs to just one parent.
|
||||
$this->addConfigurationItem('child1', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['parent1'],
|
||||
]);
|
||||
// Something that belongs to both parents.
|
||||
$this->addConfigurationItem('child2', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['parent1', 'parent2'],
|
||||
]);
|
||||
// Something that indirectly belongs to parent1.
|
||||
$this->addConfigurationItem('grandchild', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => ['child1'],
|
||||
]);
|
||||
// A dependent, not a dependency.
|
||||
$this->addConfigurationItem('grandparent', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => [],
|
||||
]);
|
||||
// Something completely unrelated.
|
||||
$this->addConfigurationItem('stranger', [], [
|
||||
'type' => 'node_type',
|
||||
'dependents' => [],
|
||||
]);
|
||||
|
||||
$this->featuresManager->initPackage(self::PACKAGE_NAME, 'My test package');
|
||||
$this->featuresManager->assignConfigPackage(self::PACKAGE_NAME, ['parent1']);
|
||||
|
||||
$other_package_name = 'other_package';
|
||||
$this->featuresManager->initPackage($other_package_name, 'Other package');
|
||||
$this->featuresManager->assignConfigPackage($other_package_name, ['parent2']);
|
||||
|
||||
$this->assigner->applyAssignmentMethod($method_id);
|
||||
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
$expected_package_names = [self::PACKAGE_NAME, $other_package_name];
|
||||
sort($expected_package_names);
|
||||
$actual_package_names = array_keys($packages);
|
||||
sort($actual_package_names);
|
||||
$this->assertEquals($expected_package_names, $actual_package_names, 'Expected packages not created.');
|
||||
|
||||
$expected_config_items = [
|
||||
'parent1',
|
||||
'child1',
|
||||
'grandchild',
|
||||
];
|
||||
sort($expected_config_items);
|
||||
$actual_config_items = $packages[self::PACKAGE_NAME]->getConfig();
|
||||
sort($actual_config_items);
|
||||
|
||||
$this->assertEquals($expected_config_items, $actual_config_items, 'Expected configuration items not present in article package.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a specified assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of an assignment method.
|
||||
* @param bool $exclusive
|
||||
* (optional) Whether to set the method as the only enabled method.
|
||||
* Defaults to TRUE.
|
||||
*/
|
||||
protected function enableAssignmentMethod($method_id, $exclusive = TRUE) {
|
||||
$settings = \Drupal::configFactory()->getEditable('features.settings');
|
||||
if ($exclusive) {
|
||||
$settings->set('assignment.enabled', [$method_id]);
|
||||
}
|
||||
else {
|
||||
$enabled = $settings->get('assignment.enabled');
|
||||
if (!in_array($method_id, $enabled)) {
|
||||
$enabled[] = $method_id;
|
||||
}
|
||||
$settings->set('assignment.enabled', $enabled);
|
||||
}
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a configuration item.
|
||||
*
|
||||
* @param string $name
|
||||
* The config name.
|
||||
* @param array $data
|
||||
* The config data.
|
||||
* @param array $properties
|
||||
* (optional) Additional properties set on the object.
|
||||
*/
|
||||
protected function addConfigurationItem($name, array $data = [], array $properties = []) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$config_collection[$name] = new ConfigurationItem($name, $data, $properties);
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Kernel;
|
||||
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesAssignerTest extends KernelTestBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'config'];
|
||||
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* Test bundle auto-creation during config import.
|
||||
*
|
||||
* We check the case where the import also causes features to be installed,
|
||||
* so at the time auto-creation happens there's not yet a default bundle.
|
||||
*/
|
||||
public function testBundleAutoCreationImport() {
|
||||
// Install the feature.
|
||||
$installer = $this->container->get('module_installer');
|
||||
// Have to do these separately so features_modules_installed() doesn't
|
||||
// just exit.
|
||||
$installer->install(['features']);
|
||||
$installer->install(['test_feature']);
|
||||
|
||||
// Save config.
|
||||
$this->copyConfig(
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.storage.sync')
|
||||
);
|
||||
|
||||
// Uninstall modules.
|
||||
$installer->uninstall(['features', 'test_feature']);
|
||||
|
||||
// Restore the config from after install..
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Find the auto-created bundle.
|
||||
$bundle_storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('features_bundle');
|
||||
$bundle = $bundle_storage->load('test');
|
||||
$this->assertNotNull($bundle, "Features bundle doesn't exist");
|
||||
$this->assertContains(
|
||||
'Auto-generated bundle',
|
||||
$bundle->getDescription(),
|
||||
"Features bundle not auto-created");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Kernel;
|
||||
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
|
||||
/**
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesGenerateTest extends KernelTestBase {
|
||||
|
||||
const PACKAGE_NAME = 'my_test_package';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['features', 'system'];
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesGeneratorInterface
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig('features');
|
||||
$this->installConfig('system');
|
||||
\Drupal::configFactory()->getEditable('features.settings')
|
||||
->set('assignment.enabled', [])
|
||||
->set('bundle.settings', [])
|
||||
->save();
|
||||
|
||||
$this->featuresManager = \Drupal::service('features.manager');
|
||||
$this->generator = \Drupal::service('features_generator');
|
||||
$this->assigner = \Drupal::service('features_assigner');
|
||||
|
||||
$this->featuresManager->initPackage(self::PACKAGE_NAME, 'My test package');
|
||||
$package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
|
||||
$package->appendConfig('system.site');
|
||||
$this->featuresManager->setPackage($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive
|
||||
*/
|
||||
public function testExportArchive() {
|
||||
$filename = file_directory_temp() . '/' . self::PACKAGE_NAME . '.tar.gz';
|
||||
if (file_exists($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
$this->assertFalse(file_exists($filename), 'Archive file already exists.');
|
||||
|
||||
$this->generator->generatePackages('archive', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
|
||||
$this->assertTrue(file_exists($filename), 'Archive file was not generated.');
|
||||
}
|
||||
|
||||
public function testGeneratorWithBundle() {
|
||||
|
||||
$filename = file_directory_temp() . '/giraffe_' . self::PACKAGE_NAME . '.tar.gz';
|
||||
if (file_exists($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
$this->assertFalse(file_exists($filename), 'Archive file already exists.');
|
||||
|
||||
$bundle = FeaturesBundle::create([
|
||||
'machine_name' => 'giraffe'
|
||||
]);
|
||||
|
||||
$this->generator->generatePackages('archive', $bundle, [self::PACKAGE_NAME]);
|
||||
|
||||
$package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
|
||||
$this->assertNull($package);
|
||||
|
||||
$package = $this->featuresManager->getPackage('giraffe_' . self::PACKAGE_NAME);
|
||||
$this->assertEquals('giraffe_' . self::PACKAGE_NAME, $package->getMachineName());
|
||||
$this->assertEquals('giraffe', $package->getBundle());
|
||||
|
||||
$this->assertTrue(file_exists($filename), 'Archive file was not generated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite
|
||||
*/
|
||||
public function testExportWrite() {
|
||||
// Set a fake drupal root, so the testbot can also write into it.
|
||||
vfsStream::setup('drupal');
|
||||
\Drupal::getContainer()->set('app.root', 'vfs://drupal');
|
||||
|
||||
$package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
|
||||
// Find out where package will be exported
|
||||
list($full_name, $path) = $this->featuresManager->getExportInfo($package, $this->assigner->getBundle());
|
||||
$path = 'vfs://drupal/' . $path . '/' . $full_name;
|
||||
if (file_exists($path)) {
|
||||
file_unmanaged_delete_recursive($path);
|
||||
}
|
||||
$this->assertFalse(file_exists($path), 'Package directory already exists.');
|
||||
|
||||
$this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
|
||||
$this->assertTrue(file_exists($path), 'Package directory was not generated.');
|
||||
$this->assertTrue(file_exists($path . '/' . self::PACKAGE_NAME . '.info.yml'), 'Package info.yml not generated.');
|
||||
$this->assertTrue(file_exists($path . '/config/install'), 'Package config/install not generated.');
|
||||
$this->assertTrue(file_exists($path . '/config/install/system.site.yml'), 'Config.yml not exported.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Unit;
|
||||
use Drupal\features\ConfigurationItem;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\features\ConfigurationItem
|
||||
* @group features
|
||||
*/
|
||||
class ConfigurationItemTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::fromConfigStringToConfigType
|
||||
*/
|
||||
public function testFromConfigStringToConfigType() {
|
||||
$this->assertEquals('system.simple', ConfigurationItem::fromConfigStringToConfigType(FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG));
|
||||
$this->assertEquals('node', ConfigurationItem::fromConfigStringToConfigType('node'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Unit;
|
||||
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Prophecy\Prophet;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass Drupal\features\Entity\FeaturesBundle
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesBundleTest extends UnitTestCase {
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Mock an assigner.
|
||||
$manager = new DummyPluginManager();
|
||||
|
||||
// Mock the container.
|
||||
$container = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerInterface');
|
||||
$container->get('plugin.manager.features_assignment_method')
|
||||
->willReturn($manager);
|
||||
\Drupal::setContainer($container->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getEnabledAssignments
|
||||
* @covers ::getAssignmentWeights
|
||||
* @covers ::getAssignmentSettings
|
||||
* @covers ::setAssignmentSettings
|
||||
* @covers ::setAssignmentWeights
|
||||
* @covers ::setEnabledAssignments
|
||||
*/
|
||||
public function testAssignmentSetting() {
|
||||
// Create an entity.
|
||||
$settings = [
|
||||
'foo' => [
|
||||
'enabled' => TRUE,
|
||||
'weight' => 0,
|
||||
'my_setting' => 42,
|
||||
],
|
||||
'bar' => [
|
||||
'enabled' => FALSE,
|
||||
'weight' => 1,
|
||||
'another_setting' => 'value',
|
||||
],
|
||||
];
|
||||
$bundle = new FeaturesBundle([
|
||||
'assignments' => $settings,
|
||||
], 'features_bundle');
|
||||
|
||||
// Get assignments and attributes.
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getEnabledAssignments(),
|
||||
['foo' => 'foo'],
|
||||
'Can get enabled assignments'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentWeights(),
|
||||
['foo' => 0, 'bar' => 1],
|
||||
'Can get assignment weights'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings('foo'),
|
||||
$settings['foo'],
|
||||
'Can get assignment settings'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings(),
|
||||
$settings,
|
||||
'Can get all assignment settings'
|
||||
);
|
||||
|
||||
// Change settings.
|
||||
$settings['foo']['my_setting'] = 97;
|
||||
$bundle->setAssignmentSettings('foo', $settings['foo']);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings('foo'),
|
||||
$settings['foo'],
|
||||
'Can change assignment settings'
|
||||
);
|
||||
|
||||
// Change weights.
|
||||
$settings['foo']['weight'] = 1;
|
||||
$settings['bar']['weight'] = 0;
|
||||
$bundle->setAssignmentWeights(['foo' => 1, 'bar' => 0]);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentWeights(),
|
||||
['foo' => 1, 'bar' => 0],
|
||||
'Can change assignment weights'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings(),
|
||||
$settings,
|
||||
'Weight changes are reflected in settings'
|
||||
);
|
||||
|
||||
// Enable existing assignment.
|
||||
$settings['bar']['enabled'] = TRUE;
|
||||
$bundle->setEnabledAssignments(['foo', 'bar']);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getEnabledAssignments(),
|
||||
['foo' => 'foo', 'bar' => 'bar'],
|
||||
'Can enable assignment'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings(),
|
||||
$settings,
|
||||
'Enabled assignment status is reflected in settings'
|
||||
);
|
||||
|
||||
// Disable existing assignments.
|
||||
$settings['foo']['enabled'] = FALSE;
|
||||
$settings['bar']['enabled'] = FALSE;
|
||||
$bundle->setEnabledAssignments([]);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getEnabledAssignments(),
|
||||
[],
|
||||
'Can disable assignments'
|
||||
);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings(),
|
||||
$settings,
|
||||
'Disabled assignment status is reflected in settings'
|
||||
);
|
||||
|
||||
// Enable a new assignment.
|
||||
$settings['foo']['enabled'] = TRUE;
|
||||
$settings['iggy'] = ['enabled' => TRUE, 'weight' => 0, 'new_setting' => 3];
|
||||
$bundle->setEnabledAssignments(['foo', 'iggy']);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getEnabledAssignments(),
|
||||
['foo' => 'foo', 'iggy' => 'iggy'],
|
||||
'Can enable new assignment'
|
||||
);
|
||||
$bundle->setAssignmentSettings('iggy', $settings['iggy']);
|
||||
$this->assertArrayEquals(
|
||||
$bundle->getAssignmentSettings(),
|
||||
$settings,
|
||||
'New enabled assignment status is reflected in settings'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy plugin manager, to help testing.
|
||||
*/
|
||||
class DummyPluginManager {
|
||||
public function getDefinition($method_id) {
|
||||
$definition = [
|
||||
'enabled' => TRUE,
|
||||
'weight' => 0,
|
||||
'default_settings' => [
|
||||
'my_setting' => 42,
|
||||
],
|
||||
];
|
||||
return $definition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,769 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Unit;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\config_update\ConfigDiffInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ConfigManagerInterface;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Extension\InfoParser;
|
||||
use Drupal\Core\Extension\InfoParserInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
use Drupal\features\FeaturesAssignerInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\features\ConfigurationItem;
|
||||
use Drupal\features\FeaturesExtensionStoragesInterface;
|
||||
use Drupal\features\FeaturesManager;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
use Drupal\features\Package;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass Drupal\features\FeaturesManager
|
||||
* @group features
|
||||
*/
|
||||
class FeaturesManagerTest extends UnitTestCase {
|
||||
/**
|
||||
* @var string
|
||||
* The name of the install profile.
|
||||
*/
|
||||
const PROFILE_NAME = 'my_profile';
|
||||
|
||||
/**
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\StorageInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$entity_type = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
|
||||
$entity_type->expects($this->any())
|
||||
->method('getConfigPrefix')
|
||||
->willReturn('custom');
|
||||
$this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
|
||||
$this->entityManager->expects($this->any())
|
||||
->method('getDefinition')
|
||||
->willReturn($entity_type);
|
||||
$this->configFactory = $this->getMock(ConfigFactoryInterface::class);
|
||||
$this->configStorage = $this->getMock(StorageInterface::class);
|
||||
$this->configManager = $this->getMock(ConfigManagerInterface::class);
|
||||
$this->moduleHandler = $this->getMock(ModuleHandlerInterface::class);
|
||||
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
|
||||
$string_translation = $this->getStringTranslationStub();
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('string_translation', $string_translation);
|
||||
$container->set('app.root', $this->root);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getActiveStorage
|
||||
*/
|
||||
public function testGetActiveStorage() {
|
||||
$this->assertInstanceOf('\Drupal\Core\Config\StorageInterface', $this->featuresManager->getActiveStorage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getExtensionStorages
|
||||
*/
|
||||
public function testGetExtensionStorages() {
|
||||
$this->assertInstanceOf('\Drupal\features\FeaturesExtensionStoragesInterface', $this->featuresManager->getExtensionStorages());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFullName
|
||||
* @dataProvider providerTestGetFullName
|
||||
*/
|
||||
public function testGetFullName($type, $name, $expected) {
|
||||
$this->assertEquals($this->featuresManager->getFullName($type, $name), $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testGetFullName().
|
||||
*/
|
||||
public function providerTestGetFullName() {
|
||||
return [
|
||||
[NULL, 'name', 'name'],
|
||||
[FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG, 'name', 'name'],
|
||||
['custom', 'name', 'custom.name'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getPackage
|
||||
* @covers ::getPackages
|
||||
* @covers ::reset
|
||||
* @covers ::setPackages
|
||||
*/
|
||||
public function testPackages() {
|
||||
$packages = ['foo' => 'bar'];
|
||||
$this->featuresManager->setPackages($packages);
|
||||
$this->assertEquals($packages, $this->featuresManager->getPackages());
|
||||
$this->assertEquals('bar', $this->featuresManager->getPackage('foo'));
|
||||
$this->featuresManager->reset();
|
||||
$this->assertArrayEquals([], $this->featuresManager->getPackages());
|
||||
$this->assertNull($this->featuresManager->getPackage('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setConfigCollection
|
||||
* @covers ::getConfigCollection
|
||||
*/
|
||||
public function testConfigCollection() {
|
||||
$config = ['config' => new ConfigurationItem('', [])];
|
||||
$this->featuresManager->setConfigCollection($config);
|
||||
$this->assertArrayEquals($config, $this->featuresManager->getConfigCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setPackage
|
||||
*/
|
||||
public function testSetPackage() {
|
||||
// @todo
|
||||
}
|
||||
|
||||
protected function getAssignInterPackageDependenciesConfigCollection() {
|
||||
$config_collection = [];
|
||||
$config_collection['example.config'] = (new ConfigurationItem('example.config', [
|
||||
'dependencies' => [
|
||||
'config' => [
|
||||
'example.config2',
|
||||
'example.config3',
|
||||
'example.config4',
|
||||
],
|
||||
],
|
||||
]))->setPackage('package');
|
||||
|
||||
$config_collection['example.config2'] = (new ConfigurationItem('example.config2', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setPackage('package2')
|
||||
->setProvider('my_feature');
|
||||
$config_collection['example.config3'] = (new ConfigurationItem('example.config3', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setProvider('my_other_feature');
|
||||
$config_collection['example.config4'] = (new ConfigurationItem('example.config3', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setProvider(static::PROFILE_NAME);
|
||||
return $config_collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignInterPackageDependencies
|
||||
*/
|
||||
public function testAssignInterPackageDependenciesWithoutBundle() {
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$bundle = $this->prophesize(FeaturesBundleInterface::class);
|
||||
// Provide a bundle without any prefix.
|
||||
$bundle->getFullName('package')->willReturn('package');
|
||||
$bundle->getFullName('package2')->willReturn('package2');
|
||||
$bundle->isDefault()->willReturn(TRUE);
|
||||
$assigner->getBundle('')->willReturn($bundle->reveal());
|
||||
// Use the wrapper because we need ::drupalGetProfile().
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setAssigner($assigner->reveal());
|
||||
|
||||
$features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection());
|
||||
|
||||
$packages = [
|
||||
'package' => new Package('package', [
|
||||
'config' => ['example.config', 'example.config3'],
|
||||
'dependencies' => [],
|
||||
'bundle' => '',
|
||||
]),
|
||||
'package2' => new Package('package2', [
|
||||
'config' => ['example.config2'],
|
||||
'dependencies' => [],
|
||||
'bundle' => '',
|
||||
]),
|
||||
];
|
||||
|
||||
$features_manager->setPackages($packages);
|
||||
// Dependencies require the full package names.
|
||||
$package_names = array_keys($packages);
|
||||
$features_manager->setPackageBundleNames($bundle->reveal(), $package_names);
|
||||
$packages = $features_manager->getPackages();
|
||||
$features_manager->assignInterPackageDependencies($bundle->reveal(), $packages);
|
||||
// example.config3 has a providing_feature but no assigned package.
|
||||
// my_package2 provides configuration required by configuration in
|
||||
// my_package.
|
||||
// Because package assignments take precedence over providing_feature ones,
|
||||
// package2 should have been assigned rather than my_feature.
|
||||
$this->assertEquals(['my_other_feature', 'package2'], $packages['package']->getDependencies());
|
||||
$this->assertEquals([], $packages['package2']->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignInterPackageDependencies
|
||||
*/
|
||||
public function testAssignInterPackageDependenciesWithBundle() {
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$bundle = $this->prophesize(FeaturesBundleInterface::class);
|
||||
// Provide a bundle without any prefix.
|
||||
$bundle->getFullName('package')->willReturn('giraffe_package');
|
||||
$bundle->getFullName('package2')->willReturn('giraffe_package2');
|
||||
$bundle->getFullName('giraffe_package')->willReturn('giraffe_package');
|
||||
$bundle->getFullName('giraffe_package2')->willReturn('giraffe_package2');
|
||||
$bundle->isDefault()->willReturn(FALSE);
|
||||
$bundle->getMachineName()->willReturn('giraffe');
|
||||
$assigner->getBundle('giraffe')->willReturn($bundle->reveal());
|
||||
// Use the wrapper because we need ::drupalGetProfile().
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setAssigner($assigner->reveal());
|
||||
$features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection());
|
||||
|
||||
$packages = [
|
||||
'package' => new Package('package', [
|
||||
'config' => ['example.config'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
'package2' => new Package('package2', [
|
||||
'config' => ['example.config2'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
];
|
||||
|
||||
$features_manager->setPackages($packages);
|
||||
// Dependencies require the full package names.
|
||||
$package_names = array_keys($packages);
|
||||
$features_manager->setPackageBundleNames($bundle->reveal(), $package_names);
|
||||
$packages = $features_manager->getPackages();
|
||||
$features_manager->assignInterPackageDependencies($bundle->reveal(), $packages);
|
||||
// example.config3 has a providing_feature but no assigned package.
|
||||
// my_package2 provides configuration required by configuration in
|
||||
// my_package.
|
||||
// Because package assignments take precedence over providing_feature ones,
|
||||
// package2 should have been assigned rather than my_feature.
|
||||
$expected = ['giraffe_package2', 'my_other_feature'];
|
||||
$this->assertEquals($expected, $packages['giraffe_package']->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignInterPackageDependencies
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage The packages have not yet been prefixed with a bundle name
|
||||
*/
|
||||
public function testAssignInterPackageDependenciesPrematureCall() {
|
||||
$bundle = $this->prophesize(FeaturesBundleInterface::class);
|
||||
$packages = [
|
||||
'package' => new Package('package', [
|
||||
'config' => ['example.config', 'example.config3'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
];
|
||||
|
||||
$this->featuresManager->assignInterPackageDependencies($bundle->reveal(), $packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::reset
|
||||
*/
|
||||
public function testReset() {
|
||||
$packages = [
|
||||
'package' => [
|
||||
'machine_name' => 'package',
|
||||
'config' => ['example.config', 'example.config3'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
],
|
||||
'package2' => [
|
||||
'machine_name' => 'package2',
|
||||
'config' => ['example.config2'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
],
|
||||
];
|
||||
$this->featuresManager->setPackages($packages);
|
||||
|
||||
$config_item = new ConfigurationItem('example', [], ['package' => 'package']);
|
||||
$config_item2 = new ConfigurationItem('example2', [], ['package' => 'package2']);
|
||||
$this->featuresManager->setConfigCollection([$config_item, $config_item2]);
|
||||
|
||||
$this->featuresManager->reset();
|
||||
$this->assertEmpty($this->featuresManager->getPackages());
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$this->assertEquals('', $config_collection[0]->getPackage());
|
||||
$this->assertEquals('', $config_collection[1]->getPackage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers ::detectMissing
|
||||
*/
|
||||
public function testDetectMissing() {
|
||||
$package = new Package('test-package', [
|
||||
'configOrig' => ['test_config', 'test_config_non_existing'],
|
||||
]);
|
||||
|
||||
$config_collection = [];
|
||||
$config_collection['test_config'] = new ConfigurationItem('test_config', []);
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$this->assertEquals(['test_config_non_existing'], $this->featuresManager->detectMissing($package));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::detectOverrides
|
||||
*/
|
||||
public function testDetectOverrides() {
|
||||
$config_diff = $this->prophesize(ConfigDiffInterface::class);
|
||||
$config_diff->same(Argument::cetera())->will(function($args) {
|
||||
return $args[0] == $args[1];
|
||||
});
|
||||
\Drupal::getContainer()->set('config_update.config_diff', $config_diff->reveal());
|
||||
|
||||
$package = new Package('test-package', [
|
||||
'config' => ['test_config', 'test_overridden'],
|
||||
]);
|
||||
|
||||
$config_storage = $this->prophesize(StorageInterface::class);
|
||||
$config_storage->read('test_config')->willReturn([
|
||||
'key' => 'value',
|
||||
]);
|
||||
$config_storage->read('test_overridden')->willReturn([
|
||||
'key2' => 'value2',
|
||||
]);
|
||||
|
||||
$extension_storage = $this->prophesize(FeaturesExtensionStoragesInterface::class);
|
||||
$extension_storage->read('test_config')->willReturn([
|
||||
'key' => 'value',
|
||||
]);
|
||||
$extension_storage->read('test_overridden')->willReturn([
|
||||
'key2' => 'value0',
|
||||
]);
|
||||
|
||||
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $config_storage->reveal(), $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setExtensionStorages($extension_storage->reveal());
|
||||
|
||||
$this->assertEquals(['test_overridden'], $features_manager->detectOverrides($package));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignConfigPackage
|
||||
*/
|
||||
public function testAssignConfigPackageWithNonProviderExcludedConfig() {
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$bundle = $this->prophesize(FeaturesBundleInterface::class);
|
||||
$bundle->isProfilePackage('test_package')->willReturn(FALSE);
|
||||
$assigner->getBundle(NULL)->willReturn($bundle->reveal());
|
||||
$this->featuresManager->setAssigner($assigner->reveal());
|
||||
|
||||
$config_collection = [
|
||||
'test_config' => new ConfigurationItem('test_config', []),
|
||||
'test_config2' => new ConfigurationItem('test_config2', [
|
||||
'dependencies' => [
|
||||
'module' => ['example'],
|
||||
]
|
||||
], [
|
||||
'subdirectory' => InstallStorage::CONFIG_INSTALL_DIRECTORY,
|
||||
]),
|
||||
];
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$package = new Package('test_package');
|
||||
$this->featuresManager->setPackage($package);
|
||||
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2']);
|
||||
|
||||
$this->assertEquals(['test_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig());
|
||||
$this->assertEquals(['example'], $this->featuresManager->getPackage('test_package')->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignConfigPackage
|
||||
*/
|
||||
public function testAssignConfigPackageWithProviderExcludedConfig() {
|
||||
$config_collection = [
|
||||
'test_config' => new ConfigurationItem('test_config', []),
|
||||
'test_config2' => new ConfigurationItem('test_config2', [], ['providerExcluded' => TRUE]),
|
||||
];
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$feature_assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$feature_assigner->getBundle(NULL)->willReturn(new FeaturesBundle(['machine_name' => 'default'], 'features_bundle'));
|
||||
$this->featuresManager->setAssigner($feature_assigner->reveal());
|
||||
|
||||
$package = new Package('test_package');
|
||||
$original_package = clone $package;
|
||||
|
||||
$this->featuresManager->setPackage($package);
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2']);
|
||||
$this->assertEquals(['test_config'], $this->featuresManager->getPackage('test_package')->getConfig(), 'just assign new packages');
|
||||
|
||||
$this->featuresManager->setPackage($original_package);
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2'], TRUE);
|
||||
$this->assertEquals(['test_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig(), 'just assign new packages');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::initPackageFromExtension
|
||||
* @covers ::getPackageObject
|
||||
*/
|
||||
public function testInitPackageFromNonInstalledExtension() {
|
||||
$extension = new Extension($this->root, 'module', 'modules/test_module/test_module.info.yml');
|
||||
|
||||
$info_parser = $this->prophesize(InfoParserInterface::class);
|
||||
$info_parser->parse($this->root . '/modules/test_module/test_module.info.yml')->willReturn([
|
||||
'name' => 'Test module',
|
||||
'description' => 'test description',
|
||||
'type' => 'module',
|
||||
]);
|
||||
\Drupal::getContainer()->set('info_parser', $info_parser->reveal());
|
||||
|
||||
$bundle = $this->prophesize(FeaturesBundle::class);
|
||||
$bundle->getShortName('test_module')->willReturn('test_module');
|
||||
$bundle->isDefault()->willReturn(TRUE);
|
||||
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$assigner->findBundle(Argument::cetera())->willReturn($bundle->reveal());
|
||||
$this->featuresManager->setAssigner($assigner->reveal());
|
||||
|
||||
$result = $this->featuresManager->initPackageFromExtension($extension);
|
||||
$this->assertInstanceOf(Package::class, $result);
|
||||
// Ensure that that calling the function twice works.
|
||||
$result = $this->featuresManager->initPackageFromExtension($extension);
|
||||
$this->assertInstanceOf(Package::class, $result);
|
||||
|
||||
$this->assertEquals('test_module', $result->getMachineName());
|
||||
$this->assertEquals('Test module', $result->getName());
|
||||
$this->assertEquals('test description', $result->getDescription());
|
||||
$this->assertEquals('module', $result->getType());
|
||||
|
||||
$this->assertEquals(FeaturesManagerInterface::STATUS_UNINSTALLED, $result->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::initPackageFromExtension
|
||||
* @covers ::getPackageObject
|
||||
*/
|
||||
public function testInitPackageFromInstalledExtension() {
|
||||
$extension = new Extension($this->root, 'module', 'modules/test_module/test_module.info.yml');
|
||||
|
||||
$info_parser = $this->prophesize(InfoParserInterface::class);
|
||||
$info_parser->parse($this->root . '/modules/test_module/test_module.info.yml')->willReturn([
|
||||
'name' => 'Test module',
|
||||
'description' => 'test description',
|
||||
'type' => 'module',
|
||||
]);
|
||||
\Drupal::getContainer()->set('info_parser', $info_parser->reveal());
|
||||
|
||||
$bundle = $this->prophesize(FeaturesBundle::class);
|
||||
$bundle->getShortName('test_module')->willReturn('test_module');
|
||||
$bundle->isDefault()->willReturn(TRUE);
|
||||
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$assigner->findBundle(Argument::cetera())->willReturn($bundle->reveal());
|
||||
$this->featuresManager->setAssigner($assigner->reveal());
|
||||
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('moduleExists')
|
||||
->with('test_module')
|
||||
->willReturn(TRUE);
|
||||
|
||||
$result = $this->featuresManager->initPackageFromExtension($extension);
|
||||
$this->assertEquals(FeaturesManagerInterface::STATUS_INSTALLED, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testDetectNewWithNoConfig() {
|
||||
$package = new Package('test_feature');
|
||||
|
||||
$this->assertEmpty($this->featuresManager->detectNew($package));
|
||||
}
|
||||
|
||||
public function testDetectNewWithNoNewConfig() {
|
||||
$package = new Package('test_feature', ['config' => ['test_config']]);
|
||||
|
||||
$extension_storage = $this->prophesize(FeaturesExtensionStoragesInterface::class);
|
||||
$extension_storage->read('test_config')->willReturn([
|
||||
'key' => 'value',
|
||||
]);
|
||||
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setExtensionStorages($extension_storage->reveal());
|
||||
|
||||
$this->assertEmpty($features_manager->detectNew($package));
|
||||
}
|
||||
|
||||
public function testDetectNewWithNewConfig() {
|
||||
$package = new Package('test_feature', ['config' => ['test_config']]);
|
||||
|
||||
$extension_storage = $this->prophesize(FeaturesExtensionStoragesInterface::class);
|
||||
$extension_storage->read('test_config')->willReturn(FALSE);
|
||||
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setExtensionStorages($extension_storage->reveal());
|
||||
|
||||
$this->assertEquals(['test_config'], $features_manager->detectNew($package));
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo This could have of course much more test coverage.
|
||||
*
|
||||
* @covers ::mergeInfoArray
|
||||
*
|
||||
* @dataProvider providerTestMergeInfoArray
|
||||
*/
|
||||
public function testMergeInfoArray($expected, $info1, $info2, $keys = []) {
|
||||
$this->assertSame($expected, $this->featuresManager->mergeInfoArray($info1, $info2, $keys));
|
||||
}
|
||||
|
||||
public function providerTestMergeInfoArray() {
|
||||
$data = [];
|
||||
$data['empty-info'] = [[], [], []];
|
||||
$data['override-info'] = [
|
||||
['name' => 'New name', 'core' => '8.x'],
|
||||
['name' => 'Old name', 'core' => '8.x'],
|
||||
['name' => 'New name']
|
||||
];
|
||||
$data['dependency-merging'] = [
|
||||
['dependencies' => ['a', 'b', 'c', 'd', 'e']],
|
||||
['dependencies' => ['b', 'd', 'c']],
|
||||
['dependencies' => ['a', 'b', 'e']],
|
||||
[],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::initPackage
|
||||
**/
|
||||
public function testInitPackageWithNewPackage() {
|
||||
$bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle');
|
||||
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
$features_manager->setAllModules([]);
|
||||
|
||||
$package = $features_manager->initPackage('test_feature', 'test name', 'test description', 'module', $bundle);
|
||||
|
||||
$this->assertInstanceOf(Package::class, $package);
|
||||
$this->assertEquals('test_feature', $package->getMachineName());
|
||||
$this->assertEquals('test name', $package->getName());
|
||||
$this->assertEquals('test description', $package->getDescription());
|
||||
$this->assertEquals('module', $package->getType());
|
||||
$this->assertEquals(['bundle' => 'test'], $package->getFeaturesInfo());
|
||||
$this->assertEquals('test', $package->getBundle());
|
||||
$this->assertEquals(FALSE, $package->getRequired());
|
||||
$this->assertEquals([], $package->getExcluded());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFeaturesInfo
|
||||
* @covers ::getFeaturesModules
|
||||
**/
|
||||
public function testInitPackageWithExistingPackage() {
|
||||
$bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle');
|
||||
|
||||
$features_manager = new TestFeaturesManager('vfs://drupal', $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
|
||||
vfsStream::setup('drupal');
|
||||
\Drupal::getContainer()->set('app.root', 'vfs://drupal');
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test_feature' => [
|
||||
'test_feature.info.yml' => <<<EOT
|
||||
name: Test feature 2
|
||||
type: module
|
||||
core: 8.x
|
||||
description: test description 2
|
||||
EOT
|
||||
,
|
||||
'test_feature.features.yml' => <<<EOT
|
||||
bundle: test
|
||||
excluded:
|
||||
- system.theme
|
||||
required: true
|
||||
EOT
|
||||
,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$extension = new Extension('vfs://drupal', 'module', 'modules/test_feature/test_feature.info.yml');
|
||||
$features_manager->setAllModules(['test_feature' => $extension]);
|
||||
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('exists')
|
||||
->with('test_feature')
|
||||
->willReturn(TRUE);
|
||||
|
||||
$info_parser = new InfoParser();
|
||||
\Drupal::getContainer()->set('info_parser', $info_parser);
|
||||
|
||||
$package = $features_manager->initPackage('test_feature', 'test name', 'test description', 'module', $bundle);
|
||||
|
||||
$this->assertEquals([
|
||||
'bundle' => 'test',
|
||||
'excluded' => [
|
||||
0 => 'system.theme',
|
||||
],
|
||||
'required' => TRUE,
|
||||
], $features_manager->getFeaturesInfo($extension));
|
||||
$this->assertEquals(['test_feature' => $extension], $features_manager->getFeaturesModules($bundle));
|
||||
|
||||
$this->assertInstanceOf(Package::class, $package);
|
||||
$this->assertEquals([
|
||||
'bundle' => 'test',
|
||||
'excluded' => [
|
||||
0 => 'system.theme',
|
||||
],
|
||||
'required' => TRUE,
|
||||
], $package->getFeaturesInfo());
|
||||
$this->assertEquals('test', $package->getBundle());
|
||||
$this->assertEquals(TRUE, $package->getRequired());
|
||||
$this->assertEquals(['system.theme'], $package->getExcluded());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::prepareFiles
|
||||
*/
|
||||
public function testPrepareFiles() {
|
||||
$packages = [];
|
||||
$packages['test_feature'] = new Package('test_feature', [
|
||||
'config' => ['test_config'],
|
||||
'name' => 'Test feature',
|
||||
]);
|
||||
|
||||
$config_collection = [];
|
||||
$config_collection['test_config'] = new ConfigurationItem('test_config', ['foo' => 'bar']);
|
||||
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
$this->featuresManager->prepareFiles($packages);
|
||||
|
||||
$files = $packages['test_feature']->getFiles();
|
||||
$this->assertCount(3, $files);
|
||||
$this->assertEquals('test_feature.info.yml', $files['info']['filename']);
|
||||
$this->assertEquals(Yaml::encode([
|
||||
'name' => 'Test feature',
|
||||
'type' => 'module',
|
||||
'core' => '8.x',
|
||||
]), $files['info']['string']);
|
||||
$this->assertEquals(Yaml::encode(TRUE), $files['features']['string']);
|
||||
|
||||
$this->assertEquals('test_config.yml', $files['test_config']['filename']);
|
||||
$this->assertEquals(Yaml::encode([
|
||||
'foo' => 'bar'
|
||||
]), $files['test_config']['string']);
|
||||
|
||||
$this->assertEquals('test_feature.features.yml', $files['features']['filename']);
|
||||
$this->assertEquals(Yaml::encode(TRUE), $files['features']['string']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getExportInfo
|
||||
*/
|
||||
public function testGetExportInfoWithoutBundle() {
|
||||
$config_factory = $this->getConfigFactoryStub([
|
||||
'features.settings' => [
|
||||
'export' => [
|
||||
'folder' => 'custom',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
|
||||
$package = new Package('test_feature');
|
||||
$result = $this->featuresManager->getExportInfo($package);
|
||||
|
||||
$this->assertEquals(['test_feature', 'modules/custom'], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getExportInfo
|
||||
*/
|
||||
public function testGetExportInfoWithBundle() {
|
||||
$config_factory = $this->getConfigFactoryStub([
|
||||
'features.settings' => [
|
||||
'export' => [
|
||||
'folder' => 'custom',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler);
|
||||
|
||||
$package = new Package('test_feature');
|
||||
$bundle = new FeaturesBundle(['machine_name' => 'test_bundle'], 'features_bundle');
|
||||
|
||||
$result = $this->featuresManager->getExportInfo($package, $bundle);
|
||||
|
||||
$this->assertEquals(['test_bundle_test_feature', 'modules/custom'], $result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestFeaturesManager extends FeaturesManager {
|
||||
|
||||
protected $allModules;
|
||||
|
||||
/**
|
||||
* @param \Drupal\features\FeaturesExtensionStoragesInterface $extensionStorages
|
||||
*/
|
||||
public function setExtensionStorages($extensionStorages) {
|
||||
$this->extensionStorages = $extensionStorages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllModules() {
|
||||
if (isset($this->allModules)) {
|
||||
return $this->allModules;
|
||||
}
|
||||
return parent::getAllModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $all_modules
|
||||
*/
|
||||
public function setAllModules($all_modules) {
|
||||
$this->allModules = $all_modules;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function drupalGetProfile() {
|
||||
return FeaturesManagerTest::PROFILE_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\features\Unit;
|
||||
|
||||
use Drupal\features\Package;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\features\Package
|
||||
* @group features
|
||||
*/
|
||||
class PackageTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::setFeaturesInfo
|
||||
*/
|
||||
public function testSetFeaturesInfo() {
|
||||
$package = new Package('test_feature', []);
|
||||
|
||||
$this->assertEquals([], $package->getFeaturesInfo());
|
||||
$package->setFeaturesInfo(['bundle' => 'test_bundle']);
|
||||
$this->assertEquals(['bundle' => 'test_bundle'], $package->getFeaturesInfo());
|
||||
$this->assertEquals('test_bundle', $package->getBundle());
|
||||
}
|
||||
|
||||
public function testGetConfig() {
|
||||
$package = new Package('test_feature', ['config' => ['test_config_a', 'test_config_b']]);
|
||||
$this->assertEquals(['test_config_a', 'test_config_b'], $package->getConfig());
|
||||
return $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetConfig
|
||||
* @covers ::appendConfig
|
||||
*/
|
||||
public function testAppendConfig(Package $package) {
|
||||
$package->appendConfig('test_config_a');
|
||||
$package->appendConfig('test_config_c');
|
||||
|
||||
$this->assertEquals(['test_config_a', 'test_config_b', 'test_config_c'], array_values($package->getConfig()));
|
||||
return $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testAppendConfig
|
||||
* @covers ::removeConfig
|
||||
*/
|
||||
public function testRemoveConfig(Package $package) {
|
||||
$package->removeConfig('test_config_a');
|
||||
|
||||
$this->assertEquals(['test_config_b', 'test_config_c'], array_values($package->getConfig()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user