فهرست منبع

added features

Bachir Soussi Chiadmi 7 سال پیش
والد
کامیت
c6cff46234
100فایلهای تغییر یافته به همراه15955 افزوده شده و 0 حذف شده
  1. 339 0
      sites/all/modules/contrib/admin/config_update/LICENSE.txt
  2. 13 0
      sites/all/modules/contrib/admin/config_update/README.txt
  3. 12 0
      sites/all/modules/contrib/admin/config_update/config_update.info.yml
  4. 21 0
      sites/all/modules/contrib/admin/config_update/config_update.module
  5. 20 0
      sites/all/modules/contrib/admin/config_update/config_update.services.yml
  6. 162 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/README.txt
  7. 232 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc
  8. 14 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml
  9. 5 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.libraries.yml
  10. 5 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.links.task.yml
  11. 30 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module
  12. 8 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.permissions.yml
  13. 45 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.routing.yml
  14. 13 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/css/config_update_ui.report.css
  15. 576 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/src/Controller/ConfigUpdateController.php
  16. 143 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php
  17. 143 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php
  18. 283 0
      sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigUpdateTest.php
  19. 37 0
      sites/all/modules/contrib/admin/config_update/src/ConfigDeleteInterface.php
  20. 46 0
      sites/all/modules/contrib/admin/config_update/src/ConfigDiffInterface.php
  21. 180 0
      sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php
  22. 74 0
      sites/all/modules/contrib/admin/config_update/src/ConfigListInterface.php
  23. 221 0
      sites/all/modules/contrib/admin/config_update/src/ConfigLister.php
  24. 66 0
      sites/all/modules/contrib/admin/config_update/src/ConfigRevertEvent.php
  25. 96 0
      sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php
  26. 218 0
      sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php
  27. 339 0
      sites/all/modules/contrib/admin/features/LICENSE.txt
  28. 8 0
      sites/all/modules/contrib/admin/features/composer.json
  29. 92 0
      sites/all/modules/contrib/admin/features/config/install/features.bundle.default.yml
  30. 3 0
      sites/all/modules/contrib/admin/features/config/install/features.settings.yml
  31. 207 0
      sites/all/modules/contrib/admin/features/config/schema/features.schema.yml
  32. 936 0
      sites/all/modules/contrib/admin/features/drush/features.drush.inc
  33. 14 0
      sites/all/modules/contrib/admin/features/features.info.yml
  34. 47 0
      sites/all/modules/contrib/admin/features/features.module
  35. 6 0
      sites/all/modules/contrib/admin/features/features.routing.yml
  36. 32 0
      sites/all/modules/contrib/admin/features/features.services.yml
  37. 271 0
      sites/all/modules/contrib/admin/features/modules/features_ui/css/features_ui.admin.css
  38. 175 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.admin.inc
  39. 14 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.info.yml
  40. 11 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.libraries.yml
  41. 5 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.action.yml
  42. 5 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.menu.yml
  43. 14 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.task.yml
  44. 55 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.module
  45. 95 0
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.routing.yml
  46. 401 0
      sites/all/modules/contrib/admin/features/modules/features_ui/js/features_ui.admin.js
  47. 193 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Controller/FeaturesUIController.php
  48. 61 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentBaseForm.php
  49. 445 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentConfigureForm.php
  50. 54 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentCoreForm.php
  51. 128 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentExcludeForm.php
  52. 160 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentFormBase.php
  53. 54 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentOptionalForm.php
  54. 88 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentProfileForm.php
  55. 54 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentSiteForm.php
  56. 270 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesDiffForm.php
  57. 1085 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesEditForm.php
  58. 539 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesExportForm.php
  59. 188 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesBundleUITest.php
  60. 179 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesCreateUITest.php
  61. 49 0
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesUITest.php
  62. 351 0
      sites/all/modules/contrib/admin/features/src/ConfigurationItem.php
  63. 77 0
      sites/all/modules/contrib/admin/features/src/Controller/FeaturesController.php
  64. 305 0
      sites/all/modules/contrib/admin/features/src/Entity/FeaturesBundle.php
  65. 446 0
      sites/all/modules/contrib/admin/features/src/FeaturesAssigner.php
  66. 228 0
      sites/all/modules/contrib/admin/features/src/FeaturesAssignerInterface.php
  67. 124 0
      sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodBase.php
  68. 56 0
      sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodInterface.php
  69. 32 0
      sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodManager.php
  70. 240 0
      sites/all/modules/contrib/admin/features/src/FeaturesBundleInterface.php
  71. 59 0
      sites/all/modules/contrib/admin/features/src/FeaturesConfigDependencyManager.php
  72. 51 0
      sites/all/modules/contrib/admin/features/src/FeaturesConfigInstaller.php
  73. 121 0
      sites/all/modules/contrib/admin/features/src/FeaturesExtensionStorages.php
  74. 87 0
      sites/all/modules/contrib/admin/features/src/FeaturesExtensionStoragesInterface.php
  75. 107 0
      sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodBase.php
  76. 67 0
      sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodInterface.php
  77. 33 0
      sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodManager.php
  78. 173 0
      sites/all/modules/contrib/admin/features/src/FeaturesGenerator.php
  79. 103 0
      sites/all/modules/contrib/admin/features/src/FeaturesGeneratorInterface.php
  80. 157 0
      sites/all/modules/contrib/admin/features/src/FeaturesInstallStorage.php
  81. 1306 0
      sites/all/modules/contrib/admin/features/src/FeaturesManager.php
  82. 601 0
      sites/all/modules/contrib/admin/features/src/FeaturesManagerInterface.php
  83. 24 0
      sites/all/modules/contrib/admin/features/src/FeaturesServiceProvider.php
  84. 549 0
      sites/all/modules/contrib/admin/features/src/Package.php
  85. 69 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentBaseType.php
  86. 37 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentCoreType.php
  87. 26 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentDependency.php
  88. 148 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentExclude.php
  89. 58 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentExisting.php
  90. 78 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentForwardDependency.php
  91. 26 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentNamespace.php
  92. 33 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentOptionalType.php
  93. 53 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentPackages.php
  94. 177 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentProfile.php
  95. 36 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentSiteType.php
  96. 270 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php
  97. 225 0
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php
  98. 136 0
      sites/all/modules/contrib/admin/features/src/ProxyClass/FeaturesConfigInstaller.php
  99. 3 0
      sites/all/modules/contrib/admin/features/tests/modules/test_feature/config/install/system.cron.yml
  100. 4 0
      sites/all/modules/contrib/admin/features/tests/modules/test_feature/test_feature.features.yml

+ 339 - 0
sites/all/modules/contrib/admin/config_update/LICENSE.txt

@@ -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.

+ 13 - 0
sites/all/modules/contrib/admin/config_update/README.txt

@@ -0,0 +1,13 @@
+Configuration Update Manager project
+------------------------------------
+
+The Configuration Update Manager project consists of two modules:
+
+Configuration Update Base: A base module providing functionality related to
+  updating and computing differences between configuration versions. No
+  user interface; used by other modules such as Features.
+
+Configuration Update Reports (in the config_ui sub-directory of this project):
+  Adds an updates report and revert functionality to configuration management.
+  Depends on Configuration Update Base. For more information, see the
+  README.txt file in the subdirectory.

+ 12 - 0
sites/all/modules/contrib/admin/config_update/config_update.info.yml

@@ -0,0 +1,12 @@
+name: Configuration Update Base
+type: module
+description: 'Provides basic revert and update functionality for other modules'
+# core: 8.x
+dependencies:
+  - config
+
+# Information added by Drupal.org packaging script on 2016-03-08
+version: '8.x-1.1'
+core: '8.x'
+project: 'config_update'
+datestamp: 1457466840

+ 21 - 0
sites/all/modules/contrib/admin/config_update/config_update.module

@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Configuration Update Base module.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Implements hook_help().
+ */
+function config_update_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.config_update':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Configuration Update Base module provides functionality that other modules can use, related to updating and computing differences between configuration versions. This module does not provide any end-user functionality itself.') . '</p>';
+      return $output;
+  }
+}

+ 20 - 0
sites/all/modules/contrib/admin/config_update/config_update.services.yml

@@ -0,0 +1,20 @@
+services:
+  config_update.config_diff:
+    class: Drupal\config_update\ConfigDiffer
+    arguments: ['@string_translation']
+
+  config_update.config_list:
+    class: Drupal\config_update\ConfigLister
+    arguments: ['@entity.manager', '@config.storage', '@config_update.extension_storage', '@config_update.extension_optional_storage']
+
+  config_update.config_update:
+    class: Drupal\config_update\ConfigReverter
+    arguments: ['@entity.manager', '@config.storage', '@config_update.extension_storage', '@config_update.extension_optional_storage', '@config.factory', '@event_dispatcher']
+
+  config_update.extension_storage:
+    class: Drupal\Core\Config\ExtensionInstallStorage
+    arguments: ['@config.storage']
+
+  config_update.extension_optional_storage:
+    class: Drupal\Core\Config\ExtensionInstallStorage
+    arguments: ['@config.storage', 'config/optional']

+ 162 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/README.txt

@@ -0,0 +1,162 @@
+Configuration Update Reports module
+-----------------------------------
+
+CONTENTS OF THIS README FILE
+- Introduction
+- Installation
+- Generating reports in the user interface
+- Generating reports using Drush commands
+- Important notes  *** Be sure to read this section ***
+
+
+INTRODUCTION
+
+This module provides a report that allows you to see the differences between the
+default configuration items provided by the current versions of your installed
+modules, themes, and install profile, and the active configuration of your
+site. From this report, you can also import new configuration provided by
+updates, and revert your site configuration to the provided values.
+
+The main use case is: You update a module, and it has either changed default
+configuration that it provides, or added new default configuration items that
+you didn't get when you first installed the module. You want to be able to
+import the new items, view the differences between the active site configuration
+and the changed configuration, and possibly "revert" (or it may be an update) to
+the newly-provided default configuration.
+
+
+INSTALLATION
+
+Install the module in the normal way for Drupal modules. The only dependencies
+are the Configuration Manager module (Drupal core), and the Configuration Update
+Base module (part of this same project download).
+
+
+GENERATING REPORTS IN THE USER INTERFACE
+
+You can generate configuration reports at Administration >> Configuration >>
+Development >> Configuration management >> Update report (path:
+admin/config/development/configuration/report ).
+
+You can generate a report for a particular type of configuration object, such as
+Actions, Tours, Views, etc. Or, you can generate a report for an installed
+module, theme, or install profile. Finally, you can generate a report that
+contains all configuration in one report.
+
+The report has three sections, depending on what type you choose:
+
+1. Missing configuration items: Configuration items that are provided as
+   defaults by your currently-installed modules, themes, and install profile
+   that are missing from your active site configuration.
+
+   Any items listed here can be imported into your site.
+
+2. Added configuration items: Configuration items that you added to the site
+   (not provided by a currently-installed module, theme, or install
+   profile). This section is only shown when you are running the report based on
+   a configuration type.
+
+   Items listed here can be exported, which is useful for developers or if you
+   want to keep your site configuration in a version control system.
+
+3. Changed configuration items: Configuration items that are in your active site
+   configuration that differ from the same item currently being provided by an
+   installed module, theme, or install profile.
+
+   You can export these items, see the differences between what is on your site
+   and what the module/theme/profile is currently providing, or "revert" to the
+   version currently being provided by the module/theme/profile in its default
+   configuration.
+
+   Note that the differences may be a bit hard to read, but hopefully they'll
+   give you the general idea of what has changed.
+
+
+GENERATING REPORTS USING DRUSH COMMANDS
+
+The reports detailed in the previous section can also be generated, in pieces,
+using Drush commands (https://drupal.org/project/drush):
+
+drush config-list-types (clt)
+  Lists all the config types on your system. Reports can be run for
+  'system.simple' (simple configuration), and 'system.all' (all types), in
+  addition to the types listed by this command.
+
+drush config-added-report (cra)
+drush config-missing-report (crm)
+drush config-different-report (crd)
+  Run config reports (see below).
+
+drush config-diff (cfd)
+  Show config differences for one item between active and imported (see below).
+
+The report commands run reports that tell what config has been added, is
+missing, or is different between your active site configuration and the imported
+default configuration from config/install directories of your installed profile,
+modules, and themes.
+
+For each report except "added", the first argument is one of:
+- type: Runs the report for a configuration type; use drush config-list-types to
+  list them.
+- module: Runs the report for an installed module.
+- theme: Runs the report for an installed theme.
+- profile: Runs the report for the install profile.
+
+The second argument for reports is the machine name of the configuration type,
+module, theme, or install profile you want to run the report for. For the
+"added" report, this is the only argument, as the added report is always by
+configuration type.
+
+These are the same as the reports you get in the UI, which is described above;
+the only difference is that in Drush the report is separated into pieces.
+
+Once you have found a configuration item with differences, you can view the
+differences using the config-diff command. This is a normalized/formatted diff
+like in the UI of this module, so see above for details.
+
+Drush examples:
+
+drush clt
+drush crm module node
+drush cra block
+drush crd theme bartik
+drush crd type system.all
+drush crd type system.simple
+drush crd profile standard
+drush cfd block.block.bartik_search
+
+Once you have figured out which configuration items are added, missing, or
+different, you can:
+
+- Export them - see drush config-export.
+
+- Import missing configuration or revert to provided default values. To do this:
+
+  (1) Locate the configuration file in the install profile, module, or theme
+      config/install directory.
+
+  (2) Copy this file to your configuration staging directory.
+
+  (3) Run drush config-import. You might want to use the --preview option to see
+      what differences you are about to import, before running the import, or
+      use the drush config-diff command to look at individual differences.
+
+
+IMPORTANT NOTES
+
+Here are some notes about how this module functions:
+
+* This module is always looking at the base configuration items, without
+  overrides (from settings.php, for example) or translations.
+
+* It is possible for an install profile on a site to provide configuration that
+  overrides configuration from a module or theme. The install profile version
+  always takes precedence. As an example, consider the case where module Foo
+  provides a configuration item called foo.settings, and install profile Bar
+  overrides this with its own file. Any reports that include foo.settings will
+  be based on the differences between your site's active configuration and the
+  version in the install profile. This is not usually a problem, but it can be
+  confusing if you're looking at the Foo module report. The foo.settings item
+  will be present, but the differences reported will be between the install
+  profile's version and your site's active configuration, not the differences
+  between the Foo module version and your site's active configuration.

+ 232 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc

@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * @file
+ * Drush commands for the Configuration Update Reports module.
+ */
+
+use Drupal\Component\Diff\DiffFormatter;
+
+/**
+ * Implements hook_drush_command().
+ */
+function config_update_ui_drush_command() {
+
+  $items = [];
+
+  $items['config-list-types'] = [
+    'description' => 'List config types',
+    'aliases' => ['clt'],
+    'core' => ['8+'],
+    'outputformat' => [
+      'default' => 'list',
+    ],
+  ];
+
+  $items['config-added-report'] = [
+    'description' => 'Display a list of config items that did not come from your installed modules, themes, or install profile',
+    'arguments' => [
+      'name' => 'The type of config to report on. See config-list-types to list them. You can also use system.all for all types, or system.simple for simple config.',
+    ],
+    'required-arguments' => 1,
+    'examples' => [
+      'drush config-added-report action' => 'Displays the added config report for action config.',
+    ],
+    'aliases' => ['cra'],
+    'core' => ['8+'],
+    'outputformat' => [
+      'default' => 'list',
+    ],
+  ];
+
+  $items['config-missing-report'] = [
+    'description' => 'Display a list of config items from your installed modules, themes, or install profile that are not currently in your active config',
+    'arguments' => [
+      'type' => 'Run the report for: module, theme, profile, or "type" for config entity type.',
+      'name' => 'The machine name of the module, theme, etc. to report on. See config-list-types to list types for config entities; you can also use system.all for all types, or system.simple for simple config.',
+    ],
+    'required-arguments' => 2,
+    'examples' => [
+      'drush config-missing-report type action' => 'Displays the missing config report for action config.',
+    ],
+    'aliases' => ['crm'],
+    'core' => ['8+'],
+    'outputformat' => [
+      'default' => 'list',
+    ],
+  ];
+
+  $items['config-inactive-report'] = [
+    'description' => 'Display a list of optional config items from your installed modules, themes, or install profile that are not currently in your active config',
+    'arguments' => [
+      'type' => 'Run the report for: module, theme, profile, or "type" for config entity type.',
+      'name' => 'The machine name of the module, theme, etc. to report on. See config-list-types to list types for config entities; you can also use system.all for all types, or system.simple for simple config.',
+    ],
+    'required-arguments' => 2,
+    'examples' => [
+      'drush config-inactive-report type action' => 'Displays the inactive config report for action config.',
+    ],
+    'aliases' => ['cri'],
+    'core' => ['8+'],
+    'outputformat' => [
+      'default' => 'list',
+    ],
+  ];
+
+  $items['config-different-report'] = [
+    'description' => 'Display a list of config items that differ from the versions imported from your installed modules, themes, or install profile. See config-diff to show what the differences are.',
+    'arguments' => [
+      'type' => 'Run the report for: module, theme, profile, or "type" for config entity type.',
+      'name' => 'The machine name of the module, theme, etc. to report on. See config-list-types to list types for config entities; you can also use system.all for all types, or system.simple for simple config.',
+    ],
+    'required-arguments' => 2,
+    'examples' => [
+      'drush config-different-report type action' => 'Displays the differing config report for action config.',
+    ],
+    'drupal dependencies' => ['config_update_ui'],
+    'aliases' => ['crd'],
+    'core' => ['8+'],
+    'outputformat' => [
+      'default' => 'list',
+    ],
+  ];
+
+  $items['config-diff'] = [
+    'description' => 'Display line-by-line differences for one config item between your active config and the version currently being provided by an installed module, theme, or install profile',
+    'arguments' => [
+      'name' => 'The config item to diff. See config-different-report to list config items that are different.',
+    ],
+    'required-arguments' => 1,
+    'examples' => [
+      'drush config-diff block.block.bartik_search' => 'Displays the config differences for the search block in the Bartik theme.',
+    ],
+    'aliases' => ['cfd'],
+    'core' => ['8+'],
+  ];
+
+  return $items;
+}
+
+/**
+ * Lists available config types.
+ */
+function drush_config_update_ui_config_list_types() {
+  $list = [];
+
+  $definitions = \Drupal::service('config_update.config_list')->listTypes();
+  return array_keys($definitions);
+}
+
+/**
+ * Runs the config added report.
+ *
+ * @param string $name
+ *   Type of config to report on.
+ */
+function drush_config_update_ui_config_added_report($name) {
+  list($active_list, $install_list, $optional_list) = \Drupal::service('config_update.config_list')->listConfig('type', $name);
+
+  $added = array_diff($active_list, $install_list, $optional_list);
+
+  if (!count($added)) {
+    drush_print(dt('No added config'), 0, STDERR);
+  }
+
+  sort($added);
+  return $added;
+}
+
+/**
+ * Runs the config missing report.
+ *
+ * @param string $type
+ *   Type of report to run: 'type', 'module', 'theme', or 'profile'.
+ * @param string $name
+ *   Machine name of item to report on.
+ */
+function drush_config_update_ui_config_missing_report($type, $name) {
+  list($active_list, $install_list, $optional_list) = \Drupal::service('config_update.config_list')->listConfig($type, $name);
+
+  $missing = array_diff($install_list, $active_list);
+  if (!count($missing)) {
+    drush_print(dt('No missing config'), 0, STDERR);
+  }
+
+  sort($missing);
+  return $missing;
+}
+
+/**
+ * Runs the config inactive report.
+ *
+ * @param string $type
+ *   Type of report to run: 'type', 'module', 'theme', or 'profile'.
+ * @param string $name
+ *   Machine name of item to report on.
+ */
+function drush_config_update_ui_config_inactive_report($type, $name) {
+  list($active_list, $install_list, $optional_list) = \Drupal::service('config_update.config_list')->listConfig($type, $name);
+
+  $missing = array_diff($optional_list, $active_list);
+  if (!count($missing)) {
+    drush_print(dt('No inactive config'), 0, STDERR);
+  }
+
+  sort($missing);
+  return $missing;
+}
+
+/**
+ * Runs the config different report.
+ *
+ * @param string $type
+ *   Type of report to run: 'type', 'module', 'theme', or 'profile'.
+ * @param string $name
+ *   Machine name of item to report on.
+ */
+function drush_config_update_ui_config_different_report($type, $name) {
+  list($active_list, $install_list, $optional_list) = \Drupal::service('config_update.config_list')->listConfig($type, $name);
+
+  $reverter = \Drupal::service('config_update.config_update');
+  $differ = \Drupal::service('config_update.config_diff');
+
+  $added = array_diff($active_list, $install_list, $optional_list);
+  $both = array_diff($active_list, $added);
+  $different = [];
+  foreach ($both as $name) {
+    $active = $reverter->getFromActive('', $name);
+    $extension = $reverter->getFromExtension('', $name);
+    if (!$differ->same($active, $extension)) {
+      $different[] = $name;
+    }
+  }
+
+  if (!count($different)) {
+    drush_print(dt('No different config'), 0, STDERR);
+  }
+
+  sort($different);
+  return $different;
+}
+
+/**
+ * Runs the drush config-diff command.
+ *
+ * @param string $name
+ *   Config item to diff.
+ */
+function drush_config_update_ui_config_diff($name) {
+  $reverter = \Drupal::service('config_update.config_update');
+  $differ = \Drupal::service('config_update.config_diff');
+  $formatter = new DiffFormatter();
+
+  $extension = $reverter->getFromExtension('', $name);
+  $active = $reverter->getFromActive('', $name);
+  if ($extension && !$active) {
+    $diff = $differ->diff($extension, $active);
+    drush_print($formatter->format($diff));
+  }
+  else {
+    drush_print(dt('Config is missing, cannot diff'), 0, STDERR);
+  }
+}

+ 14 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml

@@ -0,0 +1,14 @@
+name: Configuration Update Reports
+type: module
+description: 'Adds an updates report and revert functionality to configuration management'
+# core: 8.x
+configure: config_update.report
+dependencies:
+  - config_update
+  - config
+
+# Information added by Drupal.org packaging script on 2016-03-08
+version: '8.x-1.1'
+core: '8.x'
+project: 'config_update'
+datestamp: 1457466840

+ 5 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.libraries.yml

@@ -0,0 +1,5 @@
+report_css:
+  version: 1.x
+  css:
+    theme:
+      css/config_update_ui.report.css: {}

+ 5 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.links.task.yml

@@ -0,0 +1,5 @@
+config_update_ui.report:
+  route_name: config_update_ui.report
+  title: 'Updates report'
+  base_route: config.sync
+  weight: 1000

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module


+ 8 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.permissions.yml

@@ -0,0 +1,8 @@
+view config updates report:
+  title: 'View configuration updates report'
+revert configuration:
+  title: 'Revert any configuration'
+  restrict access: true
+delete configuration:
+  title: 'Delete any configuration'
+  restrict access: true

+ 45 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.routing.yml

@@ -0,0 +1,45 @@
+config_update_ui.report:
+  path: '/admin/config/development/configuration/report/{report_type}/{name}'
+  defaults:
+    _controller: '\Drupal\config_update_ui\Controller\ConfigUpdateController::report'
+    _title: 'Updates report'
+    report_type: NULL
+    name: NULL
+  requirements:
+    _permission: 'view config updates report'
+
+config_update_ui.import:
+  path: '/admin/config/development/configuration/report/import/{config_type}/{config_name}'
+  defaults:
+    _title: 'Import'
+    _controller: '\Drupal\config_update_ui\Controller\ConfigUpdateController::import'
+    config_type: NULL
+    config_name: NULL
+  requirements:
+    _permission: 'import configuration'
+
+config_update_ui.diff:
+  path: '/admin/config/development/configuration/report/diff/{config_type}/{config_name}'
+  defaults:
+    _title: 'Differences'
+    _controller: '\Drupal\config_update_ui\Controller\ConfigUpdateController::diff'
+    config_type: NULL
+    config_name: NULL
+  requirements:
+    _permission: 'view config updates report'
+
+config_update_ui.revert:
+  path: '/admin/config/development/configuration/report/revert/{config_type}/{config_name}'
+  defaults:
+    _title: 'Revert'
+    _form: '\Drupal\config_update_ui\Form\ConfigRevertConfirmForm'
+  requirements:
+    _permission: 'revert configuration'
+
+config_update_ui.delete:
+  path: '/admin/config/development/configuration/report/delete/{config_type}/{config_name}'
+  defaults:
+    _title: 'Delete'
+    _form: '\Drupal\config_update_ui\Form\ConfigDeleteConfirmForm'
+  requirements:
+    _permission: 'delete configuration'

+ 13 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/css/config_update_ui.report.css

@@ -0,0 +1,13 @@
+/**
+ * @file
+ * Styling for the Config Updates report.
+ */
+
+table.config-update-report {
+  margin-top: 2em;
+}
+
+table.config-update-report caption {
+  font-weight: bold;
+  font-size: 1.1em;
+}

+ 576 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/src/Controller/ConfigUpdateController.php

@@ -0,0 +1,576 @@
+<?php
+
+namespace Drupal\config_update_ui\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Diff\DiffFormatter;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Url;
+use Drupal\config_update\ConfigDiffInterface;
+use Drupal\config_update\ConfigListInterface;
+use Drupal\config_update\ConfigRevertInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for Configuration Revert module operations.
+ */
+class ConfigUpdateController extends ControllerBase {
+
+  /**
+   * The config differ.
+   *
+   * @var \Drupal\config_update\ConfigDiffInterface
+   */
+  protected $configDiff;
+
+  /**
+   * The config lister.
+   *
+   * @var \Drupal\config_update\ConfigListInterface
+   */
+  protected $configList;
+
+  /**
+   * The config reverter.
+   *
+   * @var \Drupal\config_update\ConfigRevertInterface
+   */
+  protected $configRevert;
+
+  /**
+   * The diff formatter.
+   *
+   * @var \Drupal\Core\Diff\DiffFormatter
+   */
+  protected $diffFormatter;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * Constructs a ConfigUpdateController object.
+   *
+   * @param \Drupal\config_update\ConfigDiffInterface $config_diff
+   *   The config differ.
+   * @param \Drupal\config_update\ConfigListInterface $config_list
+   *   The config lister.
+   * @param \Drupal\config_update\ConfigRevertInterface $config_update
+   *   The config reverter.
+   * @param \Drupal\Core\Diff\DiffFormatter $diff_formatter
+   *   The diff formatter to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler.
+   */
+  public function __construct(ConfigDiffInterface $config_diff, ConfigListInterface $config_list, ConfigRevertInterface $config_update, DiffFormatter $diff_formatter, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
+    $this->configDiff = $config_diff;
+    $this->configList = $config_list;
+    $this->configRevert = $config_update;
+    $this->diffFormatter = $diff_formatter;
+    $this->diffFormatter->show_header = FALSE;
+    $this->moduleHandler = $module_handler;
+    $this->themeHandler = $theme_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config_update.config_diff'),
+      $container->get('config_update.config_list'),
+      $container->get('config_update.config_update'),
+      $container->get('diff.formatter'),
+      $container->get('module_handler'),
+      $container->get('theme_handler')
+    );
+  }
+
+  /**
+   * Imports configuration from a module, theme, or profile.
+   *
+   * Configuration is assumed not to currently exist.
+   *
+   * @param string $config_type
+   *   The type of configuration.
+   * @param string $config_name
+   *   The name of the config item, without the prefix.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   Redirects to the updates report.
+   */
+  public function import($config_type, $config_name) {
+    $this->configRevert->import($config_type, $config_name);
+
+    drupal_set_message($this->t('The configuration was imported.'));
+    return $this->redirect('config_update_ui.report');
+  }
+
+  /**
+   * Shows the diff between active and provided configuration.
+   *
+   * @param string $config_type
+   *   The type of configuration.
+   * @param string $config_name
+   *   The name of the config item, without the prefix.
+   *
+   * @return array
+   *   Render array for page showing differences between them.
+   */
+  public function diff($config_type, $config_name) {
+    $diff = $this->configDiff->diff(
+      $this->configRevert->getFromExtension($config_type, $config_name),
+      $this->configRevert->getFromActive($config_type, $config_name)
+    );
+
+    $build = [];
+    $definition = $this->configList->getType($config_type);
+    $config_type_label = ($definition) ? $definition->getLabel() : $this->t('Simple configuration');
+    $build['#title'] = $this->t('Config difference for @type @name', ['@type' => $config_type_label, '@name' => $config_name]);
+    $build['#attached']['library'][] = 'system/diff';
+
+    $build['diff'] = [
+      '#type' => 'table',
+      '#header' => [
+        ['data' => $this->t('Source config'), 'colspan' => '2'],
+        ['data' => $this->t('Site config'), 'colspan' => '2'],
+      ],
+      '#rows' => $this->diffFormatter->format($diff),
+      '#attributes' => ['class' => ['diff']],
+    ];
+
+    $url = new Url('config_update_ui.report');
+
+    $build['back'] = [
+      '#type' => 'link',
+      '#attributes' => [
+        'class' => [
+          'dialog-cancel',
+        ],
+      ],
+      '#title' => $this->t("Back to 'Updates report' page."),
+      '#url' => $url,
+    ];
+
+    return $build;
+  }
+
+  /**
+   * Generates the config updates report.
+   *
+   * @param string $report_type
+   *   (optional) Type of report to run:
+   *   - type: Configuration entity type.
+   *   - module: Module.
+   *   - theme: Theme.
+   *   - profile: Install profile.
+   * @param string $name
+   *   (optional) Name of specific item to run report for (config entity type
+   *   ID, module machine name, etc.). Ignored for profile.
+   *
+   * @return array
+   *   Render array for report, with section at the top for selecting another
+   *   report to run. If either $report_type or $name is missing, the report
+   *   itself is not generated.
+   */
+  public function report($report_type = NULL, $name = NULL) {
+    $links = $this->generateReportLinks();
+
+    $report = $this->generateReport($report_type, $name);
+    if (!$report) {
+      return $links;
+    }
+
+    // If there is a report, extract the title, put table of links in a
+    // details element, and add report to build.
+    $build = [];
+    $build['#title'] = $report['#title'];
+    unset($report['#title']);
+
+    $build['links_wrapper'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Generate new report'),
+      '#children' => $links,
+    ];
+
+    $build['report'] = $report;
+
+    $build['#attached']['library'][] = 'config_update/report_css';
+
+    return $build;
+  }
+
+  /**
+   * Generates the operations links for running individual reports.
+   *
+   * @return array
+   *   Render array for the operations links for running reports.
+   */
+  protected function generateReportLinks() {
+
+    // These links are put into an 'operations' render array element. They do
+    // not look good outside of tables. Also note that the array index in
+    // operations links is used as a class on the LI element. Some classes are
+    // special in the Seven CSS, such as "contextual", so avoid hitting these
+    // accidentally by prefixing.
+    $build = [];
+
+    $build['links'] = [
+      '#type' => 'table',
+      '#header' => [
+        $this->t('Report type'),
+        $this->t('Report on'),
+      ],
+      '#rows' => [],
+    ];
+
+    $definitions = $this->configList->listTypes();
+    $links = [];
+    foreach ($definitions as $entity_type => $definition) {
+      $links['type_' . $entity_type] = [
+        'title' => $definition->getLabel(),
+        'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => $entity_type]),
+      ];
+    }
+
+    uasort($links, [$this, 'sortLinks']);
+
+    $links = [
+      'type_all' => [
+        'title' => $this->t('All types'),
+        'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => 'system.all']),
+      ],
+      'type_system.simple' => [
+        'title' => $this->t('Simple configuration'),
+        'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => 'system.simple']),
+      ],
+    ] + $links;
+
+    $build['links']['#rows'][] = [
+      $this->t('Configuration type'),
+      [
+        'data' => [
+          '#type' => 'operations',
+          '#links' => $links,
+        ],
+      ],
+    ];
+
+    // Make a list of installed modules.
+    $profile = Settings::get('install_profile');
+    $modules = $this->moduleHandler->getModuleList();
+    $links = [];
+    foreach ($modules as $machine_name => $module) {
+      if ($machine_name != $profile) {
+        $links['module_' . $machine_name] = [
+          'title' => $this->moduleHandler->getName($machine_name),
+          'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'module', 'name' => $machine_name]),
+        ];
+      }
+    }
+    uasort($links, [$this, 'sortLinks']);
+
+    $build['links']['#rows'][] = [
+      $this->t('Module'),
+      [
+        'data' => [
+          '#type' => 'operations',
+          '#links' => $links,
+        ],
+      ],
+    ];
+
+    // Make a list of installed themes.
+    $themes = $this->themeHandler->listInfo();
+    $links = [];
+    foreach ($themes as $machine_name => $theme) {
+      $links['theme_' . $machine_name] = [
+        'title' => $this->themeHandler->getName($machine_name),
+        'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'theme', 'name' => $machine_name]),
+      ];
+    }
+    uasort($links, [$this, 'sortLinks']);
+
+    $build['links']['#rows'][] = [
+      $this->t('Theme'),
+      [
+        'data' => [
+          '#type' => 'operations',
+          '#links' => $links,
+        ],
+      ],
+    ];
+
+    // Profile is just one option.
+    $links = [];
+    $links['profile_' . $profile] = [
+      'title' => $this->moduleHandler->getName($profile),
+      'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'profile']),
+    ];
+    $build['links']['#rows'][] = [
+      $this->t('Installation profile'),
+      [
+        'data' => [
+          '#type' => 'operations',
+          '#links' => $links,
+        ],
+      ],
+    ];
+
+    return $build;
+  }
+
+  /**
+   * Generates a report about config updates.
+   *
+   * @param string $report_type
+   *   Type of report to generate: 'type', 'module', 'theme', or 'profile'.
+   * @param string $value
+   *   Machine name of a configuration type, module, or theme to generate the
+   *   report for. Ignored for profile, since that uses the active profile.
+   *
+   * @return array
+   *   Render array for the updates report. Empty if invalid or missing
+   *   report type or value.
+   */
+  protected function generateReport($report_type, $value) {
+    // Figure out what to name the report, and incidentally, validate that
+    // $value exists for this type of report.
+    switch ($report_type) {
+      case 'type':
+        if ($value == 'system.all') {
+          $label = $this->t('All configuration');
+        }
+        elseif ($value == 'system.simple') {
+          $label = $this->t('Simple configuration');
+        }
+        else {
+          $definition = $this->configList->getType($value);
+          if (!$definition) {
+            return NULL;
+          }
+
+          $label = $this->t('@name configuration', ['@name' => $definition->getLabel()]);
+        }
+
+        break;
+
+      case 'module':
+        $list = $this->moduleHandler->getModuleList();
+        if (!isset($list[$value])) {
+          return NULL;
+        }
+
+        $label = $this->t('@name module', ['@name' => $this->moduleHandler->getName($value)]);
+        break;
+
+      case 'theme':
+        $list = $this->themeHandler->listInfo();
+        if (!isset($list[$value])) {
+          return NULL;
+        }
+
+        $label = $this->t('@name theme', ['@name' => $this->themeHandler->getName($value)]);
+        break;
+
+      case 'profile':
+        $profile = Settings::get('install_profile');
+        $label = $this->t('@name profile', ['@name' => $this->moduleHandler->getName($profile)]);
+        break;
+
+      default:
+        return NULL;
+    }
+
+    // List the active and extension-provided config.
+    list($active_list, $install_list, $optional_list) = $this->configList->listConfig($report_type, $value);
+
+    // Build the report.
+    $build = [];
+
+    $build['#title'] = $this->t('Configuration updates report for @label', ['@label' => $label]);
+    $build['report_header'] = ['#markup' => '<h3>' . $this->t('Updates report') . '</h3>'];
+
+    // List items missing from site.
+    $removed = array_diff($install_list, $active_list);
+    $build['removed'] = [
+      '#caption' => $this->t('Missing configuration items'),
+      '#empty' => $this->t('None: all provided configuration items are in your active configuration.'),
+    ] + $this->makeReportTable($removed, 'extension', ['import']);
+
+    // List optional items that are not installed.
+    $inactive = array_diff($optional_list, $active_list);
+    $build['inactive'] = [
+      '#caption' => $this->t('Inactive optional items'),
+      '#empty' => $this->t('None: all optional configuration items are in your active configuration.'),
+    ] + $this->makeReportTable($inactive, 'extension', ['import']);
+
+    // List items added to site, which only makes sense in the report for a
+    // config type.
+    $added = array_diff($active_list, $install_list, $optional_list);
+    if ($report_type == 'type') {
+      $build['added'] = [
+        '#caption' => $this->t('Added configuration items'),
+        '#empty' => $this->t('None: all active configuration items of this type were provided by modules, themes, or install profile.'),
+      ] + $this->makeReportTable($added, 'active', ['export', 'delete']);
+    }
+
+    // For differences, we need to go through the array of config in both
+    // and see if each config item is the same or not.
+    $both = array_diff($active_list, $added);
+    $different = [];
+    foreach ($both as $name) {
+      if (!$this->configDiff->same(
+        $this->configRevert->getFromExtension('', $name),
+        $this->configRevert->getFromActive('', $name)
+      )) {
+        $different[] = $name;
+      }
+    }
+    $build['different'] = [
+      '#caption' => $this->t('Changed configuration items'),
+      '#empty' => $this->t('None: no active configuration items differ from their current provided versions.'),
+    ] + $this->makeReportTable($different, 'active', ['diff', 'export', 'revert']);
+
+    return $build;
+  }
+
+  /**
+   * Builds a table for the report.
+   *
+   * @param string[] $names
+   *   List of machine names of config items for the table.
+   * @param string $storage
+   *   Config storage the items can be loaded from, either 'active' or
+   *   'extension'.
+   * @param string[] $actions
+   *   Action links to include, one or more of:
+   *   - diff
+   *   - revert
+   *   - export
+   *   - import
+   *   - delete
+   *
+   * @return array
+   *   Render array for the table, not including the #empty and #prefix
+   *   properties.
+   */
+  protected function makeReportTable($names, $storage, $actions) {
+    $build = [];
+
+    $build['#type'] = 'table';
+
+    $build['#attributes'] = ['class' => ['config-update-report']];
+
+    $build['#header'] = [
+      'name' => [
+        'data' => $this->t('Machine name'),
+      ],
+      'label' => [
+        'data' => $this->t('Label (if any)'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ],
+      'type' => [
+        'data' => $this->t('Type'),
+        'class' => [RESPONSIVE_PRIORITY_MEDIUM],
+      ],
+      'operations' => [
+        'data' => $this->t('Operations'),
+      ],
+    ];
+
+    $build['#rows'] = [];
+
+    foreach ($names as $name) {
+      $row = [];
+      if ($storage == 'active') {
+        $config = $this->configRevert->getFromActive('', $name);
+      }
+      else {
+        $config = $this->configRevert->getFromExtension('', $name);
+      }
+
+      // Figure out what type of config it is, and get the ID.
+      $entity_type = $this->configList->getTypeNameByConfigName($name);
+
+      if (!$entity_type) {
+        // This is simple config.
+        $id = $name;
+        $type_label = $this->t('Simple configuration');
+        $entity_type = 'system.simple';
+      }
+      else {
+        $definition = $this->configList->getType($entity_type);
+        $id_key = $definition->getKey('id');
+        $id = $config[$id_key];
+        $type_label = $definition->getLabel();
+      }
+
+      $label = (isset($config['label'])) ? $config['label'] : '';
+      $row[] = $name;
+      $row[] = $label;
+      $row[] = $type_label;
+
+      $links = [];
+      $routes = [
+        'export' => 'config.export_single',
+        'import' => 'config_update_ui.import',
+        'diff' => 'config_update_ui.diff',
+        'revert' => 'config_update_ui.revert',
+        'delete' => 'config_update_ui.delete',
+      ];
+      $titles = [
+        'export' => $this->t('Export'),
+        'import' => $this->t('Import from source'),
+        'diff' => $this->t('Show differences'),
+        'revert' => $this->t('Revert to source'),
+        'delete' => $this->t('Delete'),
+      ];
+
+      foreach ($actions as $action) {
+        $links[$action] = [
+          'url' => Url::fromRoute($routes[$action], ['config_type' => $entity_type, 'config_name' => $id]),
+          'title' => $titles[$action],
+        ];
+      }
+
+      $row[] = [
+        'data' => [
+          '#type' => 'operations',
+          '#links' => $links,
+        ],
+      ];
+
+      $build['#rows'][] = $row;
+    }
+
+    return $build;
+  }
+
+  /**
+   * Compares links for uasort(), to sort by displayed link title.
+   */
+  protected static function sortLinks($link1, $link2) {
+    $title1 = $link1['title'];
+    $title2 = $link2['title'];
+    if ($title1 == $title2) {
+      return 0;
+    }
+    return ($title1 < $title2) ? -1 : 1;
+  }
+
+}

+ 143 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\config_update_ui\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\config_update\ConfigListInterface;
+use Drupal\config_update\ConfigRevertInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a confirmation form for deleting configuration.
+ */
+class ConfigDeleteConfirmForm extends ConfirmFormBase {
+
+  /**
+   * The type of config being deleted.
+   *
+   * @var string
+   */
+  protected $type;
+
+  /**
+   * The name of the config item being deleted, without the prefix.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The config lister.
+   *
+   * @var \Drupal\config_update\ConfigListInterface
+   */
+  protected $configList;
+
+  /**
+   * The config reverter.
+   *
+   * @var \Drupal\config_update\ConfigRevertInterface
+   */
+  protected $configRevert;
+
+  /**
+   * Constructs a ConfigDeleteConfirmForm object.
+   *
+   * @param \Drupal\config_update\ConfigListInterface $config_list
+   *   The config lister.
+   * @param \Drupal\config_update\ConfigRevertInterface $config_update
+   *   The config reverter.
+   */
+  public function __construct(ConfigListInterface $config_list, ConfigRevertInterface $config_update) {
+    $this->configList = $config_list;
+    $this->configRevert = $config_update;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config_update.config_list'),
+      $container->get('config_update.config_update')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'config_delete_confirm';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    if ($this->type == 'system.simple') {
+      $type_label = $this->t('Simple configuration');
+    }
+    else {
+      $definition = $this->configList->getType($this->type);
+      $type_label = $definition->get('label');
+    }
+
+    return $this->t('Are you sure you want to delete the %type config %item?', ['%type' => $type_label, '%item' => $this->name]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('config_update_ui.report');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('This action cannot be undone. Manually deleting configuration from this page can cause problems on your site due to missing dependencies, and should only be done if there is no other way to delete a problematic piece of configuration.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $config_type = NULL, $config_name = NULL) {
+    $this->type = $config_type;
+    $this->name = $config_name;
+
+    $form = parent::buildForm($form, $form_state);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $value = $this->configRevert->getFromActive($this->type, $this->name);
+    if (!$value) {
+      $form_state->setErrorByName('', $this->t('There is no configuration @type named @name to delete', ['@type' => $this->type, '@name' => $this->name]));
+      return;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->configRevert->delete($this->type, $this->name);
+
+    drupal_set_message($this->t('The configuration was deleted.'));
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}

+ 143 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\config_update_ui\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\config_update\ConfigListInterface;
+use Drupal\config_update\ConfigRevertInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a confirmation form for reverting configuration.
+ */
+class ConfigRevertConfirmForm extends ConfirmFormBase {
+
+  /**
+   * The type of config being reverted.
+   *
+   * @var string
+   */
+  protected $type;
+
+  /**
+   * The name of the config item being reverted, without the prefix.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The config lister.
+   *
+   * @var \Drupal\config_update\ConfigListInterface
+   */
+  protected $configList;
+
+  /**
+   * The config reverter.
+   *
+   * @var \Drupal\config_update\ConfigRevertInterface
+   */
+  protected $configRevert;
+
+  /**
+   * Constructs a ConfigRevertConfirmForm object.
+   *
+   * @param \Drupal\config_update\ConfigListInterface $config_list
+   *   The config lister.
+   * @param \Drupal\config_update\ConfigRevertInterface $config_update
+   *   The config reverter.
+   */
+  public function __construct(ConfigListInterface $config_list, ConfigRevertInterface $config_update) {
+    $this->configList = $config_list;
+    $this->configRevert = $config_update;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config_update.config_list'),
+      $container->get('config_update.config_update')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'config_update_confirm';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    if ($this->type == 'system.simple') {
+      $type_label = $this->t('Simple configuration');
+    }
+    else {
+      $definition = $this->configList->getType($this->type);
+      $type_label = $definition->get('label');
+    }
+
+    return $this->t('Are you sure you want to revert the %type config %item to its source configuration?', ['%type' => $type_label, '%item' => $this->name]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('config_update_ui.report');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('Customizations will be lost. This action cannot be undone.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Revert');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $config_type = NULL, $config_name = NULL) {
+    $this->type = $config_type;
+    $this->name = $config_name;
+
+    $form = parent::buildForm($form, $form_state);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $value = $this->configRevert->getFromExtension($this->type, $this->name);
+    if (!$value) {
+      $form_state->setErrorByName('', $this->t('There is no configuration @type named @name to import', ['@type' => $this->type, '@name' => $this->name]));
+      return;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->configRevert->revert($this->type, $this->name);
+
+    drupal_set_message($this->t('The configuration was reverted to its source.'));
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}

+ 283 - 0
sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigUpdateTest.php

@@ -0,0 +1,283 @@
+<?php
+
+namespace Drupal\config_update_ui\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Verify the config revert report and its links.
+ *
+ * @group config
+ */
+class ConfigUpdateTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * Use the Search module because it has two included config items in its
+   * config/install, assuming node and user are also enabled.
+   *
+   * @var array.
+   */
+  public static $modules = ['config', 'config_update', 'config_update_ui', 'search', 'node', 'user', 'block', 'text', 'field', 'filter'];
+
+  /**
+   * The admin user that will be created.
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create user and log in.
+    $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer search', 'view config updates report', 'synchronize configuration', 'export configuration', 'import configuration', 'revert configuration', 'delete configuration']);
+    $this->drupalLogin($this->adminUser);
+
+    // Make sure local tasks and page title are showing.
+    $this->drupalPlaceBlock('local_tasks_block');
+    $this->drupalPlaceBlock('page_title_block');
+  }
+
+  /**
+   * Tests the config report and its linked pages.
+   */
+  public function testConfigReport() {
+    // Test links to report page.
+    $this->drupalGet('admin/config/development/configuration');
+    $this->clickLink('Updates report');
+    $this->assertNoReport();
+
+    // Verify some empty reports.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], [], []);
+    // Module, theme, and profile reports have no 'added' section.
+    $this->drupalGet('admin/config/development/configuration/report/module/search');
+    $this->assertReport('Search module', [], [], [], [], ['added']);
+    $this->drupalGet('admin/config/development/configuration/report/theme/classy');
+    $this->assertReport('Classy theme', [], [], [], [], ['added']);
+
+    $inactive = ['locale.settings' => 'Simple configuration'];
+    $this->drupalGet('admin/config/development/configuration/report/profile');
+    $this->assertReport('Testing profile', [], [], [], $inactive, ['added']);
+
+    // Delete the user search page from the search UI and verify report for
+    // both the search page config type and user module.
+    $this->drupalGet('admin/config/search/pages');
+    $this->clickLink('Delete');
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $inactive = ['search.page.user_search' => 'Users'];
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], [], $inactive);
+    $this->drupalGet('admin/config/development/configuration/report/module/user');
+    $this->assertReport('User module', [], [], [], $inactive, ['added', 'changed']);
+
+    // Use the import link to get it back. Do this from the search page
+    // report to make sure we are importing the right config.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->clickLink('Import from source');
+    $this->assertText('The configuration was imported');
+    $this->assertNoReport();
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], [], []);
+
+    // Edit the node search page from the search UI and verify report.
+    $this->drupalGet('admin/config/search/pages');
+    $this->clickLink('Edit');
+    $this->drupalPostForm(NULL, [
+      'label' => 'New label',
+      'path'  => 'new_path',
+    ], 'Save search page');
+    $changed = ['search.page.node_search' => 'New label'];
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], $changed, []);
+
+    // Test the show differences link.
+    $this->clickLink('Show differences');
+    $this->assertText('Content');
+    $this->assertText('New label');
+    $this->assertText('node');
+    $this->assertText('new_path');
+
+    // Test the Back link.
+    $this->clickLink("Back to 'Updates report' page.");
+    $this->assertNoReport();
+
+    // Test the export link.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->clickLink('Export');
+    $this->assertText('Here is your configuration:');
+    $this->assertText('id: node_search');
+    $this->assertText('New label');
+    $this->assertText('path: new_path');
+    $this->assertText('search.page.node_search.yml');
+
+    // Test reverting.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->clickLink('Revert to source');
+    $this->assertText('Are you sure you want to revert');
+    $this->assertText('Search page');
+    $this->assertText('node_search');
+    $this->assertText('Customizations will be lost. This action cannot be undone');
+    $this->drupalPostForm(NULL, [], 'Revert');
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], [], []);
+
+    // Add a new search page from the search UI and verify report.
+    $this->drupalPostForm('admin/config/search/pages', [
+      'search_type' => 'node_search',
+    ], 'Add new page');
+    $this->drupalPostForm(NULL, [
+      'label' => 'test',
+      'id'    => 'test',
+      'path'  => 'test',
+    ], 'Add search page');
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $added = ['search.page.test' => 'test'];
+    $this->assertReport('Search page', [], $added, [], []);
+
+    // Test the export link.
+    $this->clickLink('Export');
+    $this->assertText('Here is your configuration:');
+    $this->assertText('id: test');
+    $this->assertText('label: test');
+    $this->assertText('path: test');
+    $this->assertText('search.page.test.yml');
+
+    // Test the delete link.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->clickLink('Delete');
+    $this->assertText('Are you sure');
+    $this->assertText('cannot be undone');
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $this->assertText('The configuration was deleted');
+    // And verify the report again.
+    $this->drupalGet('admin/config/development/configuration/report/type/search_page');
+    $this->assertReport('Search page', [], [], [], []);
+
+    // Change the search module config and verify the actions work for
+    // simple config.
+    $this->drupalPostForm('admin/config/search/pages', [
+      'minimum_word_size' => 4,
+    ], 'Save configuration');
+    $changed = ['search.settings' => 'search.settings'];
+    $this->drupalGet('admin/config/development/configuration/report/module/search');
+    $this->assertReport('Search module', [], [], $changed, [], ['added']);
+
+    $this->clickLink('Show differences');
+    $this->assertText('Config difference for Simple configuration search.settings');
+    $this->assertText('index::minimum_word_size');
+    $this->assertText('4');
+
+    $this->drupalGet('admin/config/development/configuration/report/module/search');
+    $this->clickLink('Export');
+    $this->assertText('minimum_word_size: 4');
+
+    $this->drupalGet('admin/config/development/configuration/report/module/search');
+    $this->clickLink('Revert to source');
+    $this->drupalPostForm(NULL, [], 'Revert');
+
+    $this->drupalGet('admin/config/development/configuration/report/module/search');
+    $this->assertReport('Search module', [], [], [], [], ['added']);
+  }
+
+  /**
+   * Asserts that the report page has the correct content.
+   *
+   * Assumes you are already on the report page.
+   *
+   * @param string $title
+   *   Report title to check for.
+   * @param string[] $missing
+   *   Array of items that should be listed as missing, name => label.
+   * @param string[] $added
+   *   Array of items that should be listed as missing, name => label.
+   * @param string[] $changed
+   *   Array of items that should be listed as changed, name => label.
+   * @param string[] $inactive
+   *   Array of items that should be listed as inactive, name => label.
+   * @param string[] $skip
+   *   Array of report sections to skip checking.
+   */
+  protected function assertReport($title, $missing, $added, $changed, $inactive, $skip = []) {
+    $this->assertText('Configuration updates report for ' . $title);
+    $this->assertText('Generate new report');
+
+    if (!in_array('missing', $skip)) {
+      $this->assertText('Missing configuration items');
+      if (count($missing)) {
+        foreach ($missing as $name => $label) {
+          $this->assertText($name);
+          $this->assertText($label);
+        }
+        $this->assertNoText('None: all provided configuration items are in your active configuration.');
+      }
+      else {
+        $this->assertText('None: all provided configuration items are in your active configuration.');
+      }
+    }
+
+    if (!in_array('inactive', $skip)) {
+      $this->assertText('Inactive optional items');
+      if (count($inactive)) {
+        foreach ($inactive as $name => $label) {
+          $this->assertText($name);
+          $this->assertText($label);
+        }
+        $this->assertNoText('None: all optional configuration items are in your active configuration.');
+      }
+      else {
+        $this->assertText('None: all optional configuration items are in your active configuration.');
+      }
+    }
+
+    if (!in_array('added', $skip)) {
+      $this->assertText('Added configuration items');
+      if (count($added)) {
+        foreach ($added as $name => $label) {
+          $this->assertText($name);
+          $this->assertText($label);
+        }
+        $this->assertNoText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
+      }
+      else {
+        $this->assertText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
+      }
+    }
+
+    if (!in_array('changed', $skip)) {
+      $this->assertText('Changed configuration items');
+      if (count($changed)) {
+        foreach ($changed as $name => $label) {
+          $this->assertText($name);
+          $this->assertText($label);
+        }
+        $this->assertNoText('None: no active configuration items differ from their current provided versions.');
+      }
+      else {
+        $this->assertText('None: no active configuration items differ from their current provided versions.');
+      }
+    }
+  }
+
+  /**
+   * Asserts that the report is not shown.
+   *
+   * Assumes you are already on the report form page.
+   */
+  protected function assertNoReport() {
+    $this->assertText('Report type');
+    $this->assertText('Configuration type');
+    $this->assertText('Module');
+    $this->assertText('Theme');
+    $this->assertText('Installation profile');
+    $this->assertText('Updates report');
+    $this->assertNoText('Missing configuration items');
+    $this->assertNoText('Added configuration items');
+    $this->assertNoText('Changed configuration items');
+    $this->assertNoText('Unchanged configuration items');
+  }
+
+}

+ 37 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigDeleteInterface.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\config_update;
+
+/**
+ * Defines an interface for deleting config items.
+ */
+interface ConfigDeleteInterface {
+
+  /**
+   * Name of the event triggered on configuration delete.
+   *
+   * @see \Drupal\config_update\ConfigRevertEvent
+   * @see \Drupal\config_update\ConfigDeleteInterface::delete()
+   */
+  const DELETE = 'config_update.delete';
+
+  /**
+   * Deletes a configuration item.
+   *
+   * This action triggers a ConfigDeleteInterface::DELETE event.
+   *
+   * @param string $type
+   *   The type of configuration.
+   * @param string $name
+   *   The name of the config item, without the prefix.
+   *
+   * @return bool
+   *   TRUE if the operation succeeded; FALSE if the base configuration could
+   *   not be found to delete. May also throw exceptions if there is a
+   *   problem during deleting the configuration.
+   *
+   * @see \Drupal\config_update\ConfigDeleteInterface::DELETE
+   */
+  public function delete($type, $name);
+
+}

+ 46 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigDiffInterface.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\config_update;
+
+/**
+ * Defines an interface for config differences.
+ */
+interface ConfigDiffInterface {
+
+  /**
+   * Decides if two configuration arrays are considered to be the same.
+   *
+   * The two arrays are considered to be the same if, after "normalizing", they
+   * have the same keys and values. It is up to the particular implementing
+   * class to decide what normalizing means.
+   *
+   * @param array $source
+   *   Source config.
+   * @param array $target
+   *   Target config.
+   *
+   * @return bool
+   *   TRUE if the source and target are the same, and FALSE if they are
+   *   different.
+   */
+  public function same($source, $target);
+
+  /**
+   * Calculates differences between config.
+   *
+   * The two arrays are "normalized" and split into lines to compare
+   * differences. It is up to the particular implementing class to decide what
+   * normalizing means.
+   *
+   * @param array $source
+   *   Source config.
+   * @param array $target
+   *   Target config.
+   *
+   * @return \Drupal\Component\Diff\Diff
+   *   Diff object for displaying line-by-line differences between source and
+   *   target config.
+   */
+  public function diff($source, $target);
+
+}

+ 180 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace Drupal\config_update;
+
+use Drupal\Component\Diff\Diff;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Provides methods related to config differences.
+ */
+class ConfigDiffer implements ConfigDiffInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * List of elements to ignore when comparing config.
+   *
+   * @var string[]
+   *
+   * @see ConfigDiffer::format().
+   */
+  protected $ignore;
+
+  /**
+   * Prefix to use to indicate config hierarchy.
+   *
+   * @var string
+   *
+   * @see ConfigDiffer::format().
+   */
+  protected $hierarchyPrefix;
+
+  /**
+   * Prefix to use to indicate config values.
+   *
+   * @var string
+   *
+   * @see ConfigDiffer::format().
+   */
+  protected $valuePrefix;
+
+  /**
+   * Constructs a ConfigDiffer.
+   *
+   * @param TranslationInterface $translation
+   *   String translation service.
+   * @param string[] $ignore
+   *   Config components to ignore.
+   * @param string $hierarchy_prefix
+   *   Prefix to use in diffs for array hierarchy.
+   * @param string $value_prefix
+   *   Prefix to use in diffs for array value.
+   */
+  public function __construct(TranslationInterface $translation, $ignore = ['uuid', '_core'], $hierarchy_prefix = '::', $value_prefix = ' : ') {
+    $this->stringTranslation = $translation;
+    $this->hierarchyPrefix = $hierarchy_prefix;
+    $this->valuePrefix = $value_prefix;
+    $this->ignore = $ignore;
+  }
+
+  /**
+   * Normalizes config for comparison.
+   *
+   * Recursively removes elements in the ignore list from configuration,
+   * as well as empty array values, and sorts at each level by array key, so
+   * that config from different storage can be compared meaningfully.
+   *
+   * @param array $config
+   *   Configuration array to normalize.
+   *
+   * @return array
+   *   Normalized configuration array.
+   *
+   * @see ConfigDiffer::format()
+   * @see ConfigDiffer::$ignore
+   */
+  protected function normalize($config) {
+    // Remove "ignore" elements.
+    foreach ($this->ignore as $element) {
+      unset($config[$element]);
+    }
+
+    // Recursively normalize remaining elements, if they are arrays.
+    foreach ($config as $key => $value) {
+      if (is_array($value)) {
+        $new = $this->normalize($value);
+        if (count($new)) {
+          $config[$key] = $new;
+        }
+        else {
+          unset($config[$key]);
+        }
+      }
+    }
+
+    // Sort and return.
+    ksort($config);
+    return $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function same($source, $target) {
+    $source = $this->normalize($source);
+    $target = $this->normalize($target);
+    return $source == $target;
+  }
+
+  /**
+   * Formats config for showing differences.
+   *
+   * To compute differences, we need to separate the config into lines and use
+   * line-by-line differencer. The obvious way to split into lines is:
+   * @code
+   * explode("\n", Yaml::encode($config))
+   * @endcode
+   * But this would highlight meaningless differences due to the often different
+   * order of config files, and also loses the indentation and context of the
+   * config hierarchy when differences are computed, making the difference
+   * difficult to interpret.
+   *
+   * So, what we do instead is to take the YAML hierarchy and format it so that
+   * the hierarchy is shown on each line. So, if you're in element
+   * $config['foo']['bar'] and the value is 'value', you will see
+   * 'foo::bar : value'.
+   *
+   * @param array $config
+   *   Config array to format. Normalize it first if you want to do diffs.
+   * @param string $prefix
+   *   (optional) When called recursively, the prefix to put on each line. Omit
+   *   when initially calling this function.
+   *
+   * @return string[] Array of config lines formatted so that a line-by-line
+   *   diff will show the context in each line, and meaningful differences will
+   *   be computed.
+   *
+   * @see ConfigDiffer::normalize()
+   * @see ConfigDiffer::$hierarchyPrefix
+   * @see ConfigDiffer::$valuePrefix
+   */
+  protected function format($config, $prefix = '') {
+    $lines = [];
+
+    foreach ($config as $key => $value) {
+      $section_prefix = ($prefix) ? $prefix . $this->hierarchyPrefix . $key : $key;
+
+      if (is_array($value)) {
+        $lines[] = $section_prefix;
+        $newlines = $this->format($value, $section_prefix);
+        foreach ($newlines as $line) {
+          $lines[] = $line;
+        }
+      }
+      elseif (is_null($value)) {
+        $lines[] = $section_prefix . $this->valuePrefix . $this->t('(NULL)');
+      }
+      else {
+        $lines[] = $section_prefix . $this->valuePrefix . $value;
+      }
+    }
+
+    return $lines;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function diff($source, $target) {
+    $source = $this->normalize($source);
+    $target = $this->normalize($target);
+
+    $source_lines = $this->format($source);
+    $target_lines = $this->format($target);
+
+    return new Diff($source_lines, $target_lines);
+  }
+
+}

+ 74 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigListInterface.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\config_update;
+
+/**
+ * Defines an interface for config listings.
+ */
+interface ConfigListInterface {
+
+  /**
+   * Lists the types of configuration available on the system.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   *   Array of entity type definitions, keyed by machine name of the type.
+   */
+  public function listTypes();
+
+  /**
+   * Returns the entity type object for a given config type name.
+   *
+   * @param string $name
+   *   Config entity type machine name.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface
+   *   Entity type object with machine name $name.
+   */
+  public function getType($name);
+
+  /**
+   * Returns the entity type object for a given config prefix.
+   *
+   * @param string $prefix
+   *   Config prefix.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface
+   *   Entity type object corresponding to $prefix.
+   */
+  public function getTypeByPrefix($prefix);
+
+  /**
+   * Returns the entity type object for a given config object.
+   *
+   * @param string $name
+   *   Name of the config object.
+   *
+   * @return string
+   *   Name of the entity type that this is an instance of, determined by
+   *   prefix. NULL for simple configuration.
+   */
+  public function getTypeNameByConfigName($name);
+
+  /**
+   * Lists the config objects in active and extension storage.
+   *
+   * @param string $list_type
+   *   Type of list to make: 'type', 'module', 'theme', or 'profile'.
+   * @param string $name
+   *   Machine name of a configuration type, module, or theme to generate the
+   *   list for. Ignored for profile, since that uses the active profile. Use
+   *   type 'system.simple' for simple config, and 'system.all' to list all
+   *   config items.
+   *
+   * @return array
+   *   Array whose first element is the list of config objects in active
+   *   storage, second is the list of config objects in extension storage,
+   *   and third is the list of optional config objects in extension storage
+   *   (the ones with dependencies from config/optional directories).
+   *   Note that for everything except 'type' lists, the active storage list
+   *   includes all configuration items in the system, not limited to ones from
+   *   this module, theme, or profile.
+   */
+  public function listConfig($list_type, $name);
+
+}

+ 221 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigLister.php

@@ -0,0 +1,221 @@
+<?php
+
+namespace Drupal\config_update;
+
+use Drupal\Core\Config\ExtensionInstallStorage;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Extension\Extension;
+
+/**
+ * Provides methods related to config listing.
+ */
+class ConfigLister implements ConfigListInterface {
+
+  /**
+   * List of current config entity types, keyed by prefix.
+   *
+   * This is not set up until ConfigLister::listTypes() has been called.
+   *
+   * @var string[]
+   */
+  protected $typesByPrefix = [];
+
+  /**
+   * List of current config entity type definitions, keyed by entity type.
+   *
+   * This is not set up until ConfigLister::listTypes() has been called.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface[]
+   */
+  protected $definitions = [];
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The active config storage.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $activeConfigStorage;
+
+  /**
+   * The extension config storage.
+   *
+   * @var \Drupal\Core\Config\ExtensionInstallStorage
+   */
+  protected $extensionConfigStorage;
+
+  /**
+   * The extension config storage for optional config.
+   *
+   * @var \Drupal\Core\Config\ExtensionInstallStorage
+   */
+  protected $extensionOptionalConfigStorage;
+
+  /**
+   * Constructs a ConfigLister.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Config\StorageInterface $active_config_storage
+   *   The active config storage.
+   * @param \Drupal\Core\Config\ExtensionInstallStorage $extension_config_storage
+   *   The extension config storage.
+   * @param \Drupal\Core\Config\ExtensionInstallStorage $extension_optional_config_storage
+   *   The extension config storage for optional config items.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_manager, StorageInterface $active_config_storage, ExtensionInstallStorage $extension_config_storage, ExtensionInstallStorage $extension_optional_config_storage) {
+    $this->entityManager = $entity_manager;
+    $this->activeConfigStorage = $active_config_storage;
+    $this->extensionConfigStorage = $extension_config_storage;
+    $this->extensionOptionalConfigStorage = $extension_optional_config_storage;
+  }
+
+  /**
+   * Sets up and returns the entity definitions list.
+   */
+  public function listTypes() {
+    if (count($this->definitions)) {
+      return $this->definitions;
+    }
+
+    foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
+      if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+        $this->definitions[$entity_type] = $definition;
+        $prefix = $definition->getConfigPrefix();
+        $this->typesByPrefix[$prefix] = $entity_type;
+      }
+    }
+
+    ksort($this->definitions);
+
+    return $this->definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType($name) {
+    $definitions = $this->listTypes();
+    return isset($definitions[$name]) ? $definitions[$name] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypeByPrefix($prefix) {
+    $definitions = $this->listTypes();
+    return isset($this->typesByPrefix[$prefix]) ? $definitions[$this->typesByPrefix[$prefix]] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypeNameByConfigName($name) {
+    $definitions = $this->listTypes();
+    foreach ($this->typesByPrefix as $prefix => $entity_type) {
+      if (strpos($name, $prefix) === 0) {
+        return $entity_type;
+      }
+    }
+
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listConfig($list_type, $name) {
+    $active_list = [];
+    $install_list = [];
+    $optional_list = [];
+    $definitions = $this->listTypes();
+
+    switch ($list_type) {
+      case 'type':
+        if ($name == 'system.all') {
+          $active_list = $this->activeConfigStorage->listAll();
+          $install_list = $this->extensionConfigStorage->listAll();
+          $optional_list = $this->extensionOptionalConfigStorage->listAll();
+        }
+        elseif ($name == 'system.simple') {
+          // Listing is done by prefixes, and simple config doesn't have one.
+          // So list all and filter out all known prefixes.
+          $active_list = $this->omitKnownPrefixes($this->activeConfigStorage->listAll());
+          $install_list = $this->omitKnownPrefixes($this->extensionConfigStorage->listAll());
+          $optional_list = $this->omitKnownPrefixes($this->extensionOptionalConfigStorage->listAll());
+        }
+        elseif (isset($this->definitions[$name])) {
+          $definition = $this->definitions[$name];
+          $prefix = $definition->getConfigPrefix();
+          $active_list = $this->activeConfigStorage->listAll($prefix);
+          $install_list = $this->extensionConfigStorage->listAll($prefix);
+          $optional_list = $this->extensionOptionalConfigStorage->listAll($prefix);
+        }
+        break;
+
+      case 'profile':
+        $name = Settings::get('install_profile');
+        // Intentional fall-through here to the 'module' or 'theme' case.
+      case 'module':
+      case 'theme':
+        $active_list = $this->activeConfigStorage->listAll();
+        $install_list = $this->listProvidedItems($list_type, $name);
+        $optional_list = $this->listProvidedItems($list_type, $name, TRUE);
+        break;
+    }
+
+    return [$active_list, $install_list, $optional_list];
+  }
+
+  /**
+   * Returns a list of the install storage items for an extension.
+   *
+   * @param string $type
+   *   Type of extension ('module', etc.).
+   * @param string $name
+   *   Machine name of extension.
+   * @param bool $do_optional
+   *   FALSE (default) to list config/install items, TRUE to list
+   *   config/optional items.
+   *
+   * @return string[]
+   *   List of config items provided by this extension.
+   */
+  protected function listProvidedItems($type, $name, $do_optional = FALSE) {
+    $pathname = drupal_get_filename($type, $name);
+    $component = new Extension(\Drupal::root(), $type, $pathname);
+    if ($do_optional) {
+      $names = $this->extensionOptionalConfigStorage->getComponentNames([$component]);
+    }
+    else {
+      $names = $this->extensionConfigStorage->getComponentNames([$component]);
+    }
+    return array_keys($names);
+  }
+
+  /**
+   * Omits config with known prefixes from a list of config names.
+   */
+  protected function omitKnownPrefixes($list) {
+    $prefixes = array_keys($this->typesByPrefix);
+    $list = array_combine($list, $list);
+    foreach ($list as $name) {
+      foreach ($prefixes as $prefix) {
+        if (strpos($name, $prefix) === 0) {
+          unset($list[$name]);
+        }
+      }
+    }
+
+    return array_values($list);
+  }
+
+}

+ 66 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigRevertEvent.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\config_update;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event context class for configuration revert/import events.
+ *
+ * This class is passed in as the event when the
+ * \Drupal\config_update\ConfigRevertInterface::IMPORT,
+ * \Drupal\config_update\ConfigDeleteInterface::DELETE, and
+ * \Drupal\config_update\ConfigRevertInterface::REVERT events are triggered.
+ */
+class ConfigRevertEvent extends Event {
+
+  /**
+   * The type of configuration that is being imported or reverted.
+   *
+   * @var string
+   */
+  protected $type;
+
+  /**
+   * The name of the config item being imported or reverted, without prefix.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * Constructs a new ConfigRevertEvent.
+   *
+   * @param string $type
+   *   The type of configuration being imported or reverted.
+   * @param string $name
+   *   The name of the config item being imported/reverted, without prefix.
+   */
+  public function __construct($type, $name) {
+    $this->type = $type;
+    $this->name = $name;
+  }
+
+  /**
+   * Returns the type of configuration being imported or reverted.
+   *
+   * @return string
+   *   The type of configuration, either 'system.simple' or a config entity
+   *   type machine name.
+   */
+  public function getType() {
+    return $this->type;
+  }
+
+  /**
+   * Returns the name of the config item, without prefix.
+   *
+   * @return string
+   *   The name of the config item being imported/reverted/deleted, with the
+   *   prefix.
+   */
+  public function getName() {
+    return $this->name;
+  }
+
+}

+ 96 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\config_update;
+
+/**
+ * Defines an interface for config import and revert operations.
+ */
+interface ConfigRevertInterface {
+
+  /**
+   * Name of the event triggered on configuration import.
+   *
+   * @see \Drupal\config_update\ConfigRevertEvent
+   * @see \Drupal\config_update\ConfigRevertInterface::import()
+   */
+  const IMPORT = 'config_update.import';
+
+  /**
+   * Name of the event triggered on configuration revert.
+   *
+   * @see \Drupal\config_update\ConfigRevertEvent
+   * @see \Drupal\config_update\ConfigRevertInterface::revert()
+   */
+  const REVERT = 'config_update.revert';
+
+  /**
+   * Imports configuration from extension storage to active storage.
+   *
+   * This action triggers a ConfigRevertInterface::IMPORT event if the
+   * configuration could be imported.
+   *
+   * @param string $type
+   *   The type of configuration.
+   * @param string $name
+   *   The name of the config item, without the prefix.
+   *
+   * @return bool
+   *   TRUE if the operation succeeded; FALSE if the configuration could not
+   *   be found to import. May also throw exceptions if there is a problem
+   *   during saving the configuration.
+   *
+   * @see \Drupal\config_update\ConfigRevertInterface::IMPORT
+   */
+  public function import($type, $name);
+
+  /**
+   * Reverts configuration to the value from extension storage.
+   *
+   * This action triggers a ConfigRevertInterface::REVERT event.
+   *
+   * @param string $type
+   *   The type of configuration.
+   * @param string $name
+   *   The name of the config item, without the prefix.
+   *
+   * @return bool
+   *   TRUE if the operation succeeded; FALSE if the base configuration could
+   *   not be found to revert to. May also throw exceptions if there is a
+   *   problem during saving the configuration.
+   *
+   * @see \Drupal\config_update\ConfigRevertInterface::REVERT
+   */
+  public function revert($type, $name);
+
+  /**
+   * Gets the current active value of configuration.
+   *
+   * @param string $type
+   *   The type of configuration. Or pass '' to indicate that $name is the full
+   *   name.
+   * @param string $name
+   *   The name of the config item, without the prefix.
+   *
+   * @return array
+   *   The configuration value.
+   */
+  public function getFromActive($type, $name);
+
+  /**
+   * Gets the extension storage value of configuration.
+   *
+   * This is the value from a file in the config/install or config/optional
+   * directory of a module, theme, or install profile.
+   *
+   * @param string $type
+   *   The type of configuration. Or pass '' to indicate that $name is the full
+   *   name.
+   * @param string $name
+   *   The name of the config item, without the prefix.
+   *
+   * @return array|false
+   *   The configuration value, or FALSE if it could not be located.
+   */
+  public function getFromExtension($type, $name);
+
+}

+ 218 - 0
sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php

@@ -0,0 +1,218 @@
+<?php
+
+namespace Drupal\config_update;
+
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Provides methods related to config reverting, deleting, and importing.
+ *
+ * In this class, when any import or revert operation is requested, the
+ * configuration that is being reverted or imported is searched for in both the
+ * config/install repository and config/optional. This happens automatically.
+ */
+class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The active config storage.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $activeConfigStorage;
+
+  /**
+   * The extension config storage for config/install config items.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $extensionConfigStorage;
+
+  /**
+   * The extension config storage for config/optional config items.
+   *
+   * @var \Drupal\Core\Config\ExtensionInstallStorage
+   */
+  protected $extensionOptionalConfigStorage;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $dispatcher;
+
+  /**
+   * Constructs a ConfigReverter.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Config\StorageInterface $active_config_storage
+   *   The active config storage.
+   * @param \Drupal\Core\Config\StorageInterface $extension_config_storage
+   *   The extension config storage.
+   * @param \Drupal\Core\Config\StorageInterface $extension_optional_config_storage
+   *   The extension config storage for optional config items.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
+   *   The event dispatcher.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_manager, StorageInterface $active_config_storage, StorageInterface $extension_config_storage, StorageInterface $extension_optional_config_storage, ConfigFactoryInterface $config_factory, EventDispatcherInterface $dispatcher) {
+    $this->entityManager = $entity_manager;
+    $this->activeConfigStorage = $active_config_storage;
+    $this->extensionConfigStorage = $extension_config_storage;
+    $this->extensionOptionalConfigStorage = $extension_optional_config_storage;
+    $this->configFactory = $config_factory;
+    $this->dispatcher = $dispatcher;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function import($type, $name) {
+    // Read the config from the file.
+    $full_name = $this->getFullName($type, $name);
+    $value = $this->extensionConfigStorage->read($full_name);
+    if (!$value) {
+      $value = $this->extensionOptionalConfigStorage->read($full_name);
+    }
+    if (!$value) {
+      return FALSE;
+    }
+
+    // Save it as a new config entity or simple config.
+    if ($type == 'system.simple') {
+      $this->configFactory->getEditable($full_name)->setData($value)->save();
+    }
+    else {
+      $entity_storage = $this->entityManager->getStorage($type);
+      $entity = $entity_storage->createFromStorageRecord($value);
+      $entity->save();
+    }
+
+    // Trigger an event notifying of this change.
+    $event = new ConfigRevertEvent($type, $name);
+    $this->dispatcher->dispatch(ConfigRevertInterface::IMPORT, $event);
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function revert($type, $name) {
+    // Read the config from the file.
+    $full_name = $this->getFullName($type, $name);
+    $value = $this->extensionConfigStorage->read($full_name);
+    if (!$value) {
+      $value = $this->extensionOptionalConfigStorage->read($full_name);
+    }
+    if (!$value) {
+      return FALSE;
+    }
+
+    if ($type == 'system.simple') {
+      // Load the current config and replace the value.
+      $this->configFactory->getEditable($full_name)->setData($value)->save();
+    }
+    else {
+      // Load the current config entity and replace the value, with the
+      // old UUID.
+      $definition = $this->entityManager->getDefinition($type);
+      $id_key = $definition->getKey('id');
+
+      $id = $value[$id_key];
+      $entity_storage = $this->entityManager->getStorage($type);
+      $entity = $entity_storage->load($id);
+      $uuid = $entity->get('uuid');
+      $entity = $entity_storage->updateFromStorageRecord($entity, $value);
+      $entity->set('uuid', $uuid);
+      $entity->save();
+    }
+
+    // Trigger an event notifying of this change.
+    $event = new ConfigRevertEvent($type, $name);
+    $this->dispatcher->dispatch(ConfigRevertInterface::REVERT, $event);
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($type, $name) {
+    $full_name = $this->getFullName($type, $name);
+    if (!$full_name) {
+      return FALSE;
+    }
+    $config = $this->configFactory->getEditable($full_name);
+    if (!$config) {
+      return FALSE;
+    }
+    $config->delete();
+
+    // Trigger an event notifying of this change.
+    $event = new ConfigRevertEvent($type, $name);
+    $this->dispatcher->dispatch(ConfigDeleteInterface::DELETE, $event);
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromActive($type, $name) {
+    $full_name = $this->getFullName($type, $name);
+    return $this->activeConfigStorage->read($full_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromExtension($type, $name) {
+    $full_name = $this->getFullName($type, $name);
+    $value = $this->extensionConfigStorage->read($full_name);
+    if (!$value) {
+      $value = $this->extensionOptionalConfigStorage->read($full_name);
+    }
+    return $value;
+  }
+
+  /**
+   * 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.
+   */
+  protected function getFullName($type, $name) {
+    if ($type == 'system.simple' || !$type) {
+      return $name;
+    }
+
+    $definition = $this->entityManager->getDefinition($type);
+    $prefix = $definition->getConfigPrefix() . '.';
+    return $prefix . $name;
+  }
+
+}

+ 339 - 0
sites/all/modules/contrib/admin/features/LICENSE.txt

@@ -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.

+ 8 - 0
sites/all/modules/contrib/admin/features/composer.json

@@ -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": { }
+}

+ 92 - 0
sites/all/modules/contrib/admin/features/config/install/features.bundle.default.yml

@@ -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
+

+ 3 - 0
sites/all/modules/contrib/admin/features/config/install/features.settings.yml

@@ -0,0 +1,3 @@
+export:
+  folder: 'custom'
+langcode: en

+ 207 - 0
sites/all/modules/contrib/admin/features/config/schema/features.schema.yml

@@ -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

+ 936 - 0
sites/all/modules/contrib/admin/features/drush/features.drush.inc

@@ -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);
+}

+ 14 - 0
sites/all/modules/contrib/admin/features/features.info.yml

@@ -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

+ 47 - 0
sites/all/modules/contrib/admin/features/features.module

@@ -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();
+  }
+}

+ 6 - 0
sites/all/modules/contrib/admin/features/features.routing.yml

@@ -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'

+ 32 - 0
sites/all/modules/contrib/admin/features/features.services.yml

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/css/features_ui.admin.css

@@ -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;
+}

+ 175 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.admin.inc

@@ -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']);
+}

+ 14 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.info.yml

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.libraries.yml

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.action.yml

@@ -0,0 +1,5 @@
+features_ui.feature_add:
+  route_name: features.edit
+  title: 'Create new feature'
+  appears_on:
+    - features.export

+ 5 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.menu.yml

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.links.task.yml

@@ -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

+ 55 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.module

@@ -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',
+    ),
+  );
+}

+ 95 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.routing.yml

@@ -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'

+ 401 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/js/features_ui.admin.js

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Controller/FeaturesUIController.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentBaseForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentConfigureForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentCoreForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentExcludeForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentFormBase.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentOptionalForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentProfileForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/AssignmentSiteForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesDiffForm.php

@@ -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 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesEditForm.php

@@ -0,0 +1,1085 @@
+<?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\features\ConfigurationItem;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+Use Drupal\Component\Render\FormattableMarkup;
+use Drupal\config_update\ConfigRevertInterface;
+
+/**
+ * Defines the features settings form.
+ */
+class FeaturesEditForm extends FormBase {
+
+  /**
+   * The features manager.
+   *
+   * @var array
+   */
+  protected $featuresManager;
+
+  /**
+   * The package assigner.
+   *
+   * @var array
+   */
+  protected $assigner;
+
+  /**
+   * The package generator.
+   *
+   * @var array
+   */
+  protected $generator;
+
+  /**
+   * Current package being edited.
+   *
+   * @var \Drupal\features\Package
+   */
+  protected $package;
+
+  /**
+   * Current bundle machine name.
+   *
+   * NOTE: D8 cannot serialize objects within forms so you can't directly
+   * store the entire Bundle object here.
+   *
+   * @var string
+   */
+  protected $bundle;
+
+  /**
+   * Previous bundle name for ajax processing.
+   *
+   * @var string
+   */
+  protected $oldBundle;
+
+  /**
+   * Config to be specifically excluded.
+   *
+   * @var array
+   */
+  protected $excluded;
+
+  /**
+   * Config to be specifically required.
+   *
+   * @var array
+   */
+  protected $required;
+
+  /**
+   * Config referenced by other packages.
+   *
+   * @var array
+   */
+  protected $conflicts;
+
+  /**
+   * Determine if conflicts are allowed to be added.
+   *
+   * @var bool
+   */
+  protected $allowConflicts;
+
+  /**
+   * Config missing from active site.
+   *
+   * @var array
+   */
+  protected $missing;
+
+  /**
+   * The config reverter.
+   *
+   * @var \Drupal\config_update\ConfigRevertInterface
+   */
+  protected $configRevert;
+
+  /**
+   * Constructs a FeaturesEditForm object.
+   *
+   * @param \Drupal\features\FeaturesManagerInterface $features_manager
+   *   The features manager.
+   */
+  public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner, FeaturesGeneratorInterface $generator, ConfigRevertInterface $config_revert) {
+    $this->featuresManager = $features_manager;
+    $this->assigner = $assigner;
+    $this->generator = $generator;
+    $this->configRevert = $config_revert;
+    $this->excluded = [];
+    $this->required = [];
+    $this->conflicts = [];
+    $this->missing = [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('features.manager'),
+      $container->get('features_assigner'),
+      $container->get('features_generator'),
+      $container->get('features.config_update')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'features_edit_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $featurename = '') {
+    $session = $this->getRequest()->getSession();
+    $trigger = $form_state->getTriggeringElement();
+    if ($trigger['#name'] == 'package') {
+      // Save current bundle name for later ajax callback.
+      $this->oldBundle = $this->bundle;
+    }
+    elseif ($trigger['#name'] == 'conflicts') {
+      if (isset($session)) {
+        $session->set('features_allow_conflicts', $form_state->getValue('conflicts'));
+      }
+    }
+    if (!$form_state->isValueEmpty('package')) {
+      $bundle_name = $form_state->getValue('package');
+      $bundle = $this->assigner->getBundle($bundle_name);
+    }
+    else {
+      $bundle = $this->assigner->loadBundle();
+    }
+    // Only store bundle name, not full object.
+    $this->bundle = $bundle->getMachineName();
+
+    $this->allowConflicts = FALSE;
+    if (isset($session)) {
+      $this->allowConflicts = $session->get('features_allow_conflicts', FALSE);
+    }
+
+    // Pass the $force argument as TRUE because we want to include any excluded
+    // configuration items. These should show up as automatically assigned, but
+    // not selected, thus allowing the admin to reselect if desired.
+    // @see FeaturesManagerInterface::assignConfigPackage()
+    $this->assigner->assignConfigPackages(TRUE);
+
+    $packages = $this->featuresManager->getPackages();
+    if (empty($packages[$featurename])) {
+      $featurename = str_replace(array('-', ' '), '_', $featurename);
+      $this->package = $this->featuresManager->initPackage($featurename, NULL, '', 'module', $bundle);
+    }
+    else {
+      $this->package = $packages[$featurename];
+    }
+
+    if (!empty($packages[$featurename]) && $this->package->getBundle() !== $this->bundle && $form_state->isValueEmpty('package')) {
+      // Make sure the current bundle matches what is stored in the package.
+      // But only do this if the Package value hasn't been manually changed.
+      $bundle = $this->assigner->getBundle($this->package->getBundle());
+      $this->bundle = $bundle->getMachineName();
+      $this->assigner->reset();
+      $this->assigner->assignConfigPackages(TRUE);
+      $packages = $this->featuresManager->getPackages();
+      $this->package = $packages[$featurename];
+    }
+
+    $form = array(
+      '#show_operations' => FALSE,
+      '#prefix' => '<div id="features-edit-wrapper">',
+      '#suffix' => '</div>',
+    );
+
+    $form['info'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('General Information'),
+      '#tree' => FALSE,
+      '#weight' => 2,
+      '#prefix' => "<div id='features-export-info'>",
+      '#suffix' => '</div>',
+    );
+
+    $form['info']['name'] = array(
+      '#title' => $this->t('Name'),
+      '#description' => $this->t('Example: Image gallery') . ' (' . $this->t('Do not begin name with numbers.') . ')',
+      '#type' => 'textfield',
+      '#default_value' => $this->package->getName(),
+    );
+    if (!$bundle->isDefault()) {
+      $form['info']['name']['#description'] .= '<br/>' .
+        $this->t('The namespace "@name_" will be prepended to the machine name', array('@name' => $bundle->getMachineName()));
+    }
+
+    $form['info']['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#title' => $this->t('Machine-readable name'),
+      '#description' => $this->t('Example: image_gallery') . ' ' . $this->t('May only contain lowercase letters, numbers and underscores.'),
+      '#required' => TRUE,
+      '#default_value' => $bundle->getShortName($this->package->getMachineName()),
+      '#machine_name' => array(
+        'source' => array('info', 'name'),
+        'exists' => array($this, 'featureExists'),
+      ),
+    );
+    if (!$bundle->isDefault()) {
+      $form['info']['machine_name']['#description'] .= '<br/>' .
+        $this->t('NOTE: Do NOT include the namespace prefix "@name_"; it will be added automatically.', array('@name' => $bundle->getMachineName()));
+    }
+
+    $form['info']['description'] = array(
+      '#title' => $this->t('Description'),
+      '#description' => $this->t('Provide a short description of what users should expect when they install your feature.'),
+      '#type' => 'textarea',
+      '#rows' => 3,
+      '#default_value' => $this->package->getDescription(),
+    );
+
+    $form['info']['package'] = array(
+      '#title' => $this->t('Bundle'),
+      '#type' => 'select',
+      '#options' => $this->assigner->getBundleOptions(),
+      '#default_value' => $bundle->getMachineName(),
+      '#ajax' => array(
+        'callback' => '::updateBundle',
+        'wrapper' => 'features-export-info',
+      ),
+    );
+
+    $form['info']['version'] = array(
+      '#title' => $this->t('Version'),
+      '#description' => $this->t('Examples: 8.x-1.0, 8.x-1.0-beta1'),
+      '#type' => 'textfield',
+      '#required' => FALSE,
+      '#default_value' => $this->package->getVersion(),
+      '#size' => 30,
+    );
+
+    list($full_name, $path) = $this->featuresManager->getExportInfo($this->package, $bundle);
+    $form['info']['directory'] = array(
+      '#title' => $this->t('Path'),
+      '#description' => $this->t('Path to export package using Write action, relative to root directory.'),
+      '#type' => 'textfield',
+      '#required' => FALSE,
+      '#default_value' => $path,
+      '#size' => 30,
+    );
+
+    $require_all = $this->package->getRequiredAll();
+    $form['info']['require_all'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Mark all config as required'),
+      '#default_value' => $this->package->getRequiredAll(),
+      '#description' => $this->t('Required config will be assigned to this feature regardless of other assignment plugins.'),
+    );
+
+    $form['conflicts'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Allow conflicts'),
+      '#default_value' => $this->allowConflicts,
+      '#description' => $this->t('Allow configuration to be exported to more than one feature.'),
+      '#weight' => 8,
+      '#ajax' => array(
+        'callback' => '::updateForm',
+        'wrapper' => 'features-edit-wrapper',
+      ),
+    );
+
+    $generation_info = array();
+    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['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']),
+        ),
+      );
+    }
+
+    // Build the Component Listing panel on the right.
+    $form['export'] = $this->buildComponentList($form_state);
+
+    if (!empty($this->missing)) {
+      if ($this->allowConflicts) {
+        $form['actions']['#prefix'] = '<strong>' .
+          $this->t('WARNING: Package contains configuration missing from site.') . '<br>' .
+          $this->t('This configuration will be removed if you export it.') .
+          '</strong>';
+      }
+      else {
+        foreach ($generation_info as $method_id => $method) {
+          unset($form['actions'][$method_id]);
+        }
+        $form['actions']['#prefix'] = '<strong>' .
+          $this->t('Package contains configuration missing from site.') . '<br>' .
+          $this->t('Import the feature to create the missing config before you can export it.') . '<br>' .
+          $this->t('Or, enable the Allow Conflicts option above.') .
+          '</strong>';
+      }
+      $form['actions']['import_missing'] = array(
+        '#type' => 'submit',
+        '#name' => 'import_missing',
+        '#value' => $this->t('Import Missing'),
+        '#attributes' => array(
+          'title' => $this->t('Import only the missing configuration items.'),
+        ),
+      );
+    }
+
+    $form['#attached'] = array(
+      'library' => array(
+        'features_ui/drupal.features_ui.admin',
+      ),
+      'drupalSettings' => array(
+        'features' => array(
+          'excluded' => $this->excluded,
+          'required' => $this->required,
+          'conflicts' => $this->conflicts,
+          'autodetect' => TRUE,
+        ),
+      ),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Provides an ajax callback for handling conflict checkbox.
+   */
+  public function updateForm($form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * Provides an ajax callback for handling switching the bundle selector.
+   */
+  public function updateBundle($form, FormStateInterface $form_state) {
+    $old_bundle = $this->assigner->getBundle($this->oldBundle);
+    $bundle_name = $form_state->getValue('package');
+    $bundle = $this->assigner->getBundle($bundle_name);
+    if (isset($bundle) && isset($old_bundle)) {
+      $short_name = $old_bundle->getShortName($this->package->getMachineName());
+      if ($bundle->isDefault()) {
+        $short_name = $old_bundle->getFullName($short_name);
+      }
+      $this->package->setMachineName($bundle->getFullName($short_name));
+      $form['info']['machine_name']['#value'] = $bundle->getShortName($this->package->getMachineName());
+    }
+    return $form['info'];
+  }
+
+  /**
+   * Callback for machine_name exists()
+   * @param $value
+   * @param $element
+   * @param $form_state
+   * @return bool
+   */
+  public function featureExists($value, $element, $form_state) {
+    $packages = $this->featuresManager->getPackages();
+    return isset($packages[$value]) || \Drupal::moduleHandler()->moduleExists($value);
+  }
+
+  /**
+   * Returns the render array elements for the Components selection on the Edit
+   * form.
+   */
+  protected function buildComponentList(FormStateInterface $form_state) {
+    $element = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Components'),
+      '#description' => $this->t('Expand each component section and select which items should be included in this feature export.'),
+      '#tree' => FALSE,
+      '#prefix' => "<div id='features-export-wrapper'>",
+      '#suffix' => '</div>',
+      '#weight' => 1,
+    );
+
+    // Filter field used in javascript, so javascript will unhide it.
+    $element['features_filter_wrapper'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Filters'),
+      '#tree' => FALSE,
+      '#prefix' => "<div id='features-filter' class='element-invisible'>",
+      '#suffix' => '</div>',
+      '#weight' => -10,
+    );
+    $element['features_filter_wrapper']['features_filter'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Search'),
+      '#hidden' => TRUE,
+      '#default_value' => '',
+      '#suffix' => "<span class='features-filter-clear'>" . $this->t('Clear') . "</span>",
+    );
+    $element['features_filter_wrapper']['checkall'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => FALSE,
+      '#hidden' => TRUE,
+      '#title' => $this->t('Select all'),
+      '#attributes' => array(
+        'class' => array('features-checkall'),
+      ),
+    );
+
+    $sections = array('included', 'detected', 'added');
+    $config_types = $this->featuresManager->listConfigTypes();
+
+    // Generate the export array for the current feature and user selections.
+    $export = $this->getComponentList($form_state);
+
+    foreach ($export['components'] as $component => $component_info) {
+
+      $component_items_count = count($component_info['_features_options']['sources']);
+      $label = new FormattableMarkup('@component (<span class="component-count">@count</span>)',
+        array(
+          '@component' => $config_types[$component],
+          '@count' => $component_items_count,
+        )
+      );
+
+      $count = 0;
+      foreach ($sections as $section) {
+        $count += count($component_info['_features_options'][$section]);
+      }
+      $extra_class = ($count == 0) ? 'features-export-empty' : '';
+      $component_name = str_replace('_', '-', Html::escape($component));
+
+      if ($count + $component_items_count > 0) {
+        $element[$component] = array(
+          '#markup' => '',
+          '#tree' => TRUE,
+        );
+
+        $element[$component]['sources'] = array(
+          '#type' => 'details',
+          '#title' => $label,
+          '#tree' => TRUE,
+          '#open' => FALSE,
+          '#attributes' => array('class' => array('features-export-component')),
+          '#prefix' => "<div class='features-export-parent component-$component'>",
+        );
+        $element[$component]['sources']['selected'] = array(
+          '#type' => 'checkboxes',
+          '#id' => "edit-sources-$component_name",
+          '#options' => $this->domDecodeOptions($component_info['_features_options']['sources']),
+          '#default_value' => $this->domDecodeOptions($component_info['_features_selected']['sources'], FALSE),
+          '#attributes' => array('class' => array('component-select')),
+          '#prefix' => "<span class='component-select'>",
+          '#suffix' => '</span>',
+        );
+
+        $element[$component]['before-list'] = array(
+          '#markup' => "<div class='component-list features-export-list $extra_class'>",
+        );
+
+        foreach ($sections as $section) {
+          $element[$component][$section] = array(
+            '#type' => 'checkboxes',
+            '#options' => !empty($component_info['_features_options'][$section]) ?
+              $this->domDecodeOptions($component_info['_features_options'][$section]) : array(),
+            '#default_value' => !empty($component_info['_features_selected'][$section]) ?
+              $this->domDecodeOptions($component_info['_features_selected'][$section], FALSE) : array(),
+            '#attributes' => array('class' => array('component-' . $section)),
+            '#prefix' => "<span class='component-$section'>",
+            '#suffix' => '</span>',
+          );
+        }
+
+        // Close both the before-list as well as the sources div.
+        $element[$component]['after-list'] = array(
+          '#markup' => "</div></div>",
+        );
+      }
+    }
+
+    $element['features_missing'] = array(
+      '#theme' => 'item_list',
+      '#items' => $export['missing'],
+      '#title' => $this->t('Configuration missing from active site:'),
+      '#suffix' => '<div class="description">' .
+        $this->t('Import the feature to create the missing config listed above.') .
+        '</div>',
+    );
+
+    $element['features_legend'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Legend'),
+      '#tree' => FALSE,
+      '#prefix' => "<div id='features-legend'>",
+      '#suffix' => '</div>',
+    );
+    $element['features_legend']['legend'] = array(
+      '#markup' =>
+        "<span class='component-included'>" . $this->t('Normal') . "</span> " .
+        "<span class='component-added'>" . $this->t('Added') . "</span> " .
+        "<span class='component-detected'>" . $this->t('Auto detected') . "</span> " .
+        "<span class='component-conflict'>" . $this->t('Conflict') . "</span> ",
+    );
+
+    return $element;
+  }
+
+  /**
+   * Returns the full feature export array based upon user selections in
+   * form_state.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Optional form_state information for user selections. Can be updated to
+   *   reflect new selection status.
+   *
+   * @return \Drupal\features\Package
+   *   New export array to be exported
+   *   array['components'][$component_name] = $component_info
+   *     $component_info['_features_options'][$section] is list of available options
+   *     $component_info['_features_selected'][$section] is option state TRUE/FALSE
+   *   $section = array('sources', included', 'detected', 'added')
+   *     sources - options that are available to be added to the feature
+   *     included - options that have been previously exported to the feature
+   *     detected - options that have been auto-detected
+   *     added - newly added options to the feature
+   *
+   * NOTE: This routine gets a bit complex to handle all of the different
+   * possible user checkbox selections and de-selections.
+   * Cases to test:
+   *   1a) uncheck Included item -> mark as Added but unchecked
+   *   1b) re-check unchecked Added item -> return it to Included check item
+   *   2a) check Sources item -> mark as Added and checked
+   *   2b) uncheck Added item -> return it to Sources as unchecked
+   *   3a) uncheck Included item that still exists as auto-detect -> mark as
+   *       Detected but unchecked
+   *   3b) re-check Detected item -> return it to Included and checked
+   *   4a) check Sources item should also add any auto-detect items as Detected
+   *       and checked
+   *   4b) uncheck Sources item with auto-detect and auto-detect items should
+   *       return to Sources and unchecked
+   *   5a) uncheck a Detected item -> refreshing page should keep it as
+   *       unchecked Detected
+   *   6)  when nothing changes, refresh should not change any state
+   *   7)  should never see an unchecked Included item
+   */
+  protected function getComponentList(FormStateInterface $form_state) {
+    $config = $this->featuresManager->getConfigCollection();
+
+    $package_name = $this->package->getMachineName();
+    // Auto-detect dependencies for included config.
+    $package_config = $this->package->getConfig();
+    if (!empty($this->package->getConfigOrig())) {
+      $package_config = array_unique(array_merge($package_config, $this->package->getConfigOrig()));
+    }
+    if (!empty($package_config)) {
+      $this->featuresManager->assignConfigDependents($package_config, $package_name);
+    }
+
+    $packages = $this->featuresManager->getPackages();
+    // Re-fetch the package in case config was updated with Dependents above.
+    $this->package = $packages[$package_name];
+
+    // Make a map of all config data.
+    $components = array();
+    $this->conflicts = array();
+    foreach ($config as $item_name => $item) {
+      if (($item->getPackage() != $package_name) &&
+        !empty($packages[$item->getPackage()]) && ($packages[$item->getPackage()]->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) {
+        $this->conflicts[$item->getType()][$item->getShortName()] = $item->getLabel();
+      }
+      if ($this->allowConflicts
+        || !isset($this->conflicts[$item->getType()][$item->getShortName()])
+        || ($this->package->getConfigOrig() && in_array($item_name, $this->package->getConfigOrig()))) {
+        $components[$item->getType()][$item->getShortName()] = $item->getLabel();
+      }
+    }
+
+    // Make a map of the config data already exported to the Feature.
+    $this->missing = array();
+    $exported_features_info = array();
+    foreach ($this->package->getConfigOrig() as $item_name) {
+      // Make sure the extension provided item exists in the active
+      // configuration storage.
+      if (isset($config[$item_name])) {
+        $item = $config[$item_name];
+      // Remove any conflicts if those are not being allowed.
+          // if ($this->allowConflicts || !isset($this->conflicts[$item['type']][$item['name_short']])) {
+        $exported_features_info[$item->getType()][$item->getShortName()] = $item->getLabel();
+        // }
+      }
+      else {
+        $this->missing[] = $item_name;
+      }
+    }
+    $exported_features_info['dependencies'] = $this->package->getDependencyInfo();
+
+    // Make a map of any config specifically excluded and/or required.
+    foreach (array('excluded', 'required') as $constraint) {
+      $this->{$constraint} = array();
+      $info = !empty($this->package->{'get' . $constraint}()) ? $this->package->{'get' . $constraint}() : array();
+      if (($constraint == 'required') && (empty($info) || !is_array($info))) {
+        // If required is True or empty array, add all config as required
+        $info = $this->package->getConfigOrig();
+      }
+      foreach ($info as $item_name) {
+        if (!isset($config[$item_name])) {
+          continue;
+        }
+        $item = $config[$item_name];
+        $this->{$constraint}[$item->getType()][$item->getShortName()] = $item->getLabel();
+      }
+    }
+
+    // Make a map of the config data to be exported within the Feature.
+    $new_features_info = array();
+    foreach ($this->package->getConfig() as $item_name) {
+      $item = $config[$item_name];
+      $new_features_info[$item->getType()][$item->getShortName()] = $item->getLabel();
+    }
+    $new_features_info['dependencies'] = $this->package->getDependencies();
+
+    // Assemble the combined component list.
+    $config_new = array();
+    $sections = array('sources', 'included', 'detected', 'added');
+
+    // Generate list of config to be exported.
+    $config_count = array();
+    foreach ($components as $component => $component_info) {
+      // User-selected components take precedence.
+      $config_new[$component] = array();
+      $config_count[$component] = 0;
+      // Add selected items from Sources checkboxes.
+      if (!$form_state->isValueEmpty(array($component, 'sources', 'selected'))) {
+        $config_new[$component] = array_merge($config_new[$component], $this->domDecodeOptions(array_filter($form_state->getValue(array(
+          $component,
+          'sources',
+          'selected',
+        )))));
+        $config_count[$component]++;
+      }
+      // Add selected items from already Included, newly Added, auto-detected
+      // checkboxes.
+      foreach (array('included', 'added', 'detected') as $section) {
+        if (!$form_state->isValueEmpty(array($component, $section))) {
+          $config_new[$component] = array_merge($config_new[$component], $this->domDecodeOptions(array_filter($form_state->getValue(array($component, $section)))));
+          $config_count[$component]++;
+        }
+      }
+      // Only fallback to an existing feature's values if there are no export
+      // options for the component.
+      if ($component == 'dependencies') {
+        if (($config_count[$component] == 0) && !empty($exported_features_info['dependencies'])) {
+          $config_new[$component] = array_combine($exported_features_info['dependencies'], $exported_features_info['dependencies']);
+        }
+      }
+      elseif (($config_count[$component] == 0) && !empty($exported_features_info[$component])) {
+        $config_names = array_keys($exported_features_info[$component]);
+        $config_new[$component] = array_combine($config_names, $config_names);
+      }
+    }
+
+    // Generate new populated feature.
+    $export['package'] = $this->package;
+    $export['config_new'] = $config_new;
+
+    // Now fill the $export with categorized sections of component options
+    // based upon user selections and de-selections.
+    foreach ($components as $component => $component_info) {
+      $component_export = $component_info;
+      foreach ($sections as $section) {
+        $component_export['_features_options'][$section] = array();
+        $component_export['_features_selected'][$section] = array();
+      }
+      if (!empty($component_info)) {
+        $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array();
+        $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array();
+
+        foreach ($component_info as $key => $label) {
+          $config_name = $this->featuresManager->getFullName($component, $key);
+          // If checkbox in Sources is checked, move it to Added section.
+          if (!$form_state->isValueEmpty(array($component, 'sources', 'selected', $key))) {
+            $form_state->setValue(array($component, 'sources', 'selected', $key), FALSE);
+            $form_state->setValue(array($component, 'added', $key), 1);
+            $component_export['_features_options']['added'][$key] = $this->configLabel($component, $key, $label);
+            $component_export['_features_selected']['added'][$key] = $key;
+            // If this was previously excluded, we don't need to set it as
+            // required because it was automatically assigned.
+            if (isset($this->excluded[$component][$key])) {
+              unset($this->excluded[$component][$key]);
+            }
+            else {
+              $this->required[$component][$key] = $key;
+            }
+          }
+          elseif (isset($new_components[$key]) || isset($config_new[$component][$key])) {
+            // Option is in the New exported array.
+            if (isset($exported_components[$key])) {
+              // Option was already previously exported so it's part of the
+              // Included checkboxes.
+              $section = 'included';
+              $default_value = $key;
+              // If Included item was un-selected (removed from export
+              // $config_new) but was re-detected in the $new_components
+              // means it was an auto-detect that was previously part of the
+              // export and is now de-selected in UI.
+              if ($form_state->isSubmitted() &&
+                  ($form_state->hasValue(array($component, 'included', $key)) ||
+                  ($form_state->isValueEmpty(array($component, 'detected', $key)))) &&
+                  empty($config_new[$component][$key])) {
+                $section = 'detected';
+                $default_value = FALSE;
+              }
+              // Unless it's unchecked in the form, then move it to Newly
+              // disabled item.
+              elseif ($form_state->isSubmitted() &&
+                  $form_state->isValueEmpty(array($component, 'added', $key)) &&
+                  $form_state->isValueEmpty(array($component, 'detected', $key)) &&
+                  $form_state->isValueEmpty(array($component, 'included', $key))) {
+                $section = 'added';
+                $default_value = FALSE;
+              }
+            }
+            else {
+              // Option was in New exported array, but NOT in already exported
+              // so it's a user-selected or an auto-detect item.
+              $section = 'detected';
+              $default_value = NULL;
+              // Check for item explicitly excluded.
+              if (isset($this->excluded[$component][$key]) && !$form_state->isSubmitted()) {
+                $default_value = FALSE;
+              }
+              else {
+                $default_value = $key;
+              }
+              // If it's already checked in Added or Sources, leave it in Added
+              // as checked.
+              if ($form_state->isSubmitted() &&
+                  (!$form_state->isValueEmpty(array($component, 'added', $key)) ||
+                   !$form_state->isValueEmpty(array($component, 'sources', 'selected', $key)))) {
+                $section = 'added';
+                $default_value = $key;
+              }
+              // If it's already been unchecked, leave it unchecked.
+              elseif ($form_state->isSubmitted() &&
+                  $form_state->isValueEmpty(array($component, 'sources', 'selected', $key)) &&
+                  $form_state->isValueEmpty(array($component, 'detected', $key)) &&
+                  !$form_state->hasValue(array($component, 'added', $key))) {
+                $section = 'detected';
+                $default_value = FALSE;
+              }
+            }
+            $component_export['_features_options'][$section][$key] = $this->configLabel($component, $key, $label);
+            $component_export['_features_selected'][$section][$key] = $default_value;
+            // Save which dependencies are specifically excluded from
+            // auto-detection.
+            if (($section == 'detected') && ($default_value === FALSE)) {
+              // If this was previously required, we don't need to set it as
+              // excluded because it wasn't automatically assigned.
+              if (!isset($this->required[$component][$key]) || ($this->package->getRequired() === TRUE)) {
+                $this->excluded[$component][$key] = $key;
+              }
+              unset($this->required[$component][$key]);
+              // Remove excluded item from export.
+              if ($component == 'dependencies') {
+                $export['package']->removeDependency($key);
+              }
+              else {
+                $export['package']->removeConfig($config_name);
+              }
+            }
+            else {
+              unset($this->excluded[$component][$key]);
+            }
+            // Remove the 'input' and set the 'values' so Drupal stops looking
+            // at 'input'.
+            if ($form_state->isSubmitted()) {
+              if (!$default_value) {
+                $form_state->setValue(array($component, $section, $key), FALSE);
+              }
+              else {
+                $form_state->setValue(array($component, $section, $key), 1);
+              }
+            }
+          }
+          elseif (!$form_state->isSubmitted() && isset($exported_components[$key])) {
+            // Component is not part of new export, but was in original export.
+            // Mark component as Added when creating initial form.
+            $component_export['_features_options']['added'][$key] = $this->configLabel($component, $key, $label);
+            $component_export['_features_selected']['added'][$key] = $key;
+          }
+          else {
+            // Option was not part of the new export.
+            $added = FALSE;
+            foreach (array('included', 'added') as $section) {
+              // Restore any user-selected checkboxes.
+              if (!$form_state->isValueEmpty(array($component, $section, $key))) {
+                $component_export['_features_options'][$section][$key] = $this->configLabel($component, $key, $label);
+                $component_export['_features_selected'][$section][$key] = $key;
+                $added = TRUE;
+              }
+            }
+            if (!$added) {
+              // If not Included or Added, then put it back in the unchecked
+              // Sources checkboxes.
+              $component_export['_features_options']['sources'][$key] = $this->configLabel($component, $key, $label);
+              $component_export['_features_selected']['sources'][$key] = FALSE;
+            }
+          }
+        }
+      }
+      $export['components'][$component] = $component_export;
+    }
+    $export['features_exclude'] = $this->excluded;
+    $export['features_require'] = $this->required;
+    $export['conflicts'] = $this->conflicts;
+    $export['missing'] = $this->missing;
+
+    return $export;
+  }
+
+  /**
+   * Returns a formatted and sanitized label for a config item.
+   *
+   * @param string $type
+   *   The config type.
+   * @param string $key
+   *   The short machine name of the item.
+   * @param string $label
+   *   The human label for the item.
+   */
+  protected function configLabel($type, $key, $label) {
+    $value = Html::escape($label);
+    if ($key != $label) {
+      $value .= '  <span class="config-name">(' . Html::escape($key) . ')</span>';
+    }
+    if (isset($this->conflicts[$type][$key])) {
+      // Show what package the conflict is stored in.
+      $config = $this->featuresManager->getConfigCollection();
+      $config_name = $this->featuresManager->getFullName($type, $key);
+      $package_name = isset($config[$config_name]) ? $config[$config_name]->getPackage() : '';
+      // Get the full machine name instead of the short name.
+      $packages = $this->featuresManager->getPackages();
+      if (isset($packages[$package_name])) {
+        $package_name = $packages[$package_name]->getMachineName();
+      }
+      $value .= '  <span class="config-name">[' . $this->t('in') . ' ' . Html::escape($package_name) . ']</span>';
+    }
+    return Xss::filterAdmin($value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $bundle = $this->assigner->getBundle($this->bundle);
+    $this->assigner->assignConfigPackages();
+
+    $this->package->setName($form_state->getValue('name'));
+    $this->package->setMachineName($form_state->getValue('machine_name'));
+    $this->package->setDescription($form_state->getValue('description'));
+    $this->package->setVersion($form_state->getValue('version'));
+    $this->package->setDirectory($form_state->getValue('directory'));
+    $this->package->setBundle($bundle->getMachineName());
+    // Save it first just to create it in case it's a new package.
+    $this->featuresManager->setPackage($this->package);
+
+    $config = $this->updatePackageConfig($form_state);
+    $this->featuresManager->assignConfigPackage($this->package->getMachineName(), $config, TRUE);
+    $this->package->setExcluded($this->updateExcluded());
+    if ($form_state->getValue('require_all')) {
+      $this->package->setRequired(TRUE);
+    }
+    else {
+      $required = $this->updateRequired();
+      $this->package->setRequired($required);
+    }
+    // Now save it with the selected config data.
+    $this->featuresManager->setPackage($this->package);
+
+    $method_id = NULL;
+    $trigger = $form_state->getTriggeringElement();
+    $op = $form_state->getValue('op');
+    if (!empty($trigger) && empty($op)) {
+      $method_id = $trigger['#name'];
+    }
+
+    // Set default redirect, but allow generators to change it later.
+    $form_state->setRedirect('features.edit', array('featurename' => $this->package->getMachineName()));
+    if ($method_id == 'import_missing') {
+      $this->importMissing();
+    }
+    elseif (!empty($method_id)) {
+      $packages = array($this->package->getMachineName());
+      $this->generator->generatePackages($method_id, $bundle, $packages);
+      $this->generator->applyExportFormSubmit($method_id, $form, $form_state);
+    }
+
+    $this->assigner->setCurrent($bundle);
+  }
+
+  /**
+   * Updates the config stored in the package from the current edit form.
+   *
+   * @return array
+   *   Config array to be exported.
+   */
+  protected function updatePackageConfig(FormStateInterface $form_state) {
+    $config = array();
+    $components = $this->getComponentList($form_state);
+    foreach ($components['config_new'] as $config_type => $items) {
+      foreach ($items as $name) {
+        $config[] = $this->featuresManager->getFullName($config_type, $name);
+      }
+    }
+    return $config;
+  }
+
+  /**
+   * Imports the configuration missing from the active store
+   */
+  protected function importMissing() {
+    $config = $this->featuresManager->getConfigCollection();
+    $missing = $this->featuresManager->reorderMissing($this->missing);
+    foreach ($missing as $config_name) {
+      if (!isset($config[$config_name])) {
+        $item = $this->featuresManager->getConfigType($config_name);
+        $type = ConfigurationItem::fromConfigStringToConfigType($item['type']);
+        try {
+          $this->configRevert->import($type, $item['name_short']);
+          drupal_set_message($this->t('Imported @name', array('@name' => $config_name)));
+        } catch (\Exception $e) {
+          drupal_set_message($this->t('Error importing @name : @message',
+            array('@name' => $config_name, '@message' => $e->getMessage())), 'error');
+        }
+      }
+    }
+  }
+
+  /**
+   * Updates the list of excluded config.
+   *
+   * @return array
+   *   The list of excluded config in a simple array of full config names
+   *   suitable for storing in the info.yml file.
+   */
+  protected function updateExcluded() {
+    return $this->updateConstrained('excluded');
+  }
+
+  /**
+   * Updates the list of required config.
+   *
+   * @return array
+   *   The list of required config in a simple array of full config names
+   *   suitable for storing in the info.yml file.
+   */
+  protected function updateRequired() {
+    return $this->updateConstrained('required');
+  }
+
+  /**
+   * Returns a list of constrained (excluded or required) configuration.
+   *
+   * @param string $constraint
+   *   The constraint (excluded or required).
+   * @return array
+   *   The list of constrained config in a simple array of full config names
+   *   suitable for storing in the info.yml file.
+   */
+  protected function updateConstrained($constraint) {
+    $constrained = array();
+    foreach ($this->{$constraint} as $type => $item) {
+      foreach ($item as $name => $value) {
+        $constrained[] = $this->featuresManager->getFullName($type, $name);
+      }
+    }
+    return $constrained;
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * Decodes an array of option values that have been encoded by
+   * features_dom_encode_options().
+   *
+   * @param array $options
+   *   The key to encode.
+   * @param bool $keys_only
+   *   Whether to decode only the keys.
+   *
+   * @return array
+   *   An array of encoded options.
+   */
+  protected function domDecodeOptions(array $options, $keys_only = FALSE) {
+    $replacements = array_flip($this->domEncodeMap());
+    $encoded = array();
+    foreach ($options as $key => $value) {
+      $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements);
+    }
+    return $encoded;
+  }
+
+  /**
+   * 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('(') . '__',
+    );
+  }
+
+}

+ 539 - 0
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesExportForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesBundleUITest.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesCreateUITest.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesUITest.php

@@ -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.'));
+  }
+
+}

+ 351 - 0
sites/all/modules/contrib/admin/features/src/ConfigurationItem.php

@@ -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;
+  }
+
+}

+ 77 - 0
sites/all/modules/contrib/admin/features/src/Controller/FeaturesController.php

@@ -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');
+    }
+  }
+
+}

+ 305 - 0
sites/all/modules/contrib/admin/features/src/Entity/FeaturesBundle.php

@@ -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();
+  }
+
+}

+ 446 - 0
sites/all/modules/contrib/admin/features/src/FeaturesAssigner.php

@@ -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();
+    }
+  }
+
+}

+ 228 - 0
sites/all/modules/contrib/admin/features/src/FeaturesAssignerInterface.php

@@ -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);
+
+}

+ 124 - 0
sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodBase.php

@@ -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);
+    }
+  }
+
+}

+ 56 - 0
sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodInterface.php

@@ -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);
+
+}

+ 32 - 0
sites/all/modules/contrib/admin/features/src/FeaturesAssignmentMethodManager.php

@@ -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');
+  }
+
+}

+ 240 - 0
sites/all/modules/contrib/admin/features/src/FeaturesBundleInterface.php

@@ -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();
+
+}

+ 59 - 0
sites/all/modules/contrib/admin/features/src/FeaturesConfigDependencyManager.php

@@ -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;
+  }
+
+}

+ 51 - 0
sites/all/modules/contrib/admin/features/src/FeaturesConfigInstaller.php

@@ -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;
+  }
+
+}

+ 121 - 0
sites/all/modules/contrib/admin/features/src/FeaturesExtensionStorages.php

@@ -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];
+  }
+
+}

+ 87 - 0
sites/all/modules/contrib/admin/features/src/FeaturesExtensionStoragesInterface.php

@@ -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);
+
+}

+ 107 - 0
sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodBase.php

@@ -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);
+
+}

+ 67 - 0
sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodInterface.php

@@ -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);
+
+}

+ 33 - 0
sites/all/modules/contrib/admin/features/src/FeaturesGenerationMethodManager.php

@@ -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');
+  }
+
+}

+ 173 - 0
sites/all/modules/contrib/admin/features/src/FeaturesGenerator.php

@@ -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;
+  }
+
+}

+ 103 - 0
sites/all/modules/contrib/admin/features/src/FeaturesGeneratorInterface.php

@@ -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());
+
+}

+ 157 - 0
sites/all/modules/contrib/admin/features/src/FeaturesInstallStorage.php

@@ -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;
+  }
+
+}

+ 1306 - 0
sites/all/modules/contrib/admin/features/src/FeaturesManager.php

@@ -0,0 +1,1306 @@
+<?php
+
+namespace Drupal\features;
+use Drupal;
+use Drupal\Component\Serialization\Yaml;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Config\InstallStorage;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * The FeaturesManager provides helper functions for building packages.
+ */
+class FeaturesManager implements FeaturesManagerInterface {
+  use StringTranslationTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The target storage.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $configStorage;
+
+  /**
+   * The extension storages.
+   *
+   * @var \Drupal\features\FeaturesExtensionStoragesInterface
+   */
+  protected $extensionStorages;
+
+  /**
+   * The configuration manager.
+   *
+   * @var \Drupal\Core\Config\ConfigManagerInterface
+   */
+  protected $configManager;
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The Features settings.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * The configuration present on the site.
+   *
+   * @var \Drupal\features\ConfigurationItem[]
+   */
+  private $configCollection;
+
+  /**
+   * The packages to be generated.
+   *
+   * @var \Drupal\features\Package[]
+   */
+  protected $packages;
+
+  /**
+   * Whether the packages have been assigned a bundle prefix.
+   *
+   * @var boolean
+   */
+  protected $packagesPrefixed;
+
+  /**
+   * The package assigner.
+   *
+   * @var \Drupal\features\FeaturesAssigner
+   */
+  protected $assigner;
+
+  /**
+   * Cache module.features.yml data keyed by module name.
+   *
+   * @var array
+   */
+  protected $featureInfoCache;
+
+  /**
+   * Constructs a FeaturesManager object.
+   *
+   * @param string $root
+   *   The app root.
+   * @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 target storage.
+   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
+   *   The configuration manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct($root, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory,
+                              StorageInterface $config_storage, ConfigManagerInterface $config_manager,
+                              ModuleHandlerInterface $module_handler) {
+    $this->root = $root;
+    $this->entityManager = $entity_manager;
+    $this->configStorage = $config_storage;
+    $this->configManager = $config_manager;
+    $this->moduleHandler = $module_handler;
+    $this->configFactory = $config_factory;
+    $this->settings = $config_factory->getEditable('features.settings');
+    $this->extensionStorages = new FeaturesExtensionStorages($this->configStorage);
+    $this->extensionStorages->addStorage(InstallStorage::CONFIG_INSTALL_DIRECTORY);
+    $this->extensionStorages->addStorage(InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
+    $this->packages = [];
+    $this->packagesPrefixed = FALSE;
+    $this->configCollection = [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getActiveStorage() {
+    return $this->configStorage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtensionStorages() {
+    return $this->extensionStorages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFullName($type, $name) {
+    if ($type == FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG || !$type) {
+      return $name;
+    }
+
+    $definition = $this->entityManager->getDefinition($type);
+    $prefix = $definition->getConfigPrefix() . '.';
+    return $prefix . $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigType($fullname) {
+    $result = array(
+      'type' => '',
+      'name_short' => '',
+    );
+    $prefix = FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG . '.';
+    if (strpos($fullname, $prefix)) {
+      $result['type'] = FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG;
+      $result['name_short'] = substr($fullname, strlen($prefix));
+    }
+    else {
+      foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
+        if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+          $prefix = $definition->getConfigPrefix() . '.';
+          if (strpos($fullname, $prefix) === 0) {
+            $result['type'] = $entity_type;
+            $result['name_short'] = substr($fullname, strlen($prefix));
+          }
+        }
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function reset() {
+    $this->packages = [];
+    // Don't use getConfigCollection because reset() may be called in
+    // cases where we don't need to load config.
+    foreach ($this->configCollection as $config) {
+      $config->setPackage(NULL);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigCollection($reset = FALSE) {
+    $this->initConfigCollection($reset);
+    return $this->configCollection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfigCollection(array $config_collection) {
+    $this->configCollection = $config_collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPackages() {
+    return $this->packages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPackages(array $packages) {
+    $this->packages = $packages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPackage($machine_name) {
+    if (isset($this->packages[$machine_name])) {
+      return $this->packages[$machine_name];
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findPackage($machine_name) {
+    $result = $this->getPackage($machine_name);
+    if (!isset($result)) {
+      // Didn't find direct match, but now go through and look for matching
+      // full name (bundle_machinename)
+      foreach ($this->packages as $name => $package) {
+        if ($package->getFullName() == $machine_name) {
+          return $this->packages[$name];
+        }
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPackage(Package $package) {
+    if ($package->getMachineName()) {
+      $this->packages[$package->getMachineName()] = $package;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filterPackages(array $packages, $namespace = '', $only_exported = FALSE) {
+    $result = array();
+    /** @var \Drupal\features\Package $package */
+    foreach ($packages as $key => $package) {
+      // A package matches the namespace if:
+      // - it's prefixed with the namespace, or
+      // - it's assigned to a bundle named for the namespace, or
+      // - we're looking only for exported packages and it's not exported.
+      if (empty($namespace) || (strpos($package->getMachineName(), $namespace . '_') === 0) ||
+        ($package->getBundle() && $package->getBundle() === $namespace) ||
+        ($only_exported && $package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT)) {
+        $result[$key] = $package;
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAssigner() {
+    if (empty($this->assigner)) {
+      $this->setAssigner(\Drupal::service('features_assigner'));
+    }
+    return $this->assigner;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAssigner(FeaturesAssignerInterface $assigner) {
+    $this->assigner = $assigner;
+    $this->reset();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getGenerator() {
+    return $this->generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setGenerator(FeaturesGeneratorInterface $generator) {
+    $this->generator = $generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportSettings() {
+    return $this->settings->get('export');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettings() {
+    return $this->settings;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtensionInfo(Extension $extension) {
+    return \Drupal::service('info_parser')->parse(\Drupal::root() . '/' . $extension->getPathname());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFeatureModule(Extension $module, FeaturesBundleInterface $bundle = NULL) {
+    if ($features_info = $this->getFeaturesInfo($module)) {
+      // If no bundle was requested, it's enough that this is a feature.
+      if (is_null($bundle)) {
+        return TRUE;
+      }
+      // If the default bundle was requested, look for features where
+      // the bundle is not set.
+      elseif ($bundle->isDefault()) {
+        return !isset($features_info['bundle']);
+      }
+      // If we have a bundle name, look for it.
+      else {
+        return (isset($features_info['bundle']) && ($features_info['bundle'] == $bundle->getMachineName()));
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listPackageDirectories(array $machine_names = array(), FeaturesBundleInterface $bundle = NULL) {
+    if (empty($machine_names)) {
+      $machine_names = array_keys($this->getPackages());
+    }
+
+    // If the bundle is a profile, then add the profile's machine name.
+    if (isset($bundle) && $bundle->isProfile() && !in_array($bundle->getProfileName(), $machine_names)) {
+      $machine_names[] = $bundle->getProfileName();
+    }
+
+    $modules = $this->getFeaturesModules($bundle);
+    // Filter to include only the requested packages.
+    $modules = array_filter($modules, function ($module) use ($bundle, $machine_names) {
+      $short_name = $bundle->getShortName($module->getName());
+      return in_array($short_name, $machine_names);
+    });
+
+    $directories = array();
+    foreach ($modules as $module) {
+      $directories[$module->getName()] = $module->getPath();
+    }
+
+    return $directories;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAllModules() {
+    static $modules;
+
+    if (!isset($modules)) {
+      // ModuleHandler::getModuleDirectories() returns data only for installed
+      // modules. system_rebuild_module_data() includes only the site's install
+      // profile directory, while we may need to include a custom profile.
+      // @see _system_rebuild_module_data().
+      $listing = new ExtensionDiscovery(\Drupal::root());
+
+      $profile_directories = $listing->setProfileDirectoriesFromSettings()->getProfileDirectories();
+      $installed_profile = $this->drupalGetProfile();
+      if (isset($bundle) && $bundle->isProfile()) {
+        $profile_directory = 'profiles/' . $bundle->getProfileName();
+        if (($bundle->getProfileName() != $installed_profile) && is_dir($profile_directory)) {
+          $profile_directories[] = $profile_directory;
+        }
+      }
+      $listing->setProfileDirectories($profile_directories);
+
+      // Find modules.
+      $modules = $listing->scan('module');
+
+      // Find installation profiles.
+      $profiles = $listing->scan('profile');
+
+      foreach ($profiles as $key => $profile) {
+        $modules[$key] = $profile;
+      }
+    }
+
+    return $modules;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFeaturesModules(FeaturesBundleInterface $bundle = NULL, $installed = FALSE) {
+    $modules = $this->getAllModules();
+
+    // Filter by bundle.
+    if (isset($bundle)) {
+      $features_manager = $this;
+      $modules = array_filter($modules, function ($module) use ($features_manager, $bundle) {
+        return $features_manager->isFeatureModule($module, $bundle);
+      });
+    }
+    else {
+      // No bundle filter, but still only return "Feature" modules
+      $features_manager = $this;
+      $modules = array_filter($modules, function ($module) use ($features_manager) {
+        return $features_manager->isFeatureModule($module);
+      });
+    }
+
+    // Filtered by installed status.
+    if ($installed) {
+      $features_manager = $this;
+      $modules = array_filter($modules, function ($extension) use ($features_manager) {
+        return $features_manager->extensionEnabled($extension);
+      });
+    }
+
+    return $modules;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extensionEnabled(Extension $extension) {
+    return $this->moduleHandler->moduleExists($extension->getName());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initPackage($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL) {
+    if (!isset($this->packages[$machine_name])) {
+      return $this->packages[$machine_name] = $this->getPackageObject($machine_name, $name, $description, $type, $bundle, $extension);
+    }
+    return $this->packages[$machine_name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initPackageFromExtension(Extension $extension) {
+    $info = $this->getExtensionInfo($extension);
+    $features_info = $this->getFeaturesInfo($extension);
+    $bundle = $this->getAssigner()->findBundle($info, $features_info);
+    // Use the full extension name as the short_name.  Important to allow
+    // multiple modules with different namespaces such as oa_media, test_media.
+    $short_name = $extension->getName();
+    return $this->initPackage($short_name, $info['name'], !empty($info['description']) ? $info['description'] : '', $info['type'], $bundle, $extension);
+  }
+
+  /**
+   * Helper function to update dependencies array for a specific config item
+   * @param \Drupal\features\ConfigurationItem $config a config item
+   * @param array $module_list
+   * @return array $dependencies
+   */
+  protected function getConfigDependency(ConfigurationItem $config, $module_list = array()) {
+    $dependencies = [];
+    $type = $config->getType();
+    if ($type != FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
+      $provider = $this->entityManager->getDefinition($type)
+        ->getProvider();
+      // Ensure the provider is an installed module and not, for example, 'core'
+      if (isset($module_list[$provider])) {
+        $dependencies[] = $provider;
+      }
+
+      // For configuration in the InstallStorage::CONFIG_INSTALL_DIRECTORY
+      // directory, set any module dependencies of the configuration item
+      // as package dependencies.
+      // As its name implies, the core-provided
+      // InstallStorage::CONFIG_OPTIONAL_DIRECTORY should not create
+      // dependencies.
+      if ($config->getSubdirectory() === InstallStorage::CONFIG_INSTALL_DIRECTORY &&
+        isset($config->getData()['dependencies']['module'])
+      ) {
+        $dependencies = array_merge($dependencies, $config->getData()['dependencies']['module']);
+      }
+    }
+    return $dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function assignConfigPackage($package_name, array $item_names, $force = FALSE) {
+    $config_collection = $this->getConfigCollection();
+    $module_list = $this->moduleHandler->getModuleList();
+
+    $packages =& $this->packages;
+    if (isset($packages[$package_name])) {
+      $package =& $packages[$package_name];
+    }
+    else {
+      throw new \Exception($this->t('Failed to package @package_name. Package not found.', ['@package_name' => $package_name]));
+    }
+
+    foreach ($item_names as $item_name) {
+      if (isset($config_collection[$item_name])) {
+        // Add to the package if:
+        // - force is set or
+        //   - the item hasn't already been assigned elsewhere, and
+        //   - the package hasn't been excluded.
+        // - and the item isn't already in the package.
+
+        $item = &$config_collection[$item_name];
+        $already_assigned = !empty($item->getPackage());
+        // If this is the profile package, we can reassign extension-provided configuration.
+        $package_bundle = $this->getAssigner()->getBundle($package->getBundle());
+        $is_profile_package = isset($package_bundle) ? $package_bundle->isProfilePackage($package_name) : FALSE;
+        // An item is assignable if:
+        // - it is not provider excluded or this is the profile package, and
+        // - it is not flagged as excluded.
+        $assignable = (!$item->isProviderExcluded() || $is_profile_package) && !$item->isExcluded();
+        // An item is assignable if it was provided by the current package
+        $assignable = $assignable || ($item->getProvider() == $package->getFullName());
+        $excluded_from_package = in_array($package_name, $item->getPackageExcluded());
+        $already_in_package = in_array($item_name, $package->getConfig());
+        if (($force || (!$already_assigned && $assignable && !$excluded_from_package)) && !$already_in_package) {
+          // Add the item to the package's config array.
+          $package->appendConfig($item_name);
+          // Mark the item as already assigned.
+          $item->setPackage($package_name);
+
+          $module_dependencies = $this->getConfigDependency($item, $module_list);
+          $package->setDependencies($this->mergeUniqueItems($package->getDependencies(), $module_dependencies));
+        }
+        // Return memory
+        unset($item);
+      }
+    }
+
+    $this->setConfigCollection($config_collection);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function assignConfigByPattern(array $patterns) {
+    // Regular expressions for items that are likely to generate false
+    // positives when assigned by pattern.
+    $false_positives = [
+      // Blocks with the page title should not be assigned to a 'page' package.
+      '/block\.block\..*_page_title/',
+    ];
+    $config_collection = $this->getConfigCollection();
+    // Reverse sort by key so that child package will claim items before parent
+    // package. E.g., event_registration will claim before event.
+    krsort($config_collection);
+    foreach ($patterns as $pattern => $machine_name) {
+      if (isset($this->packages[$machine_name])) {
+        foreach ($config_collection as $item_name => $item) {
+          // Test for and skip false positives.
+          foreach ($false_positives as $false_positive) {
+            if (preg_match($false_positive, $item_name)) {
+              continue 2;
+            }
+          }
+
+          if (!$item->getPackage() && preg_match('/[_\-.]' . $pattern . '[_\-.]/', '.' . $item->getShortName() . '.')) {
+            try {
+              $this->assignConfigPackage($machine_name, [$item_name]);
+            }
+            catch (\Exception $exception) {
+              \Drupal::logger('features')->error($exception->getMessage());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function assignConfigDependents(array $item_names = NULL, $package = NULL) {
+    $config_collection = $this->getConfigCollection();
+    if (empty($item_names)) {
+      $item_names = array_keys($config_collection);
+    }
+    foreach ($item_names as $item_name) {
+      // Make sure the extension provided item exists in the active
+      // configuration storage.
+      if (isset($config_collection[$item_name]) && $config_collection[$item_name]->getPackage()) {
+        foreach ($config_collection[$item_name]->getDependents() as $dependent_item_name) {
+          if (isset($config_collection[$dependent_item_name]) && (!empty($package) || empty($config_collection[$dependent_item_name]->getPackage()))) {
+            try {
+              $package_name = !empty($package) ? $package : $config_collection[$item_name]->getPackage();
+              $this->assignConfigPackage($package_name, [$dependent_item_name]);
+            }
+            catch (\Exception $exception) {
+              \Drupal::logger('features')->error($exception->getMessage());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPackageBundleNames(FeaturesBundleInterface $bundle, array &$package_names = []) {
+    $this->packagesPrefixed = TRUE;
+    if (!$bundle->isDefault()) {
+      $new_package_names = [];
+      // Assign the selected bundle to the exports.
+      $packages = $this->getPackages();
+      if (empty($package_names)) {
+        $package_names = array_keys($packages);
+      }
+      foreach ($package_names as $package_name) {
+        // Rename package to use bundle prefix.
+        $package = $packages[$package_name];
+
+        // The install profile doesn't need renaming.
+        if ($package->getType() != 'profile') {
+          unset($packages[$package_name]);
+          $package->setMachineName($bundle->getFullName($package->getMachineName()));
+          $packages[$package->getMachineName()] = $package;
+        }
+
+        // Set the bundle machine name.
+        $packages[$package->getMachineName()]->setBundle($bundle->getMachineName());
+        $new_package_names[] = $package->getMachineName();
+      }
+      $this->setPackages($packages);
+      $package_names = $new_package_names;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function assignPackageDependencies(Package $package = NULL) {
+    if (is_null($package)) {
+      $packages = $this->getPackages();
+    }
+    else {
+      $packages = array($package);
+    }
+    $module_list = $this->moduleHandler->getModuleList();
+    $config_collection = $this->getConfigCollection();
+
+    foreach ($packages as $package) {
+      $module_dependencies = [];
+      foreach ($package->getConfig() as $item_name) {
+        if (isset($config_collection[$item_name])) {
+          $dependencies = $this->getConfigDependency($config_collection[$item_name], $module_list);
+          $module_dependencies = array_merge($module_dependencies, $dependencies);
+        }
+      }
+      $package->setDependencies($this->mergeUniqueItems($package->getDependencies(), $module_dependencies));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function assignInterPackageDependencies(FeaturesBundleInterface $bundle, array &$packages) {
+    if (!$this->packagesPrefixed) {
+      throw new \Exception($this->t('The packages have not yet been prefixed with a bundle name.'));
+    }
+
+    $config_collection = $this->getConfigCollection();
+
+    /** @var \Drupal\features\Package[] $packages */
+    foreach ($packages as $package) {
+      foreach ($package->getConfig() as $item_name) {
+        if (!empty($config_collection[$item_name]->getData()['dependencies']['config'])) {
+          foreach ($config_collection[$item_name]->getData()['dependencies']['config'] as $dependency_name) {
+            if (isset($config_collection[$dependency_name])) {
+              // If the required item is assigned to one of the packages, add
+              // a dependency on that package.
+              $dependency_set = FALSE;
+              if ($dependency_package = $config_collection[$dependency_name]->getPackage()) {
+                $package_name = $bundle->getFullName($dependency_package);
+                // Package shouldn't be dependent on itself.
+                if ($package_name && array_key_exists($package_name, $packages) && $package_name != $package->getMachineName()) {
+                  $package->setDependencies($this->mergeUniqueItems($package->getDependencies(), [$package_name]));
+                  $dependency_set = TRUE;
+                }
+              }
+              // Otherwise, if the dependency is provided by an existing
+              // feature, add a dependency on that feature.
+              if (!$dependency_set && $extension_name = $config_collection[$dependency_name]->getProvider()) {
+                // No extension should depend on the install profile.
+                $package_name = $bundle->getFullName($package->getMachineName());
+                if ($extension_name != $package_name && $extension_name != $this->drupalGetProfile()) {
+                  $package->setDependencies($this->mergeUniqueItems($package->getDependencies(), [$extension_name]));
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    // Unset the $package pass by reference.
+    unset($package);
+  }
+
+ /**
+  * Gets the name of the currently active installation profile.
+  *
+  * @return string|null $profile
+  *   The name of the installation profile or NULL if no installation profile is
+  *   currently active. This is the case for example during the first steps of
+  *   the installer or during unit tests.
+  */
+  protected function drupalGetProfile() {
+    return drupal_get_profile();
+  }
+
+  /**
+   * Merges a set of new item into an array and sorts the result.
+   *
+   * Only unique values are retained.
+   *
+   * @param array $items
+   *   An array of items.
+   * @param array $new_items
+   *   An array of new items to be merged in.
+   *
+   * @return array
+   *   The merged, sorted and unique items.
+   */
+  protected function mergeUniqueItems($items, $new_items) {
+    $items = array_unique(array_merge($items, $new_items));
+    sort($items);
+    return $items;
+  }
+
+  /**
+   * Initializes and returns a package or profile array.
+   *
+   * @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 \Drupal\features\Package
+   *   An array of package properties; see
+   *   FeaturesManagerInterface::getPackages().
+   */
+  protected function getPackageObject($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL) {
+    if (!isset($bundle)) {
+      $bundle = $this->getAssigner()->getBundle();
+    }
+    $package = new Package($machine_name, [
+      'name' => isset($name) ? $name : ucwords(str_replace(['_', '-'], ' ', $machine_name)),
+      'description' => $description,
+      'type' => $type,
+      'core' => Drupal::CORE_COMPATIBILITY,
+      'dependencies' => [],
+      'themes' => [],
+      'config' => [],
+      'status' => FeaturesManagerInterface::STATUS_DEFAULT,
+      'version' => '',
+      'state' => FeaturesManagerInterface::STATE_DEFAULT,
+      'files' => [],
+      'bundle' => $bundle->isDefault() ? '' : $bundle->getMachineName(),
+      'extension' => NULL,
+      'info' => [],
+      'configOrig' => [],
+    ]);
+
+    // If no extension was passed in, look for a match.
+    if (!isset($extension)) {
+      $module_list = $this->getFeaturesModules($bundle);
+      $full_name = $bundle->getFullName($package->getMachineName());
+      if (isset($module_list[$full_name])) {
+        $extension = $module_list[$full_name];
+      }
+    }
+
+    // If there is an extension, set extension-specific properties.
+    if (isset($extension)) {
+      $info = $this->getExtensionInfo($extension);
+      $features_info = $this->getFeaturesInfo($extension);
+      $package->setExtension($extension);
+      $package->setInfo($info);
+      $package->setFeaturesInfo($features_info);
+      $package->setConfigOrig($this->listExtensionConfig($extension));
+      $package->setStatus($this->extensionEnabled($extension)
+        ? FeaturesManagerInterface::STATUS_INSTALLED
+        : FeaturesManagerInterface::STATUS_UNINSTALLED);
+      $package->setVersion(isset($info['version']) ? $info['version'] : '');
+    }
+
+    return $package;
+  }
+
+  /**
+   * Generates and adds .info.yml files to a package.
+   *
+   * @param \Drupal\features\Package $package
+   *   The package.
+   */
+  protected function addInfoFile(Package $package) {
+    $info = [
+      'name' => $package->getName(),
+      'description' => $package->getDescription(),
+      'type' => $package->getType(),
+      'core' => $package->getCore(),
+      'dependencies' => $package->getDependencies(),
+      'themes' => $package->getThemes(),
+      'version' => $package->getVersion(),
+    ];
+
+    $features_info = [];
+
+    // Assign to a "package" named for the profile.
+    if ($package->getBundle()) {
+      $bundle = $this->getAssigner()->getBundle($package->getBundle());
+    }
+    // Save the current bundle in the info file so the package
+    // can be reloaded later by the AssignmentPackages plugin.
+    if (isset($bundle) && !$bundle->isDefault()) {
+      $info['package'] = $bundle->getName();
+      $features_info['bundle'] = $bundle->getMachineName();
+    }
+    else {
+      unset($features_info['bundle']);
+    }
+
+    if ($package->getConfig()) {
+      foreach (array('excluded', 'required') as $constraint) {
+        if (!empty($package->{'get' . $constraint}())) {
+          $features_info[$constraint] = $package->{'get' . $constraint}();
+        }
+        else {
+          unset($features_info[$constraint]);
+        }
+      }
+
+      if (empty($features_info)) {
+        $features_info = TRUE;
+      }
+    }
+
+    // The name and description need to be cast as strings from the
+    // TranslatableMarkup objects returned by t() to avoid raising an
+    // InvalidDataTypeException on Yaml serialization.
+    foreach (array('name', 'description') as $key) {
+      $info[$key] = (string) $info[$key];
+    }
+
+    // Add profile-specific info data.
+    if ($info['type'] == 'profile') {
+      // Set the distribution name.
+      $info['distribution'] = [
+        'name' => $info['name']
+      ];
+    }
+
+    $package->appendFile([
+      'filename' => $package->getMachineName() . '.info.yml',
+      'subdirectory' => NULL,
+      // Filter to remove any empty keys, e.g., an empty themes array.
+      'string' => Yaml::encode(array_filter($info))
+    ], 'info');
+
+    $package->appendFile([
+      'filename' => $package->getMachineName() . '.features.yml',
+      'subdirectory' => NULL,
+      'string' => Yaml::encode($features_info)
+    ], 'features');
+  }
+
+  /**
+   * Generates and adds files to a given package or profile.
+   */
+  protected function addPackageFiles(Package $package) {
+    $config_collection = $this->getConfigCollection();
+    // Only add files if there is at least one piece of configuration
+    // present.
+    if ($package->getConfig()) {
+      // Add .info.yml files.
+      $this->addInfoFile($package);
+
+      // Add configuration files.
+      foreach ($package->getConfig() as $name) {
+        $config = $config_collection[$name];
+        $data = $config->getData();
+        // The _core is site-specific, so don't export it.
+        unset($data['_core']);
+        // The UUID is site-specfic, so don't export it.
+        if ($entity_type_id = $this->configManager->getEntityTypeIdByName($name)) {
+          unset($data['uuid']);
+        }
+        $config->setData($data);
+        // User roles include all permissions currently assigned to them. To
+        // avoid extraneous additions, reset permissions.
+        if ($config->getType() == 'user_role') {
+          $data = $config->getData();
+          // Unset and not empty permissions data to prevent loss of configured
+          // role permissions in the event of a feature revert.
+          unset($data['permissions']);
+          $config->setData($data);
+        }
+        $package->appendFile([
+          'filename' => $config->getName() . '.yml',
+          'subdirectory' => $config->getSubdirectory(),
+          'string' => Yaml::encode($config->getData())
+        ], $name);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeInfoArray(array $info1, array $info2, array $keys = array()) {
+    // If keys were specified, use only those.
+    if (!empty($keys)) {
+      $info2 = array_intersect_key($info2, array_fill_keys($keys, NULL));
+    }
+
+    $info = NestedArray::mergeDeep($info1, $info2);
+
+    // Process the dependencies and themes keys.
+    $keys = ['dependencies', 'themes'];
+    foreach ($keys as $key) {
+      if (isset($info[$key]) && is_array($info[$key])) {
+        // NestedArray::mergeDeep() may produce duplicate values.
+        $info[$key] = array_unique($info[$key]);
+        sort($info[$key]);
+      }
+    }
+    return $info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listConfigTypes($bundles_only = FALSE) {
+    $definitions = [];
+    foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
+      if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+        if (!$bundles_only || $definition->getBundleOf()) {
+          $definitions[$entity_type] = $definition;
+        }
+      }
+    }
+    $entity_types = array_map(function (EntityTypeInterface $definition) {
+      return $definition->getLabel();
+    }, $definitions);
+    // Sort the entity types by label, then add the simple config to the top.
+    uasort($entity_types, 'strnatcasecmp');
+    return $bundles_only ? $entity_types : [
+      FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG => $this->t('Simple configuration'),
+    ] + $entity_types;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listExtensionConfig(Extension $extension) {
+    return $this->extensionStorages->listExtensionConfig($extension);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listExistingConfig($installed = FALSE, FeaturesBundleInterface $bundle = NULL) {
+    $config = array();
+    $existing = $this->getFeaturesModules($bundle, $installed);
+    foreach ($existing as $extension) {
+      // Keys are configuration item names and values are providing extension
+      // name.
+      $new_config = array_fill_keys($this->listExtensionConfig($extension), $extension->getName());
+      $config = array_merge($config, $new_config);
+    }
+    return $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listConfigByType($config_type) {
+    // For a given entity type, load all entities.
+    if ($config_type && $config_type !== FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
+      $entity_storage = $this->entityManager->getStorage($config_type);
+      $names = [];
+      foreach ($entity_storage->loadMultiple() as $entity) {
+        $entity_id = $entity->id();
+        $label = $entity->label() ?: $entity_id;
+        $names[$entity_id] = $label;
+      }
+    }
+    // Handle simple configuration.
+    else {
+      $definitions = [];
+      foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
+        if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+          $definitions[$entity_type] = $definition;
+        }
+      }
+      // Gather the config entity prefixes.
+      $config_prefixes = array_map(function (EntityTypeInterface $definition) {
+        return $definition->getConfigPrefix() . '.';
+      }, $definitions);
+
+      // Find all config, and then filter our anything matching a config prefix.
+      $names = $this->configStorage->listAll();
+      $names = array_combine($names, $names);
+      foreach ($names as $item_name) {
+        foreach ($config_prefixes as $config_prefix) {
+          if (strpos($item_name, $config_prefix) === 0) {
+            unset($names[$item_name]);
+          }
+        }
+      }
+    }
+    return $names;
+  }
+
+  /**
+   * Creates a high performant version of the ConfigDependencyManager.
+   *
+   * @return \Drupal\features\FeaturesConfigDependencyManager
+   *   A high performant version of the ConfigDependencyManager.
+   *
+   * @see \Drupal\Core\Config\Entity\ConfigDependencyManager
+   */
+  protected function getFeaturesConfigDependencyManager() {
+    $dependency_manager = new FeaturesConfigDependencyManager();
+    // Read all configuration using the factory. This ensures that multiple
+    // deletes during the same request benefit from the static cache. Using the
+    // factory also ensures configuration entity dependency discovery has no
+    // dependencies on the config entity classes. Assume data with UUID is a
+    // config entity. Only configuration entities can be depended on so we can
+    // ignore everything else.
+    $data = array_map(function(Drupal\Core\Config\ImmutableConfig $config) {
+      $data = $config->get();
+      if (isset($data['uuid'])) {
+        return $data;
+      }
+      return FALSE;
+    }, $this->configFactory->loadMultiple($this->configStorage->listAll()));
+    $dependency_manager->setData(array_filter($data));
+    return $dependency_manager;
+  }
+
+  /**
+   * Loads configuration from storage into a property.
+   */
+  protected function initConfigCollection($reset = FALSE) {
+    if ($reset || empty($this->configCollection)) {
+      $config_collection = [];
+      $config_types = $this->listConfigTypes();
+      $dependency_manager = $this->getFeaturesConfigDependencyManager();
+      // List configuration provided by installed features.
+      $existing_config = $this->listExistingConfig(NULL);
+      foreach (array_keys($config_types) as $config_type) {
+        $config = $this->listConfigByType($config_type);
+        foreach ($config as $item_name => $label) {
+          $name = $this->getFullName($config_type, $item_name);
+          $data = $this->configStorage->read($name);
+
+          $config_collection[$name] = (new ConfigurationItem($name, $data, [
+            'shortName' => $item_name,
+            'label' => $label,
+            'type' => $config_type,
+            'dependents' => array_keys($dependency_manager->getDependentEntities('config', $name)),
+            // Default to the install directory.
+            'subdirectory' => InstallStorage::CONFIG_INSTALL_DIRECTORY,
+            'package' => '',
+            'providerExcluded' => NULL,
+            'provider' => isset($existing_config[$name]) ? $existing_config[$name] : NULL,
+            'packageExcluded' => [],
+          ]));
+        }
+      }
+      $this->setConfigCollection($config_collection);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareFiles(array $packages) {
+    foreach ($packages as $package) {
+      $this->addPackageFiles($package);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportInfo(Package $package, FeaturesBundleInterface $bundle = NULL) {
+    $full_name = isset($bundle) ? $bundle->getFullName($package->getMachineName()) : $package->getMachineName();
+
+    $path = '';
+
+    // Adjust export directory to be in profile.
+    if (isset($bundle) && $bundle->isProfile()) {
+      $path .= 'profiles/' . $bundle->getProfileName();
+    }
+
+    // If this is not the profile package, nest the directory.
+    if (!isset($bundle) || !$bundle->isProfilePackage($package->getMachineName())) {
+      $path .= empty($path) ? 'modules' : '/modules';
+      $export_settings = $this->getExportSettings();
+      if (!empty($export_settings['folder'])) {
+        $path .= '/' . $export_settings['folder'];
+      }
+    }
+
+    // Use the same path of a package to override it.
+    if ($extension = $package->getExtension()) {
+      $extension_path = $extension->getPath();
+      $path = dirname($extension_path);
+    }
+
+    return array($full_name, $path);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function detectOverrides(Package $feature, $include_new = FALSE) {
+    /** @var \Drupal\config_update\ConfigDiffInterface $config_diff */
+    $config_diff = \Drupal::service('config_update.config_diff');
+
+    $different = array();
+    foreach ($feature->getConfig() as $name) {
+      $active = $this->configStorage->read($name);
+      $extension = $this->extensionStorages->read($name);
+      $extension = !empty($extension) ? $extension : array();
+      if (($include_new || !empty($extension)) && !$config_diff->same($extension, $active)) {
+        $different[] = $name;
+      }
+    }
+
+    if (!empty($different)) {
+      $feature->setState(FeaturesManagerInterface::STATE_OVERRIDDEN);
+    }
+    return $different;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function detectNew(Package $feature) {
+    $result = array();
+    foreach ($feature->getConfig() as $name) {
+      $extension = $this->extensionStorages->read($name);
+      if (empty($extension)) {
+        $result[] = $name;
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function detectMissing(Package $feature) {
+    $config = $this->getConfigCollection();
+    $result = array();
+    foreach ($feature->getConfigOrig() as $name) {
+      if (!isset($config[$name])) {
+        $result[] = $name;
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function reorderMissing(array $missing) {
+    $list = array();
+    $result = array();
+    foreach ($missing as $full_name) {
+      $this->addConfigList($full_name, $list);
+    }
+    foreach ($list as $full_name) {
+      if (in_array($full_name, $missing)) {
+        $result[] = $full_name;
+      }
+    }
+    return $result;
+  }
+
+  protected function addConfigList($full_name, &$list) {
+    $index = array_search($full_name, $list);
+    if ($index !== FALSE) {
+      unset($list[$index]);
+    }
+    array_unshift($list, $full_name);
+    $value = $this->extensionStorages->read($full_name);
+    if (isset($value['dependencies']['config'])) {
+      foreach ($value['dependencies']['config'] as $config_name) {
+        $this->addConfigList($config_name, $list);
+      }
+    }
+  }
+
+    /**
+   * {@inheritdoc}
+   */
+  public function statusLabel($status) {
+    switch ($status) {
+      case FeaturesManagerInterface::STATUS_NO_EXPORT:
+        return $this->t('Not exported');
+
+      case FeaturesManagerInterface::STATUS_UNINSTALLED:
+        return $this->t('Uninstalled');
+
+      case FeaturesManagerInterface::STATUS_INSTALLED:
+        return $this->t('Installed');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stateLabel($state) {
+    switch ($state) {
+      case FeaturesManagerInterface::STATE_DEFAULT:
+        return $this->t('Default');
+
+      case FeaturesManagerInterface::STATE_OVERRIDDEN:
+        return $this->t('Changed');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFeaturesInfo(Extension $extension) {
+    $module_name = $extension->getName();
+    if (isset($this->featureInfoCache[$module_name])) {
+      return $this->featureInfoCache[$module_name];
+    }
+    $features_info = NULL;
+    $filename = $this->root . '/' . $extension->getPath() . '/' . $module_name . '.features.yml';
+    if (file_exists($filename)) {
+      $features_info = Yaml::decode(file_get_contents($filename));
+    }
+    $this->featureInfoCache[$module_name] = $features_info;
+    return $features_info;
+  }
+
+}

+ 601 - 0
sites/all/modules/contrib/admin/features/src/FeaturesManagerInterface.php

@@ -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);
+
+}

+ 24 - 0
sites/all/modules/contrib/admin/features/src/FeaturesServiceProvider.php

@@ -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');
+  }
+
+}

+ 549 - 0
sites/all/modules/contrib/admin/features/src/Package.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentBaseType.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentCoreType.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentDependency.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentExclude.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentExisting.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentForwardDependency.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentNamespace.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentOptionalType.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentPackages.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentProfile.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentSiteType.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php

@@ -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']]));
+    }
+  }
+
+}

+ 136 - 0
sites/all/modules/contrib/admin/features/src/ProxyClass/FeaturesConfigInstaller.php

@@ -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
sites/all/modules/contrib/admin/features/tests/modules/test_feature/config/install/system.cron.yml

@@ -0,0 +1,3 @@
+threshold:
+  requirements_warning: 172800
+  requirements_error: 1209600

+ 4 - 0
sites/all/modules/contrib/admin/features/tests/modules/test_feature/test_feature.features.yml

@@ -0,0 +1,4 @@
+bundle: test
+excluded:
+  - system.theme
+required: true

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است