diff --git a/base-profile.md b/base-profile.md
index b940be4c3..2370ebba6 100644
--- a/base-profile.md
+++ b/base-profile.md
@@ -16,3 +16,22 @@ drupal 8 base
[a tester : configuration synchronizer](https://www.drupal.org/project/config_sync)
[features](https://www.drupal.org/project/features)
+
+[filter permissions](https://www.drupal.org/project/filter_perms)
+
+### dev
+
+[devel](https://www.drupal.org/project/devel)
+
+### mail
+
+[maillog](https://www.drupal.org/project/maillog)
+
+### theme
+
+[extlink](https://www.drupal.org/project/extlink)
+
+
+### users
+
+[profile](https://www.drupal.org/project/profile)
diff --git a/sites/all/modules/contrib/admin/filter_perms/LICENSE.txt b/sites/all/modules/contrib/admin/filter_perms/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/admin/filter_perms/filter_perms.info.yml b/sites/all/modules/contrib/admin/filter_perms/filter_perms.info.yml
new file mode 100644
index 000000000..5b652de0a
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/filter_perms.info.yml
@@ -0,0 +1,13 @@
+type: module
+name: 'Filter permissions'
+description: 'Provides role and module filters to simplify the user permissions page.'
+package: 'Administration'
+# core: 8.x
+dependencies:
+ - user
+
+# Information added by Drupal.org packaging script on 2016-07-27
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'filter_perms'
+datestamp: 1469646196
diff --git a/sites/all/modules/contrib/admin/filter_perms/filter_perms.services.yml b/sites/all/modules/contrib/admin/filter_perms/filter_perms.services.yml
new file mode 100644
index 000000000..b064ea195
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/filter_perms.services.yml
@@ -0,0 +1,5 @@
+services:
+ filter_perms.route_subscriber:
+ class: Drupal\filter_perms\Routing\RouteSubscriber
+ tags:
+ - { name: event_subscriber }
diff --git a/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsForm.php b/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsForm.php
new file mode 100644
index 000000000..9443ce0e7
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsForm.php
@@ -0,0 +1,261 @@
+keyValueExpirable = $key_value_expirable;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('user.permissions'),
+ $container->get('entity.manager')->getStorage('user_role'),
+ $container->get('module_handler'),
+ $container->get('keyvalue.expirable')->get('filter_perms_list')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ // Render role/permission overview:
+ $hide_descriptions = system_admin_compact_mode();
+
+ $form['system_compact_link'] = array(
+ '#id' => FALSE,
+ '#type' => 'system_compact_link',
+ );
+
+ $permissions = $this->permissionHandler->getPermissions();
+
+ $providers = array();
+ foreach ($permissions as $permission) {
+ $providers[$permission['provider']] = $permission['provider'];
+ }
+
+ $roles = $this->getRoles();
+
+ $defined_roles = array();
+ foreach ($roles as $role_name => $role) {
+ $defined_roles[$role_name] = $role->label();
+ }
+
+ $filter = $this->getFilterSettings();
+
+ $form['filters'] = array(
+ '#type' => 'details',
+ '#title' => $this->t('Permission Filters'),
+ '#open' => TRUE,
+ );
+ $form['filters']['container'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('form--inline', 'clearfix')),
+ );
+ // Displays all user roles.
+ $form['filters']['container']['roles'] = array(
+ '#title' => $this->t('Roles to display'),
+ '#type' => 'select',
+ '#options' => array(self::ALL_OPTIONS => '--All Roles') + $defined_roles,
+ '#default_value' => $filter['roles'],
+ '#size' => 8,
+ '#multiple' => TRUE,
+ );
+ // Displays all modules which define permissions.
+ $form['filters']['container']['modules'] = array(
+ '#title' => $this->t('Modules to display'),
+ '#type' => 'select',
+ '#options' => array(self::ALL_OPTIONS => '--All Modules') + $providers,
+ '#default_value' => $filter['modules'],
+ '#size' => 8,
+ '#multiple' => TRUE,
+ );
+ $form['filters']['action'] = array('#type' => 'actions');
+ $form['filters']['action']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Filter Permissions'),
+ '#submit' => array('::submitFormFilter'),
+ );
+
+ $role_names = $role_permissions = $admin_roles = array();
+
+ foreach ($roles as $role_name => $role) {
+ if (in_array(self::ALL_OPTIONS, $filter['roles']) || in_array($role_name, $filter['roles'])) {
+ // Retrieve role names for columns.
+ $role_names[$role_name] = $role->label();
+ // Fetch permissions for the roles.
+ $role_permissions[$role_name] = $role->getPermissions();
+ $admin_roles[$role_name] = $role->isAdmin();
+ }
+ }
+
+ // Store $role_names for use when saving the data.
+ $form['role_names'] = array(
+ '#type' => 'value',
+ '#value' => $role_names,
+ );
+
+ $permissions_by_provider = array();
+ foreach ($permissions as $permission_name => $permission) {
+ if (in_array(self::ALL_OPTIONS, $filter['modules']) || in_array($permission['provider'], $filter['modules'])) {
+ $permissions_by_provider[$permission['provider']][$permission_name] = $permission;
+ }
+ }
+
+ $form['permissions'] = array(
+ '#type' => 'table',
+ '#header' => array($this->t('Permission')),
+ '#id' => 'permissions',
+ '#attributes' => ['class' => ['permissions', 'js-permissions']],
+ '#sticky' => TRUE,
+ '#empty' => $this->t('Please select at least one value from both the Roles and Modules select boxes above and then click the "Filter Permissions" button.'),
+ );
+
+ // Only build the rest of the form if there are any filter settings.
+ if (empty($role_names) || empty($permissions_by_provider)) {
+ return $form;
+ }
+
+ foreach ($role_names as $name) {
+ $form['permissions']['#header'][] = array(
+ 'data' => $name,
+ 'class' => array('checkbox'),
+ );
+ }
+
+ foreach ($permissions_by_provider as $provider => $permissions) {
+ // Module name.
+ $form['permissions'][$provider] = array(array(
+ '#wrapper_attributes' => array(
+ 'colspan' => count($role_names) + 1,
+ 'class' => array('module'),
+ 'id' => 'module-' . $provider,
+ ),
+ '#markup' => $this->moduleHandler->getName($provider),
+ ));
+ foreach ($permissions as $perm => $perm_item) {
+ // Fill in default values for the permission.
+ $perm_item += array(
+ 'description' => '',
+ 'restrict access' => FALSE,
+ 'warning' => !empty($perm_item['restrict access']) ? $this->t('Warning: Give to trusted roles only; this permission has security implications.') : '',
+ );
+ $form['permissions'][$perm]['description'] = array(
+ '#type' => 'inline_template',
+ '#template' => '
',
+ '#context' => array(
+ 'title' => $perm_item['title'],
+ ),
+ );
+ // Show the permission description.
+ if (!$hide_descriptions) {
+ $form['permissions'][$perm]['description']['#context']['description'] = $perm_item['description'];
+ $form['permissions'][$perm]['description']['#context']['warning'] = $perm_item['warning'];
+ }
+ foreach ($role_names as $rid => $name) {
+ $form['permissions'][$perm][$rid] = array(
+ '#title' => $name . ': ' . $perm_item['title'],
+ '#title_display' => 'invisible',
+ '#wrapper_attributes' => array(
+ 'class' => array('checkbox'),
+ ),
+ '#type' => 'checkbox',
+ '#default_value' => in_array($perm, $role_permissions[$rid]) ? 1 : 0,
+ '#attributes' => array('class' => array('rid-' . $rid, 'js-rid-' . $rid)),
+ '#parents' => array($rid, $perm),
+ );
+ // Show a column of disabled but checked checkboxes.
+ if ($admin_roles[$rid]) {
+ $form['permissions'][$perm][$rid]['#disabled'] = TRUE;
+ $form['permissions'][$perm][$rid]['#default_value'] = TRUE;
+ }
+ }
+ }
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Save permissions'),
+ '#button_type' => 'primary',
+ );
+
+ $form['#attached']['library'][] = 'user/drupal.user.permissions';
+
+ return $form;
+ }
+
+ /**
+ * Saves the roles and modules selection.
+ */
+ public function submitFormFilter(array &$form, FormStateInterface $form_state) {
+ $this->saveFilterSettings($form_state->getValue('roles'), $form_state->getValue('modules'));
+ }
+
+ /**
+ * Saves the filter settings for the current user.
+ *
+ * @param array $roles
+ * The roles to filter by.
+ * @param array $modules
+ * The modules to filter by.
+ */
+ protected function saveFilterSettings(array $roles, array $modules) {
+ $values = array('roles' => $roles, 'modules' => $modules);
+ $this->keyValueExpirable->setWithExpire($this->currentUser()->id(), $values, 3600);
+ }
+
+ /**
+ * Retrieve the filter settings for the current user.
+ *
+ * @return array
+ * The filter setting for the current user.
+ */
+ protected function getFilterSettings() {
+ $default = array('roles' => array(), 'modules' => array());
+ return $this->keyValueExpirable->get($this->currentUser()->id(), $default);
+ }
+
+}
diff --git a/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsRoleSpecificForm.php b/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsRoleSpecificForm.php
new file mode 100644
index 000000000..14e594c6a
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/src/Form/PermissionsRoleSpecificForm.php
@@ -0,0 +1,24 @@
+saveFilterSettings((array) $user_role->id(), (array) self::ALL_OPTIONS);
+ return $this->redirect('user.admin_permissions');
+ }
+
+}
diff --git a/sites/all/modules/contrib/admin/filter_perms/src/Routing/RouteSubscriber.php b/sites/all/modules/contrib/admin/filter_perms/src/Routing/RouteSubscriber.php
new file mode 100644
index 000000000..7998c93fb
--- /dev/null
+++ b/sites/all/modules/contrib/admin/filter_perms/src/Routing/RouteSubscriber.php
@@ -0,0 +1,25 @@
+get('user.admin_permissions')) {
+ $route->setDefault('_form', '\Drupal\filter_perms\Form\PermissionsForm');
+ }
+ if ($route = $collection->get('entity.user_role.edit_permissions_form')) {
+ $route->setDefault('_form', '\Drupal\filter_perms\Form\PermissionsRoleSpecificForm');
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/LICENSE.txt b/sites/all/modules/contrib/dev/devel/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/dev/devel/README.txt b/sites/all/modules/contrib/dev/devel/README.txt
new file mode 100644
index 000000000..75bae16eb
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/README.txt
@@ -0,0 +1,51 @@
+Devel
+==========
+Devel module contains helper functions and pages for Drupal developers and inquisitive admins:
+
+ - A block for running custom PHP on a page
+ - A block for quickly accessing devel pages
+ - A block for masquerading as other users (useful for testing)
+ - A mail-system class which redirects outbound email to files
+ - Drush commands such as fn-hook, fn-event, ...
+ - Docs at https://api.drupal.org/api/devel
+ - more
+
+This module is safe to use on a production site. Just be sure to only grant
+'access development information' permission to developers.
+
+Devel Kint
+===================
+Provides a dpr() function, which pretty prints variables.
+Useful during development. Also see similar helpers like dpm(), dvm().
+
+Webprofiler
+==============
+Adds a debug bar at bottom of all pages with tons of useful information like a query list,
+cache hit/miss data, memory profiling, page speed, php info, session info, etc.
+
+Devel Generate
+=================
+Bulk creates nodes, users, comment, terms for development. Has Drush integration.
+
+Devel Node Access
+=================
+Prints the node_access records for a given node. Also offers hook_node_access_explain for all node access modules to implement.
+
+Devel Generate Extensions
+=========================
+Devel Images Provider [http://drupal.org/project/devel_image_provider] allows to configure external providers for images.
+
+Drush Unit Testing
+==================
+See develDrushTest.php for an example of unit testing of the Drush integration.
+This uses Drush's own test framework, based on PHPUnit. To run the tests, use
+run-tests-drush.sh. You may pass in any arguments that are valid for `phpunit`.
+
+Author/Maintainers
+======================
+- Moshe Weitzman http://www.acquia.com
+- Hans Salvisberg
+- Pedro Cambra https://drupal.org/user/122101/contact http://www.ymbra.com/
+- Juan Pablo Novillo https://www.drupal.org/u/juampynr
+- lussoluca https://www.drupal.org/u/lussoluca
+- willzyx https://www.drupal.org/u/willzyx
diff --git a/sites/all/modules/contrib/dev/devel/composer.json b/sites/all/modules/contrib/dev/devel/composer.json
new file mode 100644
index 000000000..db52e40ed
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "drupal/devel",
+ "description": "Various blocks, pages, and functions for developers.",
+ "type": "drupal-module",
+ "homepage": "http://drupal.org/project/devel",
+ "authors": [
+ {
+ "name": "Moshe Weitzman",
+ "email": "weitzman@tejasa.com",
+ "homepage": "https://github.com/weitzman",
+ "role": "Maintainer"
+ }
+ ],
+ "support": {
+ "issues": "http://drupal.org/project/devel",
+ "irc": "irc://irc.freenode.org/drupal-contribute",
+ "source": "http://cgit.drupalcode.org/devel"
+ },
+ "license": "GPL-2.0+",
+ "minimum-stability": "dev",
+ "require": { },
+ "suggest": {
+ "symfony/var-dumper": "Pretty print complex values better with var-dumper available"
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/config/install/devel.settings.yml b/sites/all/modules/contrib/dev/devel/config/install/devel.settings.yml
new file mode 100644
index 000000000..66501f0fa
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/config/install/devel.settings.yml
@@ -0,0 +1,8 @@
+page_alter: FALSE
+raw_names: FALSE
+error_handlers:
+ 1: 1
+rebuild_theme: FALSE
+debug_mail_file_format: '%to-%subject-%datetime.mail.txt'
+debug_mail_directory: 'temporary://devel-mails'
+devel_dumper: 'default'
diff --git a/sites/all/modules/contrib/dev/devel/config/install/system.menu.devel.yml b/sites/all/modules/contrib/dev/devel/config/install/system.menu.devel.yml
new file mode 100644
index 000000000..21fb00214
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/config/install/system.menu.devel.yml
@@ -0,0 +1,9 @@
+id: devel
+label: 'Development'
+description: 'Links related to Devel module.'
+langcode: en
+locked: true
+dependencies:
+ enforced:
+ module:
+ - devel
diff --git a/sites/all/modules/contrib/dev/devel/config/schema/devel.schema.yml b/sites/all/modules/contrib/dev/devel/config/schema/devel.schema.yml
new file mode 100644
index 000000000..4931709f9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/config/schema/devel.schema.yml
@@ -0,0 +1,43 @@
+# Schema for the configuration files of the Devel module.
+
+devel.settings:
+ type: config_object
+ label: 'Devel settings'
+ mapping:
+ page_alter:
+ type: boolean
+ label: 'Page alter'
+ raw_names:
+ type: boolean
+ label: 'Raw names'
+ error_handlers:
+ label: 'Error handlers'
+ type: sequence
+ sequence:
+ type: integer
+ rebuild_theme:
+ type: boolean
+ label: 'Rebuild theme information'
+ debug_mail_file_format:
+ type: string
+ label: 'Mail debug file format'
+ debug_mail_directory:
+ type: string
+ label: 'Mail debug directory'
+ devel_dumper:
+ type: string
+ label: 'Devel variable dumper'
+
+block.settings.devel_switch_user:
+ type: block_settings
+ label: 'Switch user block'
+ mapping:
+ list_size:
+ type: integer
+ label: 'List size'
+ include_anon:
+ type: boolean
+ label: 'Include Anonymous user'
+ show_form:
+ type: boolean
+ label: 'Show search form'
diff --git a/sites/all/modules/contrib/dev/devel/css/devel-rtl.css b/sites/all/modules/contrib/dev/devel/css/devel-rtl.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/sites/all/modules/contrib/dev/devel/css/devel.css b/sites/all/modules/contrib/dev/devel/css/devel.css
new file mode 100644
index 000000000..0abe966a3
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/css/devel.css
@@ -0,0 +1,42 @@
+/**
+ * Dumpers
+ */
+.devel-dumper .details-wrapper{
+ max-height: 450px;
+ margin-right: 3px;
+ overflow: auto;
+}
+
+/**
+ * Switch User block
+ */
+#content .block-devel-switch-user ul.links,
+#footer .block-devel-switch-user ul.links {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+#content .block-devel-switch-user ul.links li,
+#footer .block-devel-switch-user ul.links li {
+ display: inline-block;
+ margin-left: 0;
+ margin-right: 1.5em;
+ padding-right: 0;
+}
+
+#footer .block-devel-switch-user ul.links li a {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+#content #devel-switch-user-form .form-item-username,
+#footer #devel-switch-user-form .form-item-username {
+ float: left;
+ margin-right: 2em;
+}
+
+#content #devel-switch-user-form .button-primary,
+#footer #devel-switch-user-form .button-primary {
+ margin-top: 1em;
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel.api.php b/sites/all/modules/contrib/dev/devel/devel.api.php
new file mode 100644
index 000000000..4a413db6e
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.api.php
@@ -0,0 +1,25 @@
+moduleExists('kint');
+
+ $default_dumper = $kint_enabled ? 'kint' : 'default';
+
+ // Set the default dumper plugin to kint if kint module is available.
+ \Drupal::configFactory()->getEditable('devel.settings')
+ ->set('devel_dumper', $default_dumper)
+ ->save(TRUE);
+}
+
+/**
+ * Add enforced dependencies to system.menu.devel
+ */
+function devel_update_8002() {
+ $config = \Drupal::configFactory()->getEditable('system.menu.devel');
+ $dependencies = $config->get('dependencies');
+ $dependencies['enforced']['module'][] = 'devel';
+ $config->set('dependencies', $dependencies)->save(TRUE);
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel.libraries.yml b/sites/all/modules/contrib/dev/devel/devel.libraries.yml
new file mode 100644
index 000000000..b64362408
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.libraries.yml
@@ -0,0 +1,5 @@
+devel:
+ version: 0
+ css:
+ theme:
+ css/devel.css: {}
diff --git a/sites/all/modules/contrib/dev/devel/devel.links.menu.yml b/sites/all/modules/contrib/dev/devel/devel.links.menu.yml
new file mode 100644
index 000000000..b1a995fa0
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.links.menu.yml
@@ -0,0 +1,75 @@
+devel.admin_settings:
+ title: 'Devel settings'
+ description: 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.'
+ route_name: devel.admin_settings
+ parent: 'system.admin_config_development'
+devel.admin_settings_link:
+ title: 'Devel settings'
+ description: 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.'
+ route_name: devel.admin_settings
+ menu_name: devel
+devel.configs_list:
+ title: 'Config editor'
+ description: 'Edit configuration.'
+ route_name: devel.configs_list
+ menu_name: devel
+devel.reinstall:
+ title: 'Reinstall Modules'
+ route_name: devel.reinstall
+ menu_name: devel
+ class: \Drupal\devel\Plugin\Menu\DestinationMenuLink
+devel.menu_rebuild:
+ title: 'Rebuild Menu'
+ route_name: devel.menu_rebuild
+ menu_name: devel
+ class: \Drupal\devel\Plugin\Menu\DestinationMenuLink
+devel.menu_item:
+ title: 'Menu Item'
+ route_name: devel.menu_item
+ menu_name: devel
+ class: \Drupal\devel\Plugin\Menu\MenuItemMenuLink
+devel.state_system_page:
+ title: 'State editor'
+ description: 'Edit state system values.'
+ route_name: devel.state_system_page
+ menu_name: devel
+devel.theme_registry:
+ title: 'Theme registry'
+ route_name: devel.theme_registry
+ menu_name: devel
+devel.entity_info_page:
+ title: 'Entity Info'
+ route_name: devel.entity_info_page
+# parent: 'system.admin_config_development'
+ menu_name: devel
+devel.field_info_page:
+ title: 'Field Info'
+ route_name: devel.field_info_page
+# parent: 'system.admin_config_development'
+ menu_name: devel
+devel.phpinfo:
+ title: 'PHPinfo()'
+ route_name: system.php
+ menu_name: devel
+devel.execute_php:
+ title: 'Execute PHP'
+ route_name: devel.execute_php
+ menu_name: devel
+devel.session:
+ title: 'View Session'
+ route_name: devel.session
+ menu_name: devel
+devel.elements_page:
+ title: 'Element Info'
+ route_name: devel.elements_page
+ menu_name: devel
+devel.cache_clear:
+ title: 'Cache clear'
+ route_name: devel.cache_clear
+ menu_name: devel
+ class: \Drupal\devel\Plugin\Menu\DestinationMenuLink
+devel.run_cron:
+ title: 'Run cron'
+ route_name: devel.run_cron
+ menu_name: devel
+ class: \Drupal\devel\Plugin\Menu\DestinationMenuLink
diff --git a/sites/all/modules/contrib/dev/devel/devel.links.task.yml b/sites/all/modules/contrib/dev/devel/devel.links.task.yml
new file mode 100644
index 000000000..dd33939f0
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.links.task.yml
@@ -0,0 +1,8 @@
+devel.entities:
+ class: \Drupal\Core\Menu\LocalTaskDefault
+ deriver: \Drupal\devel\Plugin\Derivative\DevelLocalTask
+devel.admin_settings:
+ title: 'Settings'
+ route_name: devel.admin_settings
+ base_route: devel.admin_settings
+ weight: 0
diff --git a/sites/all/modules/contrib/dev/devel/devel.module b/sites/all/modules/contrib/dev/devel/devel.module
new file mode 100644
index 000000000..5a48cbc84
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.module
@@ -0,0 +1,626 @@
+' . t('Warning - will delete your module tables and configuration.') . '
';
+ $output .= '
' . t('Uninstall and then install the selected modules. hook_uninstall() and hook_install() will be executed and the schema version number will be set to the most recent update number.') . '
' . t('Here are the contents of your $_SESSION variable.') . '
';
+
+ case 'devel.state_system_page':
+ return '
' . t('This is a list of state variables and their values. For more information read online documentation of State API in Drupal 8.', array(':documentation' => "https://www.drupal.org/developing/api/8/state")) . '
';
+
+ }
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function devel_entity_type_alter(array &$entity_types) {
+ /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+ foreach ($entity_types as $entity_type_id => $entity_type) {
+ if ($entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical')) {
+ $entity_type->setLinkTemplate('devel-render', "/devel/$entity_type_id/{{$entity_type_id}}/render");
+ }
+ if (($entity_type->getFormClass('default') || $entity_type->getFormClass('edit')) && $entity_type->hasLinkTemplate('edit-form')) {
+ $entity_type->setLinkTemplate('devel-load', "/devel/$entity_type_id/{{$entity_type_id}}");
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_operation().
+ */
+function devel_entity_operation(EntityInterface $entity) {
+ $operations = array();
+ if (\Drupal::currentUser()->hasPermission('access devel information')) {
+ if ($entity->hasLinkTemplate('devel-load')) {
+ $operations['devel'] = array(
+ 'title' => t('Devel'),
+ 'weight' => 100,
+ 'url' => $entity->toUrl('devel-load'),
+ );
+ }
+ elseif ($entity->hasLinkTemplate('devel-render')) {
+ $operations['devel'] = array(
+ 'title' => t('Devel'),
+ 'weight' => 100,
+ 'url' => $entity->toUrl('devel-render'),
+ );
+ }
+ }
+ return $operations;
+}
+
+/**
+ * Sets message.
+ */
+function devel_set_message($msg, $type = NULL) {
+ if (function_exists('drush_log')) {
+ drush_log($msg, $type);
+ }
+ else {
+ drupal_set_message($msg, $type, TRUE);
+ }
+}
+
+/**
+ * Gets error handlers.
+ */
+function devel_get_handlers() {
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ if (!empty($error_handlers)) {
+ unset($error_handlers[DEVEL_ERROR_HANDLER_NONE]);
+ }
+ return $error_handlers;
+}
+
+/**
+ * Sets a new error handler or restores the prior one.
+ */
+function devel_set_handler($handlers) {
+ if (empty($handlers)) {
+ restore_error_handler();
+ }
+ elseif (count($handlers) == 1 && isset($handlers[DEVEL_ERROR_HANDLER_STANDARD])) {
+ // Do nothing.
+ }
+ else {
+ set_error_handler('backtrace_error_handler');
+ }
+}
+
+/**
+ * Displays backtrace showing the route of calls to the current error.
+ *
+ * @param int $error_level
+ * The level of the error raised.
+ * @param string $message
+ * The error message.
+ * @param string $filename
+ * The filename that the error was raised in.
+ * @param int $line
+ * The line number the error was raised at.
+ * @param array $context
+ * An array that points to the active symbol table at the point the error
+ * occurred.
+ */
+function backtrace_error_handler($error_level, $message, $filename, $line, $context) {
+ // Hide stack trace and parameters from unqualified users.
+ if (!\Drupal::currentUser()->hasPermission('access devel information')) {
+ // Do what core does in bootstrap.inc and errors.inc.
+ // (We need to duplicate the core code here rather than calling it
+ // to avoid having the backtrace_error_handler() on top of the call stack.)
+ if ($error_level & error_reporting()) {
+ $types = drupal_error_levels();
+ list($severity_msg, $severity_level) = $types[$error_level];
+ $backtrace = debug_backtrace();
+ $caller = Error::getLastCaller($backtrace);
+
+ // We treat recoverable errors as fatal.
+ _drupal_log_error(array(
+ '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
+ '@message' => $message,
+ '%function' => $caller['function'],
+ '%file' => $caller['file'],
+ '%line' => $caller['line'],
+ 'severity_level' => $severity_level,
+ 'backtrace' => $backtrace,
+ ), $error_level == E_RECOVERABLE_ERROR);
+ }
+
+ return;
+ }
+
+ // Don't respond to the error if it was suppressed with a '@'
+ if (error_reporting() == 0) {
+ return;
+ }
+
+ // Don't respond to warning caused by ourselves.
+ if (preg_match('#Cannot modify header information - headers already sent by \\([^\\)]*[/\\\\]devel[/\\\\]#', $message)) {
+ return;
+ }
+
+ if ($error_level & error_reporting()) {
+ // Only write each distinct NOTICE message once, as repeats do not give any
+ // further information and can choke the page output.
+ if ($error_level == E_NOTICE) {
+ static $written = array();
+ if (!empty($written[$line][$filename][$message])) {
+ return;
+ }
+ $written[$line][$filename][$message] = TRUE;
+ }
+
+ $types = drupal_error_levels();
+ list($severity_msg, $severity_level) = $types[$error_level];
+
+ $backtrace = debug_backtrace();
+ $caller = Error::getLastCaller($backtrace);
+ $variables = array(
+ '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
+ '@message' => $message,
+ '%function' => $caller['function'],
+ '%file' => $caller['file'],
+ '%line' => $caller['line'],
+ );
+ $msg = t('%type: @message in %function (line %line of %file).', $variables);
+
+ // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher.
+ // (This is Drupal's error_level, which is different from $error_level,
+ // and we purposely ignore the difference between _SOME and _ALL,
+ // see #970688!)
+ if (\Drupal::config('system.logging')->get('error_level') != 'hide') {
+ $error_handlers = devel_get_handlers();
+ if (!empty($error_handlers[DEVEL_ERROR_HANDLER_STANDARD])) {
+ drupal_set_message($msg, ($severity_level <= RfcLogLevel::NOTICE ? 'error' : 'warning'), TRUE);
+ }
+ if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_KINT])) {
+ print kprint_r(ddebug_backtrace(TRUE, 1), $return = TRUE, $msg);
+ }
+ if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_DPM])) {
+ dpm(ddebug_backtrace(TRUE, 1), $msg, 'warning');
+ }
+ }
+
+ \Drupal::logger('php')->log($severity_level, $msg);
+ }
+}
+
+/**
+ * Implements hook_page_attachments_alter().
+ */
+function devel_page_attachments_alter(&$page) {
+ if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('page_alter')) {
+ dpm($page, 'page');
+ }
+}
+
+/**
+ * Prints an object using either Kint (if enabled) or devel_print_object().
+ *
+ * @param array|object $object
+ * An array or object to print.
+ * @param string $prefix
+ * @todo: this parameter is not needed with Kint.
+ * Prefix for output items.
+ *
+ * @deprecated in Devel 8.x-dev, will be removed before Devel 8.0.
+ * Use kpr() or devel.dumper service instead.
+ *
+ * @TODO remove in https://www.drupal.org/node/2703343
+ */
+function kdevel_print_object($object, $prefix = NULL) {
+ return kpr($object, TRUE, $prefix);
+}
+
+/**
+ * Wrapper for DevelDumperManager::dump().
+ *
+ * Calls the http://www.firephp.org/ fb() function if it is found.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dump()
+ */
+function dfb() {
+ $args = func_get_args();
+ \Drupal::service('devel.dumper')->dump($args, NULL, 'firephp');
+}
+
+/**
+ * Wrapper for DevelDumperManager::dump().
+ *
+ * Calls dfb() to output a backtrace.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dump()
+ */
+function dfbt($label) {
+ \Drupal::service('devel.dumper')->dump(FirePHP::TRACE, $label, 'firephp');
+}
+
+/**
+ * Wrapper for DevelDumperManager::dump().
+ *
+ * Wrapper for ChromePHP Class log method.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dump()
+ */
+function dcp() {
+ $args = func_get_args();
+ \Drupal::service('devel.dumper')->dump($args, NULL, 'chromephp');
+}
+
+if (!function_exists('dd')) {
+ /**
+ * Wrapper for DevelDumperManager::debug().
+ *
+ * @see \Drupal\devel\DevelDumperManager::debug()
+ */
+ function dd($data, $label = NULL) {
+ return \Drupal::service('devel.dumper')->debug($data, $label, 'default');
+ }
+}
+
+/**
+ * Wrapper for DevelDumperManager::message().
+ *
+ * Prints a variable to the 'message' area of the page.
+ *
+ * Uses drupal_set_message().
+ *
+ * @param $input
+ * An arbitrary value to output.
+ * @param string $name
+ * Optional name for identifying the output.
+ * @param string $type
+ * Optional message type for drupal_set_message(), defaults to 'status'.
+ *
+ * @return input
+ * The unaltered input value.
+ *
+ * @see \Drupal\devel\DevelDumperManager::message()
+ */
+function dpm($input, $name = NULL, $type = 'status') {
+ \Drupal::service('devel.dumper')->message($input, $name, $type);
+ return $input;
+}
+
+/**
+ * Wrapper for DevelDumperManager::message().
+ *
+ * Displays a Variable::export() variable to the 'message' area of the page.
+ *
+ * Uses drupal_set_message().
+ *
+ * @param $input
+ * An arbitrary value to output.
+ * @param string $name
+ * Optional name for identifying the output.
+ *
+ * @return input
+ * The unaltered input value.
+ *
+ * @see \Drupal\devel\DevelDumperManager::message()
+ */
+function dvm($input, $name = NULL) {
+ \Drupal::service('devel.dumper')->message($input, $name, 'status', 'drupal_variable');
+ return $input;
+}
+
+/**
+ * An alias for dpm(), for historic reasons.
+ */
+function dsm($input, $name = NULL) {
+ return dpm($input, $name);
+}
+
+/**
+ * Wrapper for DevelDumperManager::dumpOrExport().
+ *
+ * An alias for the devel.dumper service. Saves carpal tunnel syndrome.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dumpOrExport()
+ */
+function dpr($input, $export = FALSE, $name = NULL) {
+ return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export, 'default');
+}
+
+/**
+ * Wrapper for DevelDumperManager::dumpOrExport().
+ *
+ * An alias for devel_dump(). Saves carpal tunnel syndrome.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dumpOrExport()
+ */
+function kpr($input, $export = FALSE, $name = NULL) {
+ return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export);
+}
+
+/**
+ * Kint print.
+ *
+ * @deprecated in Devel 8.x-dev, will be removed before Devel 8.0.
+ * Use kpr() or devel.dumper service instead.
+ *
+ * @TODO remove in https://www.drupal.org/node/2703343
+ */
+function kprint_r($input, $export = FALSE, $name = NULL, $function = 'print_r') {
+ return kpr($input, $export, $name);
+}
+
+/**
+ * Wrapper for DevelDumperManager::dumpOrExport().
+ *
+ * Like dpr(), but uses Variable::export() instead.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dumpOrExport()
+ */
+function dvr($input, $export = FALSE, $name = NULL) {
+ return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export, 'drupal_variable');
+}
+
+/**
+ * Prints the arguments passed into the current function.
+ */
+function dargs($always = TRUE) {
+ static $printed;
+ if ($always || !$printed) {
+ $bt = debug_backtrace();
+ print kdevel_print_object($bt[1]['args']);
+ $printed = TRUE;
+ }
+}
+
+/**
+ * Prints a SQL string from a DBTNG Select object. Includes quoted arguments.
+ *
+ * @param object $query
+ * An object that implements the SelectInterface interface.
+ * @param boolean $return
+ * Whether to return the string. Default is FALSE, meaning to print it
+ * and return $query instead.
+ * @param string $name
+ * Optional name for identifying the output.
+ *
+ * @return object|string
+ * The $query object, or the query string if $return was TRUE.
+ */
+function dpq($query, $return = FALSE, $name = NULL) {
+ if (\Drupal::currentUser()->hasPermission('access devel information')) {
+ if (method_exists($query, 'preExecute')) {
+ $query->preExecute();
+ }
+ $sql = (string) $query;
+ $quoted = array();
+ $connection = Database::getConnection();
+ foreach ((array) $query->arguments() as $key => $val) {
+ $quoted[$key] = is_null($val) ? 'NULL' : $connection->quote($val);
+ }
+ $sql = strtr($sql, $quoted);
+ if ($return) {
+ return $sql;
+ }
+ dpm($sql, $name);
+ }
+ return ($return ? NULL : $query);
+}
+
+/**
+ * Prints a renderable array element to the screen using kprint_r().
+ *
+ * #pre_render and/or #post_render pass-through callback for kprint_r().
+ *
+ * @todo Investigate appending to #suffix.
+ * @todo Investigate label derived from #id, #title, #name, and #theme.
+ */
+function devel_render() {
+ $args = func_get_args();
+ // #pre_render and #post_render pass the rendered $element as last argument.
+ kprint_r(end($args));
+ // #pre_render and #post_render expect the first argument to be returned.
+ return reset($args);
+}
+
+/**
+ * Prints the function call stack.
+ *
+ * @param $return
+ * Pass TRUE to return the formatted backtrace rather than displaying it in
+ * the browser via kprint_r().
+ * @param $pop
+ * How many items to pop from the top of the stack; useful when calling from
+ * an error handler.
+ * @param $options
+ * Options to pass on to PHP's debug_backtrace().
+ *
+ * @return string|NULL
+ * The formatted backtrace, if requested, or NULL.
+ *
+ * @see http://php.net/manual/en/function.debug-backtrace.php
+ */
+function ddebug_backtrace($return = FALSE, $pop = 0, $options = DEBUG_BACKTRACE_PROVIDE_OBJECT) {
+ if (\Drupal::currentUser()->hasPermission('access devel information')) {
+ $backtrace = debug_backtrace($options);
+ while ($pop-- > 0) {
+ array_shift($backtrace);
+ }
+ $counter = count($backtrace);
+ $path = $backtrace[$counter - 1]['file'];
+ $path = substr($path, 0, strlen($path) - 10);
+ $paths[$path] = strlen($path) + 1;
+ $paths[DRUPAL_ROOT] = strlen(DRUPAL_ROOT) + 1;
+ $nbsp = "\xC2\xA0";
+
+ // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher.
+ // (This is Drupal's error_level, which is different from $error_level,
+ // and we purposely ignore the difference between _SOME and _ALL,
+ // see #970688!)
+ if (\Drupal::config('system.logging')->get('error_level') != 'hide') {
+ while (!empty($backtrace)) {
+ $call = array();
+ if (isset($backtrace[0]['file'])) {
+ $call['file'] = $backtrace[0]['file'];
+ foreach ($paths as $path => $len) {
+ if (strpos($backtrace[0]['file'], $path) === 0) {
+ $call['file'] = substr($backtrace[0]['file'], $len);
+ }
+ }
+ $call['file'] .= ':' . $backtrace[0]['line'];
+ }
+ if (isset($backtrace[1])) {
+ if (isset($backtrace[1]['class'])) {
+ $function = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
+ }
+ else {
+ $function = $backtrace[1]['function'] . '()';
+ }
+ $backtrace[1] += array('args' => array());
+ foreach ($backtrace[1]['args'] as $key => $value) {
+ $call['args'][$key] = $value;
+ }
+ }
+ else {
+ $function = 'main()';
+ $call['args'] = $_GET;
+ }
+ $nicetrace[($counter <= 10 ? $nbsp : '') . --$counter . ': ' . $function] = $call;
+ array_shift($backtrace);
+ }
+ if ($return) {
+ return $nicetrace;
+ }
+ kprint_r($nicetrace);
+ }
+ }
+}
+
+/*
+ * Migration-related functions.
+ */
+
+/**
+ * Regenerates the data in node_comment_statistics table.
+ * Technique - http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101
+ *
+ * @return void
+ */
+function devel_rebuild_node_comment_statistics() {
+ // Empty table.
+ db_truncate('node_comment_statistics')->execute();
+
+ // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case
+ // when two comments on the same node share same timestamp.
+ $sql = "
+ INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) (
+ SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c
+ JOIN (
+ SELECT c.nid, MAX(c.created) AS created, COUNT(*) AS comment_count FROM {comment} c WHERE status = 1 GROUP BY c.nid
+ ) AS c2 ON c.nid = c2.nid AND c.created = c2.created
+ )";
+ db_query($sql, array(':published' => CommentInterface::PUBLISHED));
+
+ // Insert records into the node_comment_statistics for nodes that are missing.
+ $query = db_select('node', 'n');
+ $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid');
+ $query->addField('n', 'changed', 'last_comment_timestamp');
+ $query->addField('n', 'uid', 'last_comment_uid');
+ $query->addField('n', 'nid');
+ $query->addExpression('0', 'comment_count');
+ $query->addExpression('NULL', 'last_comment_name');
+ $query->isNull('ncs.comment_count');
+
+ db_insert('node_comment_statistics', array('return' => Database::RETURN_NULL))
+ ->from($query)
+ ->execute();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Adds mouse-over hints on the Permissions page to display
+ * language-independent machine names and module base names.
+ *
+ * @see \Drupal\user\Form\UserPermissionsForm::buildForm()
+ */
+function devel_form_user_admin_permissions_alter(&$form, FormStateInterface $form_state) {
+ if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('raw_names')) {
+ foreach (Element::children($form['permissions']) as $key) {
+ if (isset($form['permissions'][$key][0])) {
+ $form['permissions'][$key][0]['#wrapper_attributes']['title'] = $key;
+ }
+ elseif(isset($form['permissions'][$key]['description'])) {
+ $form['permissions'][$key]['description']['#wrapper_attributes']['title'] = $key;
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Adds mouse-over hints on the Modules page to display module base names.
+ *
+ * @see \Drupal\system\Form\ModulesListForm::buildForm()
+ * @see theme_system_modules_details()
+ */
+function devel_form_system_modules_alter(&$form, FormStateInterface $form_state) {
+ if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('raw_names', FALSE) && isset($form['modules']) && is_array($form['modules'])) {
+ foreach (Element::children($form['modules']) as $group) {
+ if (is_array($form['modules'][$group])) {
+ foreach (Element::children($form['modules'][$group]) as $key) {
+ if (isset($form['modules'][$group][$key]['name']['#markup'])) {
+ $form['modules'][$group][$key]['name']['#markup'] = '' . $form['modules'][$group][$key]['name']['#markup'] . '';
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * Makes debugging entity query much easier.
+ *
+ * Example usage:
+ * @code
+ * $query = \Drupal::entityQuery('node');
+ * $query->condition('status', NODE_PUBLISHED);
+ * $query->addTag('debug');
+ * $query->execute();
+ * @endcode
+ */
+function devel_query_debug_alter(AlterableInterface $query) {
+ if (!$query->hasTag('debug-semaphore')) {
+ $query->addTag('debug-semaphore');
+ dpq($query);
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel.permissions.yml b/sites/all/modules/contrib/dev/devel/devel.permissions.yml
new file mode 100644
index 000000000..256919de9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.permissions.yml
@@ -0,0 +1,14 @@
+access devel information:
+ description: 'View developer output like variable printouts, query log, etc.'
+ title: 'Access developer information'
+ restrict access: TRUE
+
+execute php code:
+ title: 'Execute PHP code'
+ description: 'Run arbitrary PHP from a block.'
+ restrict access: TRUE
+
+switch users:
+ title: 'Switch users'
+ description: 'Become any user on the site with just a click.'
+ restrict access: TRUE
diff --git a/sites/all/modules/contrib/dev/devel/devel.routing.yml b/sites/all/modules/contrib/dev/devel/devel.routing.yml
new file mode 100644
index 000000000..bfbb70c3e
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.routing.yml
@@ -0,0 +1,172 @@
+devel.admin_settings:
+ path: '/admin/config/development/devel'
+ defaults:
+ _form: '\Drupal\devel\Form\SettingsForm'
+ _title: 'Devel settings'
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.reinstall:
+ path: '/devel/reinstall'
+ defaults:
+ _form: '\Drupal\devel\Form\DevelReinstall'
+ _title: 'Reinstall modules'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.menu_rebuild:
+ path: '/devel/menu/reset'
+ defaults:
+ _form: '\Drupal\devel\Form\DevelRebuildMenus'
+ _title: 'Rebuild menus'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.configs_list:
+ path: '/devel/config/{filter}'
+ options:
+ _admin_route: TRUE
+ defaults:
+ _form: '\Drupal\devel\Form\ConfigsList'
+ _title: 'Config editor'
+ filter: ''
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.config_edit:
+ path: '/devel/config/edit/{config_name}'
+ defaults:
+ _form: '\Drupal\devel\Form\ConfigEditor'
+ _title: 'Edit configuration object: !config_name'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.state_system_page:
+ path: '/devel/state'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::stateSystemPage'
+ _title: 'State editor'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.system_state_edit:
+ path: '/devel/state/edit/{state_name}'
+ defaults:
+ _form: '\Drupal\devel\Form\SystemStateEdit'
+ _title: 'Edit state variable: @state_name'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'administer site configuration'
+
+devel.menu_item:
+ path: '/devel/menu/item'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::menuItem'
+ _title: 'Menu item'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.theme_registry:
+ path: '/devel/theme/registry'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::themeRegistry'
+ _title: 'Theme registry'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.entity_info_page:
+ path: '/devel/entity/info'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::entityInfoPage'
+ _title: 'Entity info'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.field_info_page:
+ path: '/devel/field/info'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::fieldInfoPage'
+ _title: 'Field info'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.execute_php:
+ path: '/devel/php'
+ defaults:
+ _form: '\Drupal\devel\Form\ExecutePHP'
+ _title: 'Execute PHP code'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'execute php code'
+
+devel.session:
+ path: '/devel/session'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::session'
+ _title: 'Session viewer'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.switch:
+ path: '/devel/switch/{name}'
+ defaults:
+ _controller: '\Drupal\devel\Controller\SwitchUserController::switchUser'
+ _title: 'Switch user'
+ name: ''
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'switch users'
+ _csrf_token: 'TRUE'
+
+devel.elements_page:
+ path: '/devel/elements'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::elementsPage'
+ _title: 'Element Info'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+
+devel.cache_clear:
+ path: '/devel/cache/clear'
+ defaults:
+ _controller: '\Drupal\devel\Controller\DevelController::cacheClear'
+ _title: 'Clear cache'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'access devel information'
+ _csrf_token: 'TRUE'
+
+devel.run_cron:
+ path: '/devel/run-cron'
+ defaults:
+ _controller: '\Drupal\system\CronController::runManually'
+ _title: 'Run cron'
+ options:
+ _admin_route: TRUE
+ requirements:
+ _permission: 'administer site configuration'
+ _csrf_token: 'TRUE'
diff --git a/sites/all/modules/contrib/dev/devel/devel.rules.inc b/sites/all/modules/contrib/dev/devel/devel.rules.inc
new file mode 100644
index 000000000..0a7a07808
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.rules.inc
@@ -0,0 +1,24 @@
+ array(
+ 'base' => 'devel_rules_debug_action',
+ 'label' => t('Debug value'),
+ 'group' => t('Devel'),
+ 'parameter' => array(
+ 'value' => array('type' => 'unknown', 'label' => t('Value to debug')),
+ ),
+ ),
+ );
+}
+
+/**
+ * Rules action for debugging values.
+ */
+function devel_rules_debug_action($value) {
+ dpm($value);
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel.services.yml b/sites/all/modules/contrib/dev/devel/devel.services.yml
new file mode 100644
index 000000000..57e44aa04
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel.services.yml
@@ -0,0 +1,26 @@
+services:
+ devel.event_subscriber:
+ class: Drupal\devel\EventSubscriber\DevelEventSubscriber
+ arguments: ['@config.factory', '@current_user', '@module_handler', '@url_generator']
+ tags:
+ - { name: event_subscriber }
+
+ plugin.manager.devel_dumper:
+ class: Drupal\devel\DevelDumperPluginManager
+ parent: default_plugin_manager
+
+ devel.dumper:
+ class: Drupal\devel\DevelDumperManager
+ arguments: ['@config.factory', '@current_user', '@plugin.manager.devel_dumper']
+
+ devel.route_subscriber:
+ class: Drupal\devel\Routing\RouteSubscriber
+ arguments: ['@entity_type.manager']
+ tags:
+ - { name: event_subscriber }
+
+ devel.twig.debug_extension:
+ class: Drupal\devel\Twig\Extension\Debug
+ arguments: ['@devel.dumper']
+ tags:
+ - { name: twig.extension }
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/README.txt b/sites/all/modules/contrib/dev/devel/devel_generate/README.txt
new file mode 100644
index 000000000..c5279d014
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/README.txt
@@ -0,0 +1,42 @@
+This module creates the "DevelGenerate" plugin type.
+
+All you need to do to provide a new instance for "DevelGenerate" plugin type
+is to create your class extending "DevelGenerateBase" and following the next steps.
+
+1 - Declaring your plugin with annotations:
+
+/**
+ * Provides a ExampleDevelGenerate plugin.
+ *
+ * @DevelGenerate(
+ * id = "example",
+ * label = @Translation("example"),
+ * description = @Translation("Generate a given number of example elements. Optionally delete current example elements."),
+ * url = "example",
+ * permission = "administer example",
+ * settings = {
+ * "num" = 50,
+ * "kill" = FALSE,
+ * "another_property" = "default_value"
+ * }
+ * )
+ */
+
+2 - Implement "settingsForm" method to create a form using the properties from annotations.
+
+3 - Implement "handleDrushParams" method. It should return an array of values.
+
+4 - Implement "generateElements" method. You can write here your business logic
+using the array of values.
+
+Notes:
+
+You can alter existing properties for every plugin implementing hook_devel_generate_info_alter.
+
+DevelGenerateBaseInterface details base wrapping methods that most DevelGenerate implementations
+will want to directly inherit from Drupal\devel_generate\DevelGenerateBase.
+
+DevelGenerateFieldBaseInterface details base wrapping methods that most class implementations
+for supporting new field types will want to directly inherit from Drupal\devel_generate\DevelGenerateFieldBase.
+So to give support for a new field type should be enough to create a class called
+"DevelGenerateFieldNewfieldtype" extending DevelGenerateFieldBase and to implement "generateValues" method.
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.batch.inc b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.batch.inc
new file mode 100644
index 000000000..2bfa690b8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.batch.inc
@@ -0,0 +1,25 @@
+$method($vars, $context);
+}
+
+/**
+ * Common finish batch function available for
+ * every DevelGeneratePlugin.
+ */
+function devel_generate_batch_finished($success, $results, $operations) {
+
+ if ($success) {
+ $message = t('Finished @num elements created successfully.', array('@num' => $results['num']));
+ }
+ else {
+ $message = t('Finished with an error.');
+ }
+ drupal_set_message($message);
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info.yml b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info.yml
new file mode 100644
index 000000000..068fbe98b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info.yml
@@ -0,0 +1,13 @@
+type: module
+name: 'Devel generate'
+description: 'Generate dummy users, nodes, menus, taxonomy terms...'
+package: Development
+# core: 8.x
+tags:
+ - developer
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module
new file mode 100644
index 000000000..530a4fbc8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module
@@ -0,0 +1,145 @@
+getDefinitions();
+ foreach ($devel_generate_plugins as $id => $plugin) {
+ $label = $plugin['label'];
+ $links["devel_generate.$id"] = array(
+ 'title' => "Generate $label",
+ 'parent' => 'system.admin_config_development',
+ 'description' => $plugin['description'],
+ 'route_name' => "devel_generate.$id",
+ );
+ }
+}
+
+/**
+ * Implements hook_entity_insert().
+ *
+ * Inserts nodes properly based on generation options.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The base node created on submit. Inspects $node->devel_generate.
+ */
+function devel_generate_entity_insert(EntityInterface $entity) {
+ if ($entity->getEntityTypeId() != 'node' || !isset($entity->devel_generate)) {
+ return;
+ }
+ /** @var \Drupal\node\NodeInterface $entity */
+ $results = $entity->devel_generate;
+
+ if (!empty($results['max_comments'])) {
+ foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) {
+ if ($field_definition->getType() == 'comment' && $entity->get($field_name)->status == CommentItemInterface::OPEN) {
+ // Add comments for each comment field on entity.
+ devel_generate_add_comments($entity, $field_definition, $results['users'], $results['max_comments'], $results['title_length']);
+ }
+ }
+ }
+
+ // Add an url alias. Cannot happen before save because we don't know the nid.
+ if (!empty($results['add_alias'])) {
+ $path = array(
+ 'source' => '/node/' . $entity->id(),
+ 'alias' => '/node-' . $entity->id() . '-' . $entity->bundle(),
+ );
+ \Drupal::service('path.alias_storage')->save($path['source'], $path['alias']);
+ }
+
+ // Add node statistics.
+ if (!empty($results['add_statistics']) && \Drupal::moduleHandler()->moduleExists('statistics')) {
+ devel_generate_add_statistics($entity);
+ }
+}
+
+/**
+ * Create comments and add them to a node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * Node to add comments to.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field storage definition.
+ * @param array $users
+ * Array of users to assign comment authors.
+ * @param int $max_comments
+ * Max number of comments to generate per node.
+ * @param int $title_length
+ * Max length of the title of the comments.
+ */
+function devel_generate_add_comments(NodeInterface $node, FieldDefinitionInterface $field_definition, $users, $max_comments, $title_length = 8) {
+ $parents = array();
+ $field_name = $field_definition->getName();
+ $num_comments = mt_rand(1, $max_comments);
+ for ($i = 1; $i <= $num_comments; $i++) {
+ switch ($i % 3) {
+ case 0:
+ // No parent.
+ case 1:
+ // Top level parent.
+ $parents = \Drupal::entityQuery('comment')
+ ->condition('pid', 0)
+ ->condition('entity_id', $node->id())
+ ->condition('entity_type', 'node')
+ ->condition('field_name', $field_name)
+ ->range(0, 1)
+ ->execute();
+ break;
+ case 2:
+ // Non top level parent.
+ $parents = \Drupal::entityQuery('comment')
+ ->condition('pid', 0, '>')
+ ->condition('entity_id', $node->id())
+ ->condition('entity_type', 'node')
+ ->condition('field_name', $field_name)
+ ->range(0, 1)
+ ->execute();
+ break;
+ }
+ $random = new Random();
+ $stub = array(
+ 'entity_type' => $node->getEntityTypeId(),
+ 'entity_id' => $node->id(),
+ 'field_name' => $field_name,
+ 'name' => 'devel generate',
+ 'mail' => 'devel_generate@example.com',
+ 'timestamp' => mt_rand($node->getCreatedTime(), REQUEST_TIME),
+ 'subject' => substr($random->sentences(mt_rand(2, $title_length), TRUE), 0, 63),
+ 'uid' => $users[array_rand($users)],
+ 'langcode' => $node->language()->getId(),
+ );
+ if ($parents) {
+ $stub['pid'] = current($parents);
+ }
+ $comment = entity_create('comment', $stub);
+
+ //Populate all core fields on behalf of field.module
+ DevelGenerateBase::populateFields($comment);
+ $comment->save();
+ }
+}
+
+/**
+ * Generate statistics information for a node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ */
+function devel_generate_add_statistics(NodeInterface $node) {
+ $statistic = array(
+ 'nid' => $node->id(),
+ 'totalcount' => mt_rand(0, 500),
+ 'timestamp' => REQUEST_TIME - mt_rand(0, $node->getCreatedTime()),
+ );
+ $statistic['daycount'] = mt_rand(0, $statistic['totalcount']);
+ db_insert('node_counter')->fields($statistic)->execute();
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.permissions.yml b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.permissions.yml
new file mode 100644
index 000000000..10f9479e5
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.permissions.yml
@@ -0,0 +1,5 @@
+administer devel_generate:
+ title: 'Administer devel_generate'
+
+permission_callbacks:
+ - \Drupal\devel_generate\DevelGeneratePermissions::permissions
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.routing.yml b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.routing.yml
new file mode 100644
index 000000000..eeea66824
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.routing.yml
@@ -0,0 +1,2 @@
+route_callbacks:
+ - '\Drupal\devel_generate\Routing\DevelGenerateRoutes::routes'
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.services.yml b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.services.yml
new file mode 100644
index 000000000..16025e1da
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.services.yml
@@ -0,0 +1,4 @@
+services:
+ plugin.manager.develgenerate:
+ class: Drupal\devel_generate\DevelGeneratePluginManager
+ parent: default_plugin_manager
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/drush/DevelGenerateUnishTest.php b/sites/all/modules/contrib/dev/devel/devel_generate/drush/DevelGenerateUnishTest.php
new file mode 100644
index 000000000..128c2773c
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/drush/DevelGenerateUnishTest.php
@@ -0,0 +1,151 @@
+markTestSkipped('Devel Generate Tests only available on D8+.');
+ }
+
+ if (!$this->getSites()) {
+ $this->setUpDrupal(1, TRUE, UNISH_DRUPAL_MAJOR_VERSION, 'standard');
+
+ // Symlink the devel module into the sandbox.
+ $devel_directory = dirname(dirname(__DIR__));
+ symlink($devel_directory, $this->webroot() . '/modules/devel');
+
+ // Enable the devel_generate modules.
+ $this->drush('pm-enable', ['devel_generate'], $this->getOptions());
+ }
+
+ }
+
+ /**
+ * Tests devel generate terms.
+ */
+ public function testDevelGenerateTerms() {
+ $this->drush('pm-enable', ['taxonomy'], $this->getOptions());
+
+ $this->drush('generate-terms', [], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('Please provide a vocabulary machine name.', $this->getErrorOutput());
+
+ $this->drush('generate-terms', ['unknown'], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('Invalid vocabulary name: unknown', $this->getErrorOutput());
+
+ $this->drush('generate-terms', ['tags', 'NaN'], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('Invalid number of terms: NaN', $this->getErrorOutput());
+
+ $eval_term_count = "return \\Drupal::entityQuery('taxonomy_term')->count()->execute();";
+ $eval_options = $this->getOptions() + ['format' => 'string'];
+
+ $this->drush('generate-terms', ['tags'], $this->getOptions());
+ $this->assertContains('Created the following new terms:', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_term_count], $eval_options);
+ $this->assertEquals(10, $this->getOutput());
+
+ $this->drush('generate-terms', ['tags', '1'], $this->getOptions());
+ $this->assertContains('Created the following new terms:', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_term_count], $eval_options);
+ $this->assertEquals(11, $this->getOutput());
+
+ $this->drush('generate-terms', ['tags', '1'], $this->getOptions(TRUE));
+ $this->assertContains('Deleted existing terms.', $this->getErrorOutput());
+ $this->assertContains('Created the following new terms:', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_term_count], $eval_options);
+ $this->assertEquals(1, $this->getOutput());
+
+ $this->drush('gent', ['tags', '1'], $this->getOptions());
+ $this->assertContains('Created the following new terms:', $this->getErrorOutput());
+ }
+
+ /**
+ * Tests devel generate contents.
+ */
+ public function testDevelGenerateContents() {
+ $this->drush('pm-enable', ['node'], $this->getOptions());
+
+ $eval_content_count = "return \\Drupal::entityQuery('node')->count()->execute();";
+ $eval_options = $this->getOptions() + ['format' => 'string'];
+
+ // Try to generate 10 content of type "page" or "article"
+ $this->drush('generate-content', [10], $this->getOptions(), NULL, NULL, static::EXIT_SUCCESS);
+ $this->assertContains('Finished creating 10 nodes', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(10, $this->getOutput());
+
+ // Try to generate 1 content of type "page" or "article"
+ $this->drush('generate-content', [1], $this->getOptions(), NULL, NULL, static::EXIT_SUCCESS);
+ $this->assertContains('1 node created.', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(11, $this->getOutput());
+
+ // Try to generate 5 content of type "page" or "article", removing all
+ // previous contents.
+ $this->drush('generate-content', [5], $this->getOptions(TRUE), NULL, NULL, static::EXIT_SUCCESS);
+ $this->assertContains('Finished creating 5 nodes', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(5, $this->getOutput());
+
+ // Try to generate other 5 content with "crappy" type. Output should
+ // remains 5.
+ $generate_content_wrong_ct = $this->getOptions(TRUE) + ['types' => 'crappy'];
+ $this->drush('generate-content', [5], $generate_content_wrong_ct, NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('One or more content types have been entered that don', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(5, $this->getOutput());
+
+ // Try to generate other 5 content with empty types. Output should
+ // remains 5.
+ $generate_content_no_types = $this->getOptions(TRUE) + ['types' => ''];
+ $this->drush('generate-content', [5], $generate_content_no_types, NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('No content types available', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(5, $this->getOutput());
+
+ // Try to generate other 5 content without any types. Output should
+ // remains 5.
+ $generate_content_no_types = $this->getOptions(TRUE) + ['types' => NULL];
+ $this->drush('generate-content', [5], $generate_content_no_types, NULL, NULL, static::EXIT_ERROR);
+ $this->assertContains('Wrong syntax or no content type selected. The correct syntax uses', $this->getErrorOutput());
+ $this->drush('php-eval', [$eval_content_count], $eval_options);
+ $this->assertEquals(5, $this->getOutput());
+ }
+
+ /**
+ * Default drush options.
+ *
+ * @param bool $kill
+ * Whether add kill option.
+ *
+ * @return array
+ * An array containing the default options for drush commands.
+ */
+ protected function getOptions($kill = FALSE) {
+ $options = [
+ 'yes' => NULL,
+ 'root' => $this->webroot(),
+ 'uri' => key($this->getSites()),
+ ];
+
+ if($kill) {
+ $options['kill'] = NULL;
+ }
+
+ return $options;
+ }
+
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/drush/devel_generate.drush.inc b/sites/all/modules/contrib/dev/devel/devel_generate/drush/devel_generate.drush.inc
new file mode 100644
index 000000000..33ae8a267
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/drush/devel_generate.drush.inc
@@ -0,0 +1,196 @@
+ 'drush_devel_generate',
+ 'callback arguments' => array(
+ 'plugin_id' => 'user',
+ ),
+ 'description' => 'Create users.',
+ 'arguments' => array(
+ 'num' => 'Number of users to generate.',
+ ),
+ 'options' => array(
+ 'kill' => 'Delete all users before generating new ones.',
+ 'roles' => 'A comma delimited list of role IDs which should be granted to the new users. No need to specify authenticated user role.',
+ 'pass' => 'Specify a password to be set for all generated users.',
+ ),
+ 'aliases' => array('genu'),
+ );
+ $items['generate-terms'] = array(
+ 'callback' => 'drush_devel_generate',
+ 'callback arguments' => array(
+ 'plugin_id' => 'term',
+ ),
+ 'description' => 'Create terms in specified vocabulary.',
+ 'arguments' => array(
+ 'machine_name' => 'Vocabulary machine name into which new terms will be inserted.',
+ 'num' => 'Number of terms to insert. Defaults to 10.',
+ ),
+ 'options' => array(
+ 'kill' => 'Delete all terms in specified vocabulary before generating.',
+ 'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 1000',
+ 'pipe' => 'Returns the list of generated terms, one per line.',
+ ),
+ 'aliases' => array('gent'),
+
+ );
+ $items['generate-vocabs'] = array(
+ 'callback' => 'drush_devel_generate',
+ 'callback arguments' => array(
+ 'plugin_id' => 'vocabulary',
+ ),
+ 'description' => 'Create vocabularies.',
+ 'arguments' => array(
+ 'num' => 'Number of vocabularies to create. Defaults to 1.',
+ ),
+ 'options' => array(
+ 'kill' => 'Delete all vocabularies before generating.',
+ 'pipe' => 'Returns the list of generated vocabularies, one per line.',
+ ),
+ 'aliases' => array('genv'),
+ );
+ $items['generate-content'] = array(
+ 'callback' => 'drush_devel_generate',
+ 'callback arguments' => array(
+ 'plugin_id' => 'content',
+ ),
+ 'description' => 'Create content.',
+ 'drupal dependencies' => array('devel_generate'),
+ 'arguments' => array(
+ 'num' => 'Number of nodes to generate.',
+ 'max_comments' => 'Maximum number of comments to generate.',
+ ),
+ 'options' => array(
+ 'kill' => 'Delete all content before generating new content.',
+ 'types' => 'A comma delimited list of content types to create. Defaults to page,article.',
+ 'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 1000',
+ 'skip-fields' => 'A comma delimited list of fields to omit when generating random values',
+ 'languages' => 'A comma-separated list of language codes',
+ ),
+ 'aliases' => array('genc'),
+ );
+ $items['generate-menus'] = array(
+ 'callback' => 'drush_devel_generate',
+ 'callback arguments' => array(
+ 'plugin_id' => 'menu',
+ ),
+ 'description' => 'Create menus and menu items.',
+ 'drupal dependencies' => array('devel_generate'), // Remove these once devel.module is moved down a directory. http://drupal.org/node/925246
+ 'arguments' => array(
+ 'number_menus' => 'Number of menus to generate. Defaults to 2.',
+ 'number_links' => 'Number of links to generate. Defaults to 50.',
+ 'max_depth' => 'Max link depth. Defaults to 3',
+ 'max_width' => 'Max width of first level of links. Defaults to 8.',
+ ),
+ 'options' => array(
+ 'kill' => 'Delete all previously generated menus and links before generating new menus and links.',
+ 'pipe' => 'Returns the list of generated menus, one per line.',
+ ),
+ 'aliases' => array('genm'),
+ );
+ return $items;
+}
+
+/**
+ * Implements drush_hook_COMMAND_validate().
+ */
+function drush_devel_generate_generate_users_validate() {
+
+ //Array of "Callback arguments" and "command line args".
+ $params = func_get_args();
+ _drush_plugin_validate($params);
+}
+
+/**
+ * Implements drush_hook_COMMAND_validate().
+ */
+function drush_devel_generate_generate_terms_validate() {
+
+ //Array of "Callback arguments" and "command line args".
+ $params = func_get_args();
+ _drush_plugin_validate($params);
+}
+
+/**
+ * Implements drush_hook_COMMAND_validate().
+ */
+function drush_devel_generate_generate_vocabs_validate() {
+
+ //Array of "Callback arguments" and "command line args".
+ $params = func_get_args();
+ _drush_plugin_validate($params);
+}
+
+/**
+ * Implements drush_hook_COMMAND_validate().
+ */
+function drush_devel_generate_generate_content_validate() {
+
+ //Array of "Callback arguments" and "command line args".
+ $params = func_get_args();
+ _drush_plugin_validate($params);
+}
+
+/**
+ * Implements drush_hook_COMMAND_validate().
+ */
+function drush_devel_generate_generate_menus_validate() {
+
+ //Array of "Callback arguments" and "command line args".
+ $params = func_get_args();
+ _drush_plugin_validate($params);
+}
+
+/**
+ * Helper function which returns an array with a plugin instance
+ * for a given id and the validated values ready to be used by
+ * the generate() function of the plugin.
+ */
+function _drush_plugin_validate($params) {
+
+ $instance_and_values = &drupal_static('drush_devel_generate_generate_validate');
+ //Getting plugin_id and leaving the command line args
+ $plugin_id = array_shift($params);
+
+ if (!isset($instance_and_values[$plugin_id])) {
+
+ /** @var DevelGeneratePluginManager $manager */
+ $manager = \Drupal::service('plugin.manager.develgenerate');
+
+ /** @var DevelGenerateBaseInterface $instance */
+ $instance = $manager->createInstance($plugin_id, array());
+
+ //Plugin instance suit params in order to fit for generateElements
+ $values = $instance->validateDrushParams($params);
+ $instance_and_values[$plugin_id]['instance'] = $instance;
+ $instance_and_values[$plugin_id]['values'] = $values;
+ }
+
+ return $instance_and_values[$plugin_id];
+}
+
+/**
+ * Command callback. Generate a number of elements.
+ */
+function drush_devel_generate() {
+ $params = func_get_args();
+ $plugin_id = array_shift($params);
+ $instance_and_values = drupal_static('drush_devel_generate_generate_validate');
+
+ /** @var DevelGenerateBaseInterface $instance */
+ $instance = $instance_and_values[$plugin_id]['instance'];
+ $values = $instance_and_values[$plugin_id]['values'];
+ $instance->generate($values);
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Annotation/DevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Annotation/DevelGenerate.php
new file mode 100644
index 000000000..9a0ce2525
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Annotation/DevelGenerate.php
@@ -0,0 +1,86 @@
+settings)) {
+ $this->settings = $this->getDefaultSettings();
+ }
+ return isset($this->settings[$key]) ? $this->settings[$key] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultSettings() {
+ $definition = $this->getPluginDefinition();
+ return $definition['settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSettings() {
+ return $this->settings;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function settingsFormValidate(array $form, FormStateInterface $form_state) {
+ // Validation is optional.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generate(array $values) {
+ $this->generateElements($values);
+ $this->setMessage('Generate process complete.');
+ }
+
+ /**
+ * Business logic relating with each DevelGenerate plugin
+ *
+ * @param array $values
+ * The input values from the settings form.
+ */
+ protected function generateElements(array $values) {
+
+ }
+
+ /**
+ * Populate the fields on a given entity with sample values.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity to be enriched with sample field values.
+ */
+ public static function populateFields(EntityInterface $entity) {
+ /** @var \Drupal\field\FieldConfigInterface[] $instances */
+ $instances = entity_load_multiple_by_properties('field_config', array('entity_type' => $entity->getEntityType()->id(), 'bundle' => $entity->bundle()));
+
+ if ($skips = function_exists('drush_get_option') ? drush_get_option('skip-fields', '') : @$_REQUEST['skip-fields']) {
+ foreach (explode(',', $skips) as $skip) {
+ unset($instances[$skip]);
+ }
+ }
+
+ foreach ($instances as $instance) {
+ $field_storage = $instance->getFieldStorageDefinition();
+ $max = $cardinality = $field_storage->getCardinality();
+ if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
+ // Just an arbitrary number for 'unlimited'
+ $max = rand(1, 3);
+ }
+ $field_name = $field_storage->getName();
+ $entity->$field_name->generateSampleItems($max);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handleDrushParams($args) {
+
+ }
+
+ /**
+ * Set a message for either drush or the web interface.
+ *
+ * @param string $msg
+ * The message to display.
+ * @param string $type
+ * (optional) The message type, as defined by drupal_set_message(). Defaults
+ * to 'status'
+ */
+ protected function setMessage($msg, $type = 'status') {
+ $function = 'drupal_set_message';
+ if (function_exists('drush_log')) {
+ $function = 'drush_log';
+ $msg = strip_tags($msg);
+ }
+ $function($msg, $type);
+ }
+
+ /**
+ * Check if a given param is a number.
+ *
+ * @param mixed $number
+ * The parameter to check.
+ *
+ * @return bool
+ * TRUE if the parameter is a number, FALSE otherwise.
+ */
+ public static function isNumber($number) {
+ if ($number == NULL) return FALSE;
+ if (!is_numeric($number)) return FALSE;
+ return TRUE;
+ }
+
+ /**
+ * Returns the random data generator.
+ *
+ * @return \Drupal\Component\Utility\Random
+ * The random data generator.
+ */
+ protected function getRandom() {
+ if (!$this->random) {
+ $this->random = new Random();
+ }
+ return $this->random;
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGenerateBaseInterface.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGenerateBaseInterface.php
new file mode 100644
index 000000000..c2b814a2b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGenerateBaseInterface.php
@@ -0,0 +1,77 @@
+develGeneratePluginManager = $develGeneratePluginManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static($container->get('plugin.manager.develgenerate'));
+ }
+
+ /*
+ * A permissions callback.
+ *
+ * @see devel_generate.permissions.yml.
+ *
+ * @return array
+ */
+ function permissions() {
+ $devel_generate_plugins = $this->develGeneratePluginManager->getDefinitions();
+ foreach ($devel_generate_plugins as $plugin) {
+
+ $permission = $plugin['permission'];
+ $permissions[$permission] = array(
+ 'title' => t($permission),
+ );
+ }
+
+// $permissions = array(
+// 'administer devel_generate' => array(
+// 'title' => t('Administer devel generate'),
+// ),
+// );
+ return $permissions;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGeneratePluginManager.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGeneratePluginManager.php
new file mode 100644
index 000000000..626e6dce9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/DevelGeneratePluginManager.php
@@ -0,0 +1,31 @@
+alterInfo('devel_generate_info');
+ $this->setCacheBackend($cache_backend, 'devel_generate_plugins');
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Form/DevelGenerateForm.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Form/DevelGenerateForm.php
new file mode 100644
index 000000000..63c708b49
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Form/DevelGenerateForm.php
@@ -0,0 +1,114 @@
+develGenerateManager = $devel_generate_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.develgenerate')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'devel_generate_form_' . $this->getPluginIdFromRequest();
+ }
+
+ /**
+ * Returns the value of the param _plugin_id for the current request.
+ *
+ * @see \Drupal\devel_generate\Routing\DevelGenerateRouteSubscriber
+ */
+ protected function getPluginIdFromRequest() {
+ $request = $this->getRequest();
+ return $request->get('_plugin_id');
+ }
+
+ /**
+ * Returns a DevelGenerate plugin instance for a given plugin id.
+ *
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ *
+ * @return \Drupal\devel_generate\DevelGenerateBaseInterface
+ * A DevelGenerate plugin instance.
+ */
+ public function getPluginInstance($plugin_id) {
+ $instance = $this->develGenerateManager->createInstance($plugin_id, array());
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $plugin_id = $this->getPluginIdFromRequest();
+ $instance = $this->getPluginInstance($plugin_id);
+ $form = $instance->settingsForm($form, $form_state);
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Generate'),
+ '#button_type' => 'primary',
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ $plugin_id = $this->getPluginIdFromRequest();
+ $instance = $this->getPluginInstance($plugin_id);
+ $instance->settingsFormValidate($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ try {
+ $plugin_id = $this->getPluginIdFromRequest();
+ $instance = $this->getPluginInstance($plugin_id);
+ $instance->generate($form_state->getValues());
+ }
+ catch (DevelGenerateException $e) {
+ $this->logger('DevelGenerate', $this->t('Failed to generate elements due to "%error".', array('%error' => $e->getMessage())));
+ drupal_set_message($this->t('Failed to generate elements due to "%error".', array('%error' => $e->getMessage())));
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
new file mode 100644
index 000000000..11d55bff3
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
@@ -0,0 +1,506 @@
+moduleHandler = $module_handler;
+ $this->nodeStorage = $node_storage;
+ $this->nodeTypeStorage = $node_type_storage;
+ $this->commentManager = $comment_manager;
+ $this->languageManager = $language_manager;
+ $this->urlGenerator = $url_generator;
+ $this->dateFormatter = $date_formatter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ $entity_manager = $container->get('entity.manager');
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $entity_manager->getStorage('node'),
+ $entity_manager->getStorage('node_type'),
+ $container->get('module_handler'),
+ $container->has('comment.manager') ? $container->get('comment.manager') : NULL,
+ $container->get('language_manager'),
+ $container->get('url_generator'),
+ $container->get('date.formatter')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $types = $this->nodeTypeStorage->loadMultiple();
+
+ if (empty($types)) {
+ $create_url = $this->urlGenerator->generateFromRoute('node.type_add');
+ $this->setMessage($this->t('You do not have any content types that can be generated. Go create a new content type', array(':create-type' => $create_url)), 'error', FALSE);
+ return;
+ }
+
+ $options = array();
+
+ foreach ($types as $type) {
+ $options[$type->id()] = array(
+ 'type' => array('#markup' => $type->label()),
+ );
+ if ($this->commentManager) {
+ $comment_fields = $this->commentManager->getFields('node');
+ $map = array($this->t('Hidden'), $this->t('Closed'), $this->t('Open'));
+
+ $fields = array();
+ foreach ($comment_fields as $field_name => $info) {
+ // Find all comment fields for the bundle.
+ if (in_array($type->id(), $info['bundles'])) {
+ $instance = FieldConfig::loadByName('node', $type->id(), $field_name);
+ $default_value = $instance->getDefaultValueLiteral();
+ $default_mode = reset($default_value);
+ $fields[] = new FormattableMarkup('@field: @state', array(
+ '@field' => $instance->label(),
+ '@state' => $map[$default_mode['status']],
+ ));
+ }
+ }
+ // @todo Refactor display of comment fields.
+ if (!empty($fields)) {
+ $options[$type->id()]['comments'] = array(
+ 'data' => array(
+ '#theme' => 'item_list',
+ '#items' => $fields,
+ ),
+ );
+ }
+ else {
+ $options[$type->id()]['comments'] = $this->t('No comment fields');
+ }
+ }
+ }
+
+ $header = array(
+ 'type' => $this->t('Content type'),
+ );
+ if ($this->commentManager) {
+ $header['comments'] = array(
+ 'data' => $this->t('Comments'),
+ 'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+ );
+ }
+
+ $form['node_types'] = array(
+ '#type' => 'tableselect',
+ '#header' => $header,
+ '#options' => $options,
+ );
+
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete all content in these content types before generating new content.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+ $form['num'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('How many nodes would you like to generate?'),
+ '#default_value' => $this->getSetting('num'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+
+ $options = array(1 => $this->t('Now'));
+ foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) {
+ $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago');
+ }
+ $form['time_range'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('How far back in time should the nodes be dated?'),
+ '#description' => $this->t('Node creation dates will be distributed randomly from the current time, back to the selected time.'),
+ '#options' => $options,
+ '#default_value' => 604800,
+ );
+
+ $form['max_comments'] = array(
+ '#type' => $this->moduleHandler->moduleExists('comment') ? 'number' : 'value',
+ '#title' => $this->t('Maximum number of comments per node.'),
+ '#description' => $this->t('You must also enable comments for the content types you are generating. Note that some nodes will randomly receive zero comments. Some will receive the max.'),
+ '#default_value' => $this->getSetting('max_comments'),
+ '#min' => 0,
+ '#access' => $this->moduleHandler->moduleExists('comment'),
+ );
+ $form['title_length'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Maximum number of words in titles'),
+ '#default_value' => $this->getSetting('title_length'),
+ '#required' => TRUE,
+ '#min' => 1,
+ '#max' => 255,
+ );
+ $form['add_alias'] = array(
+ '#type' => 'checkbox',
+ '#disabled' => !$this->moduleHandler->moduleExists('path'),
+ '#description' => $this->t('Requires path.module'),
+ '#title' => $this->t('Add an url alias for each node.'),
+ '#default_value' => FALSE,
+ );
+ $form['add_statistics'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Add statistics for each node (node_counter table).'),
+ '#default_value' => TRUE,
+ '#access' => $this->moduleHandler->moduleExists('statistics'),
+ );
+
+ $options = array();
+ // We always need a language.
+ $languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
+ foreach ($languages as $langcode => $language) {
+ $options[$langcode] = $language->getName();
+ }
+
+ $form['add_language'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('Set language on nodes'),
+ '#multiple' => TRUE,
+ '#description' => $this->t('Requires locale.module'),
+ '#options' => $options,
+ '#default_value' => array(
+ $this->languageManager->getDefaultLanguage()->getId(),
+ ),
+ );
+
+ $form['#redirect'] = FALSE;
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function settingsFormValidate(array $form, FormStateInterface $form_state) {
+ if (!array_filter($form_state->getValue('node_types'))) {
+ $form_state->setErrorByName('node_types', $this->t('Please select at least one content type'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function generateElements(array $values) {
+ if ($values['num'] <= 50 && $values['max_comments'] <= 10) {
+ $this->generateContent($values);
+ }
+ else {
+ $this->generateBatchContent($values);
+ }
+ }
+
+ /**
+ * Method responsible for creating content when
+ * the number of elements is less than 50.
+ */
+ private function generateContent($values) {
+ $values['node_types'] = array_filter($values['node_types']);
+ if (!empty($values['kill']) && $values['node_types']) {
+ $this->contentKill($values);
+ }
+
+ if (!empty($values['node_types'])) {
+ // Generate nodes.
+ $this->develGenerateContentPreNode($values);
+ $start = time();
+ for ($i = 1; $i <= $values['num']; $i++) {
+ $this->develGenerateContentAddNode($values);
+ if (function_exists('drush_log') && $i % drush_get_option('feedback', 1000) == 0) {
+ $now = time();
+ drush_log(dt('Completed @feedback nodes (@rate nodes/min)', array('@feedback' => drush_get_option('feedback', 1000), '@rate' => (drush_get_option('feedback', 1000) * 60) / ($now - $start))), 'ok');
+ $start = $now;
+ }
+ }
+ }
+ $this->setMessage($this->formatPlural($values['num'], '1 node created.', 'Finished creating @count nodes'));
+ }
+
+ /**
+ * Method responsible for creating content when
+ * the number of elements is greater than 50.
+ */
+ private function generateBatchContent($values) {
+ // Setup the batch operations and save the variables.
+ $operations[] = array('devel_generate_operation', array($this, 'batchContentPreNode', $values));
+
+ // Add the kill operation.
+ if ($values['kill']) {
+ $operations[] = array('devel_generate_operation', array($this, 'batchContentKill', $values));
+ }
+
+ // Add the operations to create the nodes.
+ for ($num = 0; $num < $values['num']; $num ++) {
+ $operations[] = array('devel_generate_operation', array($this, 'batchContentAddNode', $values));
+ }
+
+ // Start the batch.
+ $batch = array(
+ 'title' => $this->t('Generating Content'),
+ 'operations' => $operations,
+ 'finished' => 'devel_generate_batch_finished',
+ 'file' => drupal_get_path('module', 'devel_generate') . '/devel_generate.batch.inc',
+ );
+ batch_set($batch);
+ }
+
+ public function batchContentPreNode($vars, &$context) {
+ $context['results'] = $vars;
+ $context['results']['num'] = 0;
+ $this->develGenerateContentPreNode($context['results']);
+ }
+
+ public function batchContentAddNode($vars, &$context) {
+ $this->develGenerateContentAddNode($context['results']);
+ $context['results']['num']++;
+ }
+
+ public function batchContentKill($vars, &$context) {
+ $this->contentKill($context['results']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateDrushParams($args) {
+ $add_language = drush_get_option('languages');
+ if (!empty($add_language)) {
+ $add_language = explode(',', str_replace(' ', '', $add_language));
+ // Intersect with the enabled languages to make sure the language args
+ // passed are actually enabled.
+ $values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)));
+ }
+
+ $values['kill'] = drush_get_option('kill');
+ $values['title_length'] = 6;
+ $values['num'] = array_shift($args);
+ $values['max_comments'] = array_shift($args);
+ $all_types = array_keys(node_type_get_names());
+ $default_types = array_intersect(array('page', 'article'), $all_types);
+ $selected_types = _convert_csv_to_array(drush_get_option('types', $default_types));
+
+ // Validates the input format for content types option.
+ if (drush_get_option('types', $default_types) === TRUE) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Wrong syntax or no content type selected. The correct syntax uses "=", eg.: --types=page,article'));
+ }
+
+ if (empty($selected_types)) {
+ return drush_set_error('DEVEL_GENERATE_NO_CONTENT_TYPES', dt('No content types available'));
+ }
+
+ $values['node_types'] = array_combine($selected_types, $selected_types);
+ $node_types = array_filter($values['node_types']);
+
+ if (!empty($values['kill']) && empty($node_types)) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide content type (--types) in which you want to delete the content.'));
+ }
+
+ // Checks for any missing content types before generating nodes.
+ if (array_diff($node_types, $all_types)) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('One or more content types have been entered that don\'t exist on this site'));
+ }
+
+ return $values;
+ }
+
+ /**
+ * Deletes all nodes of given node types.
+ *
+ * @param array $values
+ * The input values from the settings form.
+ */
+ protected function contentKill($values) {
+ $nids = $this->nodeStorage->getQuery()
+ ->condition('type', $values['node_types'], 'IN')
+ ->execute();
+
+ if (!empty($nids)) {
+ $nodes = $this->nodeStorage->loadMultiple($nids);
+ $this->nodeStorage->delete($nodes);
+ $this->setMessage($this->t('Deleted %count nodes.', array('%count' => count($nids))));
+ }
+ }
+
+ /**
+ * Return the same array passed as parameter
+ * but with an array of uids for the key 'users'.
+ */
+ protected function develGenerateContentPreNode(&$results) {
+ // Get user id.
+ $users = $this->getUsers();
+ $users = array_merge($users, array('0'));
+ $results['users'] = $users;
+ }
+
+ /**
+ * Create one node. Used by both batch and non-batch code branches.
+ */
+ protected function develGenerateContentAddNode(&$results) {
+ if (!isset($results['time_range'])) {
+ $results['time_range'] = 0;
+ }
+ $users = $results['users'];
+
+ $node_type = array_rand(array_filter($results['node_types']));
+ $uid = $users[array_rand($users)];
+
+ $node = $this->nodeStorage->create(array(
+ 'nid' => NULL,
+ 'type' => $node_type,
+ 'title' => $this->getRandom()->sentences(mt_rand(1, $results['title_length']), TRUE),
+ 'uid' => $uid,
+ 'revision' => mt_rand(0, 1),
+ 'status' => TRUE,
+ 'promote' => mt_rand(0, 1),
+ 'created' => REQUEST_TIME - mt_rand(0, $results['time_range']),
+ 'langcode' => $this->getLangcode($results),
+ ));
+
+ // A flag to let hook_node_insert() implementations know that this is a
+ // generated node.
+ $node->devel_generate = $results;
+
+ // Populate all fields with sample values.
+ $this->populateFields($node);
+
+ // See devel_generate_node_insert() for actions that happen before and after
+ // this save.
+ $node->save();
+ }
+
+ /**
+ * Determine language based on $results.
+ */
+ protected function getLangcode($results) {
+ if (isset($results['add_language'])) {
+ $langcodes = $results['add_language'];
+ $langcode = $langcodes[array_rand($langcodes)];
+ }
+ else {
+ $langcode = $this->languageManager->getDefaultLanguage()->getId();
+ }
+ return $langcode;
+ }
+
+ /**
+ * Retrive 50 uids from the database.
+ */
+ protected function getUsers() {
+ $users = array();
+ $result = db_query_range("SELECT uid FROM {users}", 0, 50);
+ foreach ($result as $record) {
+ $users[] = $record->uid;
+ }
+ return $users;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php
new file mode 100644
index 000000000..7c961a6c3
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php
@@ -0,0 +1,407 @@
+menuLinkTree = $menu_tree;
+ $this->menuStorage = $menu_storage;
+ $this->menuLinkContentStorage = $menu_link_storage;
+ $this->moduleHandler = $module_handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ $entity_manager = $container->get('entity.manager');
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $container->get('menu.link_tree'),
+ $entity_manager->getStorage('menu'),
+ $entity_manager->getStorage('menu_link_content'),
+ $container->get('module_handler')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $menu_enabled = $this->moduleHandler->moduleExists('menu_ui');
+ if ($menu_enabled) {
+ $menus = array('__new-menu__' => $this->t('Create new menu(s)')) + menu_ui_get_menus();
+ }
+ else {
+ $menus = menu_list_system_menus();
+ }
+ $form['existing_menus'] = array(
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Generate links for these menus'),
+ '#options' => $menus,
+ '#default_value' => array('__new-menu__'),
+ '#required' => TRUE,
+ );
+ if ($menu_enabled) {
+ $form['num_menus'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Number of new menus to create'),
+ '#default_value' => $this->getSetting('num_menus'),
+ '#min' => 0,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="existing_menus[__new-menu__]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ $form['num_links'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Number of links to generate'),
+ '#default_value' => $this->getSetting('num_links'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+ $form['title_length'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Maximum number of characters in menu and menu link names'),
+ '#description' => $this->t('The minimum length is 2.'),
+ '#default_value' => $this->getSetting('title_length'),
+ '#required' => TRUE,
+ '#min' => 2,
+ '#max' => 128,
+ );
+ $form['link_types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Types of links to generate'),
+ '#options' => array(
+ 'node' => $this->t('Nodes'),
+ 'front' => $this->t('Front page'),
+ 'external' => $this->t('External'),
+ ),
+ '#default_value' => array('node', 'front', 'external'),
+ '#required' => TRUE,
+ );
+ $form['max_depth'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('Maximum link depth'),
+ '#options' => range(0, $this->menuLinkTree->maxDepth()),
+ '#default_value' => floor($this->menuLinkTree->maxDepth() / 2),
+ '#required' => TRUE,
+ );
+ unset($form['max_depth']['#options'][0]);
+ $form['max_width'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Maximum menu width'),
+ '#default_value' => $this->getSetting('max_width'),
+ '#description' => $this->t('Limit the width of the generated menu\'s first level of links to a certain number of items.'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete existing custom generated menus and menu links before generating new ones.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generateElements(array $values) {
+ // If the create new menus checkbox is off, set the number of new menus to 0.
+ if (!isset($values['existing_menus']['__new-menu__']) || !$values['existing_menus']['__new-menu__']) {
+ $values['num_menus'] = 0;
+ }
+ else {
+ // Unset the aux menu to avoid attach menu new items.
+ unset($values['existing_menus']['__new-menu__']);
+ }
+
+ // Delete custom menus.
+ if ($values['kill']) {
+ $this->deleteMenus();
+ $this->setMessage($this->t('Deleted existing menus and links.'));
+ }
+
+ // Generate new menus.
+ $new_menus = $this->generateMenus($values['num_menus'], $values['title_length']);
+ if (!empty($new_menus)) {
+ $this->setMessage($this->t('Created the following new menus: @menus', array('@menus' => implode(', ', $new_menus))));
+ }
+
+ // Generate new menu links.
+ $menus = $new_menus;
+ if (isset($values['existing_menus'])) {
+ $menus = $menus + $values['existing_menus'];
+ }
+ $new_links = $this->generateLinks($values['num_links'], $menus, $values['title_length'], $values['link_types'], $values['max_depth'], $values['max_width']);
+ $this->setMessage($this->t('Created @count new menu links.', array('@count' => count($new_links))));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateDrushParams($args) {
+
+ $link_types = array('node', 'front', 'external');
+ $values = array(
+ 'num_menus' => array_shift($args),
+ 'num_links' => array_shift($args),
+ 'kill' => drush_get_option('kill'),
+ 'pipe' => drush_get_option('pipe'),
+ 'link_types' => array_combine($link_types, $link_types),
+ );
+
+ $max_depth = array_shift($args);
+ $max_width = array_shift($args);
+ $values['max_depth'] = $max_depth ? $max_depth : 3;
+ $values['max_width'] = $max_width ? $max_width : 8;
+ $values['title_length'] = $this->getSetting('title_length');
+ $values['existing_menus']['__new-menu__'] = TRUE;
+
+ if ($this->isNumber($values['num_menus']) == FALSE) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of menus'));
+ }
+ if ($this->isNumber($values['num_links']) == FALSE) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of links'));
+ }
+ if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum link depth. Use a value between 1 and 9'));
+ }
+ if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum menu width. Use a positive numeric value.'));
+ }
+
+ return $values;
+ }
+
+ /**
+ * Deletes custom generated menus.
+ */
+ protected function deleteMenus() {
+ if ($this->moduleHandler->moduleExists('menu_ui')) {
+ $menu_ids = array();
+ foreach (menu_ui_get_menus(FALSE) as $menu => $menu_title) {
+ if (strpos($menu, 'devel-') === 0) {
+ $menu_ids[] = $menu;
+ }
+ }
+
+ if ($menu_ids) {
+ $menus = $this->menuStorage->loadMultiple($menu_ids);
+ $this->menuStorage->delete($menus);
+ }
+ }
+
+ // Delete menu links generated by devel.
+ $link_ids = $this->menuLinkContentStorage->getQuery()
+ ->condition('menu_name', 'devel', '<>')
+ ->condition('link__options', '%' . db_like('s:5:"devel";b:1') . '%', 'LIKE')
+ ->execute();
+
+ if ($link_ids) {
+ $links = $this->menuLinkContentStorage->loadMultiple($link_ids);
+ $this->menuLinkContentStorage->delete($links);
+ }
+
+ }
+
+ /**
+ * Generates new menus.
+ *
+ * @param int $num_menus
+ * Number of menus to create.
+ * @param int $title_length
+ * (optional) Maximum length per menu name.
+ *
+ * @return array
+ * Array containing the generated vocabularies id.
+ */
+ protected function generateMenus($num_menus, $title_length = 12) {
+ $menus = array();
+
+ if ($this->moduleHandler->moduleExists('menu_ui')) {
+ for ($i = 1; $i <= $num_menus; $i++) {
+ $name = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
+
+ $menu = $this->menuStorage->create(array(
+ 'label' => $name,
+ 'id' => 'devel-' . Unicode::strtolower($name),
+ 'description' => $this->t('Description of @name', array('@name' => $name)),
+ ));
+
+ $menu->save();
+ $menus[$menu->id()] = $menu->label();
+ }
+ }
+
+ return $menus;
+ }
+
+ /**
+ * Generates menu links in a tree structure.
+ */
+ protected function generateLinks($num_links, $menus, $title_length, $link_types, $max_depth, $max_width) {
+ $links = array();
+ $menus = array_keys(array_filter($menus));
+ $link_types = array_keys(array_filter($link_types));
+
+ $nids = array();
+ for ($i = 1; $i <= $num_links; $i++) {
+ // Pick a random menu.
+ $menu_name = $menus[array_rand($menus)];
+ // Build up our link.
+ $link_title = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
+ $link = $this->menuLinkContentStorage->create(array(
+ 'menu_name' => $menu_name,
+ 'weight' => mt_rand(-50, 50),
+ 'title' => $link_title,
+ 'bundle' => 'menu_link_content',
+ 'description' => $this->t('Description of @title.', array('@title' => $link_title)),
+ ));
+ $link->link->options = array('devel' => TRUE);
+
+ // For the first $max_width items, make first level links.
+ if ($i <= $max_width) {
+ $depth = 0;
+ }
+ else {
+ // Otherwise, get a random parent menu depth.
+ $depth = mt_rand(1, max(1, $max_depth - 1));
+ }
+ // Get a random parent link from the proper depth.
+ do {
+ $parameters = new MenuTreeParameters();
+ $parameters->setMinDepth($depth);
+ $parameters->setMaxDepth($depth);
+ $tree = $this->menuLinkTree->load($menu_name, $parameters);
+
+ if ($tree) {
+ $link->parent = array_rand($tree);
+ }
+ $depth--;
+ } while (!$link->parent && $depth > 0);
+
+ $link_type = array_rand($link_types);
+ switch ($link_types[$link_type]) {
+ case 'node':
+ // Grab a random node ID.
+ $select = db_select('node_field_data', 'n')
+ ->fields('n', array('nid', 'title'))
+ ->condition('n.status', 1)
+ ->range(0, 1)
+ ->orderRandom();
+ // Don't put a node into the menu twice.
+ if (!empty($nids[$menu_name])) {
+ $select->condition('n.nid', $nids[$menu_name], 'NOT IN');
+ }
+ $node = $select->execute()->fetchAssoc();
+ if (isset($node['nid'])) {
+ $nids[$menu_name][] = $node['nid'];
+ $link->link->uri = 'entity:node/' . $node['nid'];
+ $link->title = $node['title'];
+ break;
+ }
+
+ case 'external':
+ $link->link->uri = 'http://www.example.com/';
+ break;
+
+ case 'front':
+ $link->link->uri = 'internal:/';
+ break;
+
+ default:
+ $link->devel_link_type = $link_type;
+ break;
+ }
+
+ $link->save();
+
+ $links[$link->id()] = $link->link_title;
+ }
+
+ return $links;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php
new file mode 100644
index 000000000..f7aa39b80
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php
@@ -0,0 +1,267 @@
+vocabularyStorage = $vocabulary_storage;
+ $this->termStorage = $term_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ $entity_manager = $container->get('entity.manager');
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $entity_manager->getStorage('taxonomy_vocabulary'),
+ $entity_manager->getStorage('taxonomy_term')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $options = array();
+ foreach ($this->vocabularyStorage->loadMultiple() as $vocabulary) {
+ $options[$vocabulary->id()] = $vocabulary->label();
+ }
+ $form['vids'] = array(
+ '#type' => 'select',
+ '#multiple' => TRUE,
+ '#title' => $this->t('Vocabularies'),
+ '#required' => TRUE,
+ '#default_value' => 'tags',
+ '#options' => $options,
+ '#description' => $this->t('Restrict terms to these vocabularies.'),
+ );
+ $form['num'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Number of terms?'),
+ '#default_value' => $this->getSetting('num'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+ $form['title_length'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Maximum number of characters in term names'),
+ '#default_value' => $this->getSetting('title_length'),
+ '#required' => TRUE,
+ '#min' => 2,
+ '#max' => 255,
+ );
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete existing terms in specified vocabularies before generating new terms.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generateElements(array $values) {
+ if ($values['kill']) {
+ $this->deleteVocabularyTerms($values['vids']);
+ $this->setMessage($this->t('Deleted existing terms.'));
+ }
+
+ $vocabs = $this->vocabularyStorage->loadMultiple($values['vids']);
+ $new_terms = $this->generateTerms($values['num'], $vocabs, $values['title_length']);
+ if (!empty($new_terms)) {
+ $this->setMessage($this->t('Created the following new terms: @terms', array('@terms' => implode(', ', $new_terms))));
+ }
+ }
+
+ /**
+ * Deletes all terms of given vocabularies.
+ *
+ * @param array $vids
+ * Array of vocabulary vid.
+ */
+ protected function deleteVocabularyTerms($vids) {
+ $tids = $this->vocabularyStorage->getToplevelTids($vids);
+ $terms = $this->termStorage->loadMultiple($tids);
+ $this->termStorage->delete($terms);
+ }
+
+ /**
+ * Generates taxonomy terms for a list of given vocabularies.
+ *
+ * @param int $records
+ * Number of terms to create in total.
+ * @param \Drupal\taxonomy\TermInterface[] $vocabs
+ * List of vocabularies to populate.
+ * @param int $maxlength
+ * (optional) Maximum length per term.
+ *
+ * @return array
+ * The list of names of the created terms.
+ */
+ protected function generateTerms($records, $vocabs, $maxlength = 12) {
+ $terms = array();
+
+ // Insert new data:
+ $max = db_query('SELECT MAX(tid) FROM {taxonomy_term_data}')->fetchField();
+ $start = time();
+ for ($i = 1; $i <= $records; $i++) {
+ $name = $this->getRandom()->word(mt_rand(2, $maxlength));
+
+ $values = array(
+ 'name' => $name,
+ 'description' => 'description of ' . $name,
+ 'format' => filter_fallback_format(),
+ 'weight' => mt_rand(0, 10),
+ 'langcode' => Language::LANGCODE_NOT_SPECIFIED,
+ );
+
+ switch ($i % 2) {
+ case 1:
+ $vocab = $vocabs[array_rand($vocabs)];
+ $values['vid'] = $vocab->id();
+ $values['parent'] = array(0);
+ break;
+
+ default:
+ while (TRUE) {
+ // Keep trying to find a random parent.
+ $candidate = mt_rand(1, $max);
+ $query = db_select('taxonomy_term_data', 't');
+ $parent = $query
+ ->fields('t', array('tid', 'vid'))
+ ->condition('t.vid', array_keys($vocabs), 'IN')
+ ->condition('t.tid', $candidate, '>=')
+ ->range(0, 1)
+ ->execute()
+ ->fetchAssoc();
+ if ($parent['tid']) {
+ break;
+ }
+ }
+ $values['parent'] = array($parent['tid']);
+ // Slight speedup due to this property being set.
+ $values['vid'] = $parent['vid'];
+ break;
+ }
+
+ $term = $this->termStorage->create($values);
+
+ // Populate all fields with sample values.
+ $this->populateFields($term);
+ $term->save();
+
+ $max++;
+
+ if (function_exists('drush_log')) {
+ $feedback = drush_get_option('feedback', 1000);
+ if ($i % $feedback == 0) {
+ $now = time();
+ drush_log(dt('Completed @feedback terms (@rate terms/min)', array('@feedback' => $feedback, '@rate' => $feedback * 60 / ($now - $start))), 'ok');
+ $start = $now;
+ }
+ }
+
+ // Limit memory usage. Only report first 20 created terms.
+ if ($i < 20) {
+ $terms[] = $term->label();
+ }
+
+ unset($term);
+ }
+
+ return $terms;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateDrushParams($args) {
+ $vocabulary_name = array_shift($args);
+ $number = array_shift($args);
+
+ if ($number === NULL) {
+ $number = 10;
+ }
+
+ if (!$vocabulary_name) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide a vocabulary machine name.'));
+ }
+
+ if (!$this->isNumber($number)) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of terms: @num', array('@num' => $number)));
+ }
+
+ // Try to convert machine name to a vocabulary id.
+ if (!$vocabulary = $this->vocabularyStorage->load($vocabulary_name)) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid vocabulary name: @name', array('@name' => $vocabulary_name)));
+ }
+
+ $values = [
+ 'num' => $number,
+ 'kill' => drush_get_option('kill'),
+ 'title_length' => 12,
+ 'vids' => [$vocabulary->id()],
+ ];
+
+ return $values;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php
new file mode 100644
index 000000000..420ef8ff5
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php
@@ -0,0 +1,191 @@
+userStorage = $entity_storage;
+ $this->dateFormatter = $date_formatter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $container->get('entity.manager')->getStorage('user'),
+ $container->get('date.formatter')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $form['num'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('How many users would you like to generate?'),
+ '#default_value' => $this->getSetting('num'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete all users (except user id 1) before generating new users.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+
+ $options = user_role_names(TRUE);
+ unset($options[DRUPAL_AUTHENTICATED_RID]);
+ $form['roles'] = array(
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Which roles should the users receive?'),
+ '#description' => $this->t('Users always receive the authenticated user role.'),
+ '#options' => $options,
+ );
+
+ $form['pass'] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Password to be set'),
+ '#default_value' => $this->getSetting('pass'),
+ '#size' => 32,
+ '#description' => $this->t('Leave this field empty if you do not need to set a password'),
+ );
+
+ $options = array(1 => $this->t('Now'));
+ foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) {
+ $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago');
+ }
+ $form['time_range'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('How old should user accounts be?'),
+ '#description' => $this->t('User ages will be distributed randomly from the current time, back to the selected time.'),
+ '#options' => $options,
+ '#default_value' => 604800,
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function generateElements(array $values) {
+ $num = $values['num'];
+ $kill = $values['kill'];
+ $pass = $values['pass'];
+ $age = $values['time_range'];
+ $roles = array_filter($values['roles']);
+
+ if ($kill) {
+ $uids = $this->userStorage->getQuery()
+ ->condition('uid', 1, '>')
+ ->execute();
+ $users = $this->userStorage->loadMultiple($uids);
+ $this->userStorage->delete($users);
+
+ $this->setMessage($this->formatPlural(count($uids), '1 user deleted', '@count users deleted.'));
+ }
+
+ if ($num > 0) {
+ $names = array();
+ while (count($names) < $num) {
+ $name = $this->getRandom()->word(mt_rand(6, 12));
+ $names[$name] = '';
+ }
+
+ if (empty($roles)) {
+ $roles = array(DRUPAL_AUTHENTICATED_RID);
+ }
+ foreach ($names as $name => $value) {
+ $account = $this->userStorage->create(array(
+ 'uid' => NULL,
+ 'name' => $name,
+ 'pass' => $pass,
+ 'mail' => $name . '@example.com',
+ 'status' => 1,
+ 'created' => REQUEST_TIME - mt_rand(0, $age),
+ 'roles' => array_values($roles),
+ // A flag to let hook_user_* know that this is a generated user.
+ 'devel_generate' => TRUE,
+ ));
+
+ // Populate all fields with sample values.
+ $this->populateFields($account);
+ $account->save();
+ }
+ }
+ $this->setMessage($this->t('@num_users created.', array('@num_users' => $this->formatPlural($num, '1 user', '@count users'))));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateDrushParams($args) {
+ $values = array(
+ 'num' => array_shift($args),
+ 'roles' => drush_get_option('roles') ? explode(',', drush_get_option('roles')) : array(),
+ 'kill' => drush_get_option('kill'),
+ 'pass' => drush_get_option('pass', NULL),
+ 'time_range' => 0,
+ );
+ return $values;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php
new file mode 100644
index 000000000..a34a2ff4c
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php
@@ -0,0 +1,175 @@
+vocabularyStorage = $entity_storage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration, $plugin_id, $plugin_definition,
+ $container->get('entity.manager')->getStorage('taxonomy_vocabulary')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $form['num'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Number of vocabularies?'),
+ '#default_value' => $this->getSetting('num'),
+ '#required' => TRUE,
+ '#min' => 0,
+ );
+ $form['title_length'] = array(
+ '#type' => 'number',
+ '#title' => $this->t('Maximum number of characters in vocabulary names'),
+ '#default_value' => $this->getSetting('title_length'),
+ '#required' => TRUE,
+ '#min' => 2,
+ '#max' => 255,
+ );
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete existing vocabularies before generating new ones.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generateElements(array $values) {
+ if ($values['kill']) {
+ $this->deleteVocabularies();
+ $this->setMessage($this->t('Deleted existing vocabularies.'));
+ }
+
+ $new_vocs = $this->generateVocabularies($values['num'], $values['title_length']);
+ if (!empty($new_vocs)) {
+ $this->setMessage($this->t('Created the following new vocabularies: @vocs', array('@vocs' => implode(', ', $new_vocs))));
+ }
+ }
+
+ /**
+ * Deletes all vocabularies.
+ */
+ protected function deleteVocabularies() {
+ $vocabularies = $this->vocabularyStorage->loadMultiple();
+ $this->vocabularyStorage->delete($vocabularies);
+ }
+
+ /**
+ * Generates vocabularies.
+ *
+ * @param int $records
+ * Number of vocabularies to create.
+ * @param int $maxlength
+ * (optional) Maximum length for vocabulary name.
+ *
+ * @return array
+ * Array containing the generated vocabularies id.
+ */
+ protected function generateVocabularies($records, $maxlength = 12) {
+ $vocabularies = array();
+
+ // Insert new data:
+ for ($i = 1; $i <= $records; $i++) {
+ $name = $this->getRandom()->word(mt_rand(2, $maxlength));
+
+ $vocabulary = $this->vocabularyStorage->create(array(
+ 'name' => $name,
+ 'vid' => Unicode::strtolower($name),
+ 'langcode' => Language::LANGCODE_NOT_SPECIFIED,
+ 'description' => "Description of $name",
+ 'hierarchy' => 1,
+ 'weight' => mt_rand(0, 10),
+ 'multiple' => 1,
+ 'required' => 0,
+ 'relations' => 1,
+ ));
+
+ // Populate all fields with sample values.
+ $this->populateFields($vocabulary);
+ $vocabulary->save();
+
+ $vocabularies[] = $vocabulary->id();
+ unset($vocabulary);
+ }
+
+ return $vocabularies;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateDrushParams($args) {
+ $values = array(
+ 'num' => array_shift($args),
+ 'kill' => drush_get_option('kill'),
+ 'title_length' => 12,
+ );
+
+ if ($this->isNumber($values['num']) == FALSE) {
+ return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of vocabularies: @num.', array('@num' => $values['num'])));
+ }
+
+ return $values;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Routing/DevelGenerateRoutes.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Routing/DevelGenerateRoutes.php
new file mode 100644
index 000000000..6cf98d93b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Routing/DevelGenerateRoutes.php
@@ -0,0 +1,57 @@
+DevelGenerateManager = $devel_generate_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.develgenerate')
+ );
+ }
+
+ public function routes() {
+ $devel_generate_plugins = $this->DevelGenerateManager->getDefinitions();
+
+ $routes = array();
+ foreach ($devel_generate_plugins as $id => $plugin) {
+ $label = $plugin['label'];
+ $type_url_str = str_replace('_', '-', $plugin['url']);
+ $routes["devel_generate.$id"] = new Route(
+ "admin/config/development/generate/$type_url_str",
+ array(
+ '_form' => '\Drupal\devel_generate\Form\DevelGenerateForm',
+ '_title' => "Generate $label",
+ '_plugin_id' => $id,
+ ),
+ array(
+ '_permission' => $plugin['permission'],
+ )
+ );
+ }
+
+ return $routes;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/src/Tests/DevelGenerateTest.php b/sites/all/modules/contrib/dev/devel/devel_generate/src/Tests/DevelGenerateTest.php
new file mode 100644
index 000000000..4782d6fbb
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/src/Tests/DevelGenerateTest.php
@@ -0,0 +1,175 @@
+profile != 'standard') {
+ $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic Page'));
+ $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+ $this->addDefaultCommentField('node', 'article');
+ }
+
+ // Creating a vocabulary to associate taxonomy terms generated.
+ $this->vocabulary = entity_create('taxonomy_vocabulary', array(
+ 'name' => $this->randomMachineName(),
+ 'description' => $this->randomMachineName(),
+ 'vid' => Unicode::strtolower($this->randomMachineName()),
+ 'langcode' => Language::LANGCODE_NOT_SPECIFIED,
+ 'weight' => mt_rand(0, 10),
+ ));
+ $this->vocabulary->save();
+
+ // Creates a field of an entity reference field storage on article.
+ $field_name = 'taxonomy_' . $this->vocabulary->id();
+
+ $handler_settings = array(
+ 'target_bundles' => array(
+ $this->vocabulary->id() => $this->vocabulary->id(),
+ ),
+ 'auto_create' => TRUE,
+ );
+ $this->createEntityReferenceField('node', 'article', $field_name, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+
+ entity_get_form_display('node', 'article', 'default')
+ ->setComponent($field_name, array(
+ 'type' => 'options_select',
+ ))
+ ->save();
+
+ entity_get_display('node', 'article', 'default')
+ ->setComponent($field_name, array(
+ 'type' => 'entity_reference_label',
+ ))
+ ->save();
+
+ $admin_user = $this->drupalCreateUser(array('administer devel_generate'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Tests generate commands
+ */
+ public function testDevelGenerate() {
+ // Creating users.
+ $edit = array(
+ 'num' => 4,
+ );
+ $this->drupalPostForm('admin/config/development/generate/user', $edit, t('Generate'));
+ $this->assertText(t('4 users created.'));
+ $this->assertText(t('Generate process complete.'));
+
+ // Tests that if no content types are selected an error message is shown.
+ $edit = array(
+ 'num' => 4,
+ 'title_length' => 4,
+ );
+ $this->drupalPostForm('admin/config/development/generate/content', $edit, t('Generate'));
+ $this->assertText(t('Please select at least one content type'));
+
+ // Creating content.
+ // First we create a node in order to test the Delete content checkbox.
+ $this->drupalCreateNode(array('type' => 'article'));
+
+ $edit = array(
+ 'num' => 4,
+ 'kill' => TRUE,
+ 'node_types[article]' => TRUE,
+ 'time_range' => 604800,
+ 'max_comments' => 3,
+ 'title_length' => 4,
+ 'add_alias' => 1,
+ );
+ $this->drupalPostForm('admin/config/development/generate/content', $edit, t('Generate'));
+ $this->assertText(t('Deleted 1 nodes.'));
+ $this->assertText(t('Finished creating 4 nodes'));
+ $this->assertText(t('Generate process complete.'));
+
+ // Tests that nodes have been created in the generation process.
+ $nodes = Node::loadMultiple();
+ $this->assert(count($nodes) == 4, 'Nodes generated successfully.');
+
+ // Tests url alias for the generated nodes.
+ foreach ($nodes as $node) {
+ $alias = 'node-' . $node->id() . '-' . $node->bundle();
+ $this->drupalGet($alias);
+ $this->assertResponse('200');
+ $this->assertText($node->getTitle(), 'Generated url alias for the node works.');
+ }
+
+ // Creating terms.
+ $edit = array(
+ 'vids[]' => $this->vocabulary->id(),
+ 'num' => 5,
+ 'title_length' => 12,
+ );
+ $this->drupalPostForm('admin/config/development/generate/term', $edit, t('Generate'));
+ $this->assertText(t('Created the following new terms: '));
+ $this->assertText(t('Generate process complete.'));
+
+ // Creating vocabularies.
+ $edit = array(
+ 'num' => 5,
+ 'title_length' => 12,
+ 'kill' => TRUE,
+ );
+ $this->drupalPostForm('admin/config/development/generate/vocabs', $edit, t('Generate'));
+ $this->assertText(t('Created the following new vocabularies: '));
+ $this->assertText(t('Generate process complete.'));
+
+ // Creating menus.
+ $edit = array(
+ 'num_menus' => 5,
+ 'num_links' => 7,
+ 'title_length' => 12,
+ 'link_types[node]' => 1,
+ 'link_types[front]' => 1,
+ 'link_types[external]' => 1,
+ 'max_depth' => 4,
+ 'max_width' => 6,
+ 'kill' => 1,
+ );
+ $this->drupalPostForm('admin/config/development/generate/menu', $edit, t('Generate'));
+ $this->assertText(t('Created the following new menus: '));
+ $this->assertText(t('Created 7 new menu links'));
+ $this->assertText(t('Generate process complete.'));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml b/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml
new file mode 100644
index 000000000..43603a8ce
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml
@@ -0,0 +1,14 @@
+name: 'Devel generate Example'
+type: module
+description: 'Create an example of DevelGenerate plugin type for testing purposing.'
+package: Development
+# core: 8.x
+configure: admin/config/development/generate
+tags:
+ - developer
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.module b/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.module
new file mode 100644
index 000000000..b3d9bbc7f
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.module
@@ -0,0 +1 @@
+ 'textfield',
+ '#title' => $this->t('How many examples would you like to generate?'),
+ '#default_value' => $this->getSetting('num'),
+ '#size' => 10,
+ );
+
+ $form['kill'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Delete all examples before generating new examples.'),
+ '#default_value' => $this->getSetting('kill'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function generateElements(array $values) {
+ $num = $values['num'];
+ $kill = $values['kill'];
+
+ if ($kill) {
+ $this->setMessage($this->t('Old examples have been deleted.'));
+ }
+
+ //Creating user in order to demonstrate
+ // how to override default business login generation.
+ $edit = array(
+ 'uid' => NULL,
+ 'name' => 'example_devel_generate',
+ 'pass' => '',
+ 'mail' => 'example_devel_generate@example.com',
+ 'status' => 1,
+ 'created' => REQUEST_TIME,
+ 'roles' => '',
+ 'devel_generate' => TRUE // A flag to let hook_user_* know that this is a generated user.
+ );
+
+ $account = user_load_by_name('example_devel_generate');
+ if (!$account) {
+ $account = entity_create('user', $edit);
+ }
+
+ // Populate all fields with sample values.
+ $this->populateFields($node);
+
+ $account->save();
+
+ $this->setMessage($this->t('@num_examples created.', array('@num_examples' => $this->formatPlural($num, '1 example', '@count examples'))));
+ }
+
+ public function validateDrushParams($args) {
+ $values = array(
+ 'num' => array_shift($args),
+ 'kill' => drush_get_option('kill'),
+ );
+ return $values;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/tests/src/DevelGenerateManagerTest.php b/sites/all/modules/contrib/dev/devel/devel_generate/tests/src/DevelGenerateManagerTest.php
new file mode 100644
index 000000000..28e66e491
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_generate/tests/src/DevelGenerateManagerTest.php
@@ -0,0 +1,93 @@
+ array(
+ 'id' => 'devel_generate_example',
+ 'class' => 'Drupal\devel_generate_example\Plugin\DevelGenerate\ExampleDevelGenerate',
+ 'url' => 'devel_generate_example',
+ ),
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+ // Mock a Discovery object to replace AnnotationClassDiscovery.
+ $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
+ $this->discovery->expects($this->any())
+ ->method('getDefinitions')
+ ->will($this->returnValue($this->definitions));
+
+ }
+
+ /**
+ * Test creating an instance of the DevelGenerateManager.
+ */
+ public function testCreateInstance() {
+ $namespaces = new \ArrayObject(array('Drupal\devel_generate_example' => realpath(dirname(__FILE__) . '/../../../modules/devel_generate_example/lib')));
+ $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+
+ $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+ $manager = new TestDevelGeneratePluginManager($namespaces, $cache_backend, $module_handler);
+ $manager->setDiscovery($this->discovery);
+
+ $example_instance = $manager->createInstance('devel_generate_example');
+ $plugin_def = $example_instance->getPluginDefinition();
+
+ $this->assertInstanceOf('Drupal\devel_generate_example\Plugin\DevelGenerate\ExampleDevelGenerate', $example_instance);
+ $this->assertArrayHasKey('url', $plugin_def);
+ $this->assertTrue($plugin_def['url'] == 'devel_generate_example');
+ }
+
+}
+
+/**
+ * Provides a testing version of DevelGeneratePluginManager with an empty
+ * constructor.
+ */
+class TestDevelGeneratePluginManager extends DevelGeneratePluginManager {
+ /**
+ * Sets the discovery for the manager.
+ *
+ * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
+ * The discovery object.
+ */
+ public function setDiscovery(DiscoveryInterface $discovery) {
+ $this->discovery = $discovery;
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/README.txt b/sites/all/modules/contrib/dev/devel/devel_node_access/README.txt
new file mode 100644
index 000000000..1d9362f37
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/README.txt
@@ -0,0 +1,42 @@
+README
+======
+
+This module contains tools for developers using access control modules
+to restrict access to some nodes. It is intended to help catch some
+common mistakes and provide feedback to confirm that restricted nodes
+are in fact visible only to the intended users.
+
+Provides a summary page which queries the node_access table and
+reports common mistakes such as the presence of Drupal's default entry
+which grants all users read access to all nodes. Also reports the
+presence of nodes not represented in node_access table. This may
+occur when an access control module is installed after nodes have
+already been created.
+
+Provides a block which shows all node_access entries for the nodes
+shown on a given page. This gives developers a quick check to see
+that grants are provided as they should be. This block auto-enables
+to the footer region. You may move it as desired.
+
+If Views module is installed, allows browsing of nodes by realm,
+including those nodes not in the node_access table (NULL realm).
+
+WISHLIST
+========
+
+Things I'd like to see but haven't had time to do:
+
+* Automatically solve common problems. I.e. delete the "all" realm
+ entry, and automatically save all nodes not in the node_access table.
+
+* Nicer feedback indicating whether nodes are visible to the public or
+ not. I.e. use color coding or icons.
+
+* Summary does not differentiate between view grants and other types
+ of grants. I personally use node_access only for view grants so I'm
+ not sure exactly what else it should show.
+
+AUTHOR
+======
+
+Dave Cohen AKA yogadex on drupal.org
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/config/install/devel_node_access.settings.yml b/sites/all/modules/contrib/dev/devel/devel_node_access/config/install/devel_node_access.settings.yml
new file mode 100644
index 000000000..9937f625e
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/config/install/devel_node_access.settings.yml
@@ -0,0 +1,3 @@
+debug_mode: FALSE
+by_user_mode: FALSE
+user_ajax: FALSE
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/config/schema/devel_node_access.schema.yml b/sites/all/modules/contrib/dev/devel/devel_node_access/config/schema/devel_node_access.schema.yml
new file mode 100644
index 000000000..a8f582c7e
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/config/schema/devel_node_access.schema.yml
@@ -0,0 +1,15 @@
+# Schema for the configuration files of the Devel Node Access module.
+
+devel_node_access.settings:
+ type: mapping
+ label: 'Devel Node Access settings'
+ mapping:
+ debug_mode:
+ type: boolean
+ label: 'Debug Mode'
+ by_user_mode:
+ type: boolean
+ label: 'By-User Analysis'
+ user_ajax:
+ type: boolean
+ label: 'User AJAX'
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/css/devel_node_access.module.css b/sites/all/modules/contrib/dev/devel/devel_node_access/css/devel_node_access.module.css
new file mode 100644
index 000000000..23e0b787a
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/css/devel_node_access.module.css
@@ -0,0 +1,5 @@
+
+div.devel-node-access-inline > div {
+ display: inline-block;
+}
+
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.api.php b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.api.php
new file mode 100644
index 000000000..f33eef252
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.api.php
@@ -0,0 +1,81 @@
+realm == 'mymodule_myrealm') {
+ if ($row->grant_view) {
+ /** @var \Drupal\user\RoleInterface $role */
+ $role = \Drupal\user\Entity\Role::load($row->gid);
+ return t('Role %role may view this node.', array('%role' => $role->get('name')));
+ }
+ else {
+ return 'No access.';
+ }
+ }
+ return null;
+}
+
+/**
+ * Acknowledge ownership of 'alien' grant records.
+ *
+ * Some node access modules store grant records directly into the {node_access}
+ * table rather than returning them through hook_node_access_records(). This
+ * practice is not recommended and DNA will flag all such records as 'alien'.
+ *
+ * If this is unavoidable, a module can confess to being the owner of these
+ * grant records, so that DNA can properly attribute them.
+ *
+ * @see hook_node_access_records()
+ *
+ * @ingroup node_access
+ */
+function hook_node_access_acknowledge($grant) {
+ if ($grant['realm'] == 'mymodule_all' && $grant['nid'] == 0) {
+ return TRUE;
+ }
+ return NULL;
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.info.yml b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.info.yml
new file mode 100644
index 000000000..4ceab8257
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.info.yml
@@ -0,0 +1,16 @@
+type: module
+name: 'Devel Node Access'
+description: 'Developer blocks and page illustrating relevant node_access records.'
+package: Development
+# core: 8.x
+dependencies:
+ - devel
+ - menu_ui
+tags:
+ - developer
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.install b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.install
new file mode 100644
index 000000000..ef2508fa4
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.install
@@ -0,0 +1,7 @@
+'
+ + ''
+ + Drupal.t('Error: could not explain access')
+ + ''
+ + ''
+ )
+ .addClass('ajax-processed');
+ // Call this function again.
+ devel_node_access_user_ajax(context);
+ }
+ },
+ 3000
+ );
+
+ }
+ }
+
+ /**
+ * Attach the access by user behavior which initiates ajax.
+ */
+ Drupal.behaviors.develNodeAccessUserAjax = {
+ attach: function(context, settings) {
+ // Start the ajax.
+ devel_node_access_user_ajax(context, settings);
+ }
+ };
+
+})(jQuery);
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.libraries.yml b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.libraries.yml
new file mode 100644
index 000000000..061728a43
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.libraries.yml
@@ -0,0 +1,7 @@
+devel_node_access:
+ version: 0
+ js:
+ devel_node_access.js: {}
+ css:
+ component:
+ css/devel_node_access.module.css: {}
diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.module b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.module
new file mode 100644
index 000000000..dcef22a48
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/devel_node_access/devel_node_access.module
@@ -0,0 +1,269 @@
+' . t('This module helps in site development. Specifically, when an access control module is used to limit access to some or all nodes, this module provides feedback showing the node_access table in the database.') . "\n";
+ $output .= '
' . t('The node_access table is one method Drupal provides to hide content from some users while displaying it to others. By default, Drupal shows all nodes to all users. There are a number of optional modules which may be installed to hide content from specific users based on various criteria.') . "
\n";
+ $output .= '
' . t('If you have not installed any of these modules, you really have no need for the devel_node_access module. This module is intended for use during development, so that developers and admins can confirm that the node_access table is working as expected. You probably do not want this module enabled on a production site.') . "
\n";
+ $output .= '
' . t('To use this module, enable its Devel Node Access block in a wide region. The block shows the entries in the node_access table for any nodes on the current page, and it provides options to display much more information at the cost of some overhead (requires JavaScript).') . "
\n";
+// $output .= '
' . t('This module also provides a summary page which shows general information about your node_access table. If you have installed the Views module, you may browse node_access by realm.',
+// array('@summary_page' => url('devel/node_access/summary'))
+// ) . "
\n";
+ return $output;
+ }
+ return NULL;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function devel_node_access_menu() {
+ $items = array();
+
+ if (!\Drupal::moduleHandler()->moduleExists('devel')) {
+ // We have to create the 'Devel settings' menu item ourselves.
+ $items['admin/config/development/devel'] = array(
+ 'title' => 'Devel settings',
+ 'description' => 'Helper pages and blocks to assist Drupal developers and admins with node_access. The devel blocks can be managed via the block administration page.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('devel_node_access_admin_settings'),
+ 'access arguments' => array('administer site configuration'),
+ );
+ $items['devel/settings'] = $items['admin/config/development/devel'] + array(
+ 'menu_name' => 'devel',
+ );
+ }
+
+ // Create a callback for use by devel_node_access_user_ajax().
+ $items['devel/node_access/by_user/%/%'] = array(
+ 'page callback' => 'devel_node_access_user_ajax',
+ 'page arguments' => array(3, 4),
+ 'access arguments' => array(DNA_ACCESS_VIEW),
+ 'type' => MENU_CALLBACK,
+ );
+
+ // Add this to the custom menu 'devel' created by the devel module.
+ $items['devel/node_access/summary'] = array(
+ 'title' => 'Node_access summary',
+ 'page callback' => 'dna_summary',
+ 'access arguments' => array(DNA_ACCESS_VIEW),
+ 'menu_name' => 'devel',
+ );
+
+ return $items;
+}
+
+/**
+ * Builds the DNA Summary page.
+ */
+function dna_summary() {
+ // Warn user if they have any entries that could grant access to all nodes.
+ $output = '';
+ $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid = 0');
+ $rows = array();
+ foreach ($result as $row) {
+ $rows[] = array($row->realm);
+ }
+ if (!empty($rows)) {
+ $output .= '
' . t('Access Granted to All Nodes (All Users)') . "
\n";
+ $output .= '
' . t('Your node_access table contains entries that may be granting all users access to all nodes. Depending on which access control module(s) you use, you may want to delete these entries. If you are not using an access control module, you should probably leave these entries as is.') . "
\n";
+ $headers = array(t('realm'));
+ $output .= theme('table', array('header' => $headers, 'rows' => $rows));
+ $access_granted_to_all_nodes = TRUE;
+ }
+
+ // How many nodes are not represented in the node_access table?
+ $num = db_query('SELECT COUNT(n.nid) AS num_nodes FROM {node} n LEFT JOIN {node_access} na ON n.nid = na.nid WHERE na.nid IS NULL')->fetchField();
+ if ($num) {
+ $output .= '
' . t('Legacy Nodes') . "
\n";
+ $output .= '
' .
+ t('You have !num nodes in your node table which are not represented in your node_access table. If you have an access control module installed, these nodes may be hidden from all users. This could be caused by publishing nodes before enabling the access control module. If this is the case, manually updating each node should add it to the node_access table and fix the problem.', array('!num' => l($num, 'devel/node_access/view/NULL')))
+ . "
\n";
+ if (!empty($access_granted_to_all_nodes)) {
+ $output .= '
' .
+ t('This issue may be masked by the one above, so look into the former first.')
+ . "
\n";
+ }
+ }
+ else {
+ $output .= '
' . t('All Nodes Represented') . "
\n";
+ $output .= '
' . t('All nodes are represented in the node_access table.') . "
\n";
+ }
+
+
+ // A similar warning to the one above, but slightly more specific.
+ $result = db_query('SELECT DISTINCT realm FROM {node_access} WHERE nid = 0 AND gid <> 0');
+ $rows = array();
+ foreach ($result as $row) {
+ $rows[] = array($row->realm);
+ }
+ if (!empty($rows)) {
+ $output .= '
' . t('Access Granted to All Nodes (Some Users)') . "
\n";
+ $output .= '
' . t('Your node_access table contains entries that may be granting some users access to all nodes. This may be perfectly normal, depending on which access control module(s) you use.') . "
\n";
+ $headers = array(t('realm'));
+ $output .= theme('table', array('header' => $headers, 'rows' => $rows));
+ }
+
+
+ // Find specific nodes which may be visible to all users.
+ $result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid = 0 AND nid > 0 GROUP BY realm');
+ $rows = array();
+ foreach ($result as $row) {
+ $rows[] = array(
+ $row->realm,
+ array(
+ 'data' => $row->node_count,
+ 'align' => 'center',
+ ),
+ );
+ }
+ if (!empty($rows)) {
+ $output .= '
' . t('Access Granted to Some Nodes') . "
\n";
+ $output .= '
' .
+ t('The following realms appear to grant all users access to some specific nodes. This may be perfectly normal, if some of your content is available to the public.')
+ . "
\n";
+ $headers = array(t('realm'), t('public nodes'));
+ $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Public Nodes')));
+ }
+
+
+ // Find specific nodes protected by node_access table.
+ $result = db_query('SELECT DISTINCT realm, COUNT(DISTINCT nid) as node_count FROM {node_access} WHERE gid <> 0 AND nid > 0 GROUP BY realm');
+ $rows = array();
+ foreach ($result as $row) {
+ // No Views yet:
+ //$rows[] = array(l($row->realm, "devel/node_access/view/$row->realm"),
+ $rows[] = array(
+ $row->realm,
+ array(
+ 'data' => $row->node_count,
+ 'align' => 'center',
+ ),
+ );
+ }
+ if (!empty($rows)) {
+ $output .= '
' . t('Summary by Realm') . "
\n";
+ $output .= '
' . t('The following realms grant limited access to some specific nodes.') . "
",
+ '#type' => 'item',
+ 'lead-in' => array('#markup' => t("@username has the following access", $variables) . ' '),
+ 'items' => $account_items,
+ '#suffix' => "\n
\n",
+ );
+ }
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Builds and returns the by-user information.
+ *
+ * @return array|null
+ */
+ public static function buildByUserInfo() {
+ global $user;
+
+ $output = array();
+ return $output;
+
+ // Show which users can access this node.
+ $menu_item = menu_get_item();
+ $map = $menu_item['original_map'];
+ if ($map[0] != 'node' || !isset($map[1]) || !is_numeric($map[1]) || isset($map[2])) {
+ // Ignore anything but node/%.
+ return NULL;
+ }
+
+ if (isset($menu_item['map'][1]) && is_object($node = $menu_item['map'][1])) {
+ // We have the node.
+ }
+ elseif (is_numeric($menu_item['original_map'][1])) {
+ $node = node_load($menu_item['original_map'][1]);
+ }
+ if (isset($node)) {
+ $nid = $node->id();
+ $langcode = $node->langcode->value;
+ $language = language_load($langcode);
+ $node_type = node_type_load($node->bundle());
+ $headers = array(t('username'), ' $language->name, '@Node_type' => $node_type->name)) . '">' . t('create') . '', t('view'), t('update'), t('delete'));
+ $rows = array();
+ // Determine whether to use Ajax or pre-populate the tables.
+ if ($ajax = \Drupal::config('devel_node_access.settings')->get('user_ajax')) {
+ $output['#attached']['library'][] = 'devel_node_access/node_access';
+ }
+ // Find all users. The following operations are very inefficient, so we
+ // limit the number of users returned. It would be better to make a
+ // pager query, or at least make the number of users configurable. If
+ // anyone is up for that please submit a patch.
+ $query = db_select('users', 'u')
+ ->fields('u', array('uid'))
+ ->orderBy('u.access', 'DESC')
+ ->range(0, 9);
+ $uids = $query->execute()->fetchCol();
+ array_unshift($uids, 0);
+ $accounts = user_load_multiple($uids);
+ foreach ($accounts as $account) {
+ $username = theme('username', array('account' => $account));
+ $uid = $account->id();
+ if ($uid == $user->id()) {
+ $username = '' . $username . '';
+ }
+ $rows[] = array(
+ $username,
+ array(
+ 'id' => 'create-' . $nid . '-' . $uid,
+ 'class' => 'dna-permission',
+ 'data' => $ajax ? NULL : theme('dna_permission', array('permission' => self::explainAccess('create', $node, $account, $langcode))),
+ ),
+ array(
+ 'id' => 'view-' . $nid . '-' . $uid,
+ 'class' => 'dna-permission',
+ 'data' => $ajax ? NULL : theme('dna_permission', array('permission' => self::explainAccess('view', $node, $account, $langcode))),
+ ),
+ array(
+ 'id' => 'update-' . $nid . '-' . $uid,
+ 'class' => 'dna-permission',
+ 'data' => $ajax ? NULL : theme('dna_permission', array('permission' => self::explainAccess('update', $node, $account, $langcode))),
+ ),
+ array(
+ 'id' => 'delete-' . $nid . '-' . $uid,
+ 'class' => 'dna-permission',
+ 'data' => $ajax ? NULL : theme('dna_permission', array('permission' => self::explainAccess('delete', $node, $account, $langcode))),
+ ),
+ );
+ }
+ if (count($rows)) {
+ $output['title'] = array(
+ '#prefix' => '
',
+ '#markup' => t('Access permissions by user for the %langname language', array('%langname' => $language->name)),
+ '#postfix' => '
',
+ );
+ $output[] = array(
+ '#theme' => 'table',
+ '#header' => $headers,
+ '#rows' => $rows,
+ '#attributes' => array('style' => 'text-align: left'),
+ );
+ $output[] = array(
+ '#theme' => 'form_element',
+ '#description' => t('(This table lists the most-recently active users. Hover your mouse over each result for more details.)'),
+ );
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * Helper function to mimic \Drupal::moduleHandler()->invokeAll() and include
+ * the name of the responding module(s).
+ *
+ * @param $hook
+ * The name of the hook.
+ *
+ * @return array
+ * An array of results.
+ * In the case of scalar results, the array is keyed by the name of the
+ * modules that returned the result (rather than by numeric index), and
+ * in the case of array results, a '#module' key is added.
+ */
+ private static function simulate_module_invoke_all($hook) {
+ $args = func_get_args();
+ // Remove $hook from the arguments.
+ array_shift($args);
+ $return = array();
+ foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+ $function = $module . '_' . $hook;
+ $result = call_user_func_array($function, $args);
+ if (isset($result)) {
+ if (is_array($result)) {
+ foreach ($result as $key => $value) {
+ // Add name of module that returned the value.
+ $result[$key]['#module'] = $module;
+ }
+ }
+ else {
+ // Build array with result keyed by $module.
+ $result = array($module => $result);
+ }
+ $return = \Drupal\Component\Utility\NestedArray::mergeDeep($return, $result);
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Helper function to mimic hook_node_access_records_alter() and trace what
+ * each module does with it.
+ *
+ * @param array $records
+ * An indexed array of NA records, augmented by the '#module' key,
+ * as created by simulate_module_invoke_all('node_access_records').
+ * This array is updated by the hook_node_access_records_alter()
+ * implementations.
+ * @param $node
+ * The node that the NA records belong to.
+ *
+ * @return array
+ * A tree representation of the NA records in $records including their
+ * history:
+ * $data[$realm][$gid]
+ * ['original'] - NA record before processing
+ * ['current'] - NA record after processing (if still present)
+ * ['changes'][]['op'] - change message (add/change/delete by $module)
+ * ['record'] - NA record after change (unless deleted)
+ */
+ private static function simulate_node_access_records_alter(&$records, $node) {
+ //dpm($records, 'simulate_node_access_records_alter(): records IN');
+ $hook = 'node_access_records_alter';
+
+ // Build the initial tree (and check for duplicates).
+ $data = self::build_node_access_records_data($records, $node, 'hook_node_access_records()');
+
+ // Simulate drupal_alter('node_access_records', $records, $node).
+ foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+ // Call hook_node_access_records_alter() for one module at a time
+ // and analyze.
+ $function = $module . '_' . $hook;
+ $function($records, $node);
+
+ foreach ($records as $i => $record) {
+ if (empty($data[$record['realm']][$record['gid']]['current'])) {
+ // It's an added record.
+ $data[$record['realm']][$record['gid']]['current'] = $record;
+ $data[$record['realm']][$record['gid']]['current']['#module'] = $module;
+ $data[$record['realm']][$record['gid']]['changes'][] = array(
+ 'op' => 'added by ' . $module,
+ 'record' => $record,
+ );
+ $records[$i]['#module'] = $module;
+ }
+ else {
+ // It's an existing record, check for changes.
+ $view = $update = $delete = FALSE;
+ foreach (array('view', 'update', 'delete') as $op) {
+ $$op = $record["grant_$op"] - $data[$record['realm']][$record['gid']]['current']["grant_$op"];
+ }
+ $old_priority = isset($record['priority']) ? $record['priority'] : 0;
+ $new_priority = isset($data[$record['realm']][$record['gid']]['current']['priority']) ? $data[$record['realm']][$record['gid']]['current']['priority'] : 0;
+ if ($view || $update || $delete || $old_priority != $new_priority) {
+ // It was changed.
+ $data[$record['realm']][$record['gid']]['current'] = $record;
+ $data[$record['realm']][$record['gid']]['current']['#module'] = $module;
+ $data[$record['realm']][$record['gid']]['changes'][] = array(
+ 'op' => 'altered by ' . $module,
+ 'record' => $record,
+ );
+ $records[$i]['#module'] = $module;
+ }
+ }
+ $data[$record['realm']][$record['gid']]['found'] = TRUE;
+ }
+
+ // Check for newly introduced duplicates.
+ self::build_node_access_records_data($records, $node, 'hook_node_access_records_alter()');
+
+ // Look for records that have disappeared.
+ foreach ($data as $realm => $data2) {
+ foreach ($data2 as $gid => $data3) {
+ if (empty($data[$realm][$gid]['found']) && isset($data[$realm][$gid]['current'])) {
+ unset($data[$realm][$gid]['current']);
+ $data[$realm][$gid]['changes'][] = array('op' => 'removed by ' . $module);
+ }
+ else {
+ unset($data[$realm][$gid]['found']);
+ }
+ }
+ }
+ }
+ //dpm($data, 'simulate_node_access_records_alter() returns');
+ //dpm($records, 'simulate_node_access_records_alter(): records OUT');
+ return $data;
+ }
+
+ /**
+ * Helper function to build an associative array of node access records and
+ * their history. If there are duplicate records, display an error message.
+ *
+ * @param $records
+ * An indexed array of node access records, augmented by the '#module' key,
+ * as created by simulate_module_invoke_all('node_access_records').
+ * @param $node
+ * The node that the NA records belong to.
+ * @param $function
+ * The name of the hook that produced the records array, in case we need to
+ * display an error message.
+ *
+ * @return array
+ * See _devel_node_access_nar_alter() for the description of the result.
+ */
+ private static function build_node_access_records_data($records, $node, $function) {
+ $data = array();
+ $duplicates = array();
+ foreach ($records as $record) {
+ if (empty($data[$record['realm']][$record['gid']])) {
+ $data[$record['realm']][$record['gid']] = array('original' => $record, 'current' => $record, 'changes' => array());
+ }
+ else {
+ if (empty($duplicates[$record['realm']][$record['gid']])) {
+ $duplicates[$record['realm']][$record['gid']][] = $data[$record['realm']][$record['gid']]['original'];
+ }
+ $duplicates[$record['realm']][$record['gid']][] = $record;
+ }
+ }
+ if (!empty($duplicates)) {
+ // Generate an error message.
+ $msg = t('Devel Node Access has detected duplicate records returned from %function:', array('%function' => $function));
+ $msg .= '
';
+ drupal_set_message($msg, 'error', FALSE);
+ }
+ return $data;
+ }
+
+ /**
+ * Helper function to mimic hook_node_grants_alter() and trace what
+ * each module does with it.
+ *
+ * @param array $grants
+ * An indexed array of grant records, augmented by the '#module' key,
+ * as created by simulate_module_invoke_all('node_grants').
+ * This array is updated by the hook_node_grants_alter()
+ * implementations.
+ * @param $account
+ * The user account requesting access to content.
+ * @param $op
+ * The operation being performed, 'view', 'update' or 'delete'.
+ *
+ * @return array
+ * A tree representation of the grant records in $grants including their
+ * history:
+ * $data[$realm][$gid]
+ * ['cur'] - TRUE or FALSE whether the gid is present or not
+ * ['ori'][] - array of module names that contributed this grant (if any)
+ * ['chg'][] - array of changes, such as
+ * - 'added' if module name is a prefix if the $realm,
+ * - 'added by module' otherwise, or
+ * - 'removed by module'
+ */
+ private static function simulate_node_grants_alter(&$grants, $account, $op) {
+ //dpm($grants, "simulate_node_grants_alter($account->name, $op): grants IN");
+ $hook = 'node_grants_alter';
+
+ // Build the initial structure.
+ $data = array();
+ foreach ($grants as $realm => $gids) {
+ foreach ($gids as $i => $gid) {
+ if ($i !== '#module') {
+ $data[$realm][$gid]['cur'] = TRUE;
+ $data[$realm][$gid]['ori'][] = $gids['#module'];
+ }
+ }
+ unset($grants[$realm]['#module']);
+ }
+
+ // Simulate drupal_alter('node_grants', $grants, $account, $op).
+ foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+ // Call hook_node_grants_alter() for one module at a time and analyze.
+ $function = $module . '_' . $hook;
+ $function($grants, $account, $op);
+
+ // Check for new gids.
+ foreach ($grants as $realm => $gids) {
+ foreach ($gids as $i => $gid) {
+ if (empty($data[$realm][$gid]['cur'])) {
+ $data[$realm][$gid]['cur'] = TRUE;
+ $data[$realm][$gid]['chg'][] = 'added by ' . $module;
+ }
+ }
+ }
+
+ // Check for removed gids.
+ foreach ($data as $realm => $gids) {
+ foreach ($gids as $gid => $history) {
+ if ($history['cur'] && array_search($gid, $grants[$realm]) === FALSE) {
+ $data[$realm][$gid]['cur'] = FALSE;
+ $data[$realm][$gid]['chg'][] = 'removed by ' . $module;
+ }
+ }
+ }
+ }
+
+ //dpm($data, "simulate_node_grants_alter($account->name, $op) returns");
+ //dpm($grants, "simulate_node_grants_alter($account->name, $op): grants OUT");
+ return $data;
+ }
+
+ /**
+ * Helper function to create a list of grants returned by hook_node_grants().
+ */
+ private static function get_grant_list($grants_data) {
+ //dpm($grants_data, "get_grant_list() IN:");
+ $grants_data = array_merge(array('all' => array(0 => array('cur' => TRUE, 'ori' => array('all')))), $grants_data);
+ $items = array();
+ if (count($grants_data)) {
+ foreach ($grants_data as $realm => $gids) {
+ ksort($gids);
+ $gs = array();
+ foreach ($gids as $gid => $history) {
+ if ($history['cur']) {
+ if (isset($history['ori'])) {
+ $g = $gid; // Original grant, still active.
+ }
+ else {
+ $g = '' . $gid . ''; // New grant, still active.
+ }
+ }
+ else {
+ $g = '' . $gid . ''; // Deleted grant.
+ }
+
+ $ghs = array();
+ if (isset($history['ori']) && strpos($realm, $history['ori'][0]) !== 0) {
+ $realm = '(' . $history['ori'][0] . '::) ' . $realm;
+ }
+ if (isset($history['chg'])) {
+ foreach ($history['chg'] as $h) {
+ $ghs[] = $h;
+ }
+ }
+ if (!empty($ghs)) {
+ $g .= ' (' . implode(', ', $ghs) . ')';
+ }
+ $gs[] = $g;
+ }
+ $items[] = $realm . ': ' . implode(', ', $gs);
+ }
+ if (!empty($items)) {
+ return theme('item_list', array('items' => $items, 'type' => 'ul'));
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Helper function to return a sanitized node title.
+ */
+ private static function get_node_title(Node $node) {
+ if (isset($node)) {
+ $nid = $node->id();
+ if ($node_title = $node->getTitle()) {
+ return $node_title;
+ }
+ elseif ($nid) {
+ return $nid;
+ }
+ }
+ return '—';
+ }
+
+ /**
+ * Helper function to apply common formatting to a debug-mode table row.
+ */
+ private static function format_row($row, $may_unpack = TRUE) {
+ if ($may_unpack && isset($row['data'])) {
+ $row['data'] = self::format_row($row['data'], FALSE);
+ $row['class'][] = 'even';
+ return $row;
+ }
+ if (count($row) == 1) {
+ if (is_scalar($row['data'])) {
+ $row['data'] = array('data' => $row['data']);
+ }
+ $row['data']['colspan'] = 9;
+ }
+ else {
+ $row = array_values($row);
+ foreach (array(0, 1, 4) as $j) { // node, prio, gid
+ if (is_scalar($row[$j])) {
+ $row[$j] = array('data' => $row[$j]);
+ }
+ dpm($j);
+ dpm($row);
+// $row[$j]['style'][] = 'text-align: right;';
+ }
+ }
+ return $row;
+ }
+
+
+ /**
+ * Helper function that mimics node.module's node_access() function.
+ *
+ * Unfortunately, this needs to be updated manually whenever node.module
+ * changes!
+ *
+ * @param string $op
+ * Operation to check.
+ * @param NodeInterface $node
+ * Node to check.
+ * @param AccountInterface $account
+ * (optional) The user object for the user whose access is being checked. If
+ * omitted, the current user is used. Defaults to NULL.
+ * @param string $langcode
+ * (optional) The language code for which access is being checked. If
+ * omitted, the default language is used. Defaults to NULL.
+ *
+ * @return array
+ * An array suitable for theming with theme_dna_permission().
+ */
+ public static function explainAccess($op, NodeInterface $node, AccountInterface $account = NULL, $langcode = NULL) {
+ $user = Drupal::currentUser();
+
+ if (!$node) {
+ return array(
+ FALSE,
+ '???',
+ t('No node passed to node_access(); this should never happen!'),
+ );
+ }
+ if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
+ return array(
+ FALSE,
+ t('@NO: invalid $op', array('@NO' => t('NO'))),
+ t("'@op' is an invalid operation!", array('@op' => $op)),
+ );
+ }
+
+ if ($op == 'create' && is_object($node)) {
+ $node = $node->bundle();
+ }
+
+ if (!empty($account)) {
+ // To try to get the most authentic result we impersonate the given user!
+ // This may reveal bugs in other modules, leading to contradictory
+ // results.
+ /* @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
+ $account_switcher = Drupal::service('account_switcher');
+ $account_switcher->switchTo($account);
+ $result = DnaBlock::explainAccess($op, $node, NULL, $langcode);
+ $account_switcher->switchBack();
+ $access_handler = Drupal::entityTypeManager()->getAccessControlHandler('node');
+ $second_opinion = $access_handler->access($node, $op, $account);
+ if ($second_opinion != $result[0]) {
+ $result[1] .= '*';
+ }
+ return $result;
+ }
+
+ if (empty($langcode)) {
+ $langcode = (is_object($node) && $node->id()) ? $node->language()->getId() : '';
+ }
+
+ $variables = array(
+ '@NO' => t('NO'),
+ '@YES' => t('YES'),
+ '@bypass_node_access' => t('bypass node access'),
+ '@access_content' => t('access content'),
+ );
+
+ if (Drupal::currentUser()->hasPermission('bypass node access')) {
+ return array(
+ TRUE,
+ t('@YES: bypass node access', $variables),
+ t("@YES: This user has the '@bypass_node_access' permission and may do everything with nodes.", $variables),
+ );
+ }
+
+ if (!Drupal::currentUser()->hasPermission('access content')) {
+ return array(
+ FALSE,
+ t('@NO: access content', $variables),
+ t("@NO: This user does not have the '@access_content' permission and is denied doing anything with content.", $variables),
+ );
+ }
+
+ foreach (Drupal::moduleHandler()->getImplementations('node_access') as $module) {
+ $function = $module . '_node_access';
+ if (function_exists($function)) {
+ $result = $function($node, $op, $user, $langcode);
+ if ($module == 'node') {
+ $module = 'node (permissions)';
+ }
+ if (isset($result)) {
+ /* TODO
+ if ($result === NODE_ACCESS_DENY) {
+ $denied_by[] = $module;
+ }
+ elseif ($result === NODE_ACCESS_ALLOW) {
+ $allowed_by[] = $module;
+ }
+ */
+ $access[] = $result;
+ }
+ }
+ }
+ $variables += array(
+ '@deniers' => (empty($denied_by) ? NULL : implode(', ', $denied_by)),
+ '@allowers' => (empty($allowed_by) ? NULL : implode(', ', $allowed_by)),
+ );
+ if (!empty($denied_by)) {
+ $variables += array(
+ '%module' => $denied_by[0] . (count($denied_by) > 1 ? '+' : ''),
+ );
+ return [
+ FALSE,
+ t('@NO: by %module', $variables),
+ empty($allowed_by)
+ ? t("@NO: hook_node_access() of the following module(s) denies this: @deniers.", $variables)
+ : t("@NO: hook_node_access() of the following module(s) denies this: @deniers – even though the following module(s) would allow it: @allowers.", $variables),
+ ];
+ }
+ if (!empty($allowed_by)) {
+ $variables += array(
+ '%module' => $allowed_by[0] . (count($allowed_by) > 1 ? '+' : ''),
+ '@view_own_unpublished_content' => t('view own unpublished content'),
+ );
+ return array(
+ TRUE,
+ t('@YES: by %module', $variables),
+ t("@YES: hook_node_access() of the following module(s) allows this: @allowers.", $variables),
+ );
+ }
+
+ // TODO if ($op == 'view' && !$node->get('status', $langcode) && \Drupal::currentUser()->hasPermission('view own unpublished content') && $user->uid == $node->get('uid', $langcode) && $user->uid != 0) {
+ if ($op == 'view' && !$node->isPublished() && Drupal::currentUser()->hasPermission('view own unpublished content') && $user->id() == $node->getRevisionAuthor()->id() && $user->id() != 0) {
+ return array(
+ TRUE,
+ t('@YES: view own unpublished content', $variables),
+ t("@YES: The node is unpublished, but the user has the '@view_own_unpublished_content' permission.", $variables),
+ );
+ }
+
+ if ($op != 'create' && $node->id()) {
+ $access_handler = Drupal::entityTypeManager()->getAccessControlHandler('node');
+ // TODO if (node_access($op, $node, $user, $langcode)) { // delegate this part
+ if ($access_handler->access($node, $op, $user)) {
+ // Delegate this part.
+ $variables['@node_access_table'] = '{node_access}';
+ return array(
+ TRUE,
+ t('@YES: @node_access_table', $variables),
+ t('@YES: Node access allows this based on one or more records in the @node_access_table table (see the other DNA block!).', $variables),
+ );
+ }
+ }
+
+ return array(
+ FALSE,
+ t('@NO: no reason', $variables),
+ t("@NO: None of the checks resulted in allowing this, so it's denied.", $variables)
+ . ($op == 'create' ? ' ' . t('This is most likely due to a withheld permission.') : ''),
+ );
+ }
+
+ /**
+ * Collects the IDs of the visible nodes on the current page.
+ *
+ * @param int|null $nid
+ * A node ID to save.
+ *
+ * @return array
+ * The array of saved node IDs.
+ */
+ public static function visibleNodes($nid = NULL) {
+ static $nids = array();
+ if (isset($nid)) {
+ $nids[$nid] = $nid;
+ }
+ elseif (empty($nids)) {
+ ///** @var NodeInterface $node */
+ //$node = NULL;
+ if ($node = \Drupal::routeMatch()->getParameter('node')) {
+ $nid = $node->id();
+ $nids[$nid] = $nid;
+ }
+ }
+ return $nids;
+ }
+
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/drush/devel.drush.inc b/sites/all/modules/contrib/dev/devel/drush/devel.drush.inc
new file mode 100644
index 000000000..b38cf9aef
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/drush/devel.drush.inc
@@ -0,0 +1,274 @@
+ dt('Uninstall, and Install a list of projects.'),
+ 'drush dependencies' => array('pm'),
+ 'arguments' => array(
+ 'projects' => dt('A space-separated list of project names.'),
+ ),
+ 'allow-additional-options' => array('pm-uninstall', 'pm-enable'),
+ 'required-arguments' => 1,
+ 'aliases' => array('dre'),
+ );
+ $items['fn-hook'] = array(
+ 'description' => 'List implementations of a given hook and explore the source of the selected one.',
+ 'arguments' => array(
+ 'hook' => 'The name of the hook to explore (e.g. "menu" for hook_menu()).'
+ ),
+ 'examples' => array(
+ 'fn-hook cron' => 'List implementations of hook_cron().',
+ ),
+ 'allow-additional-options' => array('fn-view'),
+ 'required-arguments' => 1,
+ 'aliases' => array('fnh', 'hook'),
+ );
+ $items['fn-event'] = array(
+ 'description' => 'List implementations of a given event and explore source of specified one.',
+ 'arguments' => array(
+ 'event' => 'The name of the event to explore. If omitted, a list of events is shown.'
+ ),
+ 'examples' => array(
+ 'fn-event' => 'Pick a Kernel event, then pick an implementation, and then view its source code.',
+ 'fn-event kernel.terminate' => 'Pick a terminate subscribers and view its source code.',
+ ),
+ 'allow-additional-options' => array('fn-view'),
+ 'aliases' => array('fne', 'event'),
+ );
+ $items['fn-view'] = array(
+ 'description' => 'Show the source of specified function or method.',
+ 'arguments' => array(
+ 'function' => 'The name of the function or method to view.',
+ ),
+ 'options' => array(
+ 'pipe' => 'Output just the filename of the function or method',
+ 'format' => 'Specify how the filename should be printed. Available placeholders are @startline, @endline and @file',
+ ),
+ 'examples' => array(
+ 'fn-view drupal_set_breadcrumb' => 'View the source code for function "drupal_set_breadcrumb"',
+ 'vi `drush --pipe fn-view user_access --format=\'+@startline @file\'`' => 'Edit the file that contains the function "user_access"',
+ 'fn-view NodeController::load' => 'View the source code for method load in the class NodeController'
+ ),
+ 'aliases' => array('fnv'),
+ 'required-arguments' => 1,
+ );
+ $items['devel-token'] = array(
+ 'description' => dt('List available tokens'),
+ 'aliases' => array('token'),
+ //@todo support --format option for json, csv, etc.
+ );
+
+ $items['devel-container-services'] = array(
+ 'description' => 'Get a list of available container services.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+ 'core' => array('8+'),
+ 'aliases' => array('dcs'),
+ 'options' => array(
+ 'format' => 'Format to output. Allowed values are: json, export, html.',
+ ),
+ 'arguments' => array(
+ 'prefix' => 'A prefix to filter the service list by.',
+ ),
+ 'examples' => array(
+ 'drush container-services' => 'Gets a list of all available container services',
+ 'drush container-services plugin.manager' => 'Get all services containing "plugin.manager"',
+ ),
+ 'outputformat' => array(
+ 'default' => 'list',
+ 'pipe-format' => 'export',
+ ),
+ );
+
+ $items['devel-generate-uuid'] = array(
+ 'description' => 'Generate a UUID.',
+ 'core' => array('8+'),
+ 'examples' => array(
+ "drush devel-generate-uuid" => "Outputs a Universally Unique IDentifier.",
+ ),
+ 'aliases' => array('uuid'),
+ 'outputformat' => array(
+ 'default' => 'string',
+ ),
+ );
+
+ return $items;
+}
+
+/**
+ * A command callback. This is faster than 3 separate bootstraps.
+ */
+function drush_devel_reinstall() {
+ $projects = func_get_args();
+
+ $args = array_merge(array('pm-uninstall'), $projects);
+ call_user_func_array('drush_invoke', $args);
+
+ $args = array_merge(array('pm-enable'), $projects);
+ call_user_func_array('drush_invoke', $args);
+}
+
+/**
+ * Command handler. Show hook implementations.
+ */
+function drush_devel_fn_hook($hook) {
+ // Get implementations in the .install files as well.
+ include_once './core/includes/install.inc';
+ drupal_load_updates();
+
+ if ($hook_implementations = \Drupal::moduleHandler()->getImplementations($hook)) {
+ if ($choice = drush_choice(array_combine($hook_implementations, $hook_implementations), 'Enter the number of the hook implementation you wish to view.')) {
+ return drush_devel_fn_view($choice . "_$hook");
+ }
+ }
+ else {
+ drush_log(dt('No implementations.'), 'ok');
+ }
+}
+
+/**
+ * Command handler. Show hook implementations.
+ */
+function drush_devel_fn_event($event = NULL) {
+ $dispatcher = Drupal::service('event_dispatcher');
+ if (empty($event)) {
+ $events = array('kernel.controller', 'kernel.exception', 'kernel.request', 'kernel.response', 'kernel.terminate', 'kernel.view');
+ $events = array_combine($events, $events);
+ if (!$event = drush_choice($events, 'Enter the event you wish to explore.')) {
+ return drush_user_abort();
+ }
+ }
+ if ($implementations = $dispatcher->getListeners($event)) {
+ foreach ($implementations as $implementation) {
+ $callable = get_class($implementation[0]) . '::' . $implementation[1];
+ $choices[$callable] = $callable;
+ }
+ if ($choice = drush_choice($choices, 'Enter the number of the implementation you wish to view.')) {
+ return drush_devel_fn_view($choice);
+ }
+ }
+ else {
+ drush_log(dt('No implementations.'), 'ok');
+ }
+}
+/**
+ * Command handler. Show source code of specified function or method.
+ */
+function drush_devel_fn_view($function_name) {
+ // Get implementations in the .install files as well.
+ include_once './core/includes/install.inc';
+ drupal_load_updates();
+
+ if (strpos($function_name, '::') === FALSE) {
+ if (!function_exists($function_name)) {
+ return drush_set_error(dt('Function not found'));
+ }
+ $reflect = new ReflectionFunction($function_name);
+ }
+ else {
+ list($class, $method) = explode('::', $function_name);
+ if (!method_exists($class, $method)) {
+ return drush_set_error(dt('Method not found'));
+ }
+ $reflect = new ReflectionMethod($class, $method);
+ }
+ $func_info = array('@file' => $reflect->getFileName(), '@startline' => $reflect->getStartLine(), '@endline' => $reflect->getEndLine());
+ $format = drush_get_option('format', '@file');
+ drush_print_pipe(dt($format, $func_info));
+ drush_print(dt("// file: @file, lines @startline-@endline", $func_info));
+
+ _drush_devel_print_function($reflect->getFileName(), $reflect->getStartLine(), $reflect->getEndLine());
+}
+
+/**
+ * Command callback. List available tokens.
+ */
+function drush_devel_token() {
+ $rows[] = array(dt('Group'), dt('Token'), dt('Name'));
+ $all = \Drupal::token()->getInfo();
+ foreach ($all['tokens'] as $group => $tokens) {
+ foreach ($tokens as $key => $token) {
+ $rows[] = array($group, $key, $token['name']);
+ }
+ }
+ drush_print_table($rows, TRUE);
+}
+
+/**
+ * Command callback. Outputs a UUID.
+ *
+ * @return string
+ * A freshly generated UUID.
+ */
+function drush_devel_generate_uuid() {
+ $uuid = new Php();
+ return $uuid->generate();
+}
+
+/**
+ * Print the specified function, including any
+ * doxygen-style comments that come before it.
+ */
+function _drush_devel_print_function($file, $start_line, $end_line) {
+ $line_num = 0;
+ $doxygen = NULL;
+ $fp = fopen( $file, 'r' );
+
+ while (!feof($fp) && ($line_num < ($start_line - 1))) {
+ $line = fgets($fp);
+ ++$line_num;
+
+ if (substr($line,0,3) == '/**') {
+ $doxygen = $line;
+ }
+ elseif (isset($doxygen)) {
+ $doxygen .= $line;
+ if ($line_num + 1 == $start_line) {
+ drush_print(rtrim($doxygen));
+ }
+ if (strstr($line, '*/') !== FALSE) {
+ $doxygen = NULL;
+ }
+ }
+ }
+ while (!feof($fp) && ($line_num < $end_line)) {
+ $line = fgets($fp);
+ ++$line_num;
+ drush_print(rtrim($line));
+ }
+}
+
+/**
+ * Command callback to list available container services.
+ */
+function drush_devel_container_services($prefix = NULL) {
+ $container = Drupal::getContainer();
+
+ if (empty($container)) {
+ return drush_set_error(dt('No container was found.'));
+ }
+
+ // Get a list of all available service IDs.
+ $services = $container->getServiceIds();
+
+ // If there is a prefix, try to find matches.
+ if (isset($prefix)) {
+ $services = preg_grep("/$prefix/", $services);
+ }
+
+ if (empty($services)) {
+ return drush_log(dt('No container services found.'), 'ok');
+ }
+
+ sort($services);
+ return $services;
+}
diff --git a/sites/all/modules/contrib/dev/devel/drush/develDrushTest.php b/sites/all/modules/contrib/dev/devel/drush/develDrushTest.php
new file mode 100644
index 000000000..54a5cf936
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/drush/develDrushTest.php
@@ -0,0 +1,38 @@
+setUpDrupal(1, TRUE, '8');
+
+ // Symlink this module into the Site Under test so it can be enabled.
+ $target = dirname(__DIR__);
+ \symlink($target, $this->webroot() . '/modules/devel');
+ $options = array(
+ 'root' => $this->webroot(),
+ 'uri' => key($sites),
+ );
+ $this->drush('pm-enable', array('devel'), $options + array('skip' => NULL, 'yes' => NULL));
+
+ $this->drush('fn-view', array('drush_main'), $options);
+ $output = $this->getOutput();
+ $this->assertContains('@return', $output, 'Output contain @return Doxygen.');
+ $this->assertContains('function drush_main() {', $output, 'Output contains function drush_main() declaration');
+
+ // $this->drush('fn-hook', array('cron'), $options);
+ // $output = $this->getOutputAsList();
+ // $expected = array('dblog', 'file', 'field', 'system', 'update');
+ // $this->assertSame($expected, $output);
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/drush/phpstorm.drush.inc b/sites/all/modules/contrib/dev/devel/drush/phpstorm.drush.inc
new file mode 100644
index 000000000..416bdd63d
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/drush/phpstorm.drush.inc
@@ -0,0 +1,105 @@
+ 'Save the PhpStorm Metadata file to Drupal root.',
+ 'core' => array('8+'),
+ 'aliases' => array('phpm'),
+ 'category' => 'devel',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_drush_help_alter().
+ */
+function phpstorm_drush_help_alter(&$command) {
+ if ($command['command'] == 'cache-rebuild') {
+ $command['options']['storm'] = 'Write a new PHPstorm metadata file to Drupal root.';
+ }
+}
+
+/*
+ * Implements drush_hook_post_COMMAND().
+ */
+function drush_phpstorm_post_cache_rebuild() {
+ if (drush_get_option('storm')) {
+ drush_invoke_process('@self', 'phpstorm-metadata');
+ }
+}
+
+/**
+ * Generate PhpStorm Metadata file.
+ *
+ * @see http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata
+ */
+function drush_phpstorm_metadata() {
+ $container = \Drupal::getContainer();
+
+ $reflectedClass = new ReflectionClass($container);
+
+ $map = array();
+
+ // Map for all services of the container.
+ // @see \Symfony\Component\DependencyInjection\Container::getServiceIds().
+ foreach ($reflectedClass->getMethods() as $method) {
+ if (preg_match('/^get(.+)Service$/', $method->name, $match)) {
+ $id = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($match[1], '_', '.')));
+ $service = \Drupal::service($id);
+ if (is_object($service)) {
+ $map["\\Drupal::service('')"][$id] = '\\' . get_class($service);
+ }
+ }
+ }
+
+ // Entity Manager - getStorage
+ foreach (\Drupal::entityTypeManager()->getDefinitions() as $type => $definition) {
+ $class = Drupal::entityTypeManager()->getStorage($type);
+ $map["\\Drupal::entityManager()->getStorage('')"][$type] = '\\' . get_class($class);
+ $map["\\Drupal::entityTypeManager()->getStorage('')"][$type] = '\\' . get_class($class);
+ }
+
+ $content = _drush_phpstorm_metadata_phpstorm_metadata_template($map);
+ file_put_contents(DRUPAL_ROOT . '/.phpstorm.meta.php', $content);
+}
+
+function _drush_phpstorm_metadata_phpstorm_metadata_template($data) {
+ $file = ' $map) {
+ $file .= "\n";
+ $file .= " {$method} => [\n";
+
+ foreach ($map as $argument => $class) {
+ $file .= " '{$argument}' instanceof {$class},\n";
+ }
+
+ $file .= " ],";
+ $file .= "\n";
+ }
+
+ $file .= '
+ ];
+ }
+ ';
+
+ return $file;
+}
diff --git a/sites/all/modules/contrib/dev/devel/kint/README.txt b/sites/all/modules/contrib/dev/devel/kint/README.txt
new file mode 100644
index 000000000..75b68b921
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/README.txt
@@ -0,0 +1,37 @@
+WHAT IS IT?
+-----------
+Kint for PHP is a tool designed to present your debugging data in the absolutely
+best way possible.
+
+In other words, it's var_dump() and debug_backtrace() on steroids. Easy to use,
+but powerful and customizable. An essential addition to your development
+toolbox.
+
+USAGE
+-----
+This module allows to use these aliases:
+ kint($data1, $data2, $data3, ...);
+ ksm($data1, $data2, $data3, ...)
+ kint_trace();
+
+But to get the most out of Kint, you will want to use directly the Kint class:
+ kint_require();
+ Kint::dump($data);
+
+Learn more about Kint: http://raveren.github.io/kint/
+
+
+The Kint class function dd() will not work as expected, because this alias
+is already defined in devel.module for other purposes.
+
+CONTACTS
+--------
+Module author:
+ Alexander Danilenko
+ danilenko.dn@gmail.com
+ https://drupal.org/user/1072104
+
+Kint author:
+ Rokas Šleinius a.k.a. Raveren
+ raveren@gmail.com
+ https://github.com/raveren
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint.info.yml b/sites/all/modules/contrib/dev/devel/kint/kint.info.yml
new file mode 100644
index 000000000..22c7cbf35
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint.info.yml
@@ -0,0 +1,13 @@
+name: 'Devel Kint'
+type: module
+description: 'Wrapper for Kint debugging tool'
+package: Development
+# core: 8.x
+tags:
+ - developer
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint.module b/sites/all/modules/contrib/dev/devel/kint/kint.module
new file mode 100644
index 000000000..c8bef22e9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint.module
@@ -0,0 +1,65 @@
+hasPermission('access kint')) {
+ $args = func_get_args();
+ if (PHP_SAPI === 'cli') {
+ s($args);
+ }
+ else {
+ \Kint::dump($args);
+ }
+ }
+}
+
+/**
+ * Alias of Kint::trace().
+ *
+ * Prints backtrace in Kint debug tool.
+ */
+function kint_trace() {
+ kint_require();
+ if (\Drupal::currentUser()->hasPermission('access kint')) {
+ call_user_func_array(array('Kint', 'trace'), array());
+ }
+}
+
+/**
+ * Alias of Kint::kintLite().
+ *
+ * Prints with lightweight formatting, using whitespace for formatting instead
+ * of HTML.
+ */
+function kint_lite() {
+ if (\Drupal::currentUser()->hasPermission('access kint')) {
+ $args = func_get_args();
+ call_user_func_array('kintLite', $args);
+ }
+}
+
+/**
+ * Prints passed argument(s) to the 'message' area of the page.
+ */
+function ksm() {
+ kint_require();
+ if (\Drupal::currentUser()->hasPermission('access kint')) {
+ $args = func_get_args();
+ $msg = @Kint::dump($args);
+ drupal_set_message(Markup::create($msg));
+ }
+}
+
+/**
+ * Load the Kint class.
+ */
+function kint_require() {
+ return require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'kint') . '/kint/Kint.class.php';
+}
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint.permissions.yml b/sites/all/modules/contrib/dev/devel/kint/kint.permissions.yml
new file mode 100644
index 000000000..fe28b8593
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint.permissions.yml
@@ -0,0 +1,4 @@
+access kint:
+ description: 'View developer debug output.'
+ title: 'Access kint information'
+ restrict access: TRUE
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint.services.yml b/sites/all/modules/contrib/dev/devel/kint/kint.services.yml
new file mode 100644
index 000000000..5ae75ae68
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint.services.yml
@@ -0,0 +1,5 @@
+services:
+ kint.twig.kint_extension:
+ class: Drupal\kint\Twig\KintExtension
+ tags:
+ - { name: twig.extension }
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/.gitignore b/sites/all/modules/contrib/dev/devel/kint/kint/.gitignore
new file mode 100644
index 000000000..21a628423
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/.gitignore
@@ -0,0 +1,3 @@
+
+/config.php
+/.idea
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/Kint.class.php b/sites/all/modules/contrib/dev/devel/kint/kint/Kint.class.php
new file mode 100644
index 000000000..c15e3f419
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/Kint.class.php
@@ -0,0 +1,836 @@
+= 0 );
+
+require KINT_DIR . 'config.default.php';
+require KINT_DIR . 'inc/kintVariableData.class.php';
+require KINT_DIR . 'inc/kintParser.class.php';
+require KINT_DIR . 'inc/kintObject.class.php';
+require KINT_DIR . 'decorators/rich.php';
+require KINT_DIR . 'decorators/plain.php';
+
+if ( is_readable( KINT_DIR . 'config.php' ) ) {
+ require KINT_DIR . 'config.php';
+}
+
+# init settings
+if ( !empty( $GLOBALS['_kint_settings'] ) ) {
+ Kint::enabled( $GLOBALS['_kint_settings']['enabled'] );
+
+ foreach ( $GLOBALS['_kint_settings'] as $key => $val ) {
+ property_exists( 'Kint', $key ) and Kint::$$key = $val;
+ }
+
+ unset( $GLOBALS['_kint_settings'], $key, $val );
+}
+
+class Kint
+{
+ // these are all public and 1:1 config array keys so you can switch them easily
+ private static $_enabledMode; # stores mode and active statuses
+
+ public static $returnOutput;
+ public static $fileLinkFormat;
+ public static $displayCalledFrom;
+ public static $charEncodings;
+ public static $maxStrLength;
+ public static $appRootDirs;
+ public static $maxLevels;
+ public static $theme;
+ public static $expandedByDefault;
+
+ public static $cliDetection;
+ public static $cliColors;
+
+ const MODE_RICH = 'r';
+ const MODE_WHITESPACE = 'w';
+ const MODE_CLI = 'c';
+ const MODE_PLAIN = 'p';
+
+
+ public static $aliases = array(
+ 'methods' => array(
+ array( 'kint', 'dump' ),
+ array( 'kint', 'trace' ),
+ ),
+ 'functions' => array(
+ 'd',
+ 'dd',
+ 'ddd',
+ 's',
+ 'sd',
+ )
+ );
+
+ private static $_firstRun = true;
+
+ /**
+ * Enables or disables Kint, can globally enforce the rendering mode. If called without parameters, returns the
+ * current mode.
+ *
+ * @param mixed $forceMode
+ * null or void - return current mode
+ * false - disable (no output)
+ * true - enable and detect cli automatically
+ * Kint::MODE_* - enable and force selected mode disregarding detection and function
+ * shorthand (s()/d()), note that you can still override this
+ * with the "~" modifier
+ *
+ * @return mixed previously set value if a new one is passed
+ */
+ public static function enabled( $forceMode = null )
+ {
+ # act both as a setter...
+ if ( isset( $forceMode ) ) {
+ $before = self::$_enabledMode;
+ self::$_enabledMode = $forceMode;
+
+ return $before;
+ }
+
+ # ...and a getter
+ return self::$_enabledMode;
+ }
+
+ /**
+ * Prints a debug backtrace, same as Kint::dump(1)
+ *
+ * @param array $trace [OPTIONAL] you can pass your own trace, otherwise, `debug_backtrace` will be called
+ *
+ * @return mixed
+ */
+ public static function trace( $trace = null )
+ {
+ if ( !self::enabled() ) return '';
+
+ return self::dump( isset( $trace ) ? $trace : debug_backtrace( true ) );
+ }
+
+
+ /**
+ * Dump information about variables, accepts any number of parameters, supports modifiers:
+ *
+ * clean up any output before kint and place the dump at the top of page:
+ * - Kint::dump()
+ * *****
+ * expand all nodes on display:
+ * ! Kint::dump()
+ * *****
+ * dump variables disregarding their depth:
+ * + Kint::dump()
+ * *****
+ * return output instead of displaying it:
+ * @ Kint::dump()
+ * *****
+ * force output as plain text
+ * ~ Kint::dump()
+ *
+ * Modifiers are supported by all dump wrapper functions, including Kint::trace(). Space is optional.
+ *
+ *
+ * You can also use the following shorthand to display debug_backtrace():
+ * Kint::dump( 1 );
+ *
+ * Passing the result from debug_backtrace() to kint::dump() as a single parameter will display it as trace too:
+ * $trace = debug_backtrace( true );
+ * Kint::dump( $trace );
+ * Or simply:
+ * Kint::dump( debug_backtrace() );
+ *
+ *
+ * @param mixed $data
+ *
+ * @return void|string
+ */
+ public static function dump( $data = null )
+ {
+ if ( !self::enabled() ) return '';
+
+ list( $names, $modifiers, $callee, $previousCaller, $miniTrace ) = self::_getCalleeInfo(
+ defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' )
+ ? debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS )
+ : debug_backtrace()
+ );
+ $modeOldValue = self::enabled();
+ $firstRunOldValue = self::$_firstRun;
+
+ # process modifiers: @, +, !, ~ and -
+ if ( strpos( $modifiers, '-' ) !== false ) {
+ self::$_firstRun = true;
+ while ( ob_get_level() ) {
+ ob_end_clean();
+ }
+ }
+ if ( strpos( $modifiers, '!' ) !== false ) {
+ $expandedByDefaultOldValue = self::$expandedByDefault;
+ self::$expandedByDefault = true;
+ }
+ if ( strpos( $modifiers, '+' ) !== false ) {
+ $maxLevelsOldValue = self::$maxLevels;
+ self::$maxLevels = false;
+ }
+ if ( strpos( $modifiers, '@' ) !== false ) {
+ $returnOldValue = self::$returnOutput;
+ self::$returnOutput = true;
+ self::$_firstRun = true;
+ }
+ if ( strpos( $modifiers, '~' ) !== false ) {
+ self::enabled( self::MODE_WHITESPACE );
+ }
+
+ # set mode for current run
+ $mode = self::enabled();
+ if ( $mode === true ) {
+ $mode = PHP_SAPI === 'cli'
+ ? self::MODE_CLI
+ : self::MODE_RICH;
+ }
+ self::enabled( $mode );
+
+ $decorator = self::enabled() === self::MODE_RICH
+ ? 'Kint_Decorators_Rich'
+ : 'Kint_Decorators_Plain';
+
+ $output = '';
+ if ( self::$_firstRun ) {
+ $output .= call_user_func( array( $decorator, 'init' ) );
+ }
+
+
+ $trace = false;
+ if ( $names === array( null ) && func_num_args() === 1 && $data === 1 ) { # Kint::dump(1) shorthand
+ $trace = KINT_PHP53 ? debug_backtrace( true ) : debug_backtrace();
+ } elseif ( func_num_args() === 1 && is_array( $data ) ) {
+ $trace = $data; # test if the single parameter is result of debug_backtrace()
+ }
+ $trace and $trace = self::_parseTrace( $trace );
+
+
+ $output .= call_user_func( array( $decorator, 'wrapStart' ) );
+ if ( $trace ) {
+ $output .= call_user_func( array( $decorator, 'decorateTrace' ), $trace );
+ } else {
+ $data = func_num_args() === 0
+ ? array( "[[no arguments passed]]" )
+ : func_get_args();
+
+ foreach ( $data as $k => $argument ) {
+ kintParser::reset();
+ # when the dump arguments take long to generate output, user might have changed the file and
+ # Kint might not parse the arguments correctly, so check if names are set and while the
+ # displayed names might be wrong, at least don't throw an error
+ $output .= call_user_func(
+ array( $decorator, 'decorate' ),
+ kintParser::factory( $argument, isset( $names[ $k ] ) ? $names[ $k ] : '' )
+ );
+ }
+ }
+
+ $output .= call_user_func( array( $decorator, 'wrapEnd' ), $callee, $miniTrace, $previousCaller );
+
+ self::enabled( $modeOldValue );
+
+ self::$_firstRun = false;
+ if ( strpos( $modifiers, '~' ) !== false ) {
+ self::$_firstRun = $firstRunOldValue;
+ } else {
+ self::enabled( $modeOldValue );
+ }
+ if ( strpos( $modifiers, '!' ) !== false ) {
+ self::$expandedByDefault = $expandedByDefaultOldValue;
+ }
+ if ( strpos( $modifiers, '+' ) !== false ) {
+ self::$maxLevels = $maxLevelsOldValue;
+ }
+ if ( strpos( $modifiers, '@' ) !== false ) {
+ self::$returnOutput = $returnOldValue;
+ self::$_firstRun = $firstRunOldValue;
+ return $output;
+ }
+
+ if ( self::$returnOutput ) return $output;
+
+ echo $output;
+ return '';
+ }
+
+
+ /**
+ * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide
+ * as much of the path as possible.
+ *
+ * @param string $file
+ *
+ * @return string
+ */
+ public static function shortenPath( $file )
+ {
+ $file = str_replace( '\\', '/', $file );
+ $shortenedName = $file;
+ $replaced = false;
+ if ( is_array( self::$appRootDirs ) ) foreach ( self::$appRootDirs as $path => $replaceString ) {
+ if ( empty( $path ) ) continue;
+
+ $path = str_replace( '\\', '/', $path );
+
+ if ( strpos( $file, $path ) === 0 ) {
+ $shortenedName = $replaceString . substr( $file, strlen( $path ) );
+ $replaced = true;
+ break;
+ }
+ }
+
+ # fallback to find common path with Kint dir
+ if ( !$replaced ) {
+ $pathParts = explode( '/', str_replace( '\\', '/', KINT_DIR ) );
+ $fileParts = explode( '/', $file );
+ $i = 0;
+ foreach ( $fileParts as $i => $filePart ) {
+ if ( !isset( $pathParts[ $i ] ) || $pathParts[ $i ] !== $filePart ) break;
+ }
+
+ $shortenedName = ( $i ? '.../' : '' ) . implode( '/', array_slice( $fileParts, $i ) );
+ }
+
+ return $shortenedName;
+ }
+
+ public static function getIdeLink( $file, $line )
+ {
+ return str_replace( array( '%f', '%l' ), array( $file, $line ), self::$fileLinkFormat );
+ }
+
+ /**
+ * trace helper, shows the place in code inline
+ *
+ * @param string $file full path to file
+ * @param int $lineNumber the line to display
+ * @param int $padding surrounding lines to show besides the main one
+ *
+ * @return bool|string
+ */
+ private static function _showSource( $file, $lineNumber, $padding = 7 )
+ {
+ if ( !$file OR !is_readable( $file ) ) {
+ # continuing will cause errors
+ return false;
+ }
+
+ # open the file and set the line position
+ $file = fopen( $file, 'r' );
+ $line = 0;
+
+ # Set the reading range
+ $range = array(
+ 'start' => $lineNumber - $padding,
+ 'end' => $lineNumber + $padding
+ );
+
+ # set the zero-padding amount for line numbers
+ $format = '% ' . strlen( $range['end'] ) . 'd';
+
+ $source = '';
+ while ( ( $row = fgets( $file ) ) !== false ) {
+ # increment the line number
+ if ( ++$line > $range['end'] ) {
+ break;
+ }
+
+ if ( $line >= $range['start'] ) {
+ # make the row safe for output
+ $row = htmlspecialchars( $row, ENT_NOQUOTES, 'UTF-8' );
+
+ # trim whitespace and sanitize the row
+ $row = '' . sprintf( $format, $line ) . ' ' . $row;
+
+ if ( $line === $lineNumber ) {
+ # apply highlighting to this row
+ $row = '
' . $row . '
';
+ } else {
+ $row = '
' . $row . '
';
+ }
+
+ # add to the captured source
+ $source .= $row;
+ }
+ }
+
+ # close the file
+ fclose( $file );
+
+ return $source;
+ }
+
+
+ /**
+ * returns parameter names that the function was passed, as well as any predefined symbols before function
+ * call (modifiers)
+ *
+ * @param array $trace
+ *
+ * @return array( $parameters, $modifier, $callee, $previousCaller )
+ */
+ private static function _getCalleeInfo( $trace )
+ {
+ $previousCaller = array();
+ $miniTrace = array();
+ $prevStep = array();
+
+ # go from back of trace to find first occurrence of call to Kint or its wrappers
+ while ( $step = array_pop( $trace ) ) {
+
+ if ( self::_stepIsInternal( $step ) ) {
+ $previousCaller = $prevStep;
+ break;
+ } elseif ( isset( $step['file'], $step['line'] ) ) {
+ unset( $step['object'], $step['args'] );
+ array_unshift( $miniTrace, $step );
+ }
+
+ $prevStep = $step;
+ }
+ $callee = $step;
+
+ if ( !isset( $callee['file'] ) || !is_readable( $callee['file'] ) ) return false;
+
+
+ # open the file and read it up to the position where the function call expression ended
+ $file = fopen( $callee['file'], 'r' );
+ $line = 0;
+ $source = '';
+ while ( ( $row = fgets( $file ) ) !== false ) {
+ if ( ++$line > $callee['line'] ) break;
+ $source .= $row;
+ }
+ fclose( $file );
+ $source = self::_removeAllButCode( $source );
+
+
+ if ( empty( $callee['class'] ) ) {
+ $codePattern = $callee['function'];
+ } else {
+ if ( $callee['type'] === '::' ) {
+ $codePattern = $callee['class'] . "\x07*" . $callee['type'] . "\x07*" . $callee['function'];;
+ } else /*if ( $callee['type'] === '->' )*/ {
+ $codePattern = ".*\x07*" . $callee['type'] . "\x07*" . $callee['function'];;
+ }
+ }
+
+ // todo if more than one call in one line - not possible to determine variable names
+ // todo does not recognize string concat
+ # get the position of the last call to the function
+ preg_match_all( "
+ [
+ # beginning of statement
+ [\x07{(]
+
+ # search for modifiers (group 1)
+ ([-+!@~]*)?
+
+ # spaces
+ \x07*
+
+ # check if output is assigned to a variable (group 2) todo: does not detect concat
+ (
+ \\$[a-z0-9_]+ # variable
+ \x07*\\.?=\x07* # assignment
+ )?
+
+ # possibly a namespace symbol
+ \\\\?
+
+ # spaces again
+ \x07*
+
+ # main call to Kint
+ {$codePattern}
+
+ # spaces everywhere
+ \x07*
+
+ # find the character where kint's opening bracket resides (group 3)
+ (\\()
+
+ ]ix",
+ $source,
+ $matches,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $modifiers = end( $matches[1] );
+ $assignment = end( $matches[2] );
+ $bracket = end( $matches[3] );
+
+ $modifiers = $modifiers[0];
+ if ( $assignment[1] !== -1 ) {
+ $modifiers .= '@';
+ }
+
+ $paramsString = preg_replace( "[\x07+]", ' ', substr( $source, $bracket[1] + 1 ) );
+ # we now have a string like this:
+ # );
+
+ # remove everything in brackets and quotes, we don't need nested statements nor literal strings which would
+ # only complicate separating individual arguments
+ $c = strlen( $paramsString );
+ $inString = $escaped = $openedBracket = $closingBracket = false;
+ $i = 0;
+ $inBrackets = 0;
+ $openedBrackets = array();
+
+ while ( $i < $c ) {
+ $letter = $paramsString[ $i ];
+
+ if ( !$inString ) {
+ if ( $letter === '\'' || $letter === '"' ) {
+ $inString = $letter;
+ } elseif ( $letter === '(' || $letter === '[' ) {
+ $inBrackets++;
+ $openedBrackets[] = $openedBracket = $letter;
+ $closingBracket = $openedBracket === '(' ? ')' : ']';
+ } elseif ( $inBrackets && $letter === $closingBracket ) {
+ $inBrackets--;
+ array_pop( $openedBrackets );
+ $openedBracket = end( $openedBrackets );
+ $closingBracket = $openedBracket === '(' ? ')' : ']';
+ } elseif ( !$inBrackets && $letter === ')' ) {
+ $paramsString = substr( $paramsString, 0, $i );
+ break;
+ }
+ } elseif ( $letter === $inString && !$escaped ) {
+ $inString = false;
+ }
+
+ # replace whatever was inside quotes or brackets with untypeable characters, we don't
+ # need that info. We'll later replace the whole string with '...'
+ if ( $inBrackets > 0 ) {
+ if ( $inBrackets > 1 || $letter !== $openedBracket ) {
+ $paramsString[ $i ] = "\x07";
+ }
+ }
+ if ( $inString ) {
+ if ( $letter !== $inString || $escaped ) {
+ $paramsString[ $i ] = "\x07";
+ }
+ }
+
+ $escaped = !$escaped && ( $letter === '\\' );
+ $i++;
+ }
+
+ # by now we have an un-nested arguments list, lets make it to an array for processing further
+ $arguments = explode( ',', preg_replace( "[\x07+]", '...', $paramsString ) );
+
+ # test each argument whether it was passed literary or was it an expression or a variable name
+ $parameters = array();
+ $blacklist = array( 'null', 'true', 'false', 'array(...)', 'array()', '"..."', '[...]', 'b"..."', );
+ foreach ( $arguments as $argument ) {
+ $argument = trim( $argument );
+
+ if ( is_numeric( $argument )
+ || in_array( str_replace( "'", '"', strtolower( $argument ) ), $blacklist, true )
+ ) {
+ $parameters[] = null;
+ } else {
+ $parameters[] = $argument;
+ }
+ }
+
+ return array( $parameters, $modifiers, $callee, $previousCaller, $miniTrace );
+ }
+
+ /**
+ * removes comments and zaps whitespace & true, T_INLINE_HTML => true, T_DOC_COMMENT => true
+ );
+ $whiteSpaceTokens = array(
+ T_WHITESPACE => true, T_CLOSE_TAG => true,
+ T_OPEN_TAG => true, T_OPEN_TAG_WITH_ECHO => true,
+ );
+
+ $cleanedSource = '';
+ foreach ( token_get_all( $source ) as $token ) {
+ if ( is_array( $token ) ) {
+ if ( isset( $commentTokens[ $token[0] ] ) ) continue;
+
+ if ( isset( $whiteSpaceTokens[ $token[0] ] ) ) {
+ $token = "\x07";
+ } else {
+ $token = $token[1];
+ }
+ } elseif ( $token === ';' ) {
+ $token = "\x07";
+ }
+
+ $cleanedSource .= $token;
+ }
+ return $cleanedSource;
+ }
+
+ /**
+ * returns whether current trace step belongs to Kint or its wrappers
+ *
+ * @param $step
+ *
+ * @return array
+ */
+ private static function _stepIsInternal( $step )
+ {
+ if ( isset( $step['class'] ) ) {
+ foreach ( self::$aliases['methods'] as $alias ) {
+ if ( $alias[0] === strtolower( $step['class'] ) && $alias[1] === strtolower( $step['function'] ) ) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return in_array( strtolower( $step['function'] ), self::$aliases['functions'], true );
+ }
+ }
+
+ private static function _parseTrace( array $data )
+ {
+ $trace = array();
+ $traceFields = array( 'file', 'line', 'args', 'class' );
+ $fileFound = false; # file element must exist in one of the steps
+
+ # validate whether a trace was indeed passed
+ while ( $step = array_pop( $data ) ) {
+ if ( !is_array( $step ) || !isset( $step['function'] ) ) return false;
+ if ( !$fileFound && isset( $step['file'] ) && file_exists( $step['file'] ) ) {
+ $fileFound = true;
+ }
+
+ $valid = false;
+ foreach ( $traceFields as $element ) {
+ if ( isset( $step[ $element ] ) ) {
+ $valid = true;
+ break;
+ }
+ }
+ if ( !$valid ) return false;
+
+ if ( self::_stepIsInternal( $step ) ) {
+ $step = array(
+ 'file' => $step['file'],
+ 'line' => $step['line'],
+ 'function' => '',
+ );
+ array_unshift( $trace, $step );
+ break;
+ }
+ if ( $step['function'] !== 'spl_autoload_call' ) { # meaningless
+ array_unshift( $trace, $step );
+ }
+ }
+ if ( !$fileFound ) return false;
+
+ $output = array();
+ foreach ( $trace as $step ) {
+ if ( isset( $step['file'] ) ) {
+ $file = $step['file'];
+
+ if ( isset( $step['line'] ) ) {
+ $line = $step['line'];
+ # include the source of this step
+ if ( self::enabled() === self::MODE_RICH ) {
+ $source = self::_showSource( $file, $line );
+ }
+ }
+ }
+
+ $function = $step['function'];
+
+ if ( in_array( $function, array( 'include', 'include_once', 'require', 'require_once' ) ) ) {
+ if ( empty( $step['args'] ) ) {
+ # no arguments
+ $args = array();
+ } else {
+ # sanitize the included file path
+ $args = array( 'file' => self::shortenPath( $step['args'][0] ) );
+ }
+ } elseif ( isset( $step['args'] ) ) {
+ if ( empty( $step['class'] ) && !function_exists( $function ) ) {
+ # introspection on closures or language constructs in a stack trace is impossible before PHP 5.3
+ $params = null;
+ } else {
+ try {
+ if ( isset( $step['class'] ) ) {
+ if ( method_exists( $step['class'], $function ) ) {
+ $reflection = new ReflectionMethod( $step['class'], $function );
+ } else if ( isset( $step['type'] ) && $step['type'] == '::' ) {
+ $reflection = new ReflectionMethod( $step['class'], '__callStatic' );
+ } else {
+ $reflection = new ReflectionMethod( $step['class'], '__call' );
+ }
+ } else {
+ $reflection = new ReflectionFunction( $function );
+ }
+
+ # get the function parameters
+ $params = $reflection->getParameters();
+ } catch ( Exception $e ) { # avoid various PHP version incompatibilities
+ $params = null;
+ }
+ }
+
+ $args = array();
+ foreach ( $step['args'] as $i => $arg ) {
+ if ( isset( $params[ $i ] ) ) {
+ # assign the argument by the parameter name
+ $args[ $params[ $i ]->name ] = $arg;
+ } else {
+ # assign the argument by number
+ $args[ '#' . ( $i + 1 ) ] = $arg;
+ }
+ }
+ }
+
+ if ( isset( $step['class'] ) ) {
+ # Class->method() or Class::method()
+ $function = $step['class'] . $step['type'] . $function;
+ }
+
+ // todo it's possible to parse the object name out from the source!
+ $output[] = array(
+ 'function' => $function,
+ 'args' => isset( $args ) ? $args : null,
+ 'file' => isset( $file ) ? $file : null,
+ 'line' => isset( $line ) ? $line : null,
+ 'source' => isset( $source ) ? $source : null,
+ 'object' => isset( $step['object'] ) ? $step['object'] : null,
+ );
+
+ unset( $function, $args, $file, $line, $source );
+ }
+
+ return $output;
+ }
+}
+
+
+if ( !function_exists( 'd' ) ) {
+ /**
+ * Alias of Kint::dump()
+ *
+ * @return string
+ */
+ function d()
+ {
+ if ( !Kint::enabled() ) return '';
+ $_ = func_get_args();
+ return call_user_func_array( array( 'Kint', 'dump' ), $_ );
+ }
+}
+
+if ( !function_exists( 'dd' ) ) {
+ /**
+ * Alias of Kint::dump()
+ * [!!!] IMPORTANT: execution will halt after call to this function
+ *
+ * @return string
+ * @deprecated
+ */
+ function dd()
+ {
+ if ( !Kint::enabled() ) return '';
+
+ echo "
Kint: dd() is being deprecated, please use ddd() instead
\n";
+ $_ = func_get_args();
+ call_user_func_array( array( 'Kint', 'dump' ), $_ );
+ die;
+ }
+}
+
+if ( !function_exists( 'ddd' ) ) {
+ /**
+ * Alias of Kint::dump()
+ * [!!!] IMPORTANT: execution will halt after call to this function
+ *
+ * @return string
+ */
+ function ddd()
+ {
+ if ( !Kint::enabled() ) return '';
+ $_ = func_get_args();
+ call_user_func_array( array( 'Kint', 'dump' ), $_ );
+ die;
+ }
+}
+
+if ( !function_exists( 's' ) ) {
+ /**
+ * Alias of Kint::dump(), however the output is in plain htmlescaped text and some minor visibility enhancements
+ * added. If run in CLI mode, output is pure whitespace.
+ *
+ * To force rendering mode without autodetecting anything:
+ *
+ * Kint::enabled( Kint::MODE_PLAIN );
+ * Kint::dump( $variable );
+ *
+ * [!!!] IMPORTANT: execution will halt after call to this function
+ *
+ * @return string
+ */
+ function s()
+ {
+ $enabled = Kint::enabled();
+ if ( !$enabled ) return '';
+
+ if ( $enabled === Kint::MODE_WHITESPACE ) { # if already in whitespace, don't elevate to plain
+ $restoreMode = Kint::MODE_WHITESPACE;
+ } else {
+ $restoreMode = Kint::enabled( # remove cli colors in cli mode; remove rich interface in HTML mode
+ PHP_SAPI === 'cli' ? Kint::MODE_WHITESPACE : Kint::MODE_PLAIN
+ );
+ }
+
+ $params = func_get_args();
+ $dump = call_user_func_array( array( 'Kint', 'dump' ), $params );
+ Kint::enabled( $restoreMode );
+ return $dump;
+ }
+}
+
+if ( !function_exists( 'sd' ) ) {
+ /**
+ * @see s()
+ *
+ * [!!!] IMPORTANT: execution will halt after call to this function
+ *
+ * @return string
+ */
+ function sd()
+ {
+ $enabled = Kint::enabled();
+ if ( !$enabled ) return '';
+
+ if ( $enabled !== Kint::MODE_WHITESPACE ) {
+ Kint::enabled(
+ PHP_SAPI === 'cli' ? Kint::MODE_WHITESPACE : Kint::MODE_PLAIN
+ );
+ }
+
+ $params = func_get_args();
+ call_user_func_array( array( 'Kint', 'dump' ), $params );
+ die;
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/LICENCE b/sites/all/modules/contrib/dev/devel/kint/kint/LICENCE
new file mode 100644
index 000000000..936fe1f16
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/LICENCE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Rokas Šleinius (raveren@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/README.md b/sites/all/modules/contrib/dev/devel/kint/kint/README.md
new file mode 100644
index 000000000..74edcba11
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/README.md
@@ -0,0 +1,141 @@
+# Kint - debugging helper for PHP developers
+
+[](https://packagist.org/packages/raveren/kint)
+
+> **New version** v1.0.0 is released with more than two years of active development - changes are too numerous to list, but there's CLI output and literally hundreds of improvements and additions.
+
+
+
+## What am I looking at?
+
+At first glance Kint is just a pretty replacement for **[var_dump()](http://php.net/manual/en/function.var-dump.php)**, **[print_r()](http://php.net/manual/en/function.print-r.php)** and **[debug_backtrace()](http://php.net/manual/en/function.debug-backtrace.php)**.
+
+However, it's much, *much* more than that. Even the excellent `xdebug` var_dump improvements don't come close - you will eventually wonder how you developed without it.
+
+Just to list some of the most useful features:
+
+ * The **variable name and place in code** where Kint was called from is displayed;
+ * You can **disable all Kint output easily and on the fly** - so you can even debug live systems without anyone knowing (even though you know you shouldn't be doing that!:).
+ * **CLI is detected** and formatted for automatically (but everything can be overridden on the fly) - if your setup supports it, the output is colored too:
+ 
+ * **Debug backtraces** are finally fully readable, actually informative and a pleasure to the eye.
+ * Kint has been **in active development for more than six years** and is shipped with [Drupal 8](https://www.drupal.org/) by default as part of its devel suite. You can trust it not being abandoned or getting left behind in features.
+ * Variable content is **displayed in the most informative way** - and you *never, ever* miss anything! Kint guarantees you see every piece of physically available information about everything you are dumping*;
+ * in some cases, the content is truncated where it would otherwise be too large to view anyway - but the user is always made aware of that;
+ * Some variable content types have an alternative display - for example you will be able see `JSON` in its raw form - but also as an associative array:
+ 
+ There are more than ten custom variable type displays inbuilt and more are added periodically.
+
+
+## Installation and Usage
+
+One of the main goals of Kint is to be **zero setup**.
+
+[Download the archive](https://github.com/raveren/kint/releases/download/1.0.2/kint.zip) and simply
+```php
+');`) - so even if you accidentally leave a dump in production, no one will know.
+ * `sd()` and `ddd()` are shorthands for `s();die;` and `d();die;` respectively.
+ * **Important:** The older shorthand `dd()` is deprecated due to compatibility issues and will eventually be removed. Use the analogous `ddd()` instead.
+ * When looking at Kint output, press D on the keyboard and you will be able to traverse the tree with arrows and tab keys - and expand/collapse nodes with space or enter.
+ * Double clicking the `[+]` sign in the output will expand/collapse ALL nodes; triple clicking big blocks of text will select it all.
+ * Clicking the tiny arrows on the right of the output open it in a separate window where you can keep it for comparison.
+ * To catch output from Kint just assign it to a variablebeta
+```php
+$o = Kint::dump($GLOBALS);
+// yes, the assignment is automatically detected, and $o
+// now holds whatever was going to be printed otherwise.
+
+// it also supports modifiers (read on) for the variable:
+~$o = Kint::dump($GLOBALS); // this output will be in whitespace
+```
+ * There are a couple of real-time modifiers you can use:
+ * `~d($var)` this call will output in plain text format.
+ * `+d($var)` will disregard depth level limits and output everything (careful, this can hang your browser on huge objects)
+ * `!d($var)` will show expanded rich output.
+ * `-d($var)` will attempt to `ob_clean` the previous output so if you're dumping something inside a HTML page, you will still see Kint output.
+ You can combine modifiers too: `~+d($var)`
+ * To force a specific dump output type just pass it to the `Kint::enabled()` method. Available options are: `Kint::MODE_RICH` (default), `Kint::MODE_PLAIN`, `Kint::MODE_WHITESPACE` and `Kint::MODE_CLI`:
+```php
+Kint::enabled(Kint::MODE_WHITESPACE);
+$kintOutput = Kint::dump($GLOBALS);
+// now $kintOutput can be written to a text log file and
+// be perfectly readable from there
+```
+ * To change display theme, use `Kint::$theme = '';` where available options are: `'original'` (default), `'solarized'`, `'solarized-dark'` and `'aante-light'`. Here's an (outdated) preview:
+ 
+ * Kint also includes a naïve profiler you may find handy. It's for determining relatively which code blocks take longer than others:
+```php
+Kint::dump( microtime() ); // just pass microtime()
+sleep( 1 );
+Kint::dump( microtime(), 'after sleep(1)' );
+sleep( 2 );
+ddd( microtime(), 'final call, after sleep(2)' );
+```
+ 
+----
+
+[Visit the project page](http://raveren.github.com/kint/) for documentation, configuration, and more advanced usage examples.
+
+### Author
+
+**Rokas Šleinius** (Raveren)
+
+### License
+
+Licensed under the MIT License
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/composer.json b/sites/all/modules/contrib/dev/devel/kint/kint/composer.json
new file mode 100644
index 000000000..78eb1b654
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "raveren/kint",
+ "description": "Kint - debugging helper for PHP developers",
+ "keywords": ["kint", "php", "debug"],
+ "type": "library",
+ "homepage": "https://github.com/raveren/kint",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Rokas Šleinius",
+ "homepage": "https://github.com/raveren"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/raveren/kint/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.1.0"
+ },
+ "autoload": {
+ "files": ["Kint.class.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/config.default.php b/sites/all/modules/contrib/dev/devel/kint/kint/config.default.php
new file mode 100644
index 000000000..72f0a6933
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/config.default.php
@@ -0,0 +1,94 @@
+ '<ROOT>' )
+ *
+ * [!] EXAMPLE (for Kohana framework):
+ *
+ * $_kintSettings['appRootDirs'] = array(
+ * APPPATH => 'APPPATH', // make sure the constants are already defined at the time of including this config file
+ * SYSPATH => 'SYSPATH',
+ * MODPATH => 'MODPATH',
+ * DOCROOT => 'DOCROOT',
+ * );
+ *
+ * [!] EXAMPLE #2 (for a semi-universal approach)
+ *
+ * $_kintSettings['appRootDirs'] = array(
+ * realpath( __DIR__ . '/../../..' ) => 'ROOT', // go up as many levels as needed in the realpath() param
+ * );
+ *
+ * $_kintSettings['fileLinkFormat'] = 'http://localhost:8091/?message=%f:%l';
+ *
+ */
+$_kintSettings['appRootDirs'] = isset( $_SERVER['DOCUMENT_ROOT'] )
+ ? array( $_SERVER['DOCUMENT_ROOT'] => '<ROOT>' )
+ : array();
+
+
+/** @var int max length of string before it is truncated and displayed separately in full. Zero or false to disable */
+$_kintSettings['maxStrLength'] = 80;
+
+/** @var array possible alternative char encodings in order of probability, eg. array('windows-1251') */
+$_kintSettings['charEncodings'] = array(
+ 'UTF-8',
+ 'Windows-1252', # Western; includes iso-8859-1, replace this with windows-1251 if you have Russian code
+ 'euc-jp', # Japanese
+
+ # all other charsets cannot be differentiated by PHP and/or are not supported by mb_* functions,
+ # I need a better means of detecting the codeset, no idea how though :(
+
+ // 'iso-8859-13', # Baltic
+ // 'windows-1251', # Cyrillic
+ // 'windows-1250', # Central European
+ // 'shift_jis', # Japanese
+ // 'iso-2022-jp', # Japanese
+);
+
+
+/** @var int max array/object levels to go deep, if zero no limits are applied */
+$_kintSettings['maxLevels'] = 7;
+
+
+/** @var string name of theme for rich view */
+$_kintSettings['theme'] = 'original';
+
+
+/** @var bool enable detection when Kint is command line. Formats output with whitespace only; does not HTML-escape it */
+$_kintSettings['cliDetection'] = true;
+
+/** @var bool in addition to above setting, enable detection when Kint is run in *UNIX* command line.
+ * Attempts to add coloring, but if seen as plain text, the color information is visible as gibberish
+ */
+$_kintSettings['cliColors'] = true;
+
+
+unset( $_kintSettings );
\ No newline at end of file
diff --git a/sites/all/modules/contrib/dev/devel/kint/kint/decorators/plain.php b/sites/all/modules/contrib/dev/devel/kint/kint/decorators/plain.php
new file mode 100644
index 000000000..22ebd2ae9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/kint/kint/decorators/plain.php
@@ -0,0 +1,335 @@
+ '1', 'dark' => '2',
+ 'italic' => '3', 'underline' => '4',
+ 'blink' => '5', 'reverse' => '7',
+ 'concealed' => '8', 'default' => '39',
+
+ # colors
+ 'black' => '30', 'red' => '31',
+ 'green' => '32', 'yellow' => '33',
+ 'blue' => '34', 'magenta' => '35',
+ 'cyan' => '36', 'light_gray' => '37',
+ 'dark_gray' => '90', 'light_red' => '91',
+ 'light_green' => '92', 'light_yellow' => '93',
+ 'light_blue' => '94', 'light_magenta' => '95',
+ 'light_cyan' => '96', 'white' => '97',
+
+ # backgrounds
+ 'bg_default' => '49', 'bg_black' => '40',
+ 'bg_red' => '41', 'bg_green' => '42',
+ 'bg_yellow' => '43', 'bg_blue' => '44',
+ 'bg_magenta' => '45', 'bg_cyan' => '46',
+ 'bg_light_gray' => '47', 'bg_dark_gray' => '100',
+ 'bg_light_red' => '101', 'bg_light_green' => '102',
+ 'bg_light_yellow' => '103', 'bg_light_blue' => '104',
+ 'bg_light_magenta' => '105', 'bg_light_cyan' => '106',
+ 'bg_white' => '107',
+ );
+ private static $_utfSymbols = array(
+ '┌', '═', '┐',
+ '│',
+ '└', '─', '┘',
+ );
+ private static $_winShellSymbols = array(
+ "\xda", "\xdc", "\xbf",
+ "\xb3",
+ "\xc0", "\xc4", "\xd9",
+ );
+ private static $_htmlSymbols = array(
+ "┌", "▄", "┐",
+ "│",
+ "└", "─", "┘",
+ );
+
+ public static function decorate( kintVariableData $kintVar, $level = 0 )
+ {
+ $output = '';
+ if ( $level === 0 ) {
+ $name = $kintVar->name ? $kintVar->name : 'literal';
+ $kintVar->name = null;
+
+ $output .= self::_title( $name );
+ }
+
+
+ $space = str_repeat( $s = ' ', $level );
+ $output .= $space . self::_drawHeader( $kintVar );
+
+
+ if ( $kintVar->extendedValue !== null ) {
+ $output .= ' ' . ( $kintVar->type === 'array' ? '[' : '(' ) . PHP_EOL;
+
+
+ if ( is_array( $kintVar->extendedValue ) ) {
+ foreach ( $kintVar->extendedValue as $v ) {
+ $output .= self::decorate( $v, $level + 1 );
+ }
+ } elseif ( is_string( $kintVar->extendedValue ) ) {
+ $output .= $space . $s . $kintVar->extendedValue . PHP_EOL; # depth too great or similar
+ } else {
+ $output .= self::decorate( $kintVar->extendedValue, $level + 1 ); //it's kintVariableData
+ }
+ $output .= $space . ( $kintVar->type === 'array' ? ']' : ')' ) . PHP_EOL;
+ } else {
+ $output .= PHP_EOL;
+ }
+
+ return $output;
+ }
+
+ public static function decorateTrace( $traceData )
+ {
+ $output = self::_title( 'TRACE' );
+ $lastStep = count( $traceData );
+ foreach ( $traceData as $stepNo => $step ) {
+ $title = str_pad( ++$stepNo . ': ', 4, ' ' );
+
+ $title .= self::_colorize(
+ ( isset( $step['file'] ) ? self::_buildCalleeString( $step ) : 'PHP internal call' ),
+ 'title'
+ );
+
+ if ( !empty( $step['function'] ) ) {
+ $title .= ' ' . $step['function'];
+ if ( isset( $step['args'] ) ) {
+ $title .= '(';
+ if ( empty( $step['args'] ) ) {
+ $title .= ')';
+ } else {
+ }
+ $title .= PHP_EOL;
+ }
+ }
+
+ $output .= $title;
+
+ if ( !empty( $step['args'] ) ) {
+ $appendDollar = $step['function'] === '{closure}' ? '' : '$';
+
+ $i = 0;
+ foreach ( $step['args'] as $name => $argument ) {
+ $argument = kintParser::factory(
+ $argument,
+ $name ? $appendDollar . $name : '#' . ++$i
+ );
+ $argument->operator = $name ? ' =' : ':';
+ $maxLevels = Kint::$maxLevels;
+ if ( $maxLevels ) {
+ Kint::$maxLevels = $maxLevels + 2;
+ }
+ $output .= self::decorate( $argument, 2 );
+ if ( $maxLevels ) {
+ Kint::$maxLevels = $maxLevels;
+ }
+ }
+ $output .= ' )' . PHP_EOL;
+ }
+
+ if ( !empty( $step['object'] ) ) {
+ $output .= self::_colorize(
+ ' ' . self::_char( '─', 27 ) . ' Callee object ' . self::_char( '─', 34 ),
+ 'title'
+ );
+
+ $maxLevels = Kint::$maxLevels;
+ if ( $maxLevels ) {
+ # in cli the terminal window is filled too quickly to display huge objects
+ Kint::$maxLevels = Kint::enabled() === Kint::MODE_CLI
+ ? 1
+ : $maxLevels + 1;
+ }
+ $output .= self::decorate( kintParser::factory( $step['object'] ), 1 );
+ if ( $maxLevels ) {
+ Kint::$maxLevels = $maxLevels;
+ }
+ }
+
+ if ( $stepNo !== $lastStep ) {
+ $output .= self::_colorize( self::_char( '─', 80 ), 'title' );
+ }
+ }
+
+ return $output;
+ }
+
+
+ private static function _colorize( $text, $type, $nlAfter = true )
+ {
+ $nlAfter = $nlAfter ? PHP_EOL : '';
+
+ switch ( Kint::enabled() ) {
+ case Kint::MODE_PLAIN:
+ if ( !self::$_enableColors ) return $text . $nlAfter;
+
+ switch ( $type ) {
+ case 'value':
+ $text = "{$text}";
+ break;
+ case 'type':
+ $text = "{$text}";
+ break;
+ case 'title':
+ $text = "{$text}";
+ break;
+ }
+
+ return $text . $nlAfter;
+ break;
+ case Kint::MODE_CLI:
+ if ( !self::$_enableColors ) return $text . $nlAfter;
+
+ $optionsMap = array(
+ 'title' => "\x1b[36m", # cyan
+ 'type' => "\x1b[35;1m", # magenta bold
+ 'value' => "\x1b[32m", # green
+ );
+
+ return $optionsMap[ $type ] . $text . "\x1b[0m" . $nlAfter;
+ break;
+ case Kint::MODE_WHITESPACE:
+ default:
+ return $text . $nlAfter;
+ break;
+ }
+ }
+
+
+ private static function _char( $char, $repeat = null )
+ {
+ switch ( Kint::enabled() ) {
+ case Kint::MODE_PLAIN:
+ $char = self::$_htmlSymbols[ array_search( $char, self::$_utfSymbols, true ) ];
+ break;
+ case Kint::MODE_CLI:
+ $inWindowsShell = PHP_SAPI === 'cli' && DIRECTORY_SEPARATOR !== '/';
+ if ( $inWindowsShell ) {
+ $char = self::$_winShellSymbols[ array_search( $char, self::$_utfSymbols, true ) ];
+ }
+ break;
+ case Kint::MODE_WHITESPACE:
+ default:
+ break;
+ }
+
+ return $repeat ? str_repeat( $char, $repeat ) : $char;
+ }
+
+ private static function _title( $text )
+ {
+ $escaped = kintParser::escape( $text );
+ $lengthDifference = strlen( $escaped ) - strlen( $text );
+ return
+ self::_colorize(
+ self::_char( '┌' ) . self::_char( '─', 78 ) . self::_char( '┐' ) . PHP_EOL
+ . self::_char( '│' ),
+ 'title',
+ false
+ )
+
+ . self::_colorize( str_pad( $escaped, 78 + $lengthDifference, ' ', STR_PAD_BOTH ), 'title', false )
+
+ . self::_colorize( self::_char( '│' ) . PHP_EOL
+ . self::_char( '└' ) . self::_char( '─', 78 ) . self::_char( '┘' ),
+ 'title'
+ );
+ }
+
+ public static function wrapStart()
+ {
+ if ( Kint::enabled() === Kint::MODE_PLAIN ) {
+ return '
';
+
+ return $output;
+ }
+
+
+ /**
+ * called for each dump, opens the html tag
+ *
+ * @param array $callee caller information taken from debug backtrace
+ *
+ * @return string
+ */
+ public static function wrapStart()
+ {
+ return "
";
+ }
+
+
+ /**
+ * closes Kint::_wrapStart() started html tags and displays callee information
+ *
+ * @param array $callee caller information taken from debug backtrace
+ * @param array $miniTrace full path to kint call
+ * @param array $prevCaller previous caller information taken from debug backtrace
+ *
+ * @return string
+ */
+ public static function wrapEnd( $callee, $miniTrace, $prevCaller )
+ {
+ if ( !Kint::$displayCalledFrom ) {
+ return '
';
+ }
+
+ # we iterate the known full set of keys from all rows in case some appeared at later rows,
+ # as we only check the first two to assume
+ foreach ( $arrayKeys as $key ) {
+ if ( $firstRow ) {
+ $extendedValue .= '
';
+ return $this->setSafeMarkup($dump);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exportAsRenderable($input, $name = NULL) {
+ $output['container'] = [
+ '#type' => 'details',
+ '#title' => $name ? : $this->t('Variable'),
+ '#attached' => [
+ 'library' => ['devel/devel']
+ ],
+ '#attributes' => [
+ 'class' => ['container-inline', 'devel-dumper', 'devel-selectable'],
+ ],
+ 'export' => [
+ '#markup' => $this->export($input),
+ ],
+ ];
+
+ return $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function checkRequirements() {
+ return TRUE;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/FirePhp.php b/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/FirePhp.php
new file mode 100644
index 000000000..9140d00e6
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/FirePhp.php
@@ -0,0 +1,41 @@
+FirePhp debugging tool.")
+ * )
+ */
+class FirePhp extends DevelDumperBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump($input, $name = NULL) {
+ $fb = new \FB();
+ $fb->dump($name, $input);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function export($input, $name = NULL) {
+ $this->dump($input);
+ return $this->t('Dump was redirected to the console.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function checkRequirements() {
+ return class_exists('FirePHP', TRUE);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/VarDumper.php b/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/VarDumper.php
new file mode 100644
index 000000000..e8485e883
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Plugin/Devel/Dumper/VarDumper.php
@@ -0,0 +1,50 @@
+Symfony var-dumper debugging tool."),
+ * )
+ *
+ */
+class VarDumper extends DevelDumperBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump($input, $name = NULL) {
+ echo (string) $this->export($input, $name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function export($input, $name = NULL) {
+ $cloner = new VarCloner();
+ $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
+
+ $output = fopen('php://memory', 'r+b');
+ $dumper->dump($cloner->cloneVar($input), $output);
+ $output = stream_get_contents($output, -1, 0);
+
+ return $this->setSafeMarkup($output);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function checkRequirements() {
+ return class_exists('Symfony\Component\VarDumper\Cloner\VarCloner', TRUE);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Plugin/Mail/DevelMailLog.php b/sites/all/modules/contrib/dev/devel/src/Plugin/Mail/DevelMailLog.php
new file mode 100644
index 000000000..d9d2dc971
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Plugin/Mail/DevelMailLog.php
@@ -0,0 +1,98 @@
+ $value) {
+ $mimeheaders[] = $name . ': ' . Unicode::mimeHeaderEncode($value);
+ }
+
+ $line_endings = Settings::get('mail_line_endings', PHP_EOL);
+ $output = join($line_endings, $mimeheaders) . $line_endings;
+ // 'Subject:' is a mail header and should not be translated.
+ $output .= 'Subject: ' . $message['subject'] . $line_endings;
+ // Blank line to separate headers from body.
+ $output .= $line_endings;
+ $output .= preg_replace('@\r?\n@', $line_endings, $message['body']);
+ return $output;
+ }
+
+ public function getFileName($message) {
+ $output_directory = $this->getOutputDirectory();
+ $this->makeOutputDirectory($output_directory);
+ $output_file_format = \Drupal::config('devel.settings')->get('debug_mail_file_format');
+
+ $tokens = array(
+ '%to' => $message['to'],
+ '%subject' => $message['subject'],
+ '%datetime' => date('y-m-d_his'),
+ );
+ return $output_directory . '/' . $this->dirify(str_replace(array_keys($tokens), array_values($tokens), $output_file_format));
+ }
+
+ private function dirify($string) {
+ return preg_replace('/[^a-zA-Z0-9_\-\.@]/', '_', $string);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mail(array $message) {
+ $output = $this->composeMessage($message);
+ $output_file = $this->getFileName($message);
+
+ return file_put_contents($output_file, $output);
+ }
+
+ protected function makeOutputDirectory($output_directory) {
+ if (!file_prepare_directory($output_directory, FILE_CREATE_DIRECTORY)) {
+ throw new Exception("Unable to continue sending mail, $output_directory is not writable");
+ }
+ }
+
+ public function getOutputDirectory() {
+ return \Drupal::config('devel.settings')->get('debug_mail_directory');
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/DestinationMenuLink.php b/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/DestinationMenuLink.php
new file mode 100644
index 000000000..4edf1c329
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/DestinationMenuLink.php
@@ -0,0 +1,32 @@
+')->toString();
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @todo Make cacheable once https://www.drupal.org/node/2582797 lands.
+ */
+ public function getCacheMaxAge() {
+ return 0;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/MenuItemMenuLink.php b/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/MenuItemMenuLink.php
new file mode 100644
index 000000000..73faab64a
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Plugin/Menu/MenuItemMenuLink.php
@@ -0,0 +1,29 @@
+')->getInternalPath();
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheMaxAge() {
+ return 0;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Routing/RouteSubscriber.php b/sites/all/modules/contrib/dev/devel/src/Routing/RouteSubscriber.php
new file mode 100644
index 000000000..d9867fdd9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Routing/RouteSubscriber.php
@@ -0,0 +1,122 @@
+entityTypeManager = $entity_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function alterRoutes(RouteCollection $collection) {
+ foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
+ if ($route = $this->getEntityLoadRoute($entity_type)) {
+ $collection->add("entity.$entity_type_id.devel_load", $route);
+ }
+ if ($route = $this->getEntityRenderRoute($entity_type)) {
+ $collection->add("entity.$entity_type_id.devel_render", $route);
+ }
+ }
+ }
+
+ /**
+ * Gets the devel load route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function getEntityLoadRoute(EntityTypeInterface $entity_type) {
+ if ($devel_load = $entity_type->getLinkTemplate('devel-load')) {
+ $entity_type_id = $entity_type->id();
+ $route = new Route($devel_load);
+ $route
+ ->addDefaults([
+ '_controller' => '\Drupal\devel\Controller\EntityDebugController::entityLoad',
+ '_title' => 'Devel Load',
+ ])
+ ->addRequirements([
+ '_permission' => 'access devel information',
+ ])
+ ->setOption('_admin_route', TRUE)
+ ->setOption('_devel_entity_type_id', $entity_type_id)
+ ->setOption('parameters', [
+ $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+ ]);
+
+ return $route;
+ }
+ }
+
+ /**
+ * Gets the devel render route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function getEntityRenderRoute(EntityTypeInterface $entity_type) {
+ if ($devel_render = $entity_type->getLinkTemplate('devel-render')) {
+ $entity_type_id = $entity_type->id();
+ $route = new Route($devel_render);
+ $route
+ ->addDefaults([
+ '_controller' => '\Drupal\devel\Controller\EntityDebugController::entityRender',
+ '_title' => 'Devel Render',
+ ])
+ ->addRequirements([
+ '_permission' => 'access devel information'
+ ])
+ ->setOption('_admin_route', TRUE)
+ ->setOption('_devel_entity_type_id', $entity_type_id)
+ ->setOption('parameters', [
+ $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+ ]);
+
+ return $route;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events = parent::getSubscribedEvents();
+ $events[RoutingEvents::ALTER] = ['onAlterRoutes', 100];
+ return $events;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelControllerTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelControllerTest.php
new file mode 100644
index 000000000..59cc1743a
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelControllerTest.php
@@ -0,0 +1,109 @@
+randomMachineName();
+ $data = array('type' => 'entity_test', 'name' => $random_label);
+ $this->entity = entity_create('entity_test', $data);
+ $this->entity->save();
+
+ // Create a test entity with only canonical route.
+ $random_label = $this->randomMachineName();
+ $data = array('type' => 'devel_entity_test_canonical', 'name' => $random_label);
+ $this->entity_canonical = entity_create('devel_entity_test_canonical', $data);
+ $this->entity_canonical->save();
+
+ // Create a test entity with only edit route.
+ $random_label = $this->randomMachineName();
+ $data = array('type' => 'devel_entity_test_edit', 'name' => $random_label);
+ $this->entity_edit = entity_create('devel_entity_test_edit', $data);
+ $this->entity_edit->save();
+
+ // Create a test entity with no routes.
+ $random_label = $this->randomMachineName();
+ $data = array('type' => 'devel_entity_test_no_links', 'name' => $random_label);
+ $this->entity_no_links = entity_create('devel_entity_test_no_links', $data);
+ $this->entity_no_links->save();
+
+ $this->drupalPlaceBlock('local_tasks_block');
+
+ $web_user = $this->drupalCreateUser(array(
+ 'view test entity',
+ 'administer entity_test content',
+ 'access devel information',
+ ));
+ $this->drupalLogin($web_user);
+ }
+
+ function testRouteGeneration() {
+ // Test Devel load and render routes for entities with both route
+ // definitions.
+ $this->drupalGet('entity_test/' . $this->entity->id());
+ $this->assertText('Devel', 'Devel tab is present');
+ $this->drupalGet('devel/entity_test/' . $this->entity->id());
+ $this->assertResponse(200);
+ $this->assertText('Load', 'Devel load tab is present');
+ $this->assertText('Render', 'Devel load tab is present');
+ $this->assertLinkByHref('devel/entity_test/' . $this->entity->id() . '/render');
+ $this->drupalGet('devel/entity_test/' . $this->entity->id() . '/render');
+ $this->assertResponse(200);
+
+ // Test Devel load and render routes for entities with only canonical route
+ // definitions.
+ $this->drupalGet('devel_entity_test_canonical/' . $this->entity_canonical->id());
+ $this->assertText('Devel', 'Devel tab is present');
+ //TODO this fail since assertNoLinkByHref search by partial value.
+ //$this->assertNoLinkByHref('devel/devel_entity_test_canonical/' . $this->entity_canonical->id());
+ $this->assertLinkByHref('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/render');
+ $this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id());
+ $this->assertResponse(404);
+ $this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/render');
+ $this->assertResponse(200);
+
+ // Test Devel load and render routes for entities with only edit route
+ // definitions.
+ $this->drupalGet('devel_entity_test_edit/manage/' . $this->entity_edit->id());
+ $this->assertText('Devel', 'Devel tab is present');
+ $this->assertLinkByHref('devel/devel_entity_test_edit/' . $this->entity_edit->id());
+ $this->assertNoLinkByHref('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render');
+ $this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id());
+ $this->assertResponse(200);
+ $this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render');
+ $this->assertResponse(404);
+
+ // Test Devel load and render routes for entities with no route
+ // definitions.
+ $this->drupalGet('devel_entity_test_no_links/' . $this->entity_edit->id());
+ $this->assertNoText('Devel', 'Devel tab is not present');
+ $this->assertNoLinkByHref('devel/devel_entity_test_no_links/' . $this->entity_no_links->id());
+ $this->assertNoLinkByHref('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/render');
+ $this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id());
+ $this->assertResponse(404);
+ $this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/render');
+ $this->assertResponse(404);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelDumperTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelDumperTest.php
new file mode 100644
index 000000000..bf214a0ca
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelDumperTest.php
@@ -0,0 +1,159 @@
+drupalCreateUser(['administer site configuration', 'access devel information']);
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Test dumpers configuration page.
+ */
+ public function testDumpersConfiguration() {
+ $this->drupalGet('admin/config/development/devel');
+
+ // Ensures that the dumper input is present on the config page.
+ $this->assertFieldByName('dumper');
+
+ // Ensures that the 'default' dumper is enabled by default.
+ $this->assertFieldChecked('edit-dumper-default');
+
+ // Ensures that all dumpers declared by devel are present on the config page
+ // and that only the available dumpers are selectable.
+ $dumpers = ['default', 'drupal_variable', 'firephp', 'chromephp', 'var_dumper'];
+ $available_dumpers = ['default', 'drupal_variable'];
+
+ foreach ($dumpers as $dumper) {
+ $this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper, new FormattableMarkup('Radio button for @dumper found.', ['@dumper' => $dumper]));
+ if (in_array($dumper, $available_dumpers)) {
+ $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper, new FormattableMarkup('Dumper @dumper is available.', ['@dumper' => $dumper]));
+ }
+ else {
+ $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper, new FormattableMarkup('Dumper @dumper is disabled.', ['@dumper' => $dumper]));
+ }
+ }
+
+ // Ensures that dumper plugins declared by other modules are present on the
+ // config page and that only the available dumpers are selectable.
+ $this->assertFieldByName('dumper', 'available_test_dumper');
+ $this->assertText('Available test dumper.', 'Available dumper label is present');
+ $this->assertText('Drupal dumper for testing purposes (available).', 'Available dumper description is present');
+ $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', 'available_test_dumper', 'Available dumper input not is disabled.');
+
+ $this->assertFieldByName('dumper', 'not_available_test_dumper');
+ $this->assertText('Not available test dumper.', 'Non available dumper label is present');
+ $this->assertText('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.', 'Non available dumper description is present');
+ $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', 'not_available_test_dumper', 'Non available dumper input is disabled.');
+
+ // Ensures that saving of the dumpers configuration works as expected.
+ $edit = [
+ 'dumper' => 'drupal_variable',
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+
+ $config = \Drupal::config('devel.settings')->get('devel_dumper');
+ $this->assertEqual('drupal_variable', $config, 'The configuration options have been properly saved');
+
+ // Ensure that if the chosen dumper is not available (e.g. the module that
+ // provide it is uninstalled) the 'default' dumper appears selected in the
+ // config page.
+ \Drupal::service('module_installer')->install(['kint']);
+
+ $this->drupalGet('admin/config/development/devel');
+ $this->assertFieldByName('dumper', 'kint');
+
+ $edit = [
+ 'dumper' => 'kint',
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+
+ $config = \Drupal::config('devel.settings')->get('devel_dumper');
+ $this->assertEqual('kint', $config, 'The configuration options have been properly saved');
+
+ \Drupal::service('module_installer')->uninstall(['kint']);
+
+ $this->drupalGet('admin/config/development/devel');
+ $this->assertNoFieldByName('dumper', 'kint');
+ $this->assertFieldChecked('edit-dumper-default');
+ }
+
+ /**
+ * Test variable is dumped in page.
+ */
+ function testDumpersOutput() {
+ $edit = [
+ 'dumper' => 'available_test_dumper',
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+
+ $this->drupalGet('devel_dumper_test/dump');
+ $elements = $this->xpath('//body/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::dump() Test output']);
+ $this->assertTrue(!empty($elements), 'Dumped message is present.');
+
+ $this->drupalGet('devel_dumper_test/message');
+ $elements = $this->xpath('//div[contains(@class, "messages")]/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']);
+ $this->assertTrue(!empty($elements), 'Dumped message is present.');
+
+ $this->drupalGet('devel_dumper_test/export');
+ $elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']);
+ $this->assertTrue(!empty($elements), 'Dumped message is present.');
+
+ $this->drupalGet('devel_dumper_test/export_renderable');
+ $elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::exportAsRenderable() Test output']);
+ $this->assertTrue(!empty($elements), 'Dumped message is present.');
+ // Ensures that plugins can add libraries to the page when the
+ // ::exportAsRenderable() method is used.
+ $this->assertRaw('devel_dumper_test/css/devel_dumper_test.css');
+ $this->assertRaw('devel_dumper_test/js/devel_dumper_test.js');
+
+ $debug_filename = file_directory_temp() . '/drupal_debug.txt';
+
+ $this->drupalGet('devel_dumper_test/debug');
+ $file_content = file_get_contents($debug_filename);
+ $expected = <<AvailableTestDumper::export() Test output
+
+EOF;
+ $this->assertEqual($file_content, $expected, 'Dumped message is present.');
+
+ // Ensures that the DevelDumperManager::debug() is not access checked and
+ // that the dump is written in the debug file even if the user has not the
+ // 'access devel information' permission.
+ file_put_contents($debug_filename, '');
+ $this->drupalLogout();
+ $this->drupalGet('devel_dumper_test/debug');
+ $file_content = file_get_contents($debug_filename);
+ $expected = <<AvailableTestDumper::export() Test output
+
+EOF;
+ $this->assertEqual($file_content, $expected, 'Dumped message is present.');
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelErrorHandlerTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelErrorHandlerTest.php
new file mode 100644
index 000000000..9a8e9c6b5
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelErrorHandlerTest.php
@@ -0,0 +1,213 @@
+ 'Notice',
+ '@message' => 'Undefined variable: undefined',
+ '%function' => 'Drupal\devel\Form\SettingsForm->demonstrateErrorHandlers()',
+ ];
+ $error_warning = [
+ '%type' => 'Warning',
+ '@message' => 'Division by zero',
+ '%function' => 'Drupal\devel\Form\SettingsForm->demonstrateErrorHandlers()',
+ ];
+
+ $config = $this->config('system.logging');
+ $config->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
+
+ $admin_user = $this->drupalCreateUser(['administer site configuration', 'access devel information']);
+ $this->drupalLogin($admin_user);
+
+ // Ensures that the error handler config is present on the config page and
+ // by default the standard error handler is selected.
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ $this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_STANDARD => DEVEL_ERROR_HANDLER_STANDARD]);
+ $this->drupalGet('admin/config/development/devel');
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_STANDARD);
+
+ // Ensures that selecting the DEVEL_ERROR_HANDLER_NONE option no error
+ // (raw or message) is shown on the site in case of php errors.
+ $edit = [
+ 'error_handlers[]' => DEVEL_ERROR_HANDLER_NONE,
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ $this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_NONE => DEVEL_ERROR_HANDLER_NONE]);
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_NONE);
+
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertNoRawErrorMessage($error_notice);
+ $this->assertNoRawErrorMessage($error_warning);
+ $this->assertNoErrorMessage($error_notice);
+ $this->assertNoErrorMessage($error_warning);
+
+ // Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_KINT option a
+ // backtrace above the rendered page is shown on the site in case of php
+ // errors.
+ $edit = [
+ 'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ $this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT]);
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT);
+
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertRawErrorMessage($error_notice);
+ $this->assertRawErrorMessage($error_warning);
+
+ // Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_DPM option a
+ // backtrace in the message area is shown on the site in case of php errors.
+ $edit = [
+ 'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ $this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM]);
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM);
+
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertErrorMessage($error_notice);
+ $this->assertErrorMessage($error_warning);
+
+ // Ensures that when multiple handlers are selected, the output produced by
+ // every handler is shown on the site in case of php errors.
+ $edit = [
+ 'error_handlers[]' => [
+ DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
+ DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
+ ]
+ ];
+ $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+ $error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
+ $this->assertEqual($error_handlers, [
+ DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
+ DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
+ ]);
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM);
+ $this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT);
+
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertRawErrorMessage($error_notice);
+ $this->assertRawErrorMessage($error_warning);
+ $this->assertErrorMessage($error_notice);
+ $this->assertErrorMessage($error_warning);
+
+ // Ensures that setting the error reporting to all the output produced by
+ // handlers is shown on the site in case of php errors.
+ $config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertRawErrorMessage($error_notice);
+ $this->assertRawErrorMessage($error_warning);
+ $this->assertErrorMessage($error_notice);
+ $this->assertErrorMessage($error_warning);
+
+ // Ensures that setting the error reporting to some the output produced by
+ // handlers is shown on the site in case of php errors.
+ $config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->clickLink('notice+warning');
+ $this->assertRawErrorMessage($error_notice);
+ $this->assertRawErrorMessage($error_warning);
+ $this->assertErrorMessage($error_notice);
+ $this->assertErrorMessage($error_warning);
+
+ // Ensures that setting the error reporting to none the output produced by
+ // handlers is not shown on the site in case of php errors.
+ $config->set('error_level', ERROR_REPORTING_HIDE)->save();
+ $this->clickLink('notice+warning');
+ $this->assertResponse(200, 'Received expected HTTP status code.');
+ $this->assertNoRawErrorMessage($error_notice);
+ $this->assertNoRawErrorMessage($error_warning);
+ $this->assertNoErrorMessage($error_notice);
+ $this->assertNoErrorMessage($error_warning);
+
+ // The errors are expected. Do not interpret them as a test failure.
+ // Not using File API; a potential error must trigger a PHP warning.
+ unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
+ }
+
+ /**
+ * Helper function: assert that the error message is found.
+ *
+ * @param array $error
+ * The error to check.
+ */
+ protected function assertRawErrorMessage(array $error) {
+ $message = new FormattableMarkup('%type: @message in %function (line ', $error);
+ $this->assertRaw($message, new FormattableMarkup('Found raw error message: @message.', ['@message' => $message]));
+ }
+
+ /**
+ * Helper function: assert that the error message is not found.
+ *
+ *
+ * @param array $error
+ * The error to check.
+ */
+ protected function assertNoRawErrorMessage(array $error) {
+ $message = new FormattableMarkup('%type: @message in %function (line ', $error);
+ $this->assertNoRaw($message, new FormattableMarkup('Did not find raw error message: @message.', ['@message' => $message]));
+ }
+
+ /**
+ * Helper function: assert that the error message is found.
+ *
+ * @param array $error
+ * The error to check.
+ */
+ protected function assertErrorMessage(array $error) {
+ $pattern = '//div[contains(@class, "messages--warning")]//pre[contains(., :content)]';
+ $message = new FormattableMarkup('%type: @message in %function (line ', $error);
+ $message = html_entity_decode(strip_tags((string) $message));
+ $xpath = $this->xpath($pattern, [':content' => $message]);
+ $this->assertTrue(!empty($xpath), new FormattableMarkup('Found error message: @message.', ['@message' => $message]));
+ }
+
+ /**
+ * Helper function: assert that the error message is not found.
+ *
+ * @param array $error
+ * The error to check.
+ */
+ protected function assertNoErrorMessage(array $error) {
+ $pattern = '//div[contains(@class, "messages--warning")]//pre[contains(., :content)]';
+ $message = new FormattableMarkup('%type: @message in %function (line ', $error);
+ $message = html_entity_decode(strip_tags((string) $message));
+ $xpath = $this->xpath($pattern, [':content' => $message]);
+ $this->assertTrue(empty($xpath), new FormattableMarkup('Found error message: @message.', ['@message' => $message]));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelMailTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelMailTest.php
new file mode 100644
index 000000000..9e285c922
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelMailTest.php
@@ -0,0 +1,48 @@
+ 'postmaster@example.com',
+ 'X-stupid' => 'dumb',
+ );
+ $message['body'] = "I am the body of this message";
+ $d = new DevelMailLog();
+
+ $filename = $d->getFileName($message);
+ $content = $d->composeMessage($message);
+ $expected_filename = $d->getOutputDirectory() . '/drupal@example.com-Test_mail-' . date('y-m-d_his') . '.mail.txt';
+ $this->assertEqual($filename, $expected_filename);
+ $content = str_replace("\r", '', $content);
+ $this->assertEqual($content, 'From: postmaster@example.com
+X-stupid: dumb
+To: drupal@example.com
+Subject: Test mail
+
+I am the body of this message');
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelMenuLinksTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelMenuLinksTest.php
new file mode 100644
index 000000000..fdf54ebd7
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelMenuLinksTest.php
@@ -0,0 +1,147 @@
+drupalPlaceBlock('system_menu_block:devel');
+ $this->drupalPlaceBlock('page_title_block');
+
+ $this->develUser = $this->drupalCreateUser(['access devel information', 'administer site configuration']);
+ $this->drupalLogin($this->develUser);
+ }
+
+ /**
+ * Tests CSFR protected links.
+ */
+ public function testCsrfProtectedLinks() {
+ // Ensure CSRF link are not accessible directly.
+ $this->drupalGet('devel/run-cron');
+ $this->assertResponse(403);
+ $this->drupalGet('devel/cache/clear');
+ $this->assertResponse(403);
+
+ // Ensure clear cache link works properly.
+ $this->assertLink('Cache clear');
+ $this->clickLink('Cache clear');
+ $this->assertText('Cache cleared.');
+
+ // Ensure run cron link works properly.
+ $this->assertLink('Run cron');
+ $this->clickLink('Run cron');
+ $this->assertText('Cron ran successfully.');
+
+ // Ensure CSRF protected links work properly after change session.
+ $this->drupalLogout();
+ $this->drupalLogin($this->develUser);
+
+ $this->assertLink('Cache clear');
+ $this->clickLink('Cache clear');
+ $this->assertText('Cache cleared.');
+
+ $this->assertLink('Run cron');
+ $this->clickLink('Run cron');
+ $this->assertText('Cron ran successfully.');
+ }
+
+ /**
+ * Tests redirect destination links.
+ */
+ public function testRedirectDestinationLinks() {
+ // By default, in the testing profile, front page is the user canonical URI.
+ // For better testing do not use the default frontpage.
+ $url = Url::fromRoute('devel.simple_page');
+ $destination = Url::fromRoute('devel.simple_page', [], ['absolute' => FALSE]);
+
+ $this->drupalGet($url);
+ $this->assertLink(t('Reinstall Modules'));
+ $this->clickLink(t('Reinstall Modules'));
+ $this->assertUrl('devel/reinstall', ['query' => ['destination' => $destination->toString()]]);
+
+ $this->drupalGet($url);
+ $this->assertLink(t('Rebuild Menu'));
+ $this->clickLink(t('Rebuild Menu'));
+ $this->assertUrl('devel/menu/reset', ['query' => ['destination' => $destination->toString()]]);
+
+ $this->drupalGet($url);
+ $this->assertLink(t('Cache clear'));
+ $this->clickLink(t('Cache clear'));
+ $this->assertText('Cache cleared.');
+ $this->assertUrl($url);
+
+ $this->drupalGet($url);
+ $this->assertLink(t('Run cron'));
+ $this->clickLink(t('Run cron'));
+ $this->assertText(t('Cron ran successfully.'));
+ $this->assertUrl($url);
+ }
+
+ /**
+ * Tests menu item link.
+ */
+ public function testMenuItemLink() {
+ // Ensures that devel menu item works properly.
+ $url = $this->develUser->toUrl();
+ $path = '/' . $url->getInternalPath();
+
+ $this->drupalGet($url);
+ $this->clickLink(t('Menu Item'));
+ $this->assertResponse(200);
+ $this->assertText('Menu item');
+ $this->assertUrl('devel/menu/item', ['query' => ['path' => $path]]);
+
+ // Ensures that devel menu item works properly even when dynamic cache is
+ // enabled.
+ $url = Url::fromRoute('devel.simple_page');
+ $path = '/' . $url->getInternalPath();
+
+ $this->drupalGet($url);
+ $this->clickLink(t('Menu Item'));
+ $this->assertResponse(200);
+ $this->assertText('Menu item');
+ $this->assertUrl('devel/menu/item', ['query' => ['path' => $path]]);
+
+ // Ensures that if no 'path' query string is passed devel menu item does
+ // not return errors.
+ $this->drupalGet('devel/menu/item');
+ $this->assertResponse(200);
+ $this->assertText('Menu item');
+
+ // Ensures that devel menu item is accessible ony to users with the
+ // adequate permissions.
+ $this->drupalLogout();
+ $this->drupalGet('devel/menu/item');
+ $this->assertResponse(403);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelRebuildMenusTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelRebuildMenusTest.php
new file mode 100644
index 000000000..95abf19d2
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelRebuildMenusTest.php
@@ -0,0 +1,41 @@
+drupalCreateUser(array('administer site configuration'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Test routes rebuild.
+ */
+ public function testDevelRebuildMenus() {
+ $this->drupalGet('devel/menu/reset');
+ $this->assertResponse(200);
+ $this->drupalPostForm('devel/menu/reset', array(), t('Rebuild'));
+ $this->assertText(t('The menu router has been rebuilt.'));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelReinstallTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelReinstallTest.php
new file mode 100644
index 000000000..9ff5efa03
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelReinstallTest.php
@@ -0,0 +1,60 @@
+drupalCreateUser(array('administer site configuration'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Reinstall modules.
+ */
+ public function testDevelReinstallModules() {
+ // Minimal profile enables only dblog, block and node.
+ $modules = array('dblog', 'block');
+
+ // Needed for compare correctly the message.
+ sort($modules);
+
+ $this->drupalGet('devel/reinstall');
+
+ // Prepare field data in an associative array
+ $edit = array();
+ foreach ($modules as $module) {
+ $edit["reinstall[$module]"] = TRUE;
+ }
+
+ $this->drupalPostForm('devel/reinstall', $edit, t('Reinstall'));
+ $this->assertText(t('Uninstalled and installed: @names.', array('@names' => implode(', ', $modules))));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelStateEditorTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelStateEditorTest.php
new file mode 100644
index 000000000..16c2bc4a8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelStateEditorTest.php
@@ -0,0 +1,210 @@
+state = $this->container->get('state');
+
+ $this->develUser = $this->drupalCreateUser(['access devel information']);
+ $this->adminUser = $this->drupalCreateUser(['access devel information', 'administer site configuration']);
+ }
+
+ /**
+ * Tests state listing.
+ */
+ public function testStateListing() {
+ // Ensure that state listing page is accessible only by users with the
+ // adequate permissions.
+ $this->drupalGet('devel/state');
+ $this->assertResponse(403);
+
+ $this->drupalLogin($this->develUser);
+ $this->drupalGet('devel/state');
+ $this->assertResponse(200);
+ $this->assertText(t('State editor'));
+
+ // Ensure that the state variables table is visible.
+ $table = $this->xpath('//table[contains(@class, "devel-state-list")]');
+ $this->assertTrue($table, 'State list table found.');
+
+ // Ensure that all state variables are listed in the table.
+ $states = \Drupal::keyValue('state')->getAll();
+ $rows = $this->xpath('//table[contains(@class, "devel-state-list")]//tbody//tr');
+ $this->assertEqual(count($rows), count($states), 'All states are listed in the table.');
+
+ // Ensure that the added state variables are listed in the table.
+ $this->state->set('devel.simple', 'Hello!');
+ $this->drupalGet('devel/state');
+ $this->assertFieldByXpath('//table[contains(@class, "devel-state-list")]//tbody//td', 'devel.simple', 'Label found for the added state.');
+
+ $thead_xpath = '//table[contains(@class, "devel-state-list")]/thead/tr/th';
+ $action_xpath = '//table[contains(@class, "devel-state-list")]//ul[@class="dropbutton"]/li/a';
+
+ // Ensure that the operations column and the actions buttons are not
+ // available for user without 'administer site configuration' permission.
+ $elements = $this->xpath($thead_xpath);
+ $this->assertEqual(count($elements), 2, 'Correct number of table header cells found.');
+
+ $expected_items = ['Name', 'Value'];
+ foreach ($elements as $key => $element) {
+ $this->assertIdentical((string) $element[0], $expected_items[$key]);
+ }
+
+ $this->assertFalse($this->xpath($action_xpath), 'Action buttons are not visible.');
+
+ // Ensure that the operations column and the actions buttons are
+ // available for user with 'administer site configuration' permission.
+ $this->drupalLogin($this->adminUser);
+ $this->drupalGet('devel/state');
+
+ $elements = $this->xpath($thead_xpath);
+ $this->assertEqual(count($elements), 3, 'Correct number of table header cells found.');
+
+ $expected_items = ['Name', 'Value', 'Operations'];
+ foreach ($elements as $key => $element) {
+ $this->assertIdentical((string) $element[0], $expected_items[$key]);
+ }
+
+ $this->assertTrue($this->xpath($action_xpath), 'Action buttons are visible.');
+
+ // Test that the edit button works properly.
+ $this->clickLink(t('Edit'));
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Tests state edit.
+ */
+ public function testStateEdit() {
+ // Create some state variables for the test.
+ $this->state->set('devel.simple', 0);
+ $this->state->set('devel.array', ['devel' => 'value']);
+ $this->state->set('devel.object', $this->randomObject());
+
+ // Ensure that state edit form is accessible only by users with the
+ // adequate permissions.
+ $this->drupalLogin($this->develUser);
+ $this->drupalGet('devel/state/edit/devel.simple');
+ $this->assertResponse(403);
+
+ $this->drupalLogin($this->adminUser);
+
+ // Ensure that accessing an un-existent state variable cause a warning
+ // message.
+ $this->drupalGet('devel/state/edit/devel.unknown');
+ $this->assertText(t('State @name does not exist in the system.', ['@name' => 'devel.unknown']));
+
+ // Ensure that state variables that contain simple type can be edited and
+ // saved.
+ $this->drupalGet('devel/state/edit/devel.simple');
+ $this->assertResponse(200);
+ $this->assertText(t('Edit state variable: @name', ['@name' => 'devel.simple']));
+ $this->assertInputNotDisabledById('edit-new-value');
+ $this->assertInputNotDisabledById('edit-submit');
+
+ $edit = ['new_value' => 1];
+ $this->drupalPostForm('devel/state/edit/devel.simple', $edit, t('Save'));
+ $this->assertText(t('Variable @name was successfully edited.', ['@name' => 'devel.simple']));
+ $this->assertEqual(1, $this->state->get('devel.simple'));
+
+ // Ensure that state variables that contain array can be edited and saved
+ // and the new value is properly validated.
+ $this->drupalGet('devel/state/edit/devel.array');
+ $this->assertResponse(200);
+ $this->assertText(t('Edit state variable: @name', ['@name' => 'devel.array']));
+ $this->assertInputNotDisabledById('edit-new-value');
+ $this->assertInputNotDisabledById('edit-submit');
+
+ // Try to save an invalid yaml input.
+ $edit = ['new_value' => 'devel: \'value updated'];
+ $this->drupalPostForm('devel/state/edit/devel.array', $edit, t('Save'));
+ $this->assertText(t('Invalid input:'));
+
+ $edit = ['new_value' => 'devel: \'value updated\''];
+ $this->drupalPostForm('devel/state/edit/devel.array', $edit, t('Save'));
+ $this->assertText(t('Variable @name was successfully edited.', ['@name' => 'devel.array']));
+ $this->assertEqual(['devel' => 'value updated'], $this->state->get('devel.array'));
+
+ // Ensure that state variables that contain objects cannot be edited.
+ $this->drupalGet('devel/state/edit/devel.object');
+ $this->assertResponse(200);
+ $this->assertText(t('Edit state variable: @name', ['@name' => 'devel.object']));
+ $this->assertText(t('Only simple structures are allowed to be edited. State @name contains objects.', ['@name' => 'devel.object']));
+ $this->assertInputDisabledById('edit-new-value');
+ $this->assertInputDisabledById('edit-submit');
+
+ // Ensure that the cancel link works as expected.
+ $this->clickLink(t('Cancel'));
+ $this->assertUrl('devel/state');
+ }
+
+ /**
+ * Helper function for check if an input is disabled.
+ *
+ * @param string $id
+ * The ID of the input.
+ */
+ protected function assertInputDisabledById($id) {
+ $message = new FormattableMarkup('The input %id is disabled.', ['%id' => $id]);
+ $xpath = '//textarea[@id=:id and @disabled="disabled"]|//input[@id=:id and @disabled="disabled"]|//select[@id=:id and @disabled="disabled"]';
+ $query = $this->buildXPathQuery($xpath, [':id' => $id]);
+ $this->assertFieldByXPath($query, NULL, $message);
+ }
+
+ /**
+ * Helper function for check if an input is not disabled.
+ *
+ * @param string $id
+ * The ID of the input.
+ */
+ protected function assertInputNotDisabledById($id) {
+ $message = new FormattableMarkup('The input %id is not disabled.', ['%id' => $id]);
+ $xpath = '//textarea[@id=:id and not(@disabled="disabled")]|//input[@id=:id and not(@disabled="disabled")]|//select[@id=:id and not(@disabled="disabled")]';
+ $query = $this->buildXPathQuery($xpath, [':id' => $id]);
+ $this->assertFieldByXPath($query, NULL, $message);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Tests/DevelSwitchUserTest.php b/sites/all/modules/contrib/dev/devel/src/Tests/DevelSwitchUserTest.php
new file mode 100644
index 000000000..68f6136db
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Tests/DevelSwitchUserTest.php
@@ -0,0 +1,313 @@
+block = $this->drupalPlaceBlock('devel_switch_user', ['id' => 'switch-user']);
+
+ $this->develUser = $this->drupalCreateUser(['access devel information', 'switch users']);
+ $this->switchUser = $this->drupalCreateUser(['switch users']);
+ $this->webUser = $this->drupalCreateUser();
+ }
+
+ /**
+ * Tests switch user.
+ */
+ public function testSwitchUser() {
+ $this->drupalLogin($this->webUser);
+
+ $this->drupalGet('');
+ $this->assertNoText($this->block->label(), 'Block title was not found.');
+
+ // Ensure that a token is required to switch user.
+ $this->drupalGet('/devel/switch/' . $this->webUser->getUsername());
+ $this->assertResponse(403);
+
+ $this->drupalLogin($this->develUser);
+
+ $this->drupalGet('');
+ $this->assertText($this->block->label(), 'Block title was found.');
+
+ // Ensure that if name in not passed the controller returns access denied.
+ $this->drupalGet('/devel/switch');
+ $this->assertResponse(403);
+
+ // Ensure that a token is required to switch user.
+ $this->drupalGet('/devel/switch/' . $this->switchUser->getUsername());
+ $this->assertResponse(403);
+
+ // Switch to another user account.
+ $this->drupalGet('/user/' . $this->switchUser->id());
+ $this->clickLink($this->switchUser->getUsername());
+ $this->assertSessionByUid($this->switchUser->id());
+ $this->assertNoSessionByUid($this->develUser->id());
+
+ // Switch back to initial account.
+ $this->clickLink($this->develUser->getUsername());
+ $this->assertNoSessionByUid($this->switchUser->id());
+ $this->assertSessionByUid($this->develUser->id());
+
+ // Use the search form to switch to another account.
+ $edit = ['userid' => $this->switchUser->getUsername()];
+ $this->drupalPostForm(NULL, $edit, t('Switch'));
+ $this->assertSessionByUid($this->switchUser->id());
+ $this->assertNoSessionByUid($this->develUser->id());
+ }
+
+ /**
+ * Tests the switch user block configuration.
+ */
+ public function testSwitchUserBlockConfiguration() {
+ $anonymous = \Drupal::config('user.settings')->get('anonymous');
+
+ // Create some users for the test.
+ for ($i = 0; $i < 12; $i++) {
+ $this->drupalCreateUser();
+ }
+
+ $this->drupalLogin($this->develUser);
+
+ $this->drupalGet('');
+ $this->assertText($this->block->label(), 'Block title was found.');
+
+ // Ensure that block default configuration is effectively used. The block
+ // default configuration is the following:
+ // - list_size : 12
+ // - include_anon : FALSE
+ // - show_form : TRUE
+ $this->assertSwitchUserSearchForm();
+ $this->assertSwitchUserListCount(12);
+ $this->assertSwitchUserListNoContainsUser($anonymous);
+
+ // Ensure that changing the list_size configuration property the number of
+ // user displayed in the list change.
+ $this->setBlockConfiguration('list_size', 4);
+ $this->drupalGet('');
+ $this->assertSwitchUserListCount(4);
+
+ // Ensure that changing the include_anon configuration property the
+ // anonymous user is displayed in the list.
+ $this->setBlockConfiguration('include_anon', TRUE);
+ $this->drupalGet('');
+ $this->assertSwitchUserListContainsUser($anonymous);
+
+ // Ensure that changing the show_form configuration property the
+ // form is not displayed.
+ $this->setBlockConfiguration('show_form', FALSE);
+ $this->drupalGet('');
+ $this->assertSwitchUserNoSearchForm();
+ }
+
+ /**
+ * Test the user list items.
+ */
+ public function testSwitchUserListItems() {
+ $anonymous = \Drupal::config('user.settings')->get('anonymous');
+
+ $this->setBlockConfiguration('list_size', 2);
+
+ // Login as web user so we are sure that this account is prioritized
+ // in the list if not enougth user with 'switch users' permission are
+ // present.
+ $this->drupalLogin($this->webUser);
+
+ $this->drupalLogin($this->develUser);
+ $this->drupalGet('');
+
+ // Ensure that user with 'switch users' permission are prioritized.
+ $this->assertSwitchUserListCount(2);
+ $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
+ $this->assertSwitchUserListContainsUser($this->switchUser->getUsername());
+
+ // Ensure that blocked users are not shown in the list.
+ $this->switchUser->set('status', 0)->save();
+ $this->drupalGet('');
+ $this->assertSwitchUserListCount(2);
+ $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
+ $this->assertSwitchUserListContainsUser($this->webUser->getUsername());
+ $this->assertSwitchUserListNoContainsUser($this->switchUser->getUsername());
+
+ // Ensure that anonymous user are prioritized if include_anon is set to true.
+ $this->setBlockConfiguration('include_anon', TRUE);
+ $this->drupalGet('');
+ $this->assertSwitchUserListCount(2);
+ $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
+ $this->assertSwitchUserListContainsUser($anonymous);
+
+ // Ensure that the switch user block works properly even if no prioritized
+ // users are found (special handling for user 1).
+ $this->drupalLogout();
+ $this->develUser->delete();
+
+ $this->drupalLogin($this->rootUser);
+ $this->drupalGet('');
+ $this->assertSwitchUserListCount(2);
+ $this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
+ $this->assertSwitchUserListContainsUser($anonymous);
+
+ // Ensure that the switch user block works properly even if no roles have
+ // the 'switch users' permission associated (special handling for user 1).
+ $roles = user_roles(TRUE, 'switch users');
+ \Drupal::entityTypeManager()->getStorage('user_role')->delete($roles);
+
+ $this->drupalGet('');
+ $this->assertSwitchUserListCount(2);
+ $this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
+ $this->assertSwitchUserListContainsUser($anonymous);
+ }
+
+ /**
+ * Helper function for verify the number of items shown in the user list.
+ *
+ * @param int $number
+ * The expected numer of items.
+ */
+ public function assertSwitchUserListCount($number) {
+ $result = $this->xpath('//div[@id=:block]//ul/li/a', [':block' => 'block-switch-user']);
+ $this->assert(count($result) == $number, 'The number of users shown in switch user is correct.');
+ }
+
+ /**
+ * Helper function for verify if the user list contains a username.
+ *
+ * @param string $username
+ * The username to check.
+ */
+ public function assertSwitchUserListContainsUser($username) {
+ $result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]);
+ $this->assert(count($result) > 0, new FormattableMarkup('User "%user" is included in the switch user list.', ['%user' => $username]));
+ }
+
+ /**
+ * Helper function for verify if the user list not contains a username.
+ *
+ * @param string $username
+ * The username to check.
+ */
+ public function assertSwitchUserListNoContainsUser($username) {
+ $result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]);
+ $this->assert(count($result) == 0, new FormattableMarkup('User "%user" is not included in the switch user list.', ['%user' => $username]));
+ }
+
+ /**
+ * Helper function for verify if the search form is shown.
+ */
+ public function assertSwitchUserSearchForm() {
+ $result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']);
+ $this->assert(count($result) > 0, 'The search form is shown.');
+ }
+
+ /**
+ * Helper function for verify if the search form is not shown.
+ */
+ public function assertSwitchUserNoSearchForm() {
+ $result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']);
+ $this->assert(count($result) == 0, 'The search form is not shown.');
+ }
+
+ /**
+ * Protected helper method to set the test block's configuration.
+ */
+ protected function setBlockConfiguration($key, $value) {
+ $block = $this->block->getPlugin();
+ $block->setConfigurationValue($key, $value);
+ $this->block->save();
+ }
+
+ /**
+ * Asserts that there is a session for a given user ID.
+ *
+ * Based off masquarade module.
+ *
+ * @param int $uid
+ * The user ID for which to find a session record.
+ *
+ * TODO find a cleaner way to do this check.
+ */
+ protected function assertSessionByUid($uid) {
+ $query = \Drupal::database()->select('sessions');
+ $query->fields('sessions', array('uid'));
+ $query->condition('uid', $uid);
+ $result = $query->execute()->fetchAll();
+
+ if (empty($result)) {
+ $this->fail(new FormattableMarkup('No session found for uid @uid', array('@uid' => $uid)));
+ }
+ elseif (count($result) > 1) {
+ // If there is more than one session, then that must be unexpected.
+ $this->fail("Found more than 1 session for uid $uid.");
+ }
+ else {
+ $this->pass("Found session for uid $uid.");
+ }
+ }
+
+ /**
+ * Asserts that no session exists for a given uid.
+ *
+ * Based off masquarade module.
+ *
+ * @param int $uid
+ * The user ID to assert.
+ *
+ * TODO find a cleaner way to do this check.
+ */
+ protected function assertNoSessionByUid($uid) {
+ $query = \Drupal::database()->select('sessions');
+ $query->fields('sessions', array('uid'));
+ $query->condition('uid', $uid);
+ $result = $query->execute()->fetchAll();
+ $this->assert(empty($result), "No session for uid $uid found.");
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/src/Twig/Extension/Debug.php b/sites/all/modules/contrib/dev/devel/src/Twig/Extension/Debug.php
new file mode 100644
index 000000000..9922e6106
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/src/Twig/Extension/Debug.php
@@ -0,0 +1,201 @@
+dumper = $dumper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() {
+ return 'devel_debug';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFunctions() {
+ $functions = [];
+
+ foreach (['devel_dump', 'kpr'] as $function) {
+ $functions[] = new \Twig_SimpleFunction($function, [$this, 'dump'], [
+ 'is_safe' => ['html'],
+ 'needs_environment' => TRUE,
+ 'needs_context' => TRUE,
+ 'is_variadic' => TRUE,
+ ]);
+ }
+
+ foreach (['devel_message', 'dpm', 'dsm'] as $function) {
+ $functions[] = new \Twig_SimpleFunction($function, [$this, 'message'], [
+ 'is_safe' => ['html'],
+ 'needs_environment' => TRUE,
+ 'needs_context' => TRUE,
+ 'is_variadic' => TRUE,
+ ]);
+ }
+
+ foreach (['devel_breakpoint'] as $function) {
+ $functions[] = new \Twig_SimpleFunction($function, [$this, 'breakpoint'], [
+ 'needs_environment' => TRUE,
+ 'needs_context' => TRUE,
+ 'is_variadic' => TRUE,
+ ]);
+ }
+
+ return $functions;
+ }
+
+ /**
+ * Provides debug function to Twig templates.
+ *
+ * Handles 0, 1, or multiple arguments.
+ *
+ * @param \Twig_Environment $env
+ * The twig environment instance.
+ * @param array $context
+ * An array of parameters passed to the template.
+ * @param array $args
+ * An array of parameters passed the function.
+ *
+ * @return string
+ * String representation of the input variables.
+ *
+ * @see \Drupal\devel\DevelDumperManager::dump()
+ */
+ public function dump(\Twig_Environment $env, array $context, array $args = []) {
+ if (!$env->isDebug()) {
+ return;
+ }
+
+ ob_start();
+
+ // No arguments passed, display full Twig context.
+ if (empty($args)) {
+ $context_variables = $this->getContextVariables($context);
+ $this->dumper->dump($context_variables, 'Twig context');
+ }
+ else {
+ foreach ($args as $variable) {
+ $this->dumper->dump($variable);
+ }
+ }
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Provides debug function to Twig templates.
+ *
+ * Handles 0, 1, or multiple arguments.
+ *
+ * @param \Twig_Environment $env
+ * The twig environment instance.
+ * @param array $context
+ * An array of parameters passed to the template.
+ * @param array $args
+ * An array of parameters passed the function.
+ *
+ * @return void
+ *
+ * @see \Drupal\devel\DevelDumperManager::message()
+ */
+ public function message(\Twig_Environment $env, array $context, array $args = []) {
+ if (!$env->isDebug()) {
+ return;
+ }
+
+ // No arguments passed, display full Twig context.
+ if (empty($args)) {
+ $context_variables = $this->getContextVariables($context);
+ $this->dumper->message($context_variables, 'Twig context');
+ }
+ else {
+ foreach ($args as $variable) {
+ $this->dumper->message($variable);
+ }
+ }
+
+ }
+
+ /**
+ * Provides XDebug integration for Twig templates.
+ *
+ * To use this features simply put the following statement in the template
+ * of interest:
+ *
+ * @code
+ * {{ devel_breakpoint() }}
+ * @endcode
+ *
+ * When the template is evaluated is made a call to a dedicated method in
+ * devel twig debug extension in which is used xdebug_break(), that emits a
+ * breakpoint to the debug client (the debugger break on the specific line as
+ * if a normal file/line breakpoint was set on this line).
+ * In this way you'll be able to inspect any variables available in the
+ * template (environment, context, specific variables etc..) in your IDE.
+ *
+ * @param \Twig_Environment $env
+ * The twig environment instance.
+ * @param array $context
+ * An array of parameters passed to the template.
+ * @param array $args
+ * An array of parameters passed the function.
+ */
+ public function breakpoint(\Twig_Environment $env, array $context, array $args = []) {
+ if (!$env->isDebug()) {
+ return;
+ }
+
+ if (function_exists('xdebug_break')) {
+ xdebug_break();
+ }
+ }
+
+ /**
+ * Filters the Twig context variable.
+ *
+ * @param array $context
+ * The Twig context.
+ *
+ * @return array
+ * An array Twig context variables.
+ */
+ protected function getContextVariables(array $context) {
+ $context_variables = [];
+ foreach ($context as $key => $value) {
+ if (!$value instanceof \Twig_Template) {
+ $context_variables[$key] = $value;
+ }
+ }
+ return $context_variables;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml
new file mode 100644
index 000000000..b68932872
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml
@@ -0,0 +1,12 @@
+name: 'Devel dumper test module'
+type: module
+description: 'Test pluggable dumpers.'
+package: Testing
+# version: VERSION
+# core: 8.x
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml
new file mode 100644
index 000000000..8e8c2ad88
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml
@@ -0,0 +1,7 @@
+devel_dumper_test:
+ version: 0
+ css:
+ theme:
+ css/devel_dumper_test.css: {}
+ js:
+ js/devel_dumper_test.js: {}
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml
new file mode 100644
index 000000000..650b3370a
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml
@@ -0,0 +1,39 @@
+devel_dumper_test.dump:
+ path: '/devel_dumper_test/dump'
+ defaults:
+ _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::dump'
+ _title: 'Devel Dumper Test'
+ requirements:
+ _permission: 'access devel information'
+
+devel_dumper_test.message:
+ path: '/devel_dumper_test/message'
+ defaults:
+ _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::message'
+ _title: 'Devel Dumper Test'
+ requirements:
+ _permission: 'access devel information'
+
+devel_dumper_test.export:
+ path: '/devel_dumper_test/export'
+ defaults:
+ _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::export'
+ _title: 'Devel Dumper Test'
+ requirements:
+ _permission: 'access devel information'
+
+devel_dumper_test.export_renderable:
+ path: '/devel_dumper_test/export_renderable'
+ defaults:
+ _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::exportRenderable'
+ _title: 'Devel Dumper Test'
+ requirements:
+ _permission: 'access devel information'
+
+devel_dumper_test.debug:
+ path: '/devel_dumper_test/debug'
+ defaults:
+ _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::debug'
+ _title: 'Devel Dumper Test'
+ requirements:
+ _access: 'TRUE'
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/js/devel_dumper_test.js b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/js/devel_dumper_test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php
new file mode 100644
index 000000000..2a78e6e37
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php
@@ -0,0 +1,89 @@
+dumper = $devel_dumper_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('devel.dumper')
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function dump() {
+ $this->dumper->dump('Test output');
+
+ return [
+ '#markup' => 'test',
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function message() {
+ $this->dumper->message('Test output');
+
+ return [
+ '#markup' => 'test',
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function debug() {
+ $this->dumper->debug('Test output');
+
+ return [
+ '#markup' => 'test',
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function export() {
+ return [
+ '#markup' => $this->dumper->export('Test output'),
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function exportRenderable() {
+ return $this->dumper->exportAsRenderable('Test output');
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php
new file mode 100644
index 000000000..b15f5673a
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php
@@ -0,0 +1,60 @@
+' . 'AvailableTestDumper::dump() ' . $input . '';
+ echo $input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function export($input, $name = NULL) {
+ // Add a predetermined string to $input to check if this dumper has been
+ // selected successfully.
+ $input = '
' . 'AvailableTestDumper::export() ' . $input . '
';
+ return $this->setSafeMarkup($input);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exportAsRenderable($input, $name = NULL) {
+ // Add a predetermined string to $input to check if this dumper has been
+ // selected successfully.
+ $input = '
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php b/sites/all/modules/contrib/dev/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php
new file mode 100644
index 000000000..f4673eea7
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php
@@ -0,0 +1,69 @@
+cacheDataCollector = new CacheDataCollector();
+ $this->cacheBackendInterface = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+ }
+
+ /**
+ * Tests the collection of a cache miss.
+ */
+ public function testCacheCollectorMiss() {
+ $this->cacheBackendInterface->expects($this->once())
+ ->method('get')
+ ->will($this->returnValue(FALSE));
+
+ $cacheBackendWrapper = new CacheBackendWrapper($this->cacheDataCollector, $this->cacheBackendInterface, 'default');
+ $cache = $cacheBackendWrapper->get('cache_id');
+
+ $this->assertFalse($cache);
+
+ $this->assertEquals(1, $this->cacheDataCollector->getCacheMissesCount());
+ }
+
+ /**
+ * Tests the collection of a cache hit.
+ */
+ public function testCacheCollectorHit() {
+ $cache = new \StdClass();
+ $cache->cid = 'cache_id';
+ $cache->expire = 1;
+ $cache->tags = ['tag1', 'tag2'];
+ $this->cacheBackendInterface->expects($this->once())
+ ->method('get')
+ ->will($this->returnValue($cache));
+
+ $cacheBackendWrapper = new CacheBackendWrapper($this->cacheDataCollector, $this->cacheBackendInterface, 'default');
+ $cache2 = $cacheBackendWrapper->get('cache_id');
+
+ $this->assertNotNull($cache2);
+
+ $this->assertEquals(1, $this->cacheDataCollector->getCacheHitsCount());
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.info.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.info.yml
new file mode 100644
index 000000000..39b35b0e4
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.info.yml
@@ -0,0 +1,16 @@
+name: Web Profiler
+type: module
+description: 'Drupal Web Profiler.'
+package: Development
+# core: 8.x
+configure: webprofiler.settings
+tags:
+ - developer
+dependencies:
+ - devel
+
+# Information added by Drupal.org packaging script on 2016-09-03
+version: '8.x-1.0-alpha1+23-dev'
+core: '8.x'
+project: 'devel'
+datestamp: 1472897643
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.install b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.install
new file mode 100644
index 000000000..48c6e956f
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.install
@@ -0,0 +1,190 @@
+ 'Webprofiler profiles storage.',
+ 'fields' => [
+ 'token' => [
+ 'description' => 'Profile token.',
+ 'type' => 'varchar',
+ 'length' => 6,
+ 'not null' => TRUE,
+ ],
+ 'data' => [
+ 'description' => 'Profile data.',
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => TRUE,
+ ],
+ 'ip' => [
+ 'description' => 'Request IP.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ],
+ 'method' => [
+ 'description' => 'Request method.',
+ 'type' => 'varchar',
+ 'length' => 6,
+ 'not null' => TRUE,
+ ],
+ 'url' => [
+ 'description' => 'Requested URL.',
+ 'type' => 'varchar',
+ 'length' => 2048,
+ 'not null' => TRUE,
+ ],
+ 'time' => [
+ 'description' => 'Request time.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'parent' => [
+ 'description' => 'Profile parent.',
+ 'type' => 'varchar',
+ 'length' => 6,
+ 'not null' => FALSE,
+ ],
+ 'created_at' => [
+ 'description' => 'Profile created time.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'status_code' => [
+ 'description' => 'Profile status code.',
+ 'type' => 'int',
+ 'size' => 'small',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ ],
+ 'indexes' => [
+ 'created_at' => ['created_at'],
+ 'ip' => ['ip'],
+ 'method' => ['method'],
+ 'parent' => ['parent'],
+ ],
+ 'primary key' => ['token'],
+ ];
+
+ return $schema;
+}
+
+/**
+ * Implements hook_requirements().
+ */
+function webprofiler_requirements($phase) {
+ $requirements = [];
+
+ if ('runtime' == $phase) {
+ $has_d3 = _webprofiler_verify_library('webprofiler', 'd3');
+ $requirements['d3js'] = [
+ 'title' => t('D3.js library'),
+ 'value' => $has_d3 ? t('Enabled') : t('Not found'),
+ ];
+
+ if (!$has_d3) {
+ $requirements['d3js']['severity'] = REQUIREMENT_WARNING;
+ $d3_url = Url::fromUri('https://github.com/mbostock/d3', ['attributes' => ['target' => '_blank']]);
+ $requirements['d3js']['description'] = [
+ '#prefix' => ' ',
+ '#markup' => t('Webprofiler module requires D3.js library to properly render data. Download D3.js into /libraries/d3/d3.min.js.', [':d3_link' => $d3_url->toUriString()])
+ ];
+ }
+
+ $has_highlight = _webprofiler_verify_library('webprofiler', 'highlightjs');
+ $requirements['highlightjs'] = [
+ 'title' => t('highlight.js library'),
+ 'value' => $has_highlight ? t('Enabled') : t('Not found'),
+ ];
+
+ if (!$has_highlight) {
+ $requirements['highlightjs']['severity'] = REQUIREMENT_WARNING;
+ $highlight_url = Url::fromUri('http://highlightjs.org', ['attributes' => ['target' => '_blank']]);
+ $requirements['highlightjs']['description'] = [
+ '#prefix' => ' ',
+ '#markup' => t('Webprofiler module requires highlight.js library to syntax highlight collected queries. Download highlight.js assets into /libraries/highlight.', [':highlight_link' => $highlight_url->toUriString()])
+ ];
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Verify that the library files exist.
+ *
+ * @param string $extension
+ * The name of the extension that registered a library.
+ * @param string $name
+ * The name of a registered library to retrieve.
+ *
+ * @return bool
+ * TRUE if all files of this library exists, FALSE otherwise
+ *
+ * @see https://drupal.org/node/2231385
+ */
+function _webprofiler_verify_library($extension, $name) {
+ /** @var Drupal\Core\Asset\LibraryDiscovery $library_discovery */
+ $library_discovery = \Drupal::service('library.discovery');
+ $library = $library_discovery->getLibraryByName($extension, $name);
+
+ $exist = TRUE;
+ if ($library['js']) {
+ foreach ($library['js'] as $js) {
+ if ($js['type'] == 'file') {
+ if (!file_exists(DRUPAL_ROOT . '/' . $js['data'])) {
+ $exist = FALSE;
+ }
+ }
+ }
+ }
+
+ if ($library['css']) {
+ foreach ($library['css'] as $css) {
+ if ($css['type'] == 'file') {
+ if (!file_exists(DRUPAL_ROOT . '/' . $css['data'])) {
+ $exist = FALSE;
+ }
+ }
+ }
+ }
+
+ if ($library['dependencies']) {
+ foreach ($library['dependencies'] as $dependency) {
+ $parts = explode('/', $dependency);
+ $exist = _webprofiler_verify_library($parts[0], $parts[1]);
+ }
+ }
+
+ return $exist;
+}
+
+/**
+ * Add a status_code column to the webprofiler table.
+ */
+function webprofiler_update_8001() {
+ $database = \Drupal::database();
+ $schema = $database->schema();
+
+ $spec = array(
+ 'description' => 'Profile status code.',
+ 'type' => 'int',
+ 'size' => 'small',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ );
+ $schema->addField('webprofiler', 'status_code', $spec);
+}
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.libraries.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.libraries.yml
new file mode 100644
index 000000000..484cf8895
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.libraries.yml
@@ -0,0 +1,63 @@
+d3:
+ remote: https://github.com/mbostock/d3
+ version: 3.4.4
+ license:
+ name: BSD-3
+ url: https://github.com/mbostock/d3/blob/master/LICENSE
+ gpl-compatible: true
+ js:
+ libraries/d3/d3.min.js: {}
+highlight:
+ remote: https://github.com/isagalaev/highlight.js
+ version: 8.0
+ license:
+ name: BSD-3
+ url: https://github.com/isagalaev/highlight.js/blob/master/LICENSE
+ gpl-compatible: true
+ js:
+ libraries/highlightjs/highlight.pack.js: {}
+ css:
+ component:
+ libraries/highlightjs/styles/idea.css: {}
+dashboard:
+ version: 2.0
+ js:
+ js/app/main.js: {}
+ js/app/helpers.js: {}
+ js/app/models/collector.js: {}
+ js/app/collections/collectors.js: {}
+ js/app/views/collector.js: {}
+ js/app/views/collectorsList.js: {}
+ js/app/views/layout.js: {}
+ js/app/views/details.js: {}
+ js/app/routers/collectors.js: {}
+ css:
+ component:
+ css/app/dashboard.css: {}
+ dependencies:
+ - core/jquery
+ - core/jquery.once
+ - core/drupal
+ - core/drupalSettings
+ - core/backbone
+database:
+ version: 2.0
+ js:
+ js/database.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupalSettings
+ - webprofiler/highlight
+timeline:
+ version: 2.0
+ js:
+ js/timeline.js: {}
+ css:
+ component:
+ css/timeline.css: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupalSettings
+ - webprofiler/d3
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.menu.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.menu.yml
new file mode 100644
index 000000000..b2d4a7307
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.menu.yml
@@ -0,0 +1,5 @@
+webprofiler.admin_list:
+ title: 'Webprofiler'
+ description: 'List saved profiles.'
+ route_name: webprofiler.admin_list
+ parent: system.admin_reports
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.task.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.task.yml
new file mode 100644
index 000000000..741eb15a9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.links.task.yml
@@ -0,0 +1,9 @@
+webprofiler.settings:
+ title: 'Webprofiler'
+ route_name: webprofiler.settings
+ base_route: devel.admin_settings
+ weight: 10
+webprofiler.dashboard:
+ title: 'dashboard'
+ route_name: webprofiler.dashboard
+ base_route: webprofiler.dashboard
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.make.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.make.yml
new file mode 100644
index 000000000..decfe2c19
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.make.yml
@@ -0,0 +1,12 @@
+core: "8.x"
+api: 2
+
+libraries:
+ highlight:
+ download:
+ type: "git"
+ url: "git@github.com:mbostock/d3.git"
+ d3:
+ download:
+ type: "git"
+ url: "git@github.com:mbostock/d3.git"
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.module b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.module
new file mode 100644
index 000000000..01e9b95f1
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.module
@@ -0,0 +1,51 @@
+ [
+ 'template' => 'Profiler/webprofiler_loader',
+ 'variables' => ['token' => NULL, 'profiler_url' => NULL],
+ ],
+ 'webprofiler_toolbar' => [
+ 'template' => 'Profiler/webprofiler_toolbar',
+ 'variables' => [
+ 'token' => NULL,
+ 'toolbar' => NULL,
+ ],
+ ],
+ 'webprofiler_panel' => [
+ 'template' => 'Profiler/webprofiler_panel',
+ 'variables' => [
+ 'panel' => NULL,
+ ],
+ ],
+ 'webprofiler_dashboard' => [
+ 'template' => 'Profiler/webprofiler_dashboard',
+ 'variables' => [
+ 'profile' => [],
+ 'panels' => [],
+ 'spinner_path' => NULL,
+ ],
+ ],
+ ];
+}
+
+/**
+ * Implements hook_cache_flush().
+ */
+function webprofiler_cache_flush() {
+ $config = \Drupal::configFactory()->get('webprofiler.config');
+
+ if ($config->get('purge_on_cache_clear')) {
+ $profiler = \Drupal::service('profiler');
+ $profiler->purge();
+ }
+}
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.permissions.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.permissions.yml
new file mode 100644
index 000000000..b39752527
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.permissions.yml
@@ -0,0 +1,7 @@
+'access webprofiler':
+ title: 'Access webprofiler'
+ description: 'Access webprofiler collected data.'
+ 'restrict access': TRUE
+'view webprofiler toolbar':
+ title: 'View webprofiler toolbar'
+ description: 'View webprofiler toolbar.'
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.routing.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.routing.yml
new file mode 100644
index 000000000..200bc3973
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.routing.yml
@@ -0,0 +1,81 @@
+# toolbar
+webprofiler.toolbar:
+ path: '/profiler/{profile}'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\ToolbarController::toolbarAction'
+ options:
+ parameters:
+ profile:
+ type: 'webprofiler:token'
+ requirements:
+ _permission: 'view webprofiler toolbar'
+
+# save frontend data
+webprofiler.frontend.save:
+ path: '/admin/reports/profiler/frontend/{profile}/save'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\ToolbarController::savePerformanceTimingAction'
+ _title: 'Save performance timing data'
+ options:
+ parameters:
+ profile:
+ type: 'webprofiler:token'
+ methods: [POST]
+ requirements:
+ _permission: 'access webprofiler'
+
+# view profile
+webprofiler.dashboard:
+ path: '/admin/reports/profiler/view/{profile}'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\DashboardController::dashboardAction'
+ _title: 'Webprofiler'
+ options:
+ parameters:
+ profile:
+ type: 'webprofiler:token'
+ requirements:
+ _permission: 'access webprofiler'
+
+# list stored profiles
+webprofiler.admin_list:
+ path: '/admin/reports/profiler/list'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\DashboardController::listAction'
+ _title: 'Webprofiler'
+ requirements:
+ _permission: 'access webprofiler'
+
+webprofiler.rest.collector:
+ path: '/admin/reports/profiler/view/{profile}/collectors/{collector}'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\DashboardController::restCollectorAction'
+ _title: 'Webprofiler'
+ options:
+ parameters:
+ profile:
+ type: 'webprofiler:token'
+ requirements:
+ _permission: 'access webprofiler'
+
+# get query explain
+webprofiler.database.explain:
+ path: '/admin/reports/profiler/database_explain/{profile}/{qid}'
+ defaults:
+ _controller: '\Drupal\webprofiler\Controller\DatabaseController::explainAction'
+ _title: 'Query explain'
+ options:
+ parameters:
+ profile:
+ type: 'webprofiler:token'
+ requirements:
+ _permission: 'access webprofiler'
+
+# configure webprofiler
+webprofiler.settings:
+ path: '/admin/config/development/devel/webprofiler'
+ defaults:
+ _form: 'Drupal\webprofiler\Form\ConfigForm'
+ _title: 'Webprofiler settings'
+ requirements:
+ _permission: 'access webprofiler'
diff --git a/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.services.yml b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.services.yml
new file mode 100644
index 000000000..19b7e31f8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/devel/webprofiler/webprofiler.services.yml
@@ -0,0 +1,267 @@
+parameters:
+ webprofiler.only_exceptions: false
+ webprofiler.only_master_requests: false
+
+services:
+
+# profiler services
+ logger.channel.webprofiler:
+ class: Drupal\Core\Logger\LoggerChannel
+ factory: logger.factory:get
+ arguments: ['webprofiler']
+
+ profiler.file_storage:
+ class: Drupal\webprofiler\Profiler\FileProfilerStorage
+ arguments: ['%data_collector.storage%']
+ tags:
+ - { name: webprofiler_storage, title: 'File storage' }
+
+ profiler.database_storage:
+ class: Drupal\webprofiler\Profiler\DatabaseProfilerStorage
+ arguments: ['@database']
+ tags:
+ - { name: webprofiler_storage, title: 'Database storage' }
+
+ profiler.storage_manager:
+ class: Drupal\webprofiler\Profiler\ProfilerStorageManager
+
+ profiler.storage:
+ class: Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface
+ factory: Drupal\webprofiler\Profiler\ProfilerStorageFactory::getProfilerStorage
+ arguments: ['@config.factory', '@service_container']
+
+ profiler:
+ class: Drupal\webprofiler\Profiler\Profiler
+ arguments: ['@profiler.storage', '@logger.channel.webprofiler', '@config.factory']
+
+# template manager
+ template_manager:
+ class: Drupal\webprofiler\Profiler\TemplateManager
+ arguments: ['@profiler', '@twig', '@twig.loader', '%data_collector.templates%']
+
+# request matcher
+ webprofiler.matcher:
+ class: Drupal\webprofiler\RequestMatcher\WebprofilerRequestMatcher
+ arguments: ['@config.factory', '@path.matcher']
+
+# event subscribers
+ webprofiler.profiler_listener:
+ class: Symfony\Component\HttpKernel\EventListener\ProfilerListener
+ arguments: ['@profiler', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%', '@request_stack']
+ tags:
+ - { name: event_subscriber }
+
+ webprofiler.webprofiler_event_subscriber:
+ class: Drupal\webprofiler\EventSubscriber\WebprofilerEventSubscriber
+ arguments: ['@current_user', '@url_generator', '@renderer']
+ tags:
+ - { name: event_subscriber }
+
+# twig profiler
+ twig.profile:
+ class: Twig_Profiler_Profile
+
+ twig.extension.profiler:
+ class: Drupal\webprofiler\Twig\Extension\ProfilerExtension
+ arguments: ['@twig.profile', '@stopwatch', '@webprofiler.ide_link_generator', '@webprofiler.class_shortener']
+ tags:
+ - { name: twig.extension, priority: 100 }
+
+# datacollector services
+ webprofiler.drupal:
+ class: Drupal\webprofiler\DataCollector\DrupalDataCollector
+ arguments: ['@redirect.destination', '@url_generator']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/drupal.html.twig', id: 'drupal', title: 'Drupal', priority: 10 }
+
+ webprofiler.devel:
+ class: Drupal\webprofiler\DataCollector\DevelDataCollector
+ arguments: ['@menu.link_tree']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/devel.html.twig', id: 'devel', title: 'Devel', priority: 15 }
+
+ webprofiler.php_config:
+ class: Drupal\webprofiler\DataCollector\PhpConfigDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/php_config.html.twig', id: 'php_config', title: 'PHP Config', priority: 20 }
+
+ webprofiler.request:
+ class: Drupal\webprofiler\DataCollector\RequestDataCollector
+ arguments: ['@controller_resolver']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/request.html.twig', id: 'request', title: 'Request', priority: 30 }
+ - { name: event_subscriber }
+
+ webprofiler.time:
+ class: Drupal\webprofiler\DataCollector\TimeDataCollector
+ arguments: [NULL, '@stopwatch']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/time.html.twig', id: 'time', title: 'Timeline', priority: 40 }
+
+ webprofiler.performance_timing:
+ class: Drupal\webprofiler\DataCollector\PerformanceTimingDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/performance_timing.html.twig', id: 'performance_timing', title: 'Performance Timing', priority: 50 }
+
+ webprofiler.database:
+ class: Drupal\webprofiler\DataCollector\DatabaseDataCollector
+ arguments: ['@database', '@config.factory']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/database.html.twig', id: 'database', title: 'Database', priority: 60 }
+
+ webprofiler.user:
+ class: Drupal\webprofiler\DataCollector\UserDataCollector
+ arguments: ['@current_user', '@entity.manager', '@config.factory', '@authentication_collector']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/user.html.twig', id: 'user', title: 'User', priority: 70 }
+
+ webprofiler.forms:
+ class: Drupal\webprofiler\DataCollector\FormsDataCollector
+ arguments: ['@form_builder']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/forms.html.twig', id: 'forms', title: 'Forms', priority: 80 }
+
+ webprofiler.drupal_extensions:
+ class: Drupal\webprofiler\DataCollector\ExtensionDataCollector
+ arguments: ['@module_handler', '@theme_handler', '@app.root']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/extensions.html.twig', id: 'drupal_extension', title: 'Extensions', priority: 90 }
+
+ webprofiler.routing:
+ class: Drupal\webprofiler\DataCollector\RoutingDataCollector
+ arguments: ['@router.route_provider']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/routing.html.twig', id: 'routing', title: 'Routing', priority: 100 }
+
+ webprofiler.cache:
+ class: Drupal\webprofiler\DataCollector\CacheDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/cache.html.twig', id: 'cache', title: 'Cache', priority: 110 }
+
+ webprofiler.assets:
+ class: Drupal\webprofiler\DataCollector\AssetsDataCollector
+ arguments: ['@app.root']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/assets.html.twig', id: 'assets', title: 'Assets', priority: 120}
+
+ webprofiler.config:
+ class: Drupal\webprofiler\DataCollector\ConfigDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/config.html.twig', id: 'config', title: 'Config', priority: 130 }
+
+ webprofiler.state:
+ class: Drupal\webprofiler\DataCollector\StateDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/state.html.twig', id: 'state', title: 'State', priority: 140 }
+
+ webprofiler.events:
+ class: Drupal\webprofiler\DataCollector\EventsDataCollector
+ arguments: ['@event_dispatcher']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/events.html.twig', id: 'events', title: 'Events', priority: 150 }
+
+ webprofiler.services:
+ class: Drupal\webprofiler\DataCollector\ServicesDataCollector
+ arguments: ['@service_container']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/services.html.twig', id: 'services', title: 'Services', priority: 160 }
+
+ webprofiler.http:
+ class: Drupal\webprofiler\DataCollector\HttpDataCollector
+ arguments: ['@http_client_middleware.webprofiler']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/http.html.twig', id: 'http', title: 'Http', priority: 170 }
+
+ webprofiler.theme:
+ class: Drupal\webprofiler\DataCollector\ThemeDataCollector
+ arguments: ['@theme.manager', '@theme.negotiator', '@twig.profile']
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/theme.html.twig', id: 'theme', title: 'Theme', priority: 180 }
+
+ webprofiler.mail:
+ class: Drupal\webprofiler\DataCollector\MailDataCollector
+ tags:
+ - { name: data_collector, template: '@webprofiler/Collector/mail.html.twig', id: 'mail', title: 'Mail', priority: 190 }
+
+# debug services
+ stopwatch:
+ class: Drupal\webprofiler\Stopwatch
+
+ webprofiler.debug.event_dispatcher.default:
+ class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
+ arguments: ['@service_container']
+
+ webprofiler.debug.plugin.manager.mail.default:
+ class: Drupal\Core\Mail\MailManager
+ arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation']
+
+ webprofiler.debug.controller_resolver:
+ class: Symfony\Component\HttpKernel\Controller\TraceableControllerResolver
+ arguments: ['@controller_resolver', '@stopwatch']
+
+ webprofiler.debug.cache_factory:
+ class: Drupal\webprofiler\Cache\CacheFactoryWrapper
+ public: false
+ decorates: cache_factory
+ arguments: ['@webprofiler.debug.cache_factory.inner', '@webprofiler.cache']
+ properties:
+ _serviceId: 'cache_factory'
+
+ webprofiler.debug.asset.css.collection_renderer:
+ class: Drupal\webprofiler\Asset\CssCollectionRendererWrapper
+ public: false
+ decorates: asset.css.collection_renderer
+ arguments: ['@webprofiler.debug.asset.css.collection_renderer.inner', '@webprofiler.assets']
+ properties:
+ _serviceId: 'asset.css.collection_renderer'
+
+ webprofiler.debug.asset.js.collection_renderer:
+ class: Drupal\webprofiler\Asset\JsCollectionRendererWrapper
+ public: false
+ decorates: asset.js.collection_renderer
+ arguments: ['@webprofiler.debug.asset.js.collection_renderer.inner', '@webprofiler.assets']
+ properties:
+ _serviceId: 'asset.js.collection_renderer'
+
+ webprofiler.debug.entity_type.manager:
+ class: Drupal\webprofiler\Entity\EntityManagerWrapper
+ public: false
+ decorates: entity_type.manager
+ arguments: ['@webprofiler.debug.entity_type.manager.inner']
+ properties:
+ _serviceId: 'entity_type.manager'
+
+ webprofiler.debug.state:
+ class: Drupal\webprofiler\State\StateWrapper
+ public: false
+ decorates: state
+ arguments: ['@webprofiler.debug.state.inner', '@webprofiler.state']
+ properties:
+ _serviceId: 'state'
+
+# middleware
+ http_middleware.webprofiler:
+ class: Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware
+ tags:
+ - { name: http_middleware, priority: 350 }
+
+ http_client_middleware.webprofiler:
+ class: Drupal\webprofiler\Http\HttpClientMiddleware
+ tags:
+ - { name: http_client_middleware }
+
+# parameter converter service for profile token
+ webprofiler.token_converter:
+ class: Drupal\webprofiler\Routing\TokenConverter
+ arguments: ['@profiler']
+ tags:
+ - { name: paramconverter }
+
+# IDE link generator service
+ webprofiler.ide_link_generator:
+ class: Drupal\webprofiler\Helper\IdeLinkGenerator
+ arguments: ['@config.factory']
+
+# class shortener service
+ webprofiler.class_shortener:
+ class: Drupal\webprofiler\Helper\ClassShortener
diff --git a/sites/all/modules/contrib/dev/entity/.travis.yml b/sites/all/modules/contrib/dev/entity/.travis.yml
new file mode 100644
index 000000000..052224c78
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/.travis.yml
@@ -0,0 +1,58 @@
+language: php
+sudo: false
+
+php:
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+matrix:
+ allow_failures:
+ # We cannot use hhvm-nightly since that does not work in Travis CI's old
+ # Ubuntu 12.04.
+ - php: hhvm
+ # Don't wait for the allowed failures to build.
+ fast_finish: true
+
+mysql:
+ database: entity
+ username: root
+ encoding: utf8
+
+before_script:
+ # Remove Xdebug as we don't need it and it causes
+ # PHP Fatal error: Maximum function nesting level of '256' reached.
+ # We also don't care if that file exists or not on PHP 7.
+ - phpenv config-rm xdebug.ini || true
+
+ # Remember the current entity test directory for later use in the Drupal
+ # installation.
+ - TESTDIR=$(pwd)
+ # Navigate out of module directory to prevent blown stack by recursive module
+ # lookup.
+ - cd ..
+
+ # Create database.
+ - mysql -e 'create database entity'
+ # Export database variable for kernel tests.
+ - export SIMPLETEST_DB=mysql://root:@127.0.0.1/entity
+ # Download Drupal 8 core.
+ - travis_retry git clone --branch 8.1.x --depth 1 http://git.drupal.org/project/drupal.git
+ - cd drupal
+ - composer self-update
+ - composer install -n
+
+ # Reference entity in build site.
+ - ln -s $TESTDIR modules/entity
+
+ # Start a web server on port 8888, run in the background; wait for
+ # initialization.
+ - nohup php -S localhost:8888 > /dev/null 2>&1 &
+
+ # Export web server URL for browser tests.
+ - export SIMPLETEST_BASE_URL=http://localhost:8888
+
+script:
+ # Run the PHPUnit tests which also include the kernel tests.
+ - ./vendor/phpunit/phpunit/phpunit -c ./core/phpunit.xml.dist --verbose ./modules/entity
diff --git a/sites/all/modules/contrib/dev/entity/LICENSE.txt b/sites/all/modules/contrib/dev/entity/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/dev/entity/README.txt b/sites/all/modules/contrib/dev/entity/README.txt
new file mode 100644
index 000000000..c19422cd3
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/README.txt
@@ -0,0 +1,11 @@
+Entity API module
+-----------------
+
+This module contains improvements and extensions to the Drupal 8 Entity system.
+The goal is to bring useful improvements to upcoming Drupal 8 minor releases
+(e.g. 8.1, 8.2, ..) while maintaining backwards compatibility in future Entity
+API module versions (based on the improvements which went into core).
+
+@todo: Explain version compatibility pattern.
+@todo: Add overview of all items and maintainers.
+
diff --git a/sites/all/modules/contrib/dev/entity/composer.json b/sites/all/modules/contrib/dev/entity/composer.json
new file mode 100644
index 000000000..b41c15ad7
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/composer.json
@@ -0,0 +1,10 @@
+{
+ "name": "drupal/entity",
+ "type": "drupal-module",
+ "description": "Provides expanded entity APIs, which will be moved to Drupal core one day.",
+ "homepage": "http://drupal.org/project/entity",
+ "license": "GPL-2.0+",
+ "require": {
+ "drupal/core": "~8.1"
+ }
+}
diff --git a/sites/all/modules/contrib/dev/entity/config/schema/entity.schema.yml b/sites/all/modules/contrib/dev/entity/config/schema/entity.schema.yml
new file mode 100644
index 000000000..b3c192358
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/config/schema/entity.schema.yml
@@ -0,0 +1,11 @@
+action.configuration.entity_delete_action:*:
+ type: action_configuration_default
+ label: 'Delete entity configuration'
+
+views.field.rendered_entity:
+ type: views_field
+ label: 'Rendered entity'
+ mapping:
+ view_mode:
+ type: string
+ label: 'View mode'
diff --git a/sites/all/modules/contrib/dev/entity/entity.info.yml b/sites/all/modules/contrib/dev/entity/entity.info.yml
new file mode 100644
index 000000000..5f3bdd3e8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/entity.info.yml
@@ -0,0 +1,12 @@
+name: Entity
+description: Provides expanded entity APIs, which will be moved to Drupal core one day.
+type: module
+# core: 8.x
+dependencies:
+ - system (>=8.1.0)
+
+# Information added by Drupal.org packaging script on 2016-06-23
+version: '8.x-1.0-alpha3+2-dev'
+core: '8.x'
+project: 'entity'
+datestamp: 1466693538
diff --git a/sites/all/modules/contrib/dev/entity/entity.links.task.yml b/sites/all/modules/contrib/dev/entity/entity.links.task.yml
new file mode 100644
index 000000000..8116ae326
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/entity.links.task.yml
@@ -0,0 +1,3 @@
+entity.revisions_overview:
+ deriver: 'Drupal\entity\Plugin\Derivative\RevisionsOverviewDeriver'
+ weight: 100
diff --git a/sites/all/modules/contrib/dev/entity/entity.module b/sites/all/modules/contrib/dev/entity/entity.module
new file mode 100644
index 000000000..8f330b28e
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/entity.module
@@ -0,0 +1,6 @@
+getDefinitions();
+ $entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
+ return $entity_type->isSubclassOf(ContentEntityInterface::class);
+ });
+
+ $data = [];
+ foreach ($entity_types as $entity_type) {
+ /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+ $base_table = $entity_type->getBaseTable() ?: $entity_type->id();
+
+ if ($entity_type->hasViewBuilderClass()) {
+ $data[$base_table]['rendered_entity'] = [
+ 'field' => [
+ 'title' => t('Rendered entity'),
+ 'help' => t('Renders an entity in a view mode.'),
+ 'id' => 'rendered_entity',
+ ],
+ ];
+ }
+ }
+
+ return $data;
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php b/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php
new file mode 100644
index 000000000..d9556e1dc
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php
@@ -0,0 +1,159 @@
+entityTypeManager = $entity_type_manager;
+ $this->requestStack = $request_stack;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access(Route $route, AccountInterface $account, Request $request = NULL) {
+ if (empty($request)) {
+ $request = $this->requestStack->getCurrentRequest();
+ }
+
+ $operation = $route->getRequirement('_entity_access_revision');
+ list(, $operation) = explode('.', $operation, 2);
+
+ if ($operation === 'list') {
+ $_entity = $request->attributes->get('_entity', $request->attributes->get($route->getOption('entity_type_id')));
+ return AccessResult::allowedIf($this->checkAccess($_entity, $account, $operation))->cachePerPermissions();
+ }
+ else {
+ $_entity_revision = $request->attributes->get('_entity_revision');
+ return AccessResult::allowedIf($_entity_revision && $this->checkAccess($_entity_revision, $account, $operation))->cachePerPermissions();
+ }
+ }
+
+ protected function checkAccess(ContentEntityInterface $entity, AccountInterface $account, $operation = 'view') {
+ $entity_type = $entity->getEntityType();
+ $entity_type_id = $entity->getEntityTypeId();
+ $entity_access = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
+
+ /** @var \Drupal\Core\Entity\EntityStorageInterface $entity_storage */
+ $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
+
+ $map = [
+ 'view' => "view all $entity_type_id revisions",
+ 'list' => "view all $entity_type_id revisions",
+ 'update' => "revert all $entity_type_id revisions",
+ 'delete' => "delete all $entity_type_id revisions",
+ ];
+ $bundle = $entity->bundle();
+ $type_map = [
+ 'view' => "view $entity_type_id $bundle revisions",
+ 'list' => "view $entity_type_id $bundle revisions",
+ 'update' => "revert $entity_type_id $bundle revisions",
+ 'delete' => "delete $entity_type_id $bundle revisions",
+ ];
+
+ if (!$entity || !isset($map[$operation]) || !isset($type_map[$operation])) {
+ // If there was no node to check against, or the $op was not one of the
+ // supported ones, we return access denied.
+ return FALSE;
+ }
+
+ // Statically cache access by revision ID, language code, user account ID,
+ // and operation.
+ $langcode = $entity->language()->getId();
+ $cid = $entity->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $operation;
+
+ if (!isset($this->accessCache[$cid])) {
+ // Perform basic permission checks first.
+ if (!$account->hasPermission($map[$operation]) && !$account->hasPermission($type_map[$operation]) && !$account->hasPermission('administer nodes')) {
+ $this->accessCache[$cid] = FALSE;
+ return FALSE;
+ }
+
+ if (($admin_permission = $entity_type->getAdminPermission()) && $account->hasPermission($admin_permission)) {
+ $this->accessCache[$cid] = TRUE;
+ }
+ else {
+ // First check the access to the default revision and finally, if the
+ // node passed in is not the default revision then access to that, too.
+ $this->accessCache[$cid] = $entity_access->access($entity_storage->load($entity->id()), $operation, $account) && ($entity->isDefaultRevision() || $entity_access->access($entity, $operation, $account));
+ }
+ }
+
+ return $this->accessCache[$cid];
+ }
+
+
+ /**
+ * Counts the number of revisions in the default language.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity.
+ * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
+ * The entity storage.
+ *
+ * @return int
+ * The number of revisions in the default language.
+ */
+ protected function countDefaultLanguageRevisions(ContentEntityInterface $entity, EntityStorageInterface $entity_storage) {
+ $entity_type = $entity->getEntityType();
+ $count = $entity_storage->getQuery()
+ ->allRevisions()
+ ->condition($entity_type->getKey('id'), $entity->id())
+ ->condition($entity_type->getKey('default_langcode'), 1)
+ ->count()
+ ->execute();
+ return $count;
+ }
+
+ /**
+ * Resets the access cache.
+ *
+ * @return $this
+ */
+ public function resetAccessCache() {
+ $this->accessCache = [];
+ return $this;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Controller/RevisionControllerTrait.php b/sites/all/modules/contrib/dev/entity/src/Controller/RevisionControllerTrait.php
new file mode 100644
index 000000000..9751b17ae
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Controller/RevisionControllerTrait.php
@@ -0,0 +1,200 @@
+getEntityType();
+ $result = $this->entityTypeManager()->getStorage($entity_type->id())->getQuery()
+ ->allRevisions()
+ ->condition($entity_type->getKey('id'), $entity->id())
+ ->sort($entity_type->getKey('revision'), 'DESC')
+ ->execute();
+ return array_keys($result);
+ }
+
+ /**
+ * Generates an overview table of older revisions of an entity.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * An entity object.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function revisionOverview(ContentEntityInterface $entity) {
+ $langcode = $this->languageManager()
+ ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
+ ->getId();
+ $entity_storage = $this->entityTypeManager()
+ ->getStorage($entity->getEntityTypeId());
+
+ $header = [$this->t('Revision'), $this->t('Operations')];
+ $rows = [];
+
+ $revision_ids = $this->revisionIds($entity);
+ // @todo Expand the entity storage to load multiple revisions.
+ $entity_revisions = array_combine($revision_ids, array_map(function($vid) use ($entity_storage) {
+ return $entity_storage->loadRevision($vid);
+ }, $revision_ids));
+
+ foreach ($entity_revisions as $revision) {
+ $row = [];
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
+ if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)
+ ->isRevisionTranslationAffected()
+ ) {
+ $row[] = $this->getRevisionDescription($revision, $revision->isDefaultRevision());
+
+ if ($revision->isDefaultRevision()) {
+ $row[] = [
+ 'data' => [
+ '#prefix' => '',
+ '#markup' => $this->t('Current revision'),
+ '#suffix' => '',
+ ],
+ ];
+ foreach ($row as &$current) {
+ $current['class'] = ['revision-current'];
+ }
+ }
+ else {
+ $links = $this->getOperationLinks($revision);
+ $row[] = [
+ 'data' => [
+ '#type' => 'operations',
+ '#links' => $links,
+ ],
+ ];
+ }
+ }
+
+ $rows[] = $row;
+ }
+
+ $build[$entity->getEntityTypeId() . '_revisions_table'] = [
+ '#theme' => 'table',
+ '#rows' => $rows,
+ '#header' => $header,
+ ];
+
+ // We have no clue about caching yet.
+ $build['#cache']['max-age'] = 0;
+
+ return $build;
+ }
+
+ /**
+ * Get the links of the operations for an entity revision.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity_revision
+ * The entity to build the revision links for.
+ *
+ * @return array
+ * The operation links.
+ */
+ protected function getOperationLinks(EntityInterface $entity_revision) {
+ $links = [];
+ if ($this->hasRevertRevisionAccess($entity_revision)) {
+ $links['revert'] = $this->buildRevertRevisionLink($entity_revision);
+ }
+
+ if ($this->hasDeleteRevisionAccess($entity_revision)) {
+ $links['delete'] = $this->buildDeleteRevisionLink($entity_revision);
+ }
+
+ return array_filter($links);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Controller/RevisionOverviewController.php b/sites/all/modules/contrib/dev/entity/src/Controller/RevisionOverviewController.php
new file mode 100644
index 000000000..502a67cb8
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Controller/RevisionOverviewController.php
@@ -0,0 +1,161 @@
+dateFormatter = $date_formatter;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static($container->get('date.formatter'), $container->get('renderer'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function hasDeleteRevisionAccess(EntityInterface $entity) {
+ return $this->currentUser()->hasPermission("delete all {$entity->id()} revisions");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildRevertRevisionLink(EntityInterface $entity_revision) {
+ if ($entity_revision->hasLinkTemplate('revision-revert-form')) {
+ return [
+ 'title' => t('Revert'),
+ 'url' => $entity_revision->toUrl('revision-revert-form'),
+ ];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildDeleteRevisionLink(EntityInterface $entity_revision) {
+ if ($entity_revision->hasLinkTemplate('revision-delete-form')) {
+ return [
+ 'title' => t('Delete'),
+ 'url' => $entity_revision->toUrl('revision-delete-form'),
+ ];
+ }
+ }
+
+ /**
+ * Generates an overview table of older revisions of an entity.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+ * The route match.
+ *
+ * @return array
+ * A render array.
+ */
+ public function revisionOverviewController(RouteMatchInterface $route_match) {
+ return $this->revisionOverview($route_match->getParameter($route_match->getRouteObject()->getOption('entity_type_id')));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRevisionDescription(ContentEntityInterface $revision, $is_default = FALSE) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\user\EntityOwnerInterface|\Drupal\Core\Entity\RevisionLogInterface $revision */
+ if ($revision instanceof RevisionLogInterface) {
+ // Use revision link to link to revisions that are not active.
+ $date = $this->dateFormatter->format($revision->getRevisionCreationTime(), 'short');
+ $link = $revision->toLink($date, 'revision');
+
+ // @todo: Simplify this when https://www.drupal.org/node/2334319 lands.
+ $username = [
+ '#theme' => 'username',
+ '#account' => $revision->getRevisionUser(),
+ ];
+ $username = $this->renderer->render($username);
+ }
+ else {
+ $link = $revision->toLink($revision->label(), 'revision');
+ $username = '';
+
+ }
+
+ $markup = '';
+ if ($revision instanceof RevisionLogInterface) {
+ $markup = $revision->getRevisionLogMessage();
+ }
+
+ if ($username) {
+ $template = '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}
{{ message }}
{% endif %}';
+ }
+ else {
+ $template = '{% trans %} {{ date }} {% endtrans %}{% if message %}
{{ message }}
{% endif %}';
+ }
+
+ $column = [
+ 'data' => [
+ '#type' => 'inline_template',
+ '#template' => $template,
+ '#context' => [
+ 'date' => $link->toString(),
+ 'username' => $username,
+ 'message' => ['#markup' => $markup, '#allowed_tags' => Xss::getHtmlTagList()],
+ ],
+ ],
+ ];
+ return $column;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function hasRevertRevisionAccess(EntityInterface $entity) {
+ return AccessResult::allowedIfHasPermission($this->currentUser(), "revert all {$entity->getEntityTypeId()} revisions")->orIf(
+ AccessResult::allowedIfHasPermission($this->currentUser(), "revert {$entity->bundle()} {$entity->getEntityTypeId()} revisions")
+ );
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Entity/RevisionableEntityBundleInterface.php b/sites/all/modules/contrib/dev/entity/src/Entity/RevisionableEntityBundleInterface.php
new file mode 100644
index 000000000..6e543b17d
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Entity/RevisionableEntityBundleInterface.php
@@ -0,0 +1,17 @@
+getEntityTypeId();
+ if (($entity instanceof ContentEntityInterface && $entity->isDefaultRevision()) || !$entity->getEntityType()->isRevisionable()) {
+ $build['#contextual_links'][$entity_type_id] = [
+ 'route_parameters' => [
+ $entity_type_id => $entity->id()
+ ],
+ ];
+ }
+ else {
+ $build['#contextual_links'][$entity_type_id . '_revision'] = [
+ 'route_parameters' => [
+ $entity_type_id => $entity->id(),
+ $entity_type_id . '_revision' => $entity->getRevisionId(),
+ ],
+ ];
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultiple.php b/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultiple.php
new file mode 100644
index 000000000..6268a95ac
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultiple.php
@@ -0,0 +1,226 @@
+ langcodes format.
+ *
+ * @var array
+ */
+ protected $selection = [];
+
+ /**
+ * Constructs a new DeleteMultiple object.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $current_user
+ * The current user.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+ * The tempstore factory.
+ */
+ public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory) {
+ $this->currentUser = $current_user;
+ $this->entityTypeManager = $entity_type_manager;
+ $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('current_user'),
+ $container->get('entity_type.manager'),
+ $container->get('user.private_tempstore')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'entity_delete_multiple_confirm';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ return new Url('entity.' . $this->entityTypeId . '.collection');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return $this->t('Delete');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $entity_type_id
+ * The entity type id.
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
+ $this->entityTypeId = $entity_type_id;
+ $this->selection = $this->tempStore->get($this->currentUser->id());
+ if (empty($this->entityTypeId) || empty($this->selection)) {
+ return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
+ }
+
+ $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
+ /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
+ $entities = $storage->loadMultiple(array_keys($this->selection));
+ $items = [];
+ foreach ($this->selection as $id => $langcodes) {
+ foreach ($langcodes as $langcode) {
+ $entity = $entities[$id]->getTranslation($langcode);
+ $key = $id . ':' . $langcode;
+ $default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
+
+ // If we have a translated entity we build a nested list of translations
+ // that will be deleted.
+ $languages = $entity->getTranslationLanguages();
+ if (count($languages) > 1 && $entity->isDefaultTranslation()) {
+ $names = [];
+ foreach ($languages as $translation_langcode => $language) {
+ $names[] = $language->getName();
+ unset($items[$id . ':' . $translation_langcode]);
+ }
+ $items[$default_key] = [
+ 'label' => [
+ '#markup' => $this->t('@label (Original translation) - The following translations will be deleted:', ['@label' => $entity->label()]),
+ ],
+ 'deleted_translations' => [
+ '#theme' => 'item_list',
+ '#items' => $names,
+ ],
+ ];
+ }
+ elseif (!isset($items[$default_key])) {
+ $items[$key] = $entity->label();
+ }
+ }
+ }
+
+ $form['entities'] = [
+ '#theme' => 'item_list',
+ '#items' => $items,
+ ];
+ $form = parent::buildForm($form, $form_state);
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $total_count = 0;
+ $delete_entities = [];
+ $delete_translations = [];
+ $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
+ /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
+ $entities = $storage->loadMultiple(array_keys($this->selection));
+
+ foreach ($this->selection as $id => $langcodes) {
+ foreach ($langcodes as $langcode) {
+ $entity = $entities[$id]->getTranslation($langcode);
+ if ($entity->isDefaultTranslation()) {
+ $delete_entities[$id] = $entity;
+ unset($delete_translations[$id]);
+ $total_count += count($entity->getTranslationLanguages());
+ }
+ elseif (!isset($delete_entities[$id])) {
+ $delete_translations[$id][] = $entity;
+ }
+ }
+ }
+
+ if ($delete_entities) {
+ $storage->delete($delete_entities);
+ $this->logger('content')->notice('Deleted @count @entity_type items.', [
+ '@count' => count($delete_entities),
+ '@entity_type' => $this->entityTypeId,
+ ]);
+ }
+
+ if ($delete_translations) {
+ $count = 0;
+ /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
+ foreach ($delete_translations as $id => $translations) {
+ $entity = $entities[$id]->getUntranslated();
+ foreach ($translations as $translation) {
+ $entity->removeTranslation($translation->language()->getId());
+ }
+ $entity->save();
+ $count += count($translations);
+ }
+ if ($count) {
+ $total_count += $count;
+ $this->logger('content')->notice('Deleted @count @entity_type translations.', [
+ '@count' => $count,
+ '@entity_type' => $this->entityTypeId,
+ ]);
+ }
+ }
+
+ if ($total_count) {
+ drupal_set_message($this->formatPlural($total_count, 'Deleted 1 item.', 'Deleted @count items.'));
+ }
+ $this->tempStore->delete($this->currentUser->id());
+ $form_state->setRedirectUrl($this->getCancelUrl());
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php b/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php
new file mode 100644
index 000000000..c94c53a0c
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php
@@ -0,0 +1,167 @@
+dateFormatter = $date_formatter;
+ $this->bundleInformation = $bundle_information;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('date.formatter'),
+ $container->get('entity_type.bundle.info')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'entity_revision_revert_confirm';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ if ($this->revision instanceof RevisionLogInterface) {
+ return $this->t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]);
+ }
+ return $this->t('Are you sure you want to revert the revision?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ if ($this->revision->getEntityType()->hasLinkTemplate('version-history')) {
+ return $this->revision->toUrl('version-history');
+ }
+ return $this->revision->toUrl();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return t('Revert');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescription() {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $_entity_revision = NULL, Request $request = NULL) {
+ $this->revision = $_entity_revision;
+ $form = parent::buildForm($form, $form_state);
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ // The revision timestamp will be updated when the revision is saved. Keep
+ // the original one for the confirmation message.
+ $this->revision = $this->prepareRevision($this->revision);
+ if ($this->revision instanceof RevisionLogInterface) {
+ $original_revision_timestamp = $this->revision->getRevisionCreationTime();
+
+ $this->revision->setRevisionLogMessage($this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]));
+ drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)]));
+ }
+ else {
+ drupal_set_message(t('@type %title has been reverted', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label()]));
+ }
+
+ $this->revision->save();
+
+ $this->logger('content')->notice('@type: reverted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
+ $form_state->setRedirect(
+ "entity.{$this->revision->getEntityTypeId()}.version_history",
+ [$this->revision->getEntityTypeId() => $this->revision->id()]
+ );
+ }
+
+ /**
+ * Prepares a revision to be reverted.
+ *
+ * @param \Drupal\Core\Entity\RevisionableInterface $revision
+ * The revision to be reverted.
+ *
+ * @return \Drupal\Core\Entity\RevisionableInterface
+ * The prepared revision ready to be stored.
+ */
+ protected function prepareRevision(RevisionableInterface $revision) {
+ $revision->setNewRevision();
+ $revision->isDefaultRevision(TRUE);
+
+ return $revision;
+ }
+
+ /**
+ * Returns a bundle label.
+ *
+ * @param \Drupal\Core\Entity\RevisionableInterface $revision
+ * The entity revision.
+ *
+ * @return string
+ */
+ protected function getBundleLabel(RevisionableInterface $revision) {
+ /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $revision */
+ $bundle_info = $this->bundleInformation->getBundleInfo($revision->getEntityTypeId());
+ return $bundle_info[$revision->bundle()]['label'];
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php b/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php
new file mode 100644
index 000000000..00c67c93b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php
@@ -0,0 +1,159 @@
+getBundleEntity();
+
+ // Set up default values, if required.
+ if (!$this->entity->isNew()) {
+ $this->entity->setRevisionLogMessage(NULL);
+ }
+
+ if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
+ // Always use the default revision setting.
+ $this->entity->setNewRevision($bundle_entity && $bundle_entity->shouldCreateNewRevision());
+ }
+ }
+
+ /**
+ * Returns the bundle entity of the entity, or NULL if there is none.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface|null
+ */
+ protected function getBundleEntity() {
+ if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) {
+ return $this->entity->{$bundle_key}->referencedEntities()[0];
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, FormStateInterface $form_state) {
+ $entity_type = $this->entity->getEntityType();
+ $bundle_entity = $this->getBundleEntity();
+ $account = $this->currentUser();
+
+ if ($this->operation == 'edit') {
+ $form['#title'] = $this->t('Edit %bundle_label @label', [
+ '%bundle_label' => $bundle_entity ? $bundle_entity->label() : '',
+ '@label' => $this->entity->label(),
+ ]);
+ }
+
+ $form['advanced'] = [
+ '#type' => 'vertical_tabs',
+ '#weight' => 99,
+ ];
+
+ // Add a log field if the "Create new revision" option is checked, or if the
+ // current user has the ability to check that option.
+ // @todo Could we autogenerate this form by using some widget on the
+ // revision info field.
+ $form['revision_information'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Revision information'),
+ // Open by default when "Create new revision" is checked.
+ '#open' => $this->entity->isNewRevision(),
+ '#group' => 'advanced',
+ '#weight' => 20,
+ '#access' => $this->entity->isNewRevision() || $account->hasPermission($entity_type->get('admin_permission')),
+ ];
+
+ $form['revision_information']['revision'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Create new revision'),
+ '#default_value' => $this->entity->isNewRevision(),
+ '#access' => $account->hasPermission($entity_type->get('admin_permission')),
+ ];
+
+ // Check the revision log checkbox when the log textarea is filled in.
+ // This must not happen if "Create new revision" is enabled by default,
+ // since the state would auto-disable the checkbox otherwise.
+ if (!$this->entity->isNewRevision()) {
+ $form['revision_information']['revision']['#states'] = [
+ 'checked' => [
+ 'textarea[name="revision_log"]' => ['empty' => FALSE],
+ ],
+ ];
+ }
+
+ $form['revision_information']['revision_log'] = [
+ '#type' => 'textarea',
+ '#title' => $this->t('Revision log message'),
+ '#rows' => 4,
+ '#default_value' => $this->entity->getRevisionLogMessage(),
+ '#description' => $this->t('Briefly describe the changes you have made.'),
+ ];
+
+ return parent::form($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state) {
+ // Save as a new revision if requested to do so.
+ if (!$form_state->isValueEmpty('revision')) {
+ $this->entity->setNewRevision();
+ }
+
+ $insert = $this->entity->isNew();
+ $this->entity->save();
+ $context = ['@type' => $this->entity->bundle(), '%info' => $this->entity->label()];
+ $logger = $this->logger($this->entity->id());
+ $bundle_entity = $this->getBundleEntity();
+ $t_args = ['@type' => $bundle_entity ? $bundle_entity->label() : 'None', '%info' => $this->entity->label()];
+
+ if ($insert) {
+ $logger->notice('@type: added %info.', $context);
+ drupal_set_message($this->t('@type %info has been created.', $t_args));
+ }
+ else {
+ $logger->notice('@type: updated %info.', $context);
+ drupal_set_message($this->t('@type %info has been updated.', $t_args));
+ }
+
+ if ($this->entity->id()) {
+ $form_state->setValue('id', $this->entity->id());
+ $form_state->set('id', $this->entity->id());
+
+ if ($this->entity->getEntityType()->hasLinkTemplate('collection')) {
+ $form_state->setRedirectUrl($this->entity->toUrl('collection'));
+ }
+ else {
+ $form_state->setRedirectUrl($this->entity->toUrl('canonical'));
+ }
+ }
+ else {
+ // In the unlikely case something went wrong on save, the entity will be
+ // rebuilt and entity form redisplayed.
+ drupal_set_message($this->t('The entity could not be saved.'), 'error');
+ $form_state->setRebuild();
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php
new file mode 100644
index 000000000..d61983d43
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php
@@ -0,0 +1,97 @@
+currentUser = $current_user;
+ $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
+
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('user.private_tempstore'),
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeMultiple(array $entities) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
+ $selection = [];
+ foreach ($entities as $entity) {
+ $langcode = $entity->language()->getId();
+ $selection[$entity->id()][$langcode] = $langcode;
+ }
+ $this->tempStore->set($this->currentUser->id(), $selection);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($object = NULL) {
+ $this->executeMultiple([$object]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ return $object->access('delete', $account, $return_as_object);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php
new file mode 100644
index 000000000..085bd0ee0
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php
@@ -0,0 +1,78 @@
+entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id) {
+ return new static($container->get('entity_type.manager'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ if (empty($this->derivatives)) {
+ $definitions = [];
+ foreach ($this->getParticipatingEntityTypes() as $entity_type_id => $entity_type) {
+ $definition = $base_plugin_definition;
+ $definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]);
+ $definition['type'] = $entity_type_id;
+ $definition['confirm_form_route_name'] = 'entity.' . $entity_type_id . '.delete_multiple_form';
+ $definitions[$entity_type_id] = $definition;
+ }
+ $this->derivatives = $definitions;
+ }
+
+ return parent::getDerivativeDefinitions($base_plugin_definition);
+ }
+
+ /**
+ * Gets a list of participating entity types.
+ *
+ * The list consists of all content entity types with a delete-multiple-form
+ * link template.
+ *
+ * @return \Drupal\Core\Entity\EntityTypeInterface[]
+ * The participating entity types, keyed by entity type id.
+ */
+ protected function getParticipatingEntityTypes() {
+ $entity_types = $this->entityTypeManager->getDefinitions();
+ $entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
+ return $entity_type->isSubclassOf(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
+ });
+
+ return $entity_types;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Plugin/Derivative/RevisionsOverviewDeriver.php b/sites/all/modules/contrib/dev/entity/src/Plugin/Derivative/RevisionsOverviewDeriver.php
new file mode 100644
index 000000000..5fcd21b79
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Plugin/Derivative/RevisionsOverviewDeriver.php
@@ -0,0 +1,67 @@
+entityTypeManager = $entityTypeManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id) {
+ return new static(
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ $exclude = ['node'];
+
+ $this->derivatives = [];
+ foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
+ if (in_array($entity_type_id, $exclude)) {
+ continue;
+ }
+
+ if (!$entity_type->hasLinkTemplate('version-history')) {
+ continue;
+ }
+
+ $this->derivatives[$entity_type_id] = [
+ 'route_name' => "entity.$entity_type_id.version_history",
+ 'title' => 'Revisions',
+ 'base_route' => "entity.$entity_type_id.canonical",
+ 'weight' => 20,
+ ] + $base_plugin_definition;
+ }
+
+ return parent::getDerivativeDefinitions($base_plugin_definition);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Revision/RevisionableContentEntityBase.php b/sites/all/modules/contrib/dev/entity/src/Revision/RevisionableContentEntityBase.php
new file mode 100644
index 000000000..a7ec15ee9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Revision/RevisionableContentEntityBase.php
@@ -0,0 +1,30 @@
+getEntityTypeId()] = $this->id();
+ }
+ if (strpos($this->getEntityType()->getLinkTemplate($rel), $this->getEntityTypeId() . '_revision') !== FALSE) {
+ $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
+ }
+
+ return $uri_route_parameters;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php b/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php
new file mode 100644
index 000000000..a9ca06e79
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php
@@ -0,0 +1,47 @@
+deleteMultipleFormRoute($entity_type)) {
+ $routes->add('entity.' . $entity_type->id() . '.delete_multiple_form', $route);
+ }
+
+ return $routes;
+ }
+
+ /**
+ * Returns the delete multiple form route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function deleteMultipleFormRoute(EntityTypeInterface $entity_type) {
+ if ($entity_type->hasLinkTemplate('delete-multiple-form')) {
+ $route = new Route($entity_type->getLinkTemplate('delete-multiple-form'));
+ $route->setDefault('_form', '\Drupal\entity\Form\DeleteMultiple');
+ $route->setDefault('entity_type_id', $entity_type->id());
+ $route->setRequirement('_permission', $entity_type->getAdminPermission());
+
+ return $route;
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/src/Routing/RevisionRouteProvider.php b/sites/all/modules/contrib/dev/entity/src/Routing/RevisionRouteProvider.php
new file mode 100644
index 000000000..e3412958c
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/src/Routing/RevisionRouteProvider.php
@@ -0,0 +1,127 @@
+id();
+ if ($view_route = $this->getRevisionViewRoute($entity_type)) {
+ $collection->add("entity.$entity_type_id.revision", $view_route);
+ }
+ if ($view_route = $this->getRevisionRevertRoute($entity_type)) {
+ $collection->add("entity.$entity_type_id.revision_revert_form", $view_route);
+ }
+
+ if ($view_route = $this->getRevisionHistoryRoute($entity_type)) {
+ $collection->add("entity.$entity_type_id.version_history", $view_route);
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Gets the entity revision view route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function getRevisionViewRoute(EntityTypeInterface $entity_type) {
+ if ($entity_type->hasLinkTemplate('revision')) {
+ $entity_type_id = $entity_type->id();
+ $route = new Route($entity_type->getLinkTemplate('revision'));
+ $route->addDefaults([
+ '_controller' => '\Drupal\Core\Entity\Controller\EntityViewController::viewRevision',
+ '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
+ ]);
+ $route->addRequirements([
+ '_entity_access_revision' => "$entity_type_id.view",
+ ]);
+ $route->setOption('parameters', [
+ $entity_type->id() => [
+ 'type' => 'entity:' . $entity_type->id(),
+ ],
+ $entity_type->id() . '_revision' => [
+ 'type' => 'entity_revision:' . $entity_type->id(),
+ ],
+ ]);
+ return $route;
+ }
+ }
+
+ /**
+ * Gets the entity revision revert route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function getRevisionRevertRoute(EntityTypeInterface $entity_type) {
+ if ($entity_type->hasLinkTemplate('revision-revert-form')) {
+ $entity_type_id = $entity_type->id();
+ $route = new Route($entity_type->getLinkTemplate('revision-revert-form'));
+ $route->addDefaults([
+ '_form' => '\Drupal\entity\Form\RevisionRevertForm',
+ 'title' => 'Revert to earlier revision',
+ ]);
+ $route->addRequirements([
+ '_entity_access_revision' => "$entity_type_id.update",
+ ]);
+ $route->setOption('parameters', [
+ $entity_type->id() => [
+ 'type' => 'entity:' . $entity_type->id(),
+ ],
+ $entity_type->id() . '_revision' => [
+ 'type' => 'entity_revision:' . $entity_type->id(),
+ ],
+ ]);
+ return $route;
+ }
+ }
+
+ /**
+ * Gets the entity revision version history route.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return \Symfony\Component\Routing\Route|null
+ * The generated route, if available.
+ */
+ protected function getRevisionHistoryRoute($entity_type) {
+ if ($entity_type->hasLinkTemplate('version-history')) {
+ $entity_type_id = $entity_type->id();
+ $route = new Route($entity_type->getLinkTemplate('version-history'));
+ $route->addDefaults([
+ '_controller' => '\Drupal\entity\Controller\RevisionOverviewController::revisionOverviewController',
+ '_title' => 'Revisions',
+ ]);
+ $route->setRequirement('_entity_access_revision', "$entity_type_id.list");
+ $route->setOption('entity_type_id', $entity_type->id());
+ $route->setOption('parameters', [
+ $entity_type->id() => [
+ 'type' => 'entity:' . $entity_type->id(),
+ ],
+ ]);
+ return $route;
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/Kernel/RevisionOverviewIntegrationTest.php b/sites/all/modules/contrib/dev/entity/tests/Kernel/RevisionOverviewIntegrationTest.php
new file mode 100644
index 000000000..118fea62f
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/Kernel/RevisionOverviewIntegrationTest.php
@@ -0,0 +1,51 @@
+installSchema('system', 'router');
+
+ \Drupal::service('router.builder')->rebuild();
+ }
+
+ public function testIntegration() {
+ /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $local_tasks_manager */
+ $local_tasks_manager = \Drupal::service('plugin.manager.menu.local_task');
+
+ $tasks = $local_tasks_manager->getDefinitions();
+ $this->assertArrayHasKey('entity.revisions_overview:entity_test_enhanced', $tasks);
+ $this->assertArrayNotHasKey('entity.revisions_overview:node', $tasks, 'Node should have been excluded because it provides their own');
+
+ $this->assertEquals('entity.entity_test_enhanced.version_history', $tasks['entity.revisions_overview:entity_test_enhanced']['route_name']);
+ $this->assertEquals('entity.entity_test_enhanced.canonical', $tasks['entity.revisions_overview:entity_test_enhanced']['base_route']);
+
+ /** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider */
+ $route_provider = \Drupal::service('router.route_provider');
+
+ $route = $route_provider->getRouteByName('entity.entity_test_enhanced.version_history');
+ $this->assertInstanceOf(Route::class, $route);
+ $this->assertEquals('\Drupal\entity\Controller\RevisionOverviewController::revisionOverviewController', $route->getDefault('_controller'));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml
new file mode 100644
index 000000000..eb1c5a089
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml
@@ -0,0 +1,16 @@
+entity_module_test.entity_test_enhanced_bundle.*:
+ type: config_entity
+ label: 'Entity test with enhancements - Bundle'
+ mapping:
+ id:
+ type: string
+ label: 'Type'
+ label:
+ type: label
+ label: 'Label'
+ description:
+ type: text
+ label: 'Description'
+ new_revision:
+ type: boolean
+ label: 'New revision'
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml
new file mode 100644
index 000000000..d25bd5ff4
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml
@@ -0,0 +1,9 @@
+name: Entity test
+type: module
+# core: 8.x
+
+# Information added by Drupal.org packaging script on 2016-06-23
+version: '8.x-1.0-alpha3+2-dev'
+core: '8.x'
+project: 'entity'
+datestamp: 1466693538
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.links.task.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.links.task.yml
new file mode 100644
index 000000000..e306c10c9
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.links.task.yml
@@ -0,0 +1,9 @@
+entity.entity_test_enhanced.canonical:
+ title: View
+ route_name: entity.entity_test_enhanced.canonical
+ base_route: entity.entity_test_enhanced.canonical
+
+entity.entity_module_test.edit_form:
+ title: Edit
+ route_name: entity.entity_test_enhanced.edit_form
+ base_route: entity.entity_test_enhanced.canonical
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.permissions.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.permissions.yml
new file mode 100644
index 000000000..b9ff91d5b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.permissions.yml
@@ -0,0 +1,7 @@
+'administer entity_test_enhanced':
+ title: 'Administer entity_test_enhanced'
+ 'restrict access': TRUE
+
+'view all entity_test_enhanced revisions':
+ title: 'View all entity_test_enhanced revisions'
+ 'restrict access': TRUE
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.routing.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.routing.yml
new file mode 100644
index 000000000..3724a6524
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.routing.yml
@@ -0,0 +1,7 @@
+entity.entity_test_enhanced.collection:
+ path: '/entity_test_enhanced'
+ defaults:
+ _entity_list: 'entity_test_enhanced'
+ _title: 'Entity test with enhancements'
+ requirements:
+ _permission: 'administer entity_test_enhanced'
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php
new file mode 100644
index 000000000..c2cd80882
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php
@@ -0,0 +1,76 @@
+setLabel('Name')
+ ->setRevisionable(TRUE)
+ ->setDisplayOptions('view', [
+ 'label' => 'hidden',
+ 'type' => 'string',
+ 'weight' => -5,
+ ]);
+
+ return $fields;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php
new file mode 100644
index 000000000..43780746d
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php
@@ -0,0 +1,81 @@
+description;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDescription($description) {
+ $this->description = $description;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function shouldCreateNewRevision() {
+ return $this->new_revision;
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php
new file mode 100644
index 000000000..213113b9b
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php
@@ -0,0 +1,81 @@
+ 'default',
+ 'label' => 'Default',
+ ])->save();
+ $this->account = $this->drupalCreateUser(['administer entity_test_enhanced']);
+ $this->drupalLogin($this->account);
+ }
+
+ /**
+ * Tests the add page.
+ */
+ public function testForm() {
+ $entities = [];
+ $selection = [];
+ for ($i = 0; $i < 2; $i++) {
+ $entity = EnhancedEntity::create([
+ 'type' => 'default',
+ ]);
+ $entity->save();
+ $entities[$entity->id()] = $entity;
+
+ $langcode = $entity->language()->getId();
+ $selection[$entity->id()][$langcode] = $langcode;
+ }
+ // Add the selection to the tempstore just like DeleteAction would.
+ $tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
+ $tempstore->set($this->account->id(), $selection);
+
+ $this->drupalGet('/entity_test_enhanced/delete');
+ $assert = $this->assertSession();
+ $assert->statusCodeEquals(200);
+ $assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these items?');
+ $delete_button = $this->getSession()->getPage()->findButton('Delete');
+ $delete_button->click();
+ $assert = $this->assertSession();
+ $assert->addressEquals('/entity_test_enhanced');
+ $assert->responseContains('Deleted 2 items.');
+
+ \Drupal::entityTypeManager()->getStorage('entity_test_enhanced')->resetCache();
+ $remaining_entities = EnhancedEntity::loadMultiple(array_keys($selection));
+ $this->assertEmpty($remaining_entities);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php
new file mode 100644
index 000000000..8eddca491
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php
@@ -0,0 +1,97 @@
+ 'default',
+ 'label' => 'Default',
+ ])->save();
+
+ $this->placeBlock('local_tasks_block');
+ $this->placeBlock('system_breadcrumb_block');
+
+ $this->account = $this->drupalCreateUser([
+ 'administer entity_test_enhanced',
+ 'view all entity_test_enhanced revisions',
+ ]);
+
+ $this->drupalLogin($this->account);
+ }
+
+ /**
+ * Test enhanced entity revision routes access.
+ */
+ public function testRevisionRouteAccess() {
+ $entity = EnhancedEntity::create([
+ 'name' => 'rev 1',
+ 'type' => 'default',
+ ]);
+ $entity->save();
+
+ $revision = clone $entity;
+ $revision->name->value = 'rev 2';
+ $revision->setNewRevision(TRUE);
+ $revision->isDefaultRevision(FALSE);
+ $revision->save();
+
+ $this->drupalGet('/entity_test_enhanced/1/revisions');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->responseContains('Revisions');
+ $collection_link = $this->getSession()->getPage()->findLink('Entity test with enhancements');
+ $collection_link->click();
+ $this->assertSession()->addressEquals('/entity_test_enhanced');
+ $this->assertSession()->responseContains('Edit');
+ $edit_link = $this->getSession()->getPage()->findLink('Edit');
+ $edit_link->click();
+ $this->assertSession()->addressEquals('/entity_test_enhanced/1/edit');
+ // Check if we have revision tab link on edit page.
+ $this->getSession()->getPage()->findLink('Revisions')->click();
+ $this->assertSession()->addressEquals('/entity_test_enhanced/1/revisions');
+ $this->drupalGet('/entity_test_enhanced/1/revisions/2/view');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->responseContains('rev 2');
+ $revisions_link = $this->getSession()->getPage()->findLink('Revisions');
+ $revisions_link->click();
+ $this->assertSession()->addressEquals('/entity_test_enhanced/1/revisions');
+ $this->assertSession()->statusCodeEquals(200);
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php
new file mode 100644
index 000000000..140473f91
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php
@@ -0,0 +1,83 @@
+installEntitySchema('user');
+ $this->installEntitySchema('entity_test_enhanced');
+ $this->installSchema('system', ['key_value_expire', 'sequences']);
+
+ $bundle = EnhancedEntityBundle::create([
+ 'id' => 'default',
+ 'label' => 'Default',
+ ]);
+ $bundle->save();
+
+ $this->user = User::create([
+ 'name' => 'username',
+ 'status' => 1,
+ ]);
+ $this->user->save();
+ \Drupal::service('current_user')->setAccount($this->user);
+ }
+
+ public function testAction() {
+ /** @var \Drupal\system\ActionConfigEntityInterface $action */
+ $action = Action::create([
+ 'id' => 'enhanced_entity_delete_action',
+ 'label' => 'Delete enhanced entity',
+ 'plugin' => 'entity_delete_action:entity_test_enhanced',
+ ]);
+ $status = $action->save();
+ $this->assertEquals(SAVED_NEW, $status);
+ $this->assertInstanceOf(DeleteAction::class, $action->getPlugin());
+
+ $entities = [];
+ for ($i = 0; $i < 2; $i++) {
+ $entity = EnhancedEntity::create([
+ 'type' => 'default',
+ ]);
+ $entity->save();
+ $entities[$entity->id()] = $entity;
+ }
+
+ $action->execute($entities);
+ // Confirm that the entity ids and langcodes are now in the tempstore.
+ $tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
+ $selection = $tempstore->get($this->user->id());
+ $this->assertEquals(array_keys($entities), array_keys($selection));
+ $this->assertEquals([['en' => 'en'], ['en' => 'en']], array_values($selection));
+ }
+
+}
diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php
new file mode 100644
index 000000000..f282ac415
--- /dev/null
+++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php
@@ -0,0 +1,164 @@
+installEntitySchema('user');
+ $this->installEntitySchema('entity_test_enhanced');
+ $this->installSchema('system', 'router');
+ $this->installConfig(['system']);
+
+ $bundle = EnhancedEntityBundle::create([
+ 'id' => 'default',
+ 'label' => 'Default',
+ ]);
+ $bundle->save();
+
+ \Drupal::service('router.builder')->rebuild();
+ }
+
+ /**
+ * Tests the revision history controller.
+ */
+ public function testRevisionHistory() {
+ $entity = EnhancedEntity::create([
+ 'name' => 'rev 1',
+ 'type' => 'default',
+ ]);
+ $entity->save();
+
+ $revision = clone $entity;
+ $revision->name->value = 'rev 2';
+ $revision->setNewRevision(TRUE);
+ $revision->isDefaultRevision(FALSE);
+ $revision->save();
+
+ /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
+ $http_kernel = \Drupal::service('http_kernel');
+ $request = Request::create($revision->url('version-history'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(403, $response->getStatusCode());
+
+ $role = Role::create(['id' => 'test_role']);
+ $role->grantPermission('view all entity_test_enhanced revisions');
+ $role->grantPermission('administer entity_test_enhanced');
+ $role->save();
+
+ $user = User::create([
+ 'name' => 'Test user',
+ ]);
+ $user->addRole($role->id());
+ \Drupal::service('account_switcher')->switchTo($user);
+
+ $request = Request::create($revision->url('version-history'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+
+ // This ensures that the default revision is still the first revision.
+ $this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1/revisions/2/view') !== FALSE);
+ $this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1') !== FALSE);
+
+ // Publish a new revision.
+ $revision = clone $entity;
+ $revision->name->value = 'rev 3';
+ $revision->setNewRevision(TRUE);
+ $revision->isDefaultRevision(TRUE);
+ $revision->save();
+
+ $request = Request::create($revision->url('version-history'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+
+ // The first revision row should now include a revert link.
+ $this->assertTrue(strpos($response->getContent(), 'entity_test_enhanced/1/revisions/1/revert') !== FALSE);
+ }
+
+ public function testRevisionView() {
+ $entity = EnhancedEntity::create([
+ 'name' => 'rev 1',
+ 'type' => 'default',
+ ]);
+ $entity->save();
+
+ $revision = clone $entity;
+ $revision->name->value = 'rev 2';
+ $revision->setNewRevision(TRUE);
+ $revision->isDefaultRevision(FALSE);
+ $revision->save();
+
+ /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
+ $http_kernel = \Drupal::service('http_kernel');
+ $request = Request::create($revision->url('revision'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(403, $response->getStatusCode());
+
+ $role = Role::create(['id' => 'test_role']);
+ $role->grantPermission('view all entity_test_enhanced revisions');
+ $role->grantPermission('administer entity_test_enhanced');
+ $role->save();
+
+ $user = User::create([
+ 'name' => 'Test user',
+ ]);
+ $user->addRole($role->id());
+ \Drupal::service('account_switcher')->switchTo($user);
+
+ $request = Request::create($revision->url('revision'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertNotContains('rev 1', $response->getContent());
+ $this->assertContains('rev 2', $response->getContent());
+ }
+
+ public function testRevisionRevert() {
+ $entity = EnhancedEntity::create([
+ 'name' => 'rev 1',
+ 'type' => 'entity_test_enhance',
+ ]);
+ $entity->save();
+ $entity->name->value = 'rev 2';
+ $entity->setNewRevision(TRUE);
+ $entity->isDefaultRevision(TRUE);
+ $entity->save();
+
+ $role = Role::create(['id' => 'test_role']);
+ $role->grantPermission('administer entity_test_enhanced');
+ $role->grantPermission('revert all entity_test_enhanced revisions');
+ $role->save();
+
+ $user = User::create([
+ 'name' => 'Test user',
+ ]);
+ $user->addRole($role->id());
+ \Drupal::service('account_switcher')->switchTo($user);
+
+ /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
+ $http_kernel = \Drupal::service('http_kernel');
+ $request = Request::create($entity->url('revision-revert-form'));
+ $response = $http_kernel->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ }
+
+}
diff --git a/sites/all/modules/contrib/mail/maillog/LICENSE.txt b/sites/all/modules/contrib/mail/maillog/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/mail/maillog/README.txt b/sites/all/modules/contrib/mail/maillog/README.txt
new file mode 100644
index 000000000..afe807a56
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/README.txt
@@ -0,0 +1,4 @@
+
+This module defines drupal_mail_wrapper(), which takes over the handling of mail sending. This wrapper creates a node of type 'Logged Mail', a node type created by the module, sends the e-mail, and displays the e-mail and its details. All 3 tasks can be enabled/disabled from 'admin/settings/maillog'.
+
+The invoking of the handler depends on a system variable 'smtp_library'. Unfortunately, some modules may change the value of this variable, rendering the module dysfunctional. Simply disabling and re-enabling the module should fix this problem. Check 'admin/reports/status' to see the status of the SMTP library.
diff --git a/sites/all/modules/contrib/mail/maillog/config/install/maillog.settings.yml b/sites/all/modules/contrib/mail/maillog/config/install/maillog.settings.yml
new file mode 100644
index 000000000..460534a91
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/config/install/maillog.settings.yml
@@ -0,0 +1,3 @@
+send: true
+log: true
+verbose: true
diff --git a/sites/all/modules/contrib/mail/maillog/config/install/views.view.maillog_overview.yml b/sites/all/modules/contrib/mail/maillog/config/install/views.view.maillog_overview.yml
new file mode 100644
index 000000000..8b7df5361
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/config/install/views.view.maillog_overview.yml
@@ -0,0 +1,441 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - system.menu.admin
+ enforced:
+ module:
+ - maillog
+ module:
+ - user
+id: maillog_overview
+label: 'Maillog overview'
+module: views
+description: ''
+tag: ''
+base_table: maillog
+base_field: idmaillog
+core: 8.x
+display:
+ default:
+ display_plugin: default
+ id: default
+ display_title: Master
+ position: 1
+ display_options:
+ access:
+ type: perm
+ options:
+ perm: 'view maillog'
+ cache:
+ type: none
+ options: { }
+ query:
+ type: views_query
+ options:
+ disable_sql_rewrite: false
+ distinct: false
+ query_comment: ''
+ query_tags: { }
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ pager:
+ type: full
+ options:
+ items_per_page: 10
+ offset: 0
+ id: 0
+ total_pages: null
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 20, 40, 60'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ tags:
+ previous: '‹ previous'
+ next: 'next ›'
+ first: '« first'
+ last: 'last »'
+ quantity: 9
+ style:
+ type: table
+ options:
+ grouping: { }
+ row_class: ''
+ default_row_class: true
+ override: true
+ sticky: false
+ caption: ''
+ summary: ''
+ description: ''
+ columns:
+ idmaillog: idmaillog
+ sent_date: sent_date
+ subject: subject
+ header_from: header_from
+ header_to: header_to
+ info:
+ idmaillog:
+ sortable: false
+ default_sort_order: asc
+ align: ''
+ separator: ''
+ empty_column: false
+ responsive: ''
+ sent_date:
+ sortable: false
+ default_sort_order: asc
+ align: ''
+ separator: ''
+ empty_column: false
+ responsive: ''
+ subject:
+ sortable: false
+ default_sort_order: asc
+ align: ''
+ separator: ''
+ empty_column: false
+ responsive: ''
+ header_from:
+ sortable: false
+ default_sort_order: asc
+ align: ''
+ separator: ''
+ empty_column: false
+ responsive: ''
+ header_to:
+ sortable: false
+ default_sort_order: asc
+ align: ''
+ separator: ''
+ empty_column: false
+ responsive: ''
+ default: '-1'
+ empty_table: true
+ row:
+ type: fields
+ fields:
+ idmaillog:
+ id: idmaillog
+ table: maillog
+ field: idmaillog
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: '#'
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: null
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: 'There are currently no mails to report.'
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ plugin_id: standard
+ sent_date:
+ id: sent_date
+ table: maillog
+ field: sent_date
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Date
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: null
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ date_format: short
+ custom_date_format: ''
+ timezone: ''
+ plugin_id: date
+ subject:
+ id: subject
+ table: maillog
+ field: subject
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Subject
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: true
+ path: '/maillog/details/{{idmaillog}}'
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: null
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ plugin_id: standard
+ header_from:
+ id: header_from
+ table: maillog
+ field: header_from
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: From
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: null
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ plugin_id: standard
+ header_to:
+ id: header_to
+ table: maillog
+ field: header_to
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: To
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: null
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ plugin_id: standard
+ filters: { }
+ sorts:
+ sent_date:
+ id: sent_date
+ table: maillog
+ field: sent_date
+ relationship: none
+ group_type: group
+ admin_label: ''
+ order: DESC
+ exposed: false
+ expose:
+ label: ''
+ granularity: second
+ plugin_id: date
+ title: 'Maillog overview'
+ header: { }
+ footer: { }
+ empty:
+ area_text_custom:
+ id: area_text_custom
+ table: views
+ field: area_text_custom
+ relationship: none
+ group_type: group
+ admin_label: ''
+ empty: true
+ tokenize: false
+ content: 'There are no mail logs in the database.'
+ plugin_id: text_custom
+ relationships: { }
+ arguments: { }
+ display_extenders: { }
+ cache_metadata:
+ contexts:
+ - 'languages:language_interface'
+ - url.query_args
+ - user.permissions
+ cacheable: false
+ max-age: 0
+ tags: { }
+ page_1:
+ display_plugin: page
+ id: page_1
+ display_title: Page
+ position: 1
+ display_options:
+ path: admin/reports/maillog
+ menu:
+ type: normal
+ title: Maillog
+ description: ''
+ parent: system.admin_reports
+ weight: 0
+ context: '0'
+ menu_name: admin
+ display_extenders: { }
+ cache_metadata:
+ contexts:
+ - 'languages:language_interface'
+ - url.query_args
+ - user.permissions
+ cacheable: false
+ max-age: 0
+ tags: { }
diff --git a/sites/all/modules/contrib/mail/maillog/config/schema/maillog.schema.yml b/sites/all/modules/contrib/mail/maillog/config/schema/maillog.schema.yml
new file mode 100644
index 000000000..ac265aed1
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/config/schema/maillog.schema.yml
@@ -0,0 +1,12 @@
+maillog.settings:
+ type: config_object
+ mapping:
+ send:
+ type: boolean
+ label: Send emails
+ log:
+ type: boolean
+ label: Log emails
+ verbose:
+ type: boolean
+ label: Display mails
diff --git a/sites/all/modules/contrib/mail/maillog/maillog.info.yml b/sites/all/modules/contrib/mail/maillog/maillog.info.yml
new file mode 100644
index 000000000..a93f4b0eb
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/maillog.info.yml
@@ -0,0 +1,16 @@
+name: 'Maillog / Mail Developer'
+description: 'Utility to log all Mails for debugging purposes. It is possible to suppress mail delivery for e.g. dev or staging systems.'
+package: 'Mail'
+# core: 8.x
+type: module
+
+dependencies:
+- views
+
+configure: maillog.settings
+
+# Information added by Drupal.org packaging script on 2016-08-05
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'maillog'
+datestamp: 1470431946
diff --git a/sites/all/modules/contrib/mail/maillog/maillog.install b/sites/all/modules/contrib/mail/maillog/maillog.install
new file mode 100644
index 000000000..775cac975
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/maillog.install
@@ -0,0 +1,93 @@
+getEditable('system.mail');
+ $config->set('interface.default', 'maillog');
+ $config->save();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function maillog_uninstall() {
+ $config = \Drupal::configFactory()->getEditable('system.mail');
+
+ // Restore the mail configuration to php_mail if it currently uses maillog.
+ if ($config->get('interface.default') == 'maillog') {
+ $config->set('interface.default', 'php_mail');
+ $config->save();
+ }
+}
+
+/**
+ * Implements hook_schema().
+ */
+function maillog_schema() {
+ $schema['maillog'] = array(
+ 'description' => "Stores outgoing e-mail details for nodes of type 'maillog'.",
+ 'fields' => array(
+ 'idmaillog' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => "The mail_log {node}.nid",
+ ),
+ 'header_message_id' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'default' => '',
+ 'description' => "The 'message-id' field of the e-mail.",
+ ),
+ 'header_from' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => "The 'From' field of the e-mail.",
+ ),
+ 'header_to' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => "The 'To' field of the e-mail.",
+ ),
+ 'header_reply_to' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => "The 'Reply-To' field of the e-mail.",
+ ),
+ 'header_all' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => "The 'Header' field of the e-mail.",
+ ),
+ 'subject' => array(
+ 'description' => "The 'Subject' fieldof the e-mail.",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'body' => array(
+ 'description' => 'The body of this version.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ ),
+ 'sent_date' => array(
+ 'description' => 'The Unix timestamp when the mail was sent.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('idmaillog'),
+ );
+
+ return $schema;
+}
diff --git a/sites/all/modules/contrib/mail/maillog/maillog.links.menu.yml b/sites/all/modules/contrib/mail/maillog/maillog.links.menu.yml
new file mode 100644
index 000000000..4b4b8b7e0
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/maillog.links.menu.yml
@@ -0,0 +1,5 @@
+maillog.settings:
+ title: 'Maillog Settings'
+ parent: system.admin_config_development
+ description: 'Configure the settings of Maillog module.'
+ route_name: maillog.settings
diff --git a/sites/all/modules/contrib/mail/maillog/maillog.module b/sites/all/modules/contrib/mail/maillog/maillog.module
new file mode 100644
index 000000000..54c6fb8f5
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/maillog.module
@@ -0,0 +1,28 @@
+' . t('About') . '';
+ $output .= '
' . t('Maillog module provides an opportunity to save the mail log for debugging purposes.') . '
';
+ $output .= '
' . t('Uses') . '
';
+ $output .= '
';
+ $output .= '
' . t('Saves the mail log') . '
';
+ $output .= '
' . t('View integration') . '
';
+ $output .= '
' . t('Displays verbose output of the mail for users with permission "view maillog"') . '
',
+ );
+
+ return $output;
+ }
+
+ /**
+ * Page Callback - Delete a specific maillog entry.
+ *
+ * @param int $maillog_id
+ * The maillog ID.
+ */
+ public function delete($maillog_id) {
+ $idmaillog = intval($maillog_id);
+ $this->database->query("DELETE FROM {maillog} WHERE idmaillog = :id", array(':id' => $idmaillog));
+ drupal_set_message(t('Mail with ID @idmaillog has been deleted!', array('@idmaillog' => $idmaillog)));
+
+ return $this->redirect('view.maillog_overview.page_1');
+ }
+
+ /**
+ * Loads the Maillog Entry.
+ *
+ * @param int $maillog_id
+ * The maillog ID.
+ *
+ * @return array
+ * Maillog entry as Array
+ */
+ protected function getMaillogEntry($maillog_id) {
+ $result = $this->database->query("SELECT idmaillog, header_from, header_to, header_reply_to, header_all, subject, body FROM {maillog} WHERE idmaillog=:id", array(
+ ':id' => $maillog_id,
+ ));
+
+ if ($maillog = $result->fetchAssoc()) {
+ // Unserialize values.
+ $maillog['header_all'] = unserialize($maillog['header_all']);
+ }
+ return $maillog;
+ }
+}
diff --git a/sites/all/modules/contrib/mail/maillog/src/Form/MaillogClearConfirmForm.php b/sites/all/modules/contrib/mail/maillog/src/Form/MaillogClearConfirmForm.php
new file mode 100644
index 000000000..352a51b06
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/src/Form/MaillogClearConfirmForm.php
@@ -0,0 +1,64 @@
+t('All maillog database entries will be deleted. This action cannot be undone.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ return $this->t('Are you sure you want to clear all the maillog entries?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ return new Url('maillog.settings');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return $this->t('Clear');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ Database::getConnection('default')->truncate('maillog')->execute();
+ drupal_set_message($this->t("All maillog entries have been deleted."));
+ $form_state->setRedirect('maillog.settings');
+ }
+
+}
diff --git a/sites/all/modules/contrib/mail/maillog/src/Form/MaillogSettingsForm.php b/sites/all/modules/contrib/mail/maillog/src/Form/MaillogSettingsForm.php
new file mode 100644
index 000000000..25c78be35
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/src/Form/MaillogSettingsForm.php
@@ -0,0 +1,108 @@
+config('maillog.settings');
+
+ $form = array();
+
+ $form['clear_maillog'] = array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('Clear Maillog'),
+ );
+
+ $form['clear_maillog']['clear'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Clear all maillog entries'),
+ '#submit' => ['::clearLog'],
+ );
+
+ $form['maillog_send'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Allow the e-mails to be sent."),
+ '#default_value' => $config->get('send'),
+ );
+
+ $form['maillog_log'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Create table entries in maillog table for each e-mail."),
+ '#default_value' => $config->get('log'),
+ );
+
+ $form['maillog_verbose'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("Display the e-mails on page."),
+ '#default_value' => $config->get('verbose'),
+ '#description' => $this->t('If enabled, anonymous users with permissions will see any verbose output mail.'),
+ );
+
+ /*if (\Drupal::moduleHandler()->moduleExists('mimemail')) {
+ $engines = mimemail_get_engines();
+ // maillog will be unset, because ist would cause an recursion
+ unset($engines['maillog']);
+ $form['maillog_engine'] = array(
+ '#type' => 'select',
+ '#title' => t("Select the mailengine which should be used."),
+ '#default_value' => $config->get('maillog_engine'),
+ '#options' => $engines,
+ );
+ }*/
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->config('maillog.settings')
+ ->set('send', $form_state->getValue('maillog_send'))
+ ->set('log', $form_state->getValue('maillog_log'))
+ ->set('verbose', $form_state->getValue('maillog_verbose'))->save();
+
+ parent::submitForm($form, $form_state);
+
+ if ($this->config('maillog.settings')->get('verbose') == TRUE) {
+ drupal_set_message(t('Any user having the permission "view maillog" will see output of any mail that is sent.'), 'warning');
+ }
+ }
+
+ /**
+ * Clear all the maillog entries.
+ */
+ public function clearLog(array $form, FormStateInterface $form_state) {
+ $form_state->setRedirect('maillog.clear_log');
+ }
+
+}
diff --git a/sites/all/modules/contrib/mail/maillog/src/Plugin/Mail/Maillog.php b/sites/all/modules/contrib/mail/maillog/src/Plugin/Mail/Maillog.php
new file mode 100755
index 000000000..55c0e8933
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/src/Plugin/Mail/Maillog.php
@@ -0,0 +1,104 @@
+format($message);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function mail(array $message) {
+ $config = \Drupal::configFactory()->get('maillog.settings');
+ // Log the e-mail
+ if ($config->get('log')) {
+ $record = new \stdClass;
+
+ // In case the subject/from/to is already encoded, decode with
+ // Unicode::mimeHeaderDecode().
+ $record->header_message_id = isset($message['headers']['Message-ID']) ? $message['headers']['Message-ID'] : NULL;
+ $record->subject = $message['subject'];
+ $record->subject = Unicode::substr(Unicode::mimeHeaderDecode($record->subject), 0, 255);
+ $record->body = $message['body'];
+ $record->header_from = isset($message['from']) ? $message['from'] : NULL;
+ $record->header_from = Unicode::mimeHeaderDecode($record->header_from);
+
+ $header_to = array();
+ if (isset($message['to'])) {
+ if (is_array($message['to'])) {
+ foreach ($message['to'] as $value) {
+ $header_to[] = Unicode::mimeHeaderDecode($value);
+ }
+ }
+ else {
+ $header_to[] = Unicode::mimeHeaderDecode($message['to']);
+ }
+ }
+ $record->header_to = implode(', ', $header_to);
+
+ $record->header_reply_to = isset($message['headers']['Reply-To']) ? $message['headers']['Reply-To'] : '';
+ $record->header_all = serialize($message['headers']);
+ $record->sent_date = REQUEST_TIME;
+
+ Database::getConnection()->insert('maillog')
+ ->fields((array) $record)
+ ->execute();
+ }
+
+ // Display the e-mail if the verbose is enabled.
+ if ($config->get('verbose') && \Drupal::currentUser()->hasPermission('view maillog')) {
+
+ // Print the message.
+ $header_output = print_r($message['headers'], TRUE);
+ $output = t('A mail has been sent: [Subject] => @subject [From] => @from [To] => @to [Reply-To] => @reply
[Header] => @header [Body] => @body
', [
+ '@subject' => $message['subject'],
+ '@from' => $message['from'],
+ '@to' => $message['to'],
+ '@reply' => isset($message['reply_to']) ? $message['reply_to'] : NULL,
+ '@header' => $header_output,
+ '@body' => $message['body']
+ ]);
+ drupal_set_message($output, 'status', TRUE);
+ }
+
+ if ($config->get('send')) {
+ $default = new PhpMail();
+ $result = $default->mail($message);
+ }
+ elseif (\Drupal::currentUser()->hasPermission('administer maillog')) {
+ $message = t('Sending of e-mail messages is disabled by Maillog module. Go @here to enable.', ['@here' => \Drupal::l('here', Url::fromRoute('maillog.settings'))]);
+
+ drupal_set_message($message, 'warning', TRUE);
+ }
+ else {
+ \Drupal::logger('maillog')->notice('Attempted to send an email, but sending emails is disabled.');
+ }
+ return isset($result) ? $result : TRUE;
+ }
+}
diff --git a/sites/all/modules/contrib/mail/maillog/src/Plugin/views/field/MaillogFieldDelete.php b/sites/all/modules/contrib/mail/maillog/src/Plugin/views/field/MaillogFieldDelete.php
new file mode 100644
index 000000000..1b00adc9c
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/src/Plugin/views/field/MaillogFieldDelete.php
@@ -0,0 +1,38 @@
+hasPermission('delete maillog')) {
+ return;
+ }
+
+ $id = $this->getValue($values);
+
+ $text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
+
+ return \Drupal::l($text, 'maillog.delete', array('maillog_id' => $id), array('query' => drupal_get_destination()));
+ }
+}
+
diff --git a/sites/all/modules/contrib/mail/maillog/src/Tests/Mail/MailUiTest.php b/sites/all/modules/contrib/mail/maillog/src/Tests/Mail/MailUiTest.php
new file mode 100644
index 000000000..07bb49c1d
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/src/Tests/Mail/MailUiTest.php
@@ -0,0 +1,149 @@
+config('system.mail')->set('interface.default', 'maillog')->save();
+ // The system.site.mail setting goes into the From header of outgoing mails.
+ $this->config('system.site')->set('mail', 'simpletest@example.com')->save();
+
+ // Disable e-mail sending.
+ $this->config('maillog.settings')
+ ->set('send', FALSE)
+ ->save();
+ }
+
+ /**
+ * Tests logging mail with maillog module.
+ */
+ public function testLogging() {
+ $mail = \Drupal::service('plugin.manager.mail')->mail('maillog', 'ui_test', 'test@example.com', \Drupal::languageManager()->getCurrentLanguage(), [], 'me@example.com', FALSE);
+ $mail['subject'] = 'This is a test subject.';
+ $mail['body'] = 'This message is a test email body.';
+
+ // Send the prepared email.
+ $sender = new Maillog();
+ $sender->mail($mail);
+
+ // Create a user with valid permissions and go to the maillog overview page.
+ $this->drupalLogin($this->drupalCreateUser(['view maillog', 'administer maillog']));
+ $this->drupalGet('admin/reports/maillog');
+
+ // Assert some values and click the subject link.
+ $this->assertText('simpletest@example.com');
+ $this->assertText('test@example.com');
+ $this->clickLink('This is a test subject.');
+ $this->assertText('This message is a test email body.');
+
+ // Test clear log.
+ $this->drupalPostForm('admin/config/development/maillog', [], 'Clear all maillog entries');
+ $this->drupalPostForm(NULL, [], 'Clear');
+ $this->drupalGet('admin/reports/maillog');
+ $this->assertNoText('simpletest@example.com');
+ $this->assertText(t('There are no mail logs in the database.'));
+ }
+
+ /**
+ * Checks the drupal_set_message() for disabled mail sending.
+ */
+ protected function testNotice() {
+
+ // Create a user with valid permissions and a recipient for a message.
+ $recipient = $this->drupalCreateUser();
+ $this->drupalLogin($this->drupalCreateUser(['access user contact forms', 'access user profiles', 'administer maillog']));
+
+ // Send the recipient a message and check the expected notice.
+ $this->drupalPostForm('user/' . $recipient->id() . '/contact', [
+ 'subject[0][value]' => 'Test Message',
+ 'message[0][value]' => 'This is a test.',
+ ], t('Send message'));
+ $this->clickLink('here');
+ $this->assertResponse(200);
+ $this->assertTitle('Maillog Settings | Drupal');
+ }
+
+ /**
+ * Tests verbose output after send an email.
+ */
+ public function testVerboseOutput() {
+
+ // Create a user with valid permissions.
+ $user = $this->drupalCreateUser();
+ $this->drupalLogin($this->drupalCreateUser([
+ 'access user contact forms',
+ 'access user profiles',
+ 'view maillog'
+ ]));
+
+ // Send the message.
+ $this->drupalPostForm('user/' . $user->id() . '/contact', [
+ 'subject[0][value]' => 'Test Message',
+ 'message[0][value]' => 'This is a test.',
+ ], t('Send message'));
+
+ // Assert the verbose output.
+ $this->assertText('A mail has been sent:');
+ $this->assertRaw('[To] => ' . $user->getUsername() . '@example.com');
+ $this->assertRaw('[Header] => Array');
+ $this->assertRaw('[X-Mailer] => Drupal');
+ $this->assertRaw('[Content-Type] => text/plain; charset=UTF-8; format=flowed; delsp=yes');
+ $this->assertRaw('[Body] => Hello ' . $user->getUsername());
+
+ // Set verbose to false.
+ $this->config('maillog.settings')->set('verbose', FALSE)->save();
+ $this->drupalPostForm('user/' . $user->id() . '/contact', [
+ 'subject[0][value]' => 'Test Message',
+ 'message[0][value]' => 'This is a test.',
+ ], t('Send message'));
+
+ // Assert there is no output.
+ $this->assertNoText('A mail has been sent:');
+ $this->assertNoRaw('[To] => ' . $user->getUsername() . '@example.com');
+ $this->assertNoRaw('[Header] => Array');
+
+ // Tests that users without permission cannot see verbose output.
+ $this->config('maillog.settings')->set('verbose', TRUE)->save();
+ $this->drupalLogin($this->drupalCreateUser([
+ 'access user contact forms',
+ 'access user profiles',
+ ]));
+
+ $this->drupalPostForm('user/' . $user->id() . '/contact', [
+ 'subject[0][value]' => 'Test Message',
+ 'message[0][value]' => 'This is a test.',
+ ], t('Send message'));
+
+ // Assert there is no output.
+ $this->assertNoText('A mail has been sent:');
+ $this->assertNoRaw('[To] => ' . $user->getUsername() . '@example.com');
+ $this->assertNoRaw('[Header] => Array');
+ }
+
+}
diff --git a/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.info.yml b/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.info.yml
new file mode 100644
index 000000000..c78813a81
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.info.yml
@@ -0,0 +1,12 @@
+name: 'Maillog test'
+description: 'Contains helper logic for the Maillog tests.'
+package: 'Mail'
+# core: 8.x
+type: module
+hidden: TRUE
+
+# Information added by Drupal.org packaging script on 2016-08-05
+version: '8.x-1.x-dev'
+core: '8.x'
+project: 'maillog'
+datestamp: 1470431946
diff --git a/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.module b/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.module
new file mode 100644
index 000000000..3da505c97
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/tests/modules/maillog_test/maillog_test.module
@@ -0,0 +1,34 @@
+alert("XSS test")';
+ $message['body'][] = t('Test email body.');
+ }
+
+ // This has an XSS test for the body field.
+ elseif ($key == 'maillog_body_xss_test') {
+ $message['subject'] = t('Test email subject');
+ $message['body'][] = '';
+ }
+}
diff --git a/sites/all/modules/contrib/mail/maillog/tests/src/Kernel/MailTest.php b/sites/all/modules/contrib/mail/maillog/tests/src/Kernel/MailTest.php
new file mode 100644
index 000000000..313707d9e
--- /dev/null
+++ b/sites/all/modules/contrib/mail/maillog/tests/src/Kernel/MailTest.php
@@ -0,0 +1,98 @@
+installSchema('maillog', 'maillog');
+ $this->installConfig(['system', 'maillog']);
+ // The system.site.mail setting goes into the From header of outgoing mails.
+ $this->config('system.site')->set('mail', 'simpletest@example.com')->save();
+
+ // Use the maillog mail plugin.
+ $GLOBALS['config']['system.mail']['interface']['default'] = 'maillog';
+
+ // Disables E-Mail Sending, only tracking.
+ $this->config('maillog.settings')
+ ->set('send', FALSE)
+ ->save();
+ }
+
+ /**
+ * Tests logging mail with maillog module.
+ */
+ public function testLogging() {
+ $language = \Drupal::languageManager()->getCurrentLanguage();
+
+ // Send an email.
+ $from_email = 'simpletest@example.com';
+ $reply_email = 'someone_else@example.com';
+ \Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $reply_email);
+
+ // Compare the maillog db entry with the sent mail.
+ $logged_email = $this->getLatestMaillogEntry();
+ $this->assertTrue(is_array($logged_email), 'Email is captured.');
+ $this->assertEqual($from_email, $logged_email['header_from'], 'Email is sent correctly.');
+ $this->assertEqual($reply_email, $logged_email['header_all']['Reply-to'], 'Message is sent with correct reply address.');
+ }
+
+ /**
+ * Gets the latest Maillog entry.
+ *
+ * @return array
+ * Maillog entry
+ */
+ protected function getLatestMaillogEntry() {
+ $query = 'SELECT idmaillog, header_from, header_to, header_reply_to, header_all, subject, body FROM {maillog} ORDER BY idmaillog DESC';
+ $result = \Drupal::database()->queryRange($query, 0, 1);
+
+ if ($maillog = $result->fetchAssoc()) {
+ // Unserialize values.
+ $maillog['header_all'] = unserialize($maillog['header_all']);
+ }
+ return $maillog;
+ }
+
+ /**
+ * Confirm what happens with long subject lines.
+ */
+ public function testLongSubject() {
+ // Send an email.
+ $from_email = 'from_test@example.com';
+ $to_email = 'to_test@example.com';
+ \Drupal::service('plugin.manager.mail')->mail('maillog_test', 'maillog_long_subject_test', $to_email, 'en', [], $from_email);
+
+ // Compare the maillog db entry with the sent mail.
+ $logged_email = $this->getLatestMaillogEntry();
+ $this->assertTrue(!empty($logged_email), 'The test email was captured.');
+
+ // The original subject line, as copied from maillog_test_mail().
+ $subject = str_repeat(t('Test email subject@space', ['@space' => ' ']), 20);
+
+ // Duplicate the string trimming.
+ $subject_trimmed = Unicode::substr($subject, 0, 255);
+ self::assertEquals($subject_trimmed, $logged_email['subject'], 'Email subject was trimmed correctly.');
+ self::assertNotEquals($subject, $logged_email['subject'], 'Email subject is not untrimmed.');
+ }
+
+}
diff --git a/sites/all/modules/contrib/theme/extlink/LICENSE.txt b/sites/all/modules/contrib/theme/extlink/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/theme/extlink/README.txt b/sites/all/modules/contrib/theme/extlink/README.txt
new file mode 100644
index 000000000..8b44719e6
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/README.txt
@@ -0,0 +1,37 @@
+The External Links module is a very simple approach to adding icons to links
+to external websites or e-mail addresses. It is a JavaScript-based
+implementation, so the icons are only shown to users that have JavaScript
+enabled.
+
+External Links was written by Nathan Haug.
+Built by Robots: http://www.lullabot.com
+
+Install
+-------
+Simply install External Links like you would install any other module.
+
+1) Copy the extlink folder in the modules folder in your Drupal docroot.
+
+2) Enable the module using Manage -> Extend (/admin/modules).
+
+3) No additional configuration is necessary though you may fine-tune settings at
+ Manage -> Configuration -> External Links (/admin/config/user-interface/extlink).
+
+A note about the CSS
+--------------------
+This module adds a CSS file that is only a few lines in length. You may choose
+to move this CSS to your theme to prevent the file from needing to be loaded
+separately. To do this:
+
+1) Open the .info.yml file for your theme and add those lines of code to prevent
+ the extlink.css file from loading:
+
+ stylesheets-remove:
+ - extlink.css
+
+2) Open the extlink.css file within the extlink directory and copy all the code
+ from the file into your theme's style.css file.
+3) Copy the extlink.png and mailto.png files to your theme's directory.
+
+Note that you DO NOT need to make an extlink.css file. Specifying the file in the
+.info.yml file is enough to tell Drupal not to load the original file.
diff --git a/sites/all/modules/contrib/theme/extlink/config/extlink.settings.yml b/sites/all/modules/contrib/theme/extlink/config/extlink.settings.yml
new file mode 100644
index 000000000..1a650813c
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/config/extlink.settings.yml
@@ -0,0 +1,8 @@
+extlink_target: 0
+extlink_subdomains: 1
+extlink_alert: 0
+extlink_alert_text: 'This link will take you to an external web site. We are not responsible for their content.'
+extlink_exclude: ''
+extlink_include: ''
+extlink_class: 'ext'
+extlink_mailto_class: 'mailto'
\ No newline at end of file
diff --git a/sites/all/modules/contrib/theme/extlink/config/schema/extlink.schema.yml b/sites/all/modules/contrib/theme/extlink/config/schema/extlink.schema.yml
new file mode 100644
index 000000000..08fe220cb
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/config/schema/extlink.schema.yml
@@ -0,0 +1,40 @@
+# Schema for the configuration files of the exlink module.
+
+extlink.settings:
+ type: mapping
+ label: 'External Links Settings'
+ mapping:
+ extlink_target:
+ type: boolean
+ label: 'Open external links in a new window'
+ extlink_subdomains:
+ type: boolean
+ label: 'Consider subdomains internal'
+ extlink_alert:
+ type: boolean
+ label: 'Enable Pop-up warnings'
+ extlink_alert_text:
+ type: string
+ label: 'Message to display when Pop-up warning is shown'
+ extlink_exclude:
+ type: string
+ label: 'Exclude links matching the pattern'
+ extlink_include:
+ type: string
+ label: 'Include links matching the pattern'
+ extlink_class:
+ type: string
+ label: 'Add icon to external links'
+ extlink_img_class:
+ type: boolean
+ label: 'Treat images wrapped in an anchor tag as external links'
+ extlink_css_exclude:
+ type: string
+ label: 'Comma-separated list of CSS selectors to exclude'
+ extlink_css_explicit:
+ type: string
+ label: 'Comma-separated list of CSS selectors to include'
+ extlink_mailto_class:
+ type: string
+ label: 'Add icon to mailto links'
+
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.css b/sites/all/modules/contrib/theme/extlink/extlink.css
new file mode 100644
index 000000000..f912d8b8f
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.css
@@ -0,0 +1,22 @@
+span.ext {
+ background: url(extlink_s.png) 2px center no-repeat;
+ width: 10px;
+ height: 10px;
+ padding-right: 12px;
+ text-decoration: none;
+}
+span.mailto {
+ background: url(extlink_s.png) -20px center no-repeat;
+ width: 10px;
+ height: 10px;
+ padding-right: 12px;
+ text-decoration: none;
+}
+
+/* Hide the extra spans when printing. */
+@media print {
+ span.ext, span.mailto {
+ display: none;
+ padding: 0;
+ }
+}
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.info.yml b/sites/all/modules/contrib/theme/extlink/extlink.info.yml
new file mode 100644
index 000000000..e70b4f818
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.info.yml
@@ -0,0 +1,13 @@
+name: External Links
+type: module
+description: Modify behavior and appearance of external links.
+package: User interface
+# version: 8.x
+# core: 8.x
+configure: extlink_admin.settings
+
+# Information added by Drupal.org packaging script on 2016-08-31
+version: '8.x-1.0-alpha2+8-dev'
+core: '8.x'
+project: 'extlink'
+datestamp: 1472650450
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.install b/sites/all/modules/contrib/theme/extlink/extlink.install
new file mode 100644
index 000000000..92aa2bcc1
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.install
@@ -0,0 +1,21 @@
+configure the External Links module for your site.',
+ ['@extlink_admin' => $url->toString()]), 'status');
+ }
+}
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.js b/sites/all/modules/contrib/theme/extlink/extlink.js
new file mode 100644
index 000000000..5af9fccf0
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.js
@@ -0,0 +1,171 @@
+(function ($, Drupal, drupalSettings) {
+
+ "use strict";
+
+ Drupal.extlink = Drupal.extlink || {};
+
+ Drupal.extlink.attach = function (context, drupalSettings) {
+ if (!drupalSettings.data.hasOwnProperty('extlink')) {
+ return;
+ }
+
+ // Strip the host name down, removing ports, subdomains, or www.
+ var pattern = /^(([^\/:]+?\.)*)([^\.:]{1,})((\.[a-z0-9]{1,253})*)(:[0-9]{1,5})?$/;
+ var host = window.location.host.replace(pattern, '$3$4');
+ var subdomain = window.location.host.replace(pattern, '$1');
+
+ // Determine what subdomains are considered internal.
+ var subdomains;
+ if (drupalSettings.data.extlink.extSubdomains) {
+ subdomains = "([^/]*\\.)?";
+ }
+ else if (subdomain === 'www.' || subdomain === '') {
+ subdomains = "(www\\.)?";
+ }
+ else {
+ subdomains = subdomain.replace(".", "\\.");
+ }
+
+ // Build regular expressions that define an internal link.
+ var internal_link = new RegExp("^https?://" + subdomains + host, "i");
+
+ // Extra internal link matching.
+ var extInclude = false;
+ if (drupalSettings.data.extlink.extInclude) {
+ extInclude = new RegExp(drupalSettings.data.extlink.extInclude.replace(/\\/, '\\'), "i");
+ }
+
+ // Extra external link matching.
+ var extExclude = false;
+ if (drupalSettings.data.extlink.extExclude) {
+ extExclude = new RegExp(drupalSettings.data.extlink.extExclude.replace(/\\/, '\\'), "i");
+ }
+
+ // Extra external link CSS selector exclusion.
+ var extCssExclude = false;
+ if (drupalSettings.data.extlink.extCssExclude) {
+ extCssExclude = drupalSettings.data.extlink.extCssExclude;
+ }
+
+ // Extra external link CSS selector explicit.
+ var extCssExplicit = false;
+ if (drupalSettings.data.extlink.extCssExplicit) {
+ extCssExplicit = drupalSettings.data.extlink.extCssExplicit;
+ }
+
+ // Find all links which are NOT internal and begin with http as opposed
+ // to ftp://, javascript:, etc. other kinds of links.
+ // When operating on the 'this' variable, the host has been appended to
+ // all links by the browser, even local ones.
+ // In jQuery 1.1 and higher, we'd use a filter method here, but it is not
+ // available in jQuery 1.0 (Drupal 5 default).
+ var external_links = [];
+ var mailto_links = [];
+ $("a:not(." + drupalSettings.data.extlink.extClass + ", ." + drupalSettings.data.extlink.mailtoClass + "), area:not(." + drupalSettings.data.extlink.extClass + ", ." + drupalSettings.data.extlink.mailtoClass + ")", context).each(function (el) {
+ try {
+ if (typeof this.href == 'string') {
+ var url = this.href.toLowerCase();
+ }
+ // Handle SVG links (xlink:href).
+ else if (typeof this.href == 'object') {
+ var url = this.href.baseVal;
+ }
+ if (url.indexOf('http') === 0
+ && ((!url.match(internal_link) && !(extExclude && url.match(extExclude))) || (extInclude && url.match(extInclude)))
+ && !(extCssExclude && $(this).parents(extCssExclude).length > 0)
+ && !(extCssExplicit && $(this).parents(extCssExplicit).length < 1)) {
+ external_links.push(this);
+ }
+ // Do not include area tags with begin with mailto: (this prohibits
+ // icons from being added to image-maps).
+ else if (this.tagName !== 'AREA'
+ && url.indexOf('mailto:') === 0
+ && !(extCssExclude && $(this).parents(extCssExclude).length > 0)
+ && !(extCssExplicit && $(this).parents(extCssExplicit).length < 1)) {
+ mailto_links.push(this);
+ }
+ }
+ // IE7 throws errors often when dealing with irregular links, such as:
+ // Empty tags.
+ // example User:pass syntax.
+ catch (error) {
+ return false;
+ }
+ });
+
+ if (drupalSettings.data.extlink.extClass) {
+ Drupal.extlink.applyClassAndSpan(external_links, drupalSettings.data.extlink.extClass);
+ }
+
+ if (drupalSettings.data.extlink.mailtoClass) {
+ Drupal.extlink.applyClassAndSpan(mailto_links, drupalSettings.data.extlink.mailtoClass);
+ }
+
+ if (drupalSettings.data.extlink.extTarget) {
+ // Apply the target attribute to all links.
+ if(drupalSettings.data.extlink.extTarget){
+ $(external_links).attr('target', '_blank');
+ }
+ }
+
+ Drupal.extlink = Drupal.extlink || {};
+
+ // Set up default click function for the external links popup. This should be
+ // overridden by modules wanting to alter the popup.
+ Drupal.extlink.popupClickHandler = Drupal.extlink.popupClickHandler || function () {
+ if (drupalSettings.data.extlink.extAlert) {
+ return confirm(drupalSettings.data.extlink.extAlertText);
+ }
+ };
+
+ $(external_links).click(function (e) {
+ return Drupal.extlink.popupClickHandler(e, this);
+ });
+ };
+
+ /**
+ * Apply a class and a trailing to all links not containing images.
+ *
+ * @param links
+ * An array of DOM elements representing the links.
+ * @param class_name
+ * The class to apply to the links.
+ */
+ Drupal.extlink.applyClassAndSpan = function (links, class_name) {
+ var $links_to_process;
+ if (drupalSettings.data.extlink.extImgClass) {
+ $links_to_process = $(links);
+ }
+ else {
+ var links_with_images = $(links).find('img').parents('a');
+ $links_to_process = $(links).not(links_with_images);
+ }
+ $links_to_process.addClass(class_name);
+ var i;
+ var length = $links_to_process.length;
+ for (i = 0; i < length; i++) {
+ var $link = $($links_to_process[i]);
+ if ($link.css('display') === 'inline' || $link.css('display') === 'inline-block') {
+ if (class_name === drupalSettings.data.extlink.mailtoClass) {
+ $link.append(' ' + drupalSettings.data.extlink.mailtoLabel + '');
+ }
+ else {
+ $link.append(' ' + drupalSettings.data.extlink.extLabel + '');
+ }
+ }
+ }
+ };
+
+ Drupal.behaviors.extlink = Drupal.behaviors.extlink || {};
+ Drupal.behaviors.extlink.attach = function (context, drupalSettings) {
+ // Backwards compatibility, for the benefit of modules overriding extlink
+ // functionality by defining an "extlinkAttach" global function.
+ if (typeof extlinkAttach === 'function') {
+ extlinkAttach(context);
+ }
+ else {
+ Drupal.extlink.attach(context, drupalSettings);
+ }
+ };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.libraries.yml b/sites/all/modules/contrib/theme/extlink/extlink.libraries.yml
new file mode 100644
index 000000000..dae673e6c
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.libraries.yml
@@ -0,0 +1,12 @@
+drupal.extlink:
+ version: VERSION
+ js:
+ extlink.min.js: {}
+ css:
+ theme:
+ extlink.css: { every_page: 1 }
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupalSettings
+ - core/jquery.once
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.links.menu.yml b/sites/all/modules/contrib/theme/extlink/extlink.links.menu.yml
new file mode 100644
index 000000000..4392012c2
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.links.menu.yml
@@ -0,0 +1,5 @@
+extlink_admin.settings:
+ title: External Links
+ description: 'External Links administration page.'
+ route_name: extlink_admin.settings
+ parent: system.admin_config_ui
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.min.js b/sites/all/modules/contrib/theme/extlink/extlink.min.js
new file mode 100644
index 000000000..c4d7aeaf9
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.min.js
@@ -0,0 +1 @@
+!function(t,a,e){"use strict";a.extlink=a.extlink||{},a.extlink.attach=function(e,n){if(n.data.hasOwnProperty("extlink")){var l,i=/^(([^\/:]+?\.)*)([^\.:]{4,})((\.[a-z]{1,4})*)(:[0-9]{1,5})?$/,s=window.location.host.replace(i,"$3$4"),x=window.location.host.replace(i,"$1");l=n.data.extlink.extSubdomains?"([^/]*\\.)?":"www."===x||""===x?"(www\\.)?":x.replace(".","\\.");var p=new RegExp("^https?://"+l+s,"i"),d=!1;n.data.extlink.extInclude&&(d=new RegExp(n.data.extlink.extInclude.replace(/\\/,"\\"),"i"));var r=!1;n.data.extlink.extExclude&&(r=new RegExp(n.data.extlink.extExclude.replace(/\\/,"\\"),"i"));var k=!1;n.data.extlink.extCssExclude&&(k=n.data.extlink.extCssExclude);var c=!1;n.data.extlink.extCssExplicit&&(c=n.data.extlink.extCssExplicit);var o=[],h=[];t("a:not(."+n.data.extlink.extClass+", ."+n.data.extlink.mailtoClass+"), area:not(."+n.data.extlink.extClass+", ."+n.data.extlink.mailtoClass+")",e).each(function(a){try{var e=this.href.toLowerCase();0!==e.indexOf("http")||(e.match(p)||r&&e.match(r))&&(!d||!e.match(d))||k&&t(this).parents(k).length>0||c&&t(this).parents(c).length<1?"AREA"===this.tagName||0!==e.indexOf("mailto:")||k&&t(this).parents(k).length>0||c&&t(this).parents(c).length<1||h.push(this):o.push(this)}catch(n){return!1}}),n.data.extlink.extClass&&a.extlink.applyClassAndSpan(o,n.data.extlink.extClass),n.data.extlink.mailtoClass&&a.extlink.applyClassAndSpan(h,n.data.extlink.mailtoClass),n.data.extlink.extTarget&&n.data.extlink.extTarget&&t(o).attr("target","_blank"),a.extlink=a.extlink||{},a.extlink.popupClickHandler=a.extlink.popupClickHandler||function(){return n.data.extlink.extAlert?confirm(n.data.extlink.extAlertText):void 0},t(o).click(function(t){return a.extlink.popupClickHandler(t)})}},a.extlink.applyClassAndSpan=function(a,n){var l;if(e.data.extlink.extImgClass)l=t(a);else{var i=t(a).find("img").parents("a");l=t(a).not(i)}l.addClass(n);var s,x=l.length;for(s=0;x>s;s++){var p=t(l[s]);("inline"===p.css("display")||"inline-block"===p.css("display"))&&(n===e.data.extlink.mailtoClass?p.append(' '+e.data.extlink.mailtoLabel+""):p.append(' '+e.data.extlink.extLabel+""))}},a.behaviors.extlink=a.behaviors.extlink||{},a.behaviors.extlink.attach=function(t,e){"function"==typeof extlinkAttach?extlinkAttach(t):a.extlink.attach(t,e)}}(jQuery,Drupal,drupalSettings);
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.module b/sites/all/modules/contrib/theme/extlink/extlink.module
new file mode 100644
index 000000000..92d744467
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.module
@@ -0,0 +1,32 @@
+ $config->get('extlink_target', 0),
+ 'extClass' => $config->get('extlink_class', 'ext'),
+ 'extLabel' => Html::escape($config->get('extlink_label', t('(link is external)'))),
+ 'extImgClass' => $config->get('extlink_img_class', 0),
+ 'extSubdomains' => $config->get('extlink_subdomains', 1),
+ 'extExclude' => $config->get('extlink_exclude', ''),
+ 'extInclude' => $config->get('extlink_include', ''),
+ 'extCssExclude' => $config->get('extlink_css_exclude', ''),
+ 'extCssExplicit' => $config->get('extlink_css_explicit', ''),
+ 'extAlert' => $config->get('extlink_alert', 0),
+ 'extAlertText' => $config->get('extlink_alert_text', t('This link will take you to an external web site. We are not responsible for their content.')),
+ 'mailtoClass' => $config->get('extlink_mailto_class', 'mailto'),
+ 'mailtoLabel' => Html::escape($config->get('extlink_mailto_label', t('(link sends e-mail)'))),
+ );
+}
diff --git a/sites/all/modules/contrib/theme/extlink/extlink.routing.yml b/sites/all/modules/contrib/theme/extlink/extlink.routing.yml
new file mode 100644
index 000000000..63bd78df8
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/extlink.routing.yml
@@ -0,0 +1,7 @@
+extlink_admin.settings:
+ path: '/admin/config/user-interface/extlink'
+ defaults:
+ _form: '\Drupal\extlink\Form\ExtlinkAdminSettingsForm'
+ _title: 'External Links'
+ requirements:
+ _permission: 'administer site configuration'
\ No newline at end of file
diff --git a/sites/all/modules/contrib/theme/extlink/extlink_s.png b/sites/all/modules/contrib/theme/extlink/extlink_s.png
new file mode 100644
index 000000000..778fb586c
Binary files /dev/null and b/sites/all/modules/contrib/theme/extlink/extlink_s.png differ
diff --git a/sites/all/modules/contrib/theme/extlink/src/Form/ExtlinkAdminSettingsForm.php b/sites/all/modules/contrib/theme/extlink/src/Form/ExtlinkAdminSettingsForm.php
new file mode 100644
index 000000000..46a677ffb
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/src/Form/ExtlinkAdminSettingsForm.php
@@ -0,0 +1,200 @@
+ 'checkbox',
+ '#title' => $this->t('Place an icon next to external links.'),
+ '#return_value' => 'ext',
+ '#default_value' => $config->get('extlink_class'),
+ '#description' => $this->t('Places an icon next to external links.'),
+ );
+
+ $form['extlink_mailto_class'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Place an icon next to mailto links'),
+ '#return_value' => 'mailto',
+ '#default_value' => $config->get('extlink_mailto_class'),
+ '#description' => $this->t('Places an icon next to mailto links.'),
+ );
+
+ $form['extlink_img_class'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Place an icon next to image links'),
+ '#return_value' => TRUE,
+ '#default_value' => $config->get('extlink_img_class', FALSE),
+ '#description' => $this->t('If checked, images wrapped in an anchor tag will be treated as external links.'),
+ );
+
+ $form['extlink_subdomains'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Exclude links with the same primary domain.'),
+ '#default_value' => $config->get('extlink_subdomains'),
+ '#description' => $this->t("For example, a link from 'www.example.com' to the subdomain of 'my.example.com' would be excluded."),
+ );
+
+ $form['extlink_target'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Open external links in a new window'),
+ '#return_value' => '_blank',
+ '#default_value' => $config->get('extlink_target'),
+ );
+
+ $form['extlink_alert'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Display a pop-up warning when any external link is clicked.'),
+ '#return_value' => '_blank',
+ '#default_value' => $config->get('extlink_alert'),
+ );
+
+ $form['extlink_alert_text'] = array(
+ '#type' => 'textarea',
+ '#title' => $this->t('Text to display in the pop-up warning box.'),
+ '#rows' => 3,
+ '#default_value' => $config->get('extlink_alert_text'),
+ '#description' => $this->t('Text to display in the pop-up external link warning box.'),
+ '#wysiwyg' => FALSE,
+ '#states' => array(
+ // Only show this field when user opts to display a pop-up warning.
+ 'visible' => array(
+ ':input[name="extlink_alert"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $patterns = array(
+ '#theme' => 'item_list',
+ '#items' => array(
+ array('#markup' => '(example\.com) ' . $this->t('Matches example.com.')),
+ array('#markup' => '(example\.com)|(example\.net) ' . $this->t('Multiple patterns can be strung together by using a pipe. Matches example.com OR example.net.')),
+ array('#markup' => '(links/goto/[0-9]+/[0-9]+) ' . $this->t('Matches links that go through the Links module redirect.')),
+ ),
+ );
+
+ $wildcards = array(
+ '#theme' => 'item_list',
+ '#items' => array(
+ array('#markup' => '. ' . $this->t('Matches any character.')),
+ array('#markup' => '? ' . $this->t('The previous character or set is optional.')),
+ array('#markup' => '\d ' . $this->t('Matches any digit (0-9).')),
+ array('#markup' => '[a-z] ' . $this->t('Brackets may be used to match a custom set of characters. This matches any alphabetic letter.')),
+ ),
+ );
+
+ $form['patterns'] = array(
+ '#type' => 'details',
+ '#title' => $this->t('Pattern matching'),
+ '#description' =>
+ '
' . $this->t('External links uses patterns (regular expressions) to match the "href" property of links.') . '
' .
+ $this->t('Here are some common patterns.') .
+ $renderer->render($patterns) .
+ $this->t('Common special characters:') .
+ $renderer->render($wildcards) .
+ '
' . $this->t('All special characters (@characters) must also be escaped with backslashes. Patterns are not case-sensitive. Any pattern supported by JavaScript may be used.', array('@characters' => '^ $ . ? ( ) | * +')) . '
',
+ '#open' => FALSE,
+ );
+
+ $form['patterns']['extlink_exclude'] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Exclude links matching the pattern'),
+ '#maxlength' => NULL,
+ '#default_value' => $config->get('extlink_exclude'),
+ '#description' => $this->t('Enter a regular expression for links that you wish to exclude from being considered external.'),
+ );
+
+ $form['patterns']['extlink_include'] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Include links matching the pattern'),
+ '#maxlength' => NULL,
+ '#default_value' => $config->get('extlink_include'),
+ '#description' => $this->t('Enter a regular expression for internal links that you wish to be considered external.'),
+ );
+
+ $form['css_matching'] = array(
+ '#tree' => FALSE,
+ '#type' => 'fieldset',
+ '#title' => $this->t('CSS Matching'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#description' =>
+ '
' . $this->t('Use CSS selectors to exclude entirely or only look inside explicitly specified classes and IDs for external links. These will be passed straight to jQuery for matching.') . '
',
+ );
+
+ $form['css_matching']['extlink_css_exclude'] = array(
+ '#type' => 'textarea',
+ '#title' => $this->t('Exclude links inside these CSS selectors'),
+ '#maxlength' => NULL,
+ '#default_value' => $config->get('extlink_css_exclude', ''),
+ '#description' => $this->t('Enter a comma-separated list of CSS selectors (ie "#block-block-2 .content, ul.menu")'),
+ );
+
+ $form['css_matching']['extlink_css_explicit'] = array(
+ '#type' => 'textarea',
+ '#title' => $this->t('Only look for links inside these CSS selectors'),
+ '#maxlength' => NULL,
+ '#default_value' => $config->get('extlink_css_explicit', ''),
+ '#description' => $this->t('Enter a comma-separated list of CSS selectors (ie "#block-block-2 .content, ul.menu")'),
+ );
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ parent::validateForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $values = $form_state->getValues();
+
+ \Drupal::configFactory()->getEditable('extlink.settings')
+ ->set('extlink_include', $values['extlink_include'])
+ ->set('extlink_exclude', $values['extlink_exclude'])
+ ->set('extlink_alert_text', $values['extlink_alert_text'])
+ ->set('extlink_alert', $values['extlink_alert'])
+ ->set('extlink_target', $values['extlink_target'])
+ ->set('extlink_subdomains', $values['extlink_subdomains'])
+ ->set('extlink_mailto_class', $values['extlink_mailto_class'])
+ ->set('extlink_img_class', $values['extlink_img_class'])
+ ->set('extlink_class', $values['extlink_class'])
+ ->set('extlink_css_exclude', $values['extlink_css_exclude'])
+ ->set('extlink_css_explicit', $values['extlink_css_explicit'])
+ ->save();
+
+ parent::SubmitForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEditableConfigNames() {
+ return ['extlink.settings'];
+ }
+
+}
diff --git a/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkAdminTest.php b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkAdminTest.php
new file mode 100644
index 000000000..9d4feb246
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkAdminTest.php
@@ -0,0 +1,26 @@
+drupalLogin($this->normal_user);
+ $this->drupalGet(self::EXTLINK_ADMIN_PATH);
+ $this->assertText(t('Access denied'), 'Normal users should not be able to access the External Links admin pages', 'External Links');
+
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet(self::EXTLINK_ADMIN_PATH);
+ $this->assertNoText(t('Access denied'), 'Admin users should be able to access the External Links admin pages', 'External Links');
+ }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTest.php b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTest.php
new file mode 100644
index 000000000..581b191cb
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTest.php
@@ -0,0 +1,18 @@
+drupalGet('');
+ }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTestBase.php b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTestBase.php
new file mode 100644
index 000000000..962d1ef47
--- /dev/null
+++ b/sites/all/modules/contrib/theme/extlink/src/Tests/ExtlinkTestBase.php
@@ -0,0 +1,67 @@
+normal_user = $this->drupalCreateUser($permissions);
+
+ // Create an admin user.
+ $permissions[] = 'administer site configuration';
+ $permissions[] = 'administer permissions';
+ $this->admin_user = $this->drupalCreateUser($permissions);
+ }
+
+ protected function getNodeFormValues() {
+ $edit = array(
+ 'title' => 'node_title ' . $this->randomName(32),
+ 'body[' . LANGUAGE_NONE . '][0][value]' => 'node_body ' . $this->randomName(256) . ' Google!',
+ );
+ return $edit;
+ }
+
+ /**
+ * Test if External Link is present
+ */
+ protected function assertExternalLinkPresence() {
+ $elements = $this->xpath('//span[@class="ext"]');
+ if (count($elements) > 0)
+ $this->pass('There should be an External Link on the form.', 'External Links');
+ else
+ $this->fail('There should be an External Link on the form.', 'External Links');
+ }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/users/profile/.travis-phpcs.sh b/sites/all/modules/contrib/users/profile/.travis-phpcs.sh
new file mode 100644
index 000000000..8803b3b4f
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/.travis-phpcs.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e $DRUPAL_TI_DEBUG
+
+# Ensure the right Drupal version is installed.
+# Note: This function is re-entrant.
+drupal_ti_ensure_drupal
+
+phpcs --report=full --standard=Drupal "$DRUPAL_TI_DRUPAL_DIR/$DRUPAL_TI_MODULES_PATH/$DRUPAL_TI_MODULE_NAME" || true
diff --git a/sites/all/modules/contrib/users/profile/.travis.yml b/sites/all/modules/contrib/users/profile/.travis.yml
new file mode 100644
index 000000000..d4c22ddb5
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/.travis.yml
@@ -0,0 +1,119 @@
+# @file
+# .travis.yml - Drupal for Travis CI Integration
+#
+# Template provided by https://github.com/LionsAd/drupal_ti.
+#
+# Based for simpletest upon:
+# https://github.com/sonnym/travis-ci-drupal-module-example
+
+language: php
+sudo: false
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+php:
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: 7
+ - php: hhvm
+branches:
+ only:
+ - "8.x-1.x"
+
+env:
+ global:
+ # add composer's global bin directory to the path
+ # see: https://github.com/drush-ops/drush#install---composer
+ - PATH="$PATH:$HOME/.composer/vendor/bin"
+
+ # Configuration variables.
+ - DRUPAL_TI_MODULE_NAME="profile"
+ - DRUPAL_TI_SIMPLETEST_GROUP="profile"
+
+ # Define runners and environment vars to include before and after the
+ # main runners / environment vars.
+ #- DRUPAL_TI_SCRIPT_DIR_BEFORE="./drupal_ti/before"
+ #- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after"
+
+ # The environment to use, supported are: drupal-7, drupal-8
+ - DRUPAL_TI_ENVIRONMENT="drupal-8"
+
+ # Drupal specific variables.
+ - DRUPAL_TI_DB="drupal_travis_db"
+ - DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db"
+ # Note: Do not add a trailing slash here.
+ - DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
+ - DRUPAL_TI_WEBSERVER_PORT="8080"
+
+ # Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end.
+ - DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
+
+ # === Behat specific variables.
+ # This is relative to $TRAVIS_BUILD_DIR
+ - DRUPAL_TI_BEHAT_DIR="./tests/behat"
+ # These arguments are passed to the bin/behat command.
+ - DRUPAL_TI_BEHAT_ARGS=""
+ # Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables.
+ - DRUPAL_TI_BEHAT_YML="behat.yml.dist"
+ # This is used to setup Xvfb.
+ - DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16"
+ # The version of seleniumthat should be used.
+ - DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.44"
+
+ # PHPUnit specific commandline arguments.
+ - DRUPAL_TI_PHPUNIT_ARGS=""
+
+ # Code coverage via coveralls.io
+ - DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*"
+ # This needs to match your .coveralls.yml file.
+ - DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml"
+
+ # Debug options
+ #- DRUPAL_TI_DEBUG="-x -v"
+ # Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium",
+ # etc. to only output those channels.
+ #- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver"
+
+ matrix:
+ # [[[ SELECT ANY OR MORE OPTIONS ]]]
+ #- DRUPAL_TI_RUNNERS="phpunit"
+ - DRUPAL_TI_RUNNERS="simpletest"
+ #- DRUPAL_TI_RUNNERS="behat"
+ #- DRUPAL_TI_RUNNERS="phpunit simpletest behat"
+ # Uncomment once unit tests added.
+ #- DRUPAL_TI_RUNNERS="phpunit-core simpletest"
+
+mysql:
+ database: drupal_travis_db
+ username: root
+ encoding: utf8
+
+before_install:
+ - composer self-update
+ - composer global require "lionsad/drupal_ti:1.*"
+ - drupal-ti before_install
+ # Codesniffer and Coder
+ - composer global require "squizlabs/php_codesniffer:2.0.*@dev"
+ - composer global require drupal/coder:8.2.0-beta1
+ - ln -s ~/.composer/vendor/drupal/coder/coder_sniffer/Drupal ~/.composer/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/
+
+install:
+ - drupal-ti install
+
+before_script:
+ - drupal-ti before_script
+
+script:
+ - drupal-ti --include .travis-phpcs.sh
+ - export SIMPLETEST_DB=mysql://root:@127.0.0.1/drupal_travis_db; drupal-ti script
+
+after_script:
+ - drupal-ti after_script
diff --git a/sites/all/modules/contrib/users/profile/LICENSE.txt b/sites/all/modules/contrib/users/profile/LICENSE.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/sites/all/modules/contrib/users/profile/README.md b/sites/all/modules/contrib/users/profile/README.md
new file mode 100644
index 000000000..fb77c5bf4
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/README.md
@@ -0,0 +1,23 @@
+Profile
+-------
+
+The *Profile* module provides configurable user profiles.
+
+## Requirements
+
+* [Entity API](https://www.drupal.org/project/entity)
+
+## Comparison to user account fields
+
+Why use profiles instead of user account fields?
+
+* With profile, user account settings and user profiles are conceptually different things, e.g. with the "Profile" module enabled users get two separate menu links "My account" and "My profile".
+* Profile allows for creating multiple profile types, which may be assigned to roles via permissions (e.g. a general profile + a customer profile)
+* Profile supports private profile fields, which are only shown to the user owning the profile and to administrators.
+
+## Features
+
+* Multiple profile types may be created via the UI (e.g. a general profile + a customer profile), whereas the module provides separated permissions for those.
+* Optionally, profile forms are shown during user account registration.
+* Fields may be configured to be private - thus visible only to the profile owner and administrators.
+* Profile types are displayed on the user view page, and can be configured through "Manage Display" on account settings.
\ No newline at end of file
diff --git a/sites/all/modules/contrib/users/profile/composer.json b/sites/all/modules/contrib/users/profile/composer.json
new file mode 100644
index 000000000..3b0970111
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/composer.json
@@ -0,0 +1,10 @@
+{
+ "name": "drupal/profile",
+ "type": "drupal-module",
+ "description": "Provides configurable user profiles.",
+ "homepage": "http://drupal.org/project/profile",
+ "license": "GPL-2.0+",
+ "require": {
+ "drupal/core": "~8.1"
+ }
+}
diff --git a/sites/all/modules/contrib/users/profile/config/install/system.action.profile_delete_action.yml b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_delete_action.yml
new file mode 100644
index 000000000..8805221e8
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_delete_action.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - profile
+id: profile_delete_action
+label: 'Delete selected profile'
+type: profile
+plugin: profile_delete_action
diff --git a/sites/all/modules/contrib/users/profile/config/install/system.action.profile_publish_action.yml b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_publish_action.yml
new file mode 100644
index 000000000..36e5eda64
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_publish_action.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - profile
+id: profile_publish_action
+label: 'Publish selected profile'
+type: profile
+plugin: profile_publish_action
diff --git a/sites/all/modules/contrib/users/profile/config/install/system.action.profile_unpublish_action.yml b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_unpublish_action.yml
new file mode 100644
index 000000000..ac0e0f330
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/config/install/system.action.profile_unpublish_action.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - profile
+id: profile_unpublish_action
+label: 'Unpublish selected profile'
+type: profile
+plugin: profile_unpublish_action
diff --git a/sites/all/modules/contrib/users/profile/config/install/views.view.profiles.yml b/sites/all/modules/contrib/users/profile/config/install/views.view.profiles.yml
new file mode 100644
index 000000000..9f6953648
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/config/install/views.view.profiles.yml
@@ -0,0 +1,379 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - entity
+ - profile
+id: profiles
+label: Profiles
+module: views
+description: ''
+tag: ''
+base_table: profile
+base_field: profile_id
+core: 8.x
+display:
+ default:
+ display_plugin: default
+ id: default
+ display_title: Master
+ position: 0
+ display_options:
+ access:
+ type: none
+ options: { }
+ cache:
+ type: tag
+ options: { }
+ query:
+ type: views_query
+ options:
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_comment: ''
+ query_tags: { }
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ pager:
+ type: none
+ options:
+ offset: 0
+ style:
+ type: grid
+ options:
+ grouping: { }
+ columns: 4
+ automatic_width: true
+ alignment: horizontal
+ col_class_default: true
+ col_class_custom: ''
+ row_class_default: true
+ row_class_custom: ''
+ row:
+ type: fields
+ options:
+ inline: { }
+ separator: ''
+ hide_empty: false
+ default_field_elements: true
+ fields:
+ rendered_entity:
+ id: rendered_entity
+ table: profile
+ field: rendered_entity
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: ''
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: false
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ view_mode: default
+ entity_type: profile
+ plugin_id: rendered_entity
+ operations:
+ id: operations
+ table: profile
+ field: operations
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: ''
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: false
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ destination: true
+ entity_type: profile
+ plugin_id: entity_operations
+ filters: { }
+ sorts: { }
+ header: { }
+ footer: { }
+ empty: { }
+ relationships: { }
+ arguments:
+ uid:
+ id: uid
+ table: profile
+ field: uid
+ relationship: none
+ group_type: group
+ admin_label: ''
+ default_action: default
+ exception:
+ value: all
+ title_enable: false
+ title: All
+ title_enable: false
+ title: ''
+ default_argument_type: user
+ default_argument_options:
+ user: false
+ default_argument_skip_url: false
+ summary_options:
+ base_path: ''
+ count: true
+ items_per_page: 25
+ override: false
+ summary:
+ sort_order: asc
+ number_of_records: 0
+ format: default_summary
+ specify_validation: false
+ validate:
+ type: none
+ fail: 'not found'
+ validate_options: { }
+ break_phrase: false
+ not: false
+ entity_type: profile
+ entity_field: uid
+ plugin_id: numeric
+ type:
+ id: type
+ table: profile
+ field: type
+ relationship: none
+ group_type: group
+ admin_label: ''
+ default_action: ignore
+ exception:
+ value: all
+ title_enable: false
+ title: All
+ title_enable: false
+ title: ''
+ default_argument_type: fixed
+ default_argument_options:
+ argument: ''
+ default_argument_skip_url: false
+ summary_options:
+ base_path: ''
+ count: true
+ items_per_page: 25
+ override: false
+ summary:
+ sort_order: asc
+ number_of_records: 0
+ format: default_summary
+ specify_validation: true
+ validate:
+ type: 'entity:profile_type'
+ fail: 'not found'
+ validate_options: { }
+ glossary: false
+ limit: 0
+ case: none
+ path_case: none
+ transform_dash: false
+ break_phrase: false
+ entity_type: profile
+ entity_field: type
+ plugin_id: string
+ status:
+ id: status
+ table: profile
+ field: status
+ relationship: none
+ group_type: group
+ admin_label: ''
+ default_action: ignore
+ exception:
+ value: all
+ title_enable: false
+ title: All
+ title_enable: false
+ title: ''
+ default_argument_type: fixed
+ default_argument_options:
+ argument: ''
+ default_argument_skip_url: false
+ summary_options:
+ base_path: ''
+ count: true
+ items_per_page: 25
+ override: false
+ summary:
+ sort_order: asc
+ number_of_records: 0
+ format: default_summary
+ specify_validation: false
+ validate:
+ type: none
+ fail: 'not found'
+ validate_options: { }
+ break_phrase: false
+ not: false
+ entity_type: profile
+ entity_field: status
+ plugin_id: numeric
+ display_extenders: { }
+ title: Profiles
+ cache_metadata:
+ max-age: 0
+ contexts:
+ - 'languages:language_interface'
+ - url
+ profile_type_listing:
+ display_plugin: embed
+ id: profile_type_listing
+ display_title: 'Profile type listing'
+ position: 1
+ display_options:
+ display_extenders: { }
+ display_description: ''
+ cache_metadata:
+ max-age: 0
+ contexts:
+ - 'languages:language_interface'
+ - url
+ user_view:
+ display_plugin: embed
+ id: user_view
+ display_title: 'User view'
+ position: 2
+ display_options:
+ display_extenders: { }
+ fields:
+ rendered_entity:
+ id: rendered_entity
+ table: profile
+ field: rendered_entity
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: ''
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: false
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ view_mode: default
+ entity_type: profile
+ plugin_id: rendered_entity
+ defaults:
+ fields: false
+ display_description: ''
+ cache_metadata:
+ max-age: 0
+ contexts:
+ - 'languages:language_interface'
+ - url
diff --git a/sites/all/modules/contrib/users/profile/config/schema/profile.schema.yml b/sites/all/modules/contrib/users/profile/config/schema/profile.schema.yml
new file mode 100644
index 000000000..000fc746a
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/config/schema/profile.schema.yml
@@ -0,0 +1,53 @@
+profile.type.*:
+ type: config_entity
+ label: 'Profile type settings'
+ mapping:
+ id:
+ type: string
+ label: 'Machine-readable name'
+ uuid:
+ type: string
+ label: 'UUID'
+ label:
+ type: label
+ label: 'Label'
+ registration:
+ type: boolean
+ label: 'Registration'
+ multiple:
+ type: boolean
+ label: 'Allow multiple profiles'
+ roles:
+ label: 'Allowed roles'
+ type: sequence
+ sequence:
+ type: string
+ weight:
+ type: integer
+ label: 'Weight'
+ status:
+ type: boolean
+ label: 'Status'
+ langcode:
+ type: string
+ label: 'Default language'
+
+field.field.*.*.*.third_party.profile:
+ type: mapping
+ label: 'Profile private'
+ mapping:
+ profile_private:
+ type: boolean
+ label: 'Whether this field is private or not.'
+
+action.configuration.profile_delete_action:
+ type: action_configuration_default
+ label: 'Delete profile configuration'
+
+action.configuration.profile_publish_action:
+ type: action_configuration_default
+ label: 'Publish selected profile configuration'
+
+action.configuration.profile_unpublish_action:
+ type: action_configuration_default
+ label: 'Unpublish selected profile configuration'
diff --git a/sites/all/modules/contrib/users/profile/css/profile.base.css b/sites/all/modules/contrib/users/profile/css/profile.base.css
new file mode 100644
index 000000000..dde5e01a4
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/css/profile.base.css
@@ -0,0 +1,11 @@
+.profile-title {
+ font-weight: bold;
+}
+
+.profile-item {
+ margin-bottom: 20px;
+}
+
+.profile-item-action-links {
+ margin-bottom: 10px;
+}
diff --git a/sites/all/modules/contrib/users/profile/profile.api.php b/sites/all/modules/contrib/users/profile/profile.api.php
new file mode 100644
index 000000000..d299016c1
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.api.php
@@ -0,0 +1,51 @@
+getType() == 'secret' && !\Drupal::currentUser()->hasPermission('custom permission')) {
+ return FALSE;
+ }
+ // For profiles other than the default profile grant access.
+ if ($profile->getType() != 'main' && \Drupal::currentUser()->hasPermission('custom permission')) {
+ return TRUE;
+ }
+ // In other cases do not alter access.
+ return NULL;
+}
+
+/**
+ * @}
+ */
diff --git a/sites/all/modules/contrib/users/profile/profile.info.yml b/sites/all/modules/contrib/users/profile/profile.info.yml
new file mode 100644
index 000000000..27e628ab4
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.info.yml
@@ -0,0 +1,17 @@
+name: Profile
+type: module
+description: 'Provides configurable user profiles.'
+# core: 8.x
+configure: entity.profile_type.collection
+dependencies:
+ - user
+ - entity
+ - field
+ - views
+ - system (>=8.1.0)
+
+# Information added by Drupal.org packaging script on 2016-08-31
+version: '8.x-1.0-alpha5+4-dev'
+core: '8.x'
+project: 'profile'
+datestamp: 1472611474
diff --git a/sites/all/modules/contrib/users/profile/profile.libraries.yml b/sites/all/modules/contrib/users/profile/profile.libraries.yml
new file mode 100644
index 000000000..8b1a352b4
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.libraries.yml
@@ -0,0 +1,5 @@
+drupal.profile-items:
+ version: VERSION
+ css:
+ component:
+ css/profile.base.css: {}
diff --git a/sites/all/modules/contrib/users/profile/profile.links.action.yml b/sites/all/modules/contrib/users/profile/profile.links.action.yml
new file mode 100644
index 000000000..0ed924ea8
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.links.action.yml
@@ -0,0 +1,5 @@
+profile_type_add:
+ route_name: entity.profile_type.add_form
+ title: 'Add profile type'
+ appears_on:
+ - entity.profile_type.collection
diff --git a/sites/all/modules/contrib/users/profile/profile.links.menu.yml b/sites/all/modules/contrib/users/profile/profile.links.menu.yml
new file mode 100644
index 000000000..533fbd53e
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.links.menu.yml
@@ -0,0 +1,5 @@
+entity.profile.collection:
+ title: 'User profiles'
+ description: 'Manage profile types, including fields, and profiles for users.'
+ route_name: entity.profile.collection
+ parent: user.admin_index
diff --git a/sites/all/modules/contrib/users/profile/profile.links.task.yml b/sites/all/modules/contrib/users/profile/profile.links.task.yml
new file mode 100644
index 000000000..0adc857d2
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.links.task.yml
@@ -0,0 +1,14 @@
+# Tabs on the profile admin page
+entity.profile.collection_tab:
+ route_name: entity.profile.collection
+ base_route: entity.profile.collection
+ title: 'Profiles'
+
+entity.profile_type.collection_tab:
+ route_name: entity.profile_type.collection
+ base_route: entity.profile.collection
+ title: 'Profile types'
+
+# Add tabs for each profile type
+entity.profile.user_profile_form:
+ deriver: Drupal\profile\Plugin\Derivative\ProfileLocalTask
diff --git a/sites/all/modules/contrib/users/profile/profile.module b/sites/all/modules/contrib/users/profile/profile.module
new file mode 100644
index 000000000..bc5718924
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.module
@@ -0,0 +1,345 @@
+' . t('About') . '';
+ $output .= '
' . t('The Profile module provides a fieldable entity, that allows administrators to define different sets of fields for user profiles, which are then displayed in the My Account section. This permits users of a site to share more information about themselves, and can help community-based sites organize users around specific information.', ['!user' => Url::fromRoute('user.page')]) . '
';
+ $output .= '
';
+ $output .= '
' . t('Types of profiles') . '
';
+ $output .= '
' . t('Profile types provide a way of grouping similar data for user profiles e.g. Personal information, Work etc. A default "Personal information type is provided. You may create more types and manage fields for each type from the Profile types admin page. When creating a new profile type, you will be able to specify whether a user may create multiple profiles or make the profile form available when registering a new user.', ['!profile-types' => Url::fromRoute('entity.profile_type.collection')]) . '
';
+ $output .= '
' . t('Creating profiles') . '
';
+ $output .= '
' . t('A user will see tabs they have access to, when editing their main user account e.g. "Add personal information profile". The visibility of a tab depends on whether they can create multiple profiles or if they haven\'t created a profile of the type that doesn\'t allow multiple instances.') . '
';
+ $output .= '
';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function profile_theme() {
+ return [
+ 'profile' => [
+ 'render element' => 'elements',
+ ],
+ ];
+}
+
+/**
+ * Prepares variables for profile templates.
+ *
+ * Default template: profile.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - elements: An associative array containing rendered fields.
+ * - attributes: HTML attributes for the containing element.
+ */
+function template_preprocess_profile(array &$variables) {
+ /** @var Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $variables['elements']['#profile'];
+
+ $variables['profile'] = $profile;
+ $variables['url'] = $profile->toUrl();
+ // Helpful $content variable for templates.
+ $variables['content'] = [];
+ foreach (\Drupal\Core\Render\Element::children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+}
+
+/**
+ * Implements hook_user_view().
+ */
+function profile_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display, $view_mode) {
+ // Iterate through each bundle and see if it's component exists.
+ foreach (ProfileType::loadMultiple() as $bundle) {
+ $component_key = 'profile_' . $bundle->id();
+ if ($display->getComponent($component_key)) {
+
+ // Embed the view of active profiles for profile type.
+ $build[$component_key] = [
+ '#type' => 'view',
+ '#name' => 'profiles',
+ '#display_id' => 'user_view',
+ '#arguments' => [$account->id(), $bundle->id(), 1],
+ '#embed' => TRUE,
+ '#title' => $bundle->label(),
+ '#pre_render' => [
+ ['\Drupal\views\Element\View', 'preRenderViewElement'],
+ 'profile_views_add_title_pre_render',
+ ],
+ ];
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_extra_field_info().
+ */
+function profile_entity_extra_field_info() {
+ $extra = [];
+
+ // Add each profile type as an extra field for display. Enabled by default.
+ foreach (ProfileType::loadMultiple() as $bundle) {
+ $extra['user']['user']['display']['profile_' . $bundle->id()] = array(
+ 'label' => $bundle->label(),
+ 'description' => t('Display @type profiles', ['@type' => $bundle->label()]),
+ 'weight' => 10,
+ 'visible' => TRUE,
+ );
+ }
+ return $extra;
+}
+
+/**
+ * Implements hook_user_delete().
+ */
+function profile_user_delete(EntityInterface $entity) {
+ $list = \Drupal::entityTypeManager()
+ ->getStorage('profile')
+ ->loadByProperties([
+ 'uid' => $entity->id(),
+ ]);
+
+ foreach ($list as $profile) {
+ $profile->delete();
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function profile_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+ $field = $form_state->getFormObject()->getEntity();
+ if ($field->getTargetEntityTypeId() != 'profile') {
+ return;
+ }
+
+ $form['field']['profile']['profile_private'] = [
+ '#type' => 'checkbox',
+ '#title' => t('This is a private field.'),
+ '#default_value' => $field->getThirdPartySetting('profile', 'profile_private', FALSE),
+ ];
+ $form['actions']['submit']['#submit'][] = 'profile_form_field_config_edit_form_submit';
+}
+
+/**
+ * Form submission handler for profile_form_field_config_edit_form_alter.
+ *
+ * @param array $form
+ * The form array.
+ * @param FormStateInterface $form_state
+ * The form state.
+ */
+function profile_form_field_config_edit_form_submit(array $form, FormStateInterface $form_state) {
+ $field = $form_state->getFormObject()->getEntity();
+ $form_fields = &$form_state->getValues();
+
+ // If the private option is checked, update settings.
+ if ($form_fields['profile_private']) {
+ $field->setThirdPartySetting('profile', 'profile_private', TRUE);
+ $field->save();
+ }
+ else {
+ $field->unsetThirdPartySetting('profile', 'profile_private');
+ $field->save();
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Add available profile forms to the user registration form.
+ */
+function profile_form_user_register_form_alter(&$form, FormStateInterface $form_state) {
+ $attached_profile_form = FALSE;
+ $weight = 90;
+
+ /** @var ProfileType[] $profile_types */
+ $profile_types = ProfileType::loadMultiple();
+ foreach ($profile_types as $profile_type) {
+ $instances = array_filter(\Drupal::service('entity_field.manager')->getFieldDefinitions('profile', $profile_type->id()), function ($field_definition) {
+ return $field_definition instanceof FieldConfigInterface;
+ });
+
+ if ($profile_type->getRegistration() === TRUE && count($instances)) {
+ $property = ['profiles', $profile_type->id()];
+ $profile = $form_state->get($property);
+ if (empty($profile)) {
+ $profile = Profile::create([
+ 'type' => $profile_type->id(),
+ 'langcode' => $profile_type->language() ?
+ $profile_type->language() : \Drupal::languageManager()->getDefaultLanguage()->getId(),
+ ]);
+
+ // Attach profile entity form.
+ $form_state->set($property, $profile);
+ }
+ $form_state->set('form_display_' . $profile_type->id(), EntityFormDisplay::collectRenderDisplay($profile, 'default'));
+ $form['entity_' . $profile_type->id()] = [
+ '#type' => 'details',
+ '#title' => $profile_type->label(),
+ '#tree' => TRUE,
+ '#parents' => ['entity_' . $profile_type->id()],
+ '#weight' => ++$weight,
+ '#open' => TRUE,
+ ];
+ $form_state
+ ->get('form_display_' . $profile_type->id())
+ ->buildForm($profile, $form['entity_' . $profile_type->id()], $form_state);
+
+ $attached_profile_form = TRUE;
+ }
+ }
+
+ if ($attached_profile_form) {
+ $form['actions']['submit']['#validate'][] = 'profile_form_user_register_form_validate';
+ $form['actions']['submit']['#submit'][] = 'profile_form_user_register_form_submit';
+ }
+}
+
+/**
+ * Extra form validation handler for the user registration form.
+ */
+function profile_form_user_register_form_validate(array &$form, FormStateInterface $form_state) {
+ $profiles = $form_state->get('profiles');
+ if (!empty($profiles)) {
+ foreach ($profiles as $bundle => $entity) {
+ /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
+ $form_display = $form_state->get('form_display_' . $bundle);
+ $form_display->extractFormValues($entity, $form['entity_' . $bundle], $form_state);
+ $form_display->validateFormValues($entity, $form['entity_' . $bundle], $form_state);
+ }
+ }
+ // Entity was validated in entityFormValidate(). This will prevent validation
+ // exception from being thrown.
+ $form_state->getFormObject()->getEntity()->validate();
+}
+
+/**
+ * Extra form submission handler for the user registration form.
+ */
+function profile_form_user_register_form_submit(array &$form, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Session\AccountInterface $account */
+ $account = $form_state->getFormObject()->getEntity();
+ $profiles = $form_state->get('profiles');
+ if (!empty($profiles)) {
+ foreach ($profiles as $bundle => $entity) {
+ $entity->setOwnerId($account->id());
+ $entity->setActive(TRUE);
+ $entity->save();
+ }
+ }
+}
+
+/**
+ * Pre render callback for profile embedded views to ensure a title is set.
+ * @param $element
+ *
+ * @return mixed
+ */
+function profile_views_add_title_pre_render($element) {
+ /** @var \Drupal\views\ViewExecutable $view */
+ if (isset($element['#title'])) {
+ $view = $element['view_build']['#view'];
+ $view->setTitle($element['#title']);
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ */
+function profile_preprocess_views_view(&$variables) {
+ // We have to manually add back the title since it was removed by Views.
+ // @see template_preprocess_views_view()
+ /** @var \Drupal\views\ViewExecutable $view */
+ $view = $variables['view'];
+ if ($view->storage->id() == 'profiles' && !empty($view->result)) {
+ // Test access to the profile.
+ /** @var \Drupal\profile\Entity\profile $entity */
+ $entity = reset($view->result)->_entity;
+ if ($entity->access('view')) {
+ $variables['title'] = $view->getTitle();
+ }
+ }
+}
+
+/**
+ * Implements hook_views_data_alter().
+ *
+ * Adds a relationship from the user table to its' profile entity.
+ */
+function profile_views_data_alter(&$data) {
+ $data['users_field_data']['profile']['relationship'] = [
+ 'title' => t('Profile'),
+ 'label' => t('Profile'),
+ 'group' => 'User',
+ 'help' => t('Reference to the profile of a user.'),
+ 'id' => 'standard',
+ 'base' => 'profile',
+ 'base field' => 'uid',
+ 'field' => 'uid',
+ ];
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function profile_theme_suggestions_profile(array $variables) {
+ $original = $variables['theme_hook_original'];
+ $entity = $variables['elements']['#profile'];
+ $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
+
+ $suggestions = [];
+ $suggestions[] = $original;
+ $suggestions[] = $original . '__' . $sanitized_view_mode;
+ $suggestions[] = $original . '__' . $entity->bundle();
+ $suggestions[] = $original . '__' . $entity->bundle() . '__' . $sanitized_view_mode;
+ $suggestions[] = $original . '__' . $entity->id();
+ $suggestions[] = $original . '__' . $entity->id() . '__' . $sanitized_view_mode;
+
+ return $suggestions;
+}
diff --git a/sites/all/modules/contrib/users/profile/profile.permissions.yml b/sites/all/modules/contrib/users/profile/profile.permissions.yml
new file mode 100644
index 000000000..2f9ed375f
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.permissions.yml
@@ -0,0 +1,13 @@
+administer profile types:
+ title: 'Administer profile types'
+ restrict access: true
+administer profiles:
+ title: 'Administer profiles'
+ restrict access: true
+bypass profile access:
+ title: 'Bypass profile access control'
+ description: 'View, edit and delete all user profiles, including private field values, regardless of permission restrictions.'
+ restrict access: true
+
+permission_callbacks:
+ - \Drupal\profile\ProfilePermissions::profileTypePermissions
diff --git a/sites/all/modules/contrib/users/profile/profile.routing.yml b/sites/all/modules/contrib/users/profile/profile.routing.yml
new file mode 100644
index 000000000..f655f59b5
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.routing.yml
@@ -0,0 +1,60 @@
+# edit_form, delete_form routes are generated by DefaultHtmlRouteProvider.
+entity.profile.collection:
+ path: '/admin/config/people/profiles'
+ defaults:
+ _entity_list: 'profile'
+ _title: 'Profiles'
+ requirements:
+ _permission: 'administer profiles'
+
+entity.profile_type.collection:
+ path: '/admin/config/people/profiles/types'
+ defaults:
+ _entity_list: 'profile_type'
+ _title: 'Profile types'
+ requirements:
+ _permission: 'administer profile types'
+
+entity.profile_type.add_form:
+ path: '/admin/config/people/profiles/types/add'
+ defaults:
+ _entity_form: profile_type.add
+ _title: 'Add'
+ requirements:
+ _permission: 'administer profile types'
+
+entity.profile.add_form:
+ path: '/user/{user}/profile/{profile_type}'
+ defaults:
+ _controller: '\Drupal\profile\Controller\ProfileController::addProfile'
+ _title: 'Add new profile'
+ operation: 'add'
+ requirements:
+ _profile_access_check: 'profile:{profile_type}'
+
+entity.profile.canonical:
+ path: '/profile/{profile}'
+ defaults:
+ _controller: '\Drupal\profile\Controller\ProfileViewController::view'
+ _title_callback: '\Drupal\profile\Controller\ProfileViewController::title'
+ requirements:
+ _entity_access: 'profile.view'
+
+entity.profile.multiple_delete_confirm:
+ path: '/admin/content/profile/delete'
+ defaults:
+ _form: '\Drupal\profile\Form\DeleteMultiple'
+ requirements:
+ _permission: 'administer profiles'
+
+entity.profile.set_default:
+ path: '/profile/{profile}/set-default'
+ defaults:
+ _controller: '\Drupal\profile\Controller\ProfileController::setDefault'
+ options:
+ parameters:
+ profile:
+ type: entity:profile
+ requirements:
+ _entity_access: 'profile.edit'
+ _csrf_token: 'TRUE'
diff --git a/sites/all/modules/contrib/users/profile/profile.services.yml b/sites/all/modules/contrib/users/profile/profile.services.yml
new file mode 100644
index 000000000..97b7ed71b
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/profile.services.yml
@@ -0,0 +1,7 @@
+services:
+
+ access_check.profile_type.profile_access:
+ class: Drupal\profile\Access\ProfileAccessCheck
+ arguments: ['@entity.manager']
+ tags:
+ - { name: access_check, applies_to: _profile_access_check }
diff --git a/sites/all/modules/contrib/users/profile/src/Access/ProfileAccessCheck.php b/sites/all/modules/contrib/users/profile/src/Access/ProfileAccessCheck.php
new file mode 100644
index 000000000..90e121063
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Access/ProfileAccessCheck.php
@@ -0,0 +1,72 @@
+entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * Checks access to the profile add page for the profile type.
+ *
+ * @param \Symfony\Component\Routing\Route $route
+ * The route to check against.
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The currently logged in account.
+ * @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
+ * The profile type entity.
+ *
+ * @return bool|\Drupal\Core\Access\AccessResultInterface
+ * The access result.
+ */
+ public function access(Route $route, AccountInterface $account, ProfileTypeInterface $profile_type = NULL) {
+ $access_control_handler = $this->entityTypeManager->getAccessControlHandler('profile');
+
+ if ($account->hasPermission('administer profile types')) {
+ return AccessResult::allowed()->cachePerPermissions();
+ }
+ $operation = $route->getRequirement('_profile_access_check');
+ if ($operation == 'add') {
+ return $access_control_handler->access($profile_type, $operation, $account, TRUE);
+ }
+
+ if ($profile_type) {
+ return $access_control_handler->createAccess($profile_type->id(), $account, [], TRUE);
+ }
+ // If checking whether a profile of any type may be created.
+ foreach ($this->entityTypeManager->getStorage('profile_type')->loadMultiple() as $profile_type) {
+ if (($access = $access_control_handler->createAccess($profile_type->id(), $account, [], TRUE)) && $access->isAllowed()) {
+ return $access;
+ }
+ }
+
+ // No opinion.
+ return AccessResult::neutral();
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Controller/ProfileController.php b/sites/all/modules/contrib/users/profile/src/Controller/ProfileController.php
new file mode 100644
index 000000000..915fc3a32
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Controller/ProfileController.php
@@ -0,0 +1,177 @@
+entityTypeManager()->getStorage('profile')->create([
+ 'uid' => $user->id(),
+ 'type' => $profile_type->id(),
+ ]);
+
+ return $this->entityFormBuilder()->getForm($profile, 'add', ['uid' => $user->id(), 'created' => REQUEST_TIME]);
+ }
+
+ /**
+ * Provides the profile edit form.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+ * The route match.
+ * @param \Drupal\user\UserInterface $user
+ * The user account.
+ * @param \Drupal\profile\Entity\ProfileInterface $profile
+ * The profile entity to edit.
+ *
+ * @return array
+ * The profile edit form.
+ */
+ public function editProfile(RouteMatchInterface $route_match, UserInterface $user, ProfileInterface $profile) {
+ return $this->entityFormBuilder()->getForm($profile, 'edit');
+ }
+
+ /**
+ * Provides profile delete form.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+ * The route match.
+ * @param \Drupal\user\UserInterface $user
+ * The user account.
+ * @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
+ * The profile type entity for the profile.
+ * @param int $id
+ * The id of the profile to delete.
+ *
+ * @return array
+ * Returns form array.
+ */
+ public function deleteProfile(RouteMatchInterface $route_match, UserInterface $user, ProfileTypeInterface $profile_type, $id) {
+ return $this->entityFormBuilder()->getForm(Profile::load($id), 'delete');
+ }
+
+ /**
+ * The _title_callback for the entity.profile.add_form route.
+ *
+ * @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
+ * The current profile type.
+ *
+ * @return string
+ * The page title.
+ */
+ public function addPageTitle(ProfileTypeInterface $profile_type) {
+ // @todo: edit profile uses this form too?
+ return $this->t('Create @label', ['@label' => $profile_type->label()]);
+ }
+
+ /**
+ * Provides profile create form.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+ * The route match.
+ * @param \Drupal\user\UserInterface $user
+ * The user account.
+ * @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
+ * The profile type entity for the profile.
+ *
+ * @return array
+ * Returns form array.
+ */
+ public function userProfileForm(RouteMatchInterface $route_match, UserInterface $user, ProfileTypeInterface $profile_type) {
+ /** @var \Drupal\profile\Entity\ProfileType $profile_type */
+
+ /** @var \Drupal\profile\Entity\ProfileInterface|bool $active_profile */
+ $active_profile = $this->entityTypeManager()->getStorage('profile')
+ ->loadByUser($user, $profile_type->id());
+
+ // If the profile type does not support multiple, only display an add form
+ // if there are no entities, or an edit for the current.
+ if (!$profile_type->getMultiple()) {
+
+ // If there is an active profile, provide edit form.
+ if ($active_profile) {
+ return $this->editProfile($route_match, $user, $active_profile);
+ }
+
+ // Else show the add form.
+ return $this->addProfile($route_match, $user, $profile_type);
+ }
+ // Display active, and link to create a profile.
+ else {
+ $build = [];
+
+ // If there is no active profile, display add form.
+ if (!$active_profile) {
+ return $this->addProfile($route_match, $user, $profile_type);
+ }
+
+ $build['add_profile'] = Link::createFromRoute(
+ $this->t('Add new @type', ['@type' => $profile_type->label()]),
+ "entity.profile.type.{$profile_type->id()}.user_profile_form.add",
+ ['user' => \Drupal::currentUser()->id(), 'profile_type' => $profile_type->id()])
+ ->toRenderable();
+
+ // Render the active profiles.
+ $build['active_profiles'] = [
+ '#type' => 'view',
+ '#name' => 'profiles',
+ '#display_id' => 'profile_type_listing',
+ '#arguments' => [$user->id(), $profile_type->id(), 1],
+ '#embed' => TRUE,
+ '#title' => $this->t('Active @type', ['@type' => $profile_type->label()]),
+ '#pre_render' => [
+ ['\Drupal\views\Element\View', 'preRenderViewElement'],
+ 'profile_views_add_title_pre_render',
+ ],
+ ];
+
+ return $build;
+ }
+ }
+
+ /**
+ * Mark profile as default.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
+ * The route match.
+ *
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ * A redirect back to the currency listing.
+ */
+ public function setDefault(RouteMatchInterface $routeMatch) {
+ $profile = $routeMatch->getParameter('profile');
+ $profile->setDefault(TRUE);
+ $profile->save();
+
+ drupal_set_message($this->t('The %label profile has been marked as default.', ['%label' => $profile->label()]));
+
+ $url = $profile->urlInfo('collection');
+ return $this->redirect($url->getRouteName(), $url->getRouteParameters(), $url->getOptions());
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Controller/ProfileViewController.php b/sites/all/modules/contrib/users/profile/src/Controller/ProfileViewController.php
new file mode 100644
index 000000000..b99985dcf
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Controller/ProfileViewController.php
@@ -0,0 +1,61 @@
+ \Drupal::entityTypeManager()
+ ->getViewBuilder($profile->getEntityTypeId())
+ ->view($profile, $view_mode, $langcode),
+ ];
+ $build['#title'] = $profile->label();
+
+ foreach ($profile->uriRelationships() as $rel) {
+ // Set the profile path as the canonical URL to prevent duplicate content.
+ $build['#attached']['html_head_link'][] = [
+ [
+ 'rel' => $rel,
+ 'href' => $profile->toUrl($rel)->toString(),
+ ],
+ TRUE,
+ ];
+
+ if ($rel == 'canonical') {
+ // Set the non-aliased canonical path as a default shortlink.
+ $build['#attached']['html_head_link'][] = [
+ [
+ 'rel' => 'shortlink',
+ 'href' => $profile->toUrl($rel, ['alias' => TRUE])->toString(),
+ ],
+ TRUE,
+ ];
+ }
+ }
+ return $build;
+ }
+
+ /**
+ * The _title_callback for the page that renders a profile entity.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $profile
+ * The current profile.
+ *
+ * @return string
+ * The page title.
+ */
+ public function title(EntityInterface $profile) {
+ return $this->entityManager->getTranslationFromContext($profile)->label();
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Entity/Profile.php b/sites/all/modules/contrib/users/profile/src/Entity/Profile.php
new file mode 100644
index 000000000..6902ec43a
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Entity/Profile.php
@@ -0,0 +1,275 @@
+setLabel(t('Owner'))
+ ->setDescription(t('The user that owns this profile.'))
+ ->setRevisionable(TRUE)
+ ->setSetting('target_type', 'user')
+ ->setSetting('handler', 'default');
+
+ $fields['status'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Active status'))
+ ->setDescription(t('A boolean indicating whether the profile is active.'))
+ ->setDefaultValue(TRUE)
+ ->setRevisionable(TRUE);
+
+ $fields['is_default'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Default'))
+ ->setDescription(t('A boolean indicating whether the profile is the default one.'))
+ ->setRevisionable(TRUE);
+
+ $fields['created'] = BaseFieldDefinition::create('created')
+ ->setLabel(t('Created'))
+ ->setDescription(t('The time that the profile was created.'))
+ ->setRevisionable(TRUE);
+
+ $fields['changed'] = BaseFieldDefinition::create('changed')
+ ->setLabel(t('Changed'))
+ ->setDescription(t('The time that the profile was last edited.'))
+ ->setRevisionable(TRUE);
+
+ return $fields;
+ }
+
+ /**
+ * Overrides Entity::id().
+ */
+ public function id() {
+ return $this->get('profile_id')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function label() {
+ $profile_type = ProfileType::load($this->bundle());
+ return
+ t('@type profile of @username (uid: @uid)',
+ [
+ '@type' => $profile_type->label(),
+ '@username' => $this->getOwner()->getDisplayName(),
+ '@uid' => $this->getOwnerId(),
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType() {
+ return $this->bundle();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setType($type) {
+ $this->set('type', $this->bundle());
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOwnerId() {
+ return $this->get('uid')->target_id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOwnerId($uid) {
+ $this->set('uid', $uid);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOwner() {
+ return $this->get('uid')->entity;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOwner(UserInterface $account) {
+ $this->set('uid', $account->id());
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCreatedTime() {
+ return $this->get('created')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCreatedTime($timestamp) {
+ $this->set('created', $timestamp);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRevisionCreationTime() {
+ return $this->get('revision_timestamp')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setRevisionCreationTime($timestamp) {
+ $this->set('revision_timestamp', $timestamp);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRevisionAuthor() {
+ return $this->get('revision_uid')->entity;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setRevisionAuthorId($uid) {
+ $this->set('revision_uid', $uid);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isActive() {
+ return (bool) $this->get('status')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setActive($active) {
+ $this->set('status', $active ? PROFILE_ACTIVE : PROFILE_NOT_ACTIVE);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isDefault() {
+ return (bool) $this->get('is_default')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDefault($is_default) {
+ $this->set('is_default', $is_default ? PROFILE_DEFAULT : PROFILE_NOT_DEFAULT);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheTagsToInvalidate() {
+ $tags = parent::getCacheTagsToInvalidate();
+ return Cache::mergeTags($tags, [
+ 'user:' . $this->getOwnerId(),
+ 'user_view',
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+ /** @var \Drupal\profile\ProfileStorage $storage */
+ parent::postSave($storage, $update);
+
+ // Check if this profile is, or became the default.
+ if ($this->isDefault()) {
+ /** @var \Drupal\profile\Entity\ProfileInterface[] $profiles */
+ $profiles = $storage->loadMultipleByUser($this->getOwner(), $this->getType());
+
+ // Ensure that all other profiles are set to not default.
+ foreach ($profiles as $profile) {
+ if ($profile->id() != $this->id() && $profile->isDefault()) {
+ $profile->setDefault(FALSE);
+ $profile->save();
+ }
+ }
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Entity/ProfileInterface.php b/sites/all/modules/contrib/users/profile/src/Entity/ProfileInterface.php
new file mode 100644
index 000000000..0c948bb10
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Entity/ProfileInterface.php
@@ -0,0 +1,126 @@
+registration;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setRegistration($registration) {
+ $this->registration = $registration;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMultiple() {
+ return $this->multiple;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMultiple($multiple) {
+ $this->multiple = $multiple;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRoles() {
+ return $this->roles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setRoles($roles) {
+ $this->roles = $roles;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getWeight() {
+ return $this->weight;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setWeight($weight) {
+ $this->weight = $weight;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+ parent::postSave($storage, $update);
+
+ // @todo Setting ->setRebuildNeeded isn't enough. Investigate.
+ \Drupal::service('router.builder')->rebuild();
+ }
+
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Entity/ProfileTypeInterface.php b/sites/all/modules/contrib/users/profile/src/Entity/ProfileTypeInterface.php
new file mode 100644
index 000000000..d831ddc50
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Entity/ProfileTypeInterface.php
@@ -0,0 +1,73 @@
+privateTempStoreFactory = $temp_store_factory;
+ $this->storage = $manager->getStorage('profile');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('user.private_tempstore'),
+ $container->get('entity.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'profile_multiple_delete_confirm';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ return \Drupal::translation()->formatPlural(count($this->profiles), 'Are you sure you want to delete this profile?', 'Are you sure you want to delete these profiles?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ return new Url('entity.profile.collection');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return t('Delete');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $this->profiles = $this->privateTempStoreFactory->get('profile_multiple_delete_confirm')->get(\Drupal::currentUser()->id());
+ if (empty($this->profiles)) {
+ return new RedirectResponse(\Drupal::url('entity.profile.collection', [], ['absolute' => TRUE]));
+ }
+
+ $form['profiles'] = [
+ '#theme' => 'item_list',
+ '#items' => array_map(function ($profile) {
+ return $profile->label();
+ }, $this->profiles),
+ ];
+ $form = parent::buildForm($form, $form_state);
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ if ($form_state->getValue('confirm') && !empty($this->profiles)) {
+ $this->storage->delete($this->profiles);
+ $this->privateTempStoreFactory->get('profile_multiple_delete_confirm')->delete(\Drupal::currentUser()->id());
+ $count = count($this->profiles);
+ $this->logger('content')->notice('Deleted @count profiles.', ['@count' => $count]);
+ drupal_set_message(\Drupal::translation()->formatPlural($count, 'Deleted 1 profile.', 'Deleted @count profiles.'));
+ }
+ $form_state->setRedirect('entity.profile.collection');
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Form/ProfileDeleteForm.php b/sites/all/modules/contrib/users/profile/src/Form/ProfileDeleteForm.php
new file mode 100644
index 000000000..e6ef1ff19
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Form/ProfileDeleteForm.php
@@ -0,0 +1,29 @@
+ $this->entity->getOwnerId(),
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRedirectUrl() {
+ return $this->getCancelUrl();
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Form/ProfileForm.php b/sites/all/modules/contrib/users/profile/src/Form/ProfileForm.php
new file mode 100644
index 000000000..ae2ef0768
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Form/ProfileForm.php
@@ -0,0 +1,89 @@
+isNew()) {
+ $entity->setCreatedTime(REQUEST_TIME);
+ }
+ return $entity;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function actions(array $form, FormStateInterface $form_state) {
+ $element = parent::actions($form, $form_state);
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $this->entity;
+
+ // Add an "Activate" button.
+ $element['set_default'] = $element['submit'];
+ $element['set_default']['#value'] = t('Save and make default');
+ $element['set_default']['#weight'] = 10;
+ $element['set_default']['#access'] = !$profile->isDefault();
+ array_unshift($element['set_default']['#submit'], [$this, 'setDefault']);
+
+ return $element;
+ }
+
+ /**
+ * Form submission handler for the 'set_default' action.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * A reference to a keyed array containing the current state of the form.
+ */
+ public function setDefault(array $form, FormStateInterface $form_state) {
+ $form_state->setValue('is_default', TRUE);
+ }
+
+ /**
+ * Form submission handler for the 'deactivate' action.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * A reference to a keyed array containing the current state of the form.
+ */
+ public function deactivate(array $form, FormStateInterface $form_state) {
+ $form_state->setValue('status', FALSE);
+ $form_state->setValue('is_default', TRUE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state) {
+ $profile_type = ProfileType::load($this->entity->bundle());
+
+ switch ($this->entity->save()) {
+ case SAVED_NEW:
+ drupal_set_message($this->t('%label profile has been created.', ['%label' => $profile_type->label()]));
+ break;
+
+ case SAVED_UPDATED:
+ drupal_set_message($this->t('%label profile has been updated.', ['%label' => $profile_type->label()]));
+ break;
+ }
+
+ $form_state->setRedirect('entity.user.canonical', [
+ 'user' => $this->entity->getOwnerId(),
+ ]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeDeleteForm.php b/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeDeleteForm.php
new file mode 100644
index 000000000..5d1def15c
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeDeleteForm.php
@@ -0,0 +1,60 @@
+queryFactory = $query_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity.query')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $num_profiles = $this->queryFactory->get('profile')
+ ->condition('type', $this->entity->id())
+ ->count()
+ ->execute();
+ if ($num_profiles) {
+ $caption = '
' . \Drupal::translation()
+ ->formatPlural($num_profiles, '%type is used by 1 profile on your site. You can not remove this profile type until you have removed all of the %type profiles.', '%type is used by @count profiles on your site. You may not remove %type until you have removed all of the %type profiles.', ['%type' => $this->entity->label()]) . '
';
+ $form['#title'] = $this->entity->label();
+ $form['description'] = ['#markup' => $caption];
+ return $form;
+ }
+
+ return parent::buildForm($form, $form_state);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeForm.php b/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeForm.php
new file mode 100644
index 000000000..840f16645
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Form/ProfileTypeForm.php
@@ -0,0 +1,126 @@
+entity;
+
+ if ($this->operation == 'add') {
+ $form['#title'] = $this->t('Add profile type');
+ }
+ else {
+ $form['#title'] = $this->t('Edit %label profile type', ['%label' => $type->label()]);
+ }
+
+ $form['label'] = [
+ '#title' => t('Label'),
+ '#type' => 'textfield',
+ '#default_value' => $type->label(),
+ '#description' => t('The human-readable name of this profile type.'),
+ '#required' => TRUE,
+ '#size' => 30,
+ ];
+ $form['id'] = [
+ '#type' => 'machine_name',
+ '#default_value' => $type->id(),
+ '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
+ '#machine_name' => [
+ 'exists' => '\Drupal\profile\Entity\ProfileType::load',
+ 'source' => ['label'],
+ ],
+ ];
+ $form['registration'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Include in user registration form'),
+ '#default_value' => $type->getRegistration(),
+ ];
+ $form['multiple'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Allow multiple profiles'),
+ '#default_value' => $type->getMultiple(),
+ ];
+
+ $form['roles'] = [
+ '#type' => 'checkboxes',
+ '#title' => t('Allowed roles'),
+ '#description' => $this->t('Limit the users that can have this profile by role.None will indicate that all users can have this profile type.'),
+ '#options' => [],
+ '#default_value' => $type->getRoles(),
+ ];
+ foreach (Role::loadMultiple() as $role) {
+ /** @var \Drupal\user\Entity\Role $role */
+ // We aren't interested in anon role.
+ if ($role->id() !== Role::ANONYMOUS_ID) {
+ $form['roles']['#options'][$role->id()] = $role->label();
+ }
+ }
+
+ return $this->protectBundleIdElement($form);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function actions(array $form, FormStateInterface $form_state) {
+ $actions = parent::actions($form, $form_state);
+ if (\Drupal::moduleHandler()->moduleExists('field_ui') &&
+ $this->getEntity()->isNew()
+ ) {
+ $actions['save_continue'] = $actions['submit'];
+ $actions['save_continue']['#value'] = t('Save and manage fields');
+ $actions['save_continue']['#submit'][] = [$this, 'redirectToFieldUI'];
+ }
+ return $actions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state) {
+ $type = $this->entity;
+ $status = $type->save();
+
+ if ($status == SAVED_UPDATED) {
+ drupal_set_message(t('%label profile type has been updated.', ['%label' => $type->label()]));
+ }
+ else {
+ drupal_set_message(t('%label profile type has been created.', ['%label' => $type->label()]));
+ }
+ $form_state->setRedirect('entity.profile_type.collection');
+ }
+
+ /**
+ * Form submission handler to redirect to Manage fields page of Field UI.
+ */
+ public function redirectToFieldUI(array $form, FormStateInterface $form_state) {
+ if ($form_state->getTriggeringElement()['#parents'][0] === 'save_continue' && $route_info = FieldUI::getOverviewRouteInfo('profile', $this->entity->id())) {
+ $form_state->setRedirectUrl($route_info);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(array $form, FormStateInterface $form_state) {
+ $form_state->setRedirect('entity.profile_type.delete_form', [
+ 'profile_type' => $this->entity->id(),
+ ]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/Action/DeleteProfile.php b/sites/all/modules/contrib/users/profile/src/Plugin/Action/DeleteProfile.php
new file mode 100644
index 000000000..6b2ec57bb
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/Action/DeleteProfile.php
@@ -0,0 +1,77 @@
+privateTempStore = $private_temp_store->get('profile_multiple_delete_confirm');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static($configuration, $plugin_id, $plugin_definition, $container->get('user.private_tempstore'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeMultiple(array $entities) {
+ $this->privateTempStore->set(\Drupal::currentUser()->id(), $entities);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($object = NULL) {
+ $this->executeMultiple([$object]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\profile\Entity\ProfileInterface $object */
+ return $object->access('delete', $account, $return_as_object);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/Action/PublishProfile.php b/sites/all/modules/contrib/users/profile/src/Plugin/Action/PublishProfile.php
new file mode 100644
index 000000000..2a62437ca
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/Action/PublishProfile.php
@@ -0,0 +1,39 @@
+setActive(TRUE);
+ $entity->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\profile\Entity\ProfileInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/Action/UnpublishProfile.php b/sites/all/modules/contrib/users/profile/src/Plugin/Action/UnpublishProfile.php
new file mode 100644
index 000000000..dea0dde9a
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/Action/UnpublishProfile.php
@@ -0,0 +1,39 @@
+setActive(FALSE);
+ $entity->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\profile\Entity\ProfileInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/Derivative/ProfileLocalTask.php b/sites/all/modules/contrib/users/profile/src/Plugin/Derivative/ProfileLocalTask.php
new file mode 100644
index 000000000..a74006f72
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/Derivative/ProfileLocalTask.php
@@ -0,0 +1,66 @@
+entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_definition) {
+ return new static(
+ $base_plugin_definition,
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ $this->derivatives = [];
+
+ // Starting weight for ordering the local tasks.
+ $weight = 10;
+
+ foreach ($this->entityTypeManager->getStorage('profile_type')->loadMultiple() as $profile_type_id => $profile_type) {
+ $this->derivatives["profile.type.$profile_type_id"] = [
+ 'title' => $profile_type->label(),
+ 'route_name' => "entity.profile.type.$profile_type_id.user_profile_form",
+ 'base_route' => 'entity.user.canonical',
+ 'route_parameters' => ['profile_type' => $profile_type_id],
+ 'weight' => ++$weight,
+ ] + $base_plugin_definition;
+ }
+
+ return $this->derivatives;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/EntityReferenceSelection/ProfileSelection.php b/sites/all/modules/contrib/users/profile/src/Plugin/EntityReferenceSelection/ProfileSelection.php
new file mode 100644
index 000000000..55c5b64ef
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/EntityReferenceSelection/ProfileSelection.php
@@ -0,0 +1,29 @@
+t('Profile types');
+ return $form;
+ }
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Plugin/views/field/ProfileBulkForm.php b/sites/all/modules/contrib/users/profile/src/Plugin/views/field/ProfileBulkForm.php
new file mode 100644
index 000000000..97294584d
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Plugin/views/field/ProfileBulkForm.php
@@ -0,0 +1,21 @@
+prepareUser($account);
+
+ if ($account->hasPermission('bypass profile access')) {
+ $result = AccessResult::allowed()->cachePerPermissions();
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+ $result = parent::createAccess($entity_bundle, $account, $context, TRUE)->cachePerPermissions();
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * When the $operation is 'add' then the $entity is of type 'profile_type',
+ * otherwise $entity is of type 'profile'.
+ */
+ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+ $account = $this->prepareUser($account);
+
+ $user_page = \Drupal::request()->attributes->get('user');
+
+ // Some times, operation edit is called update.
+ // Use edit in any case.
+ if ($operation == 'update') {
+ $operation = 'edit';
+ }
+
+ // Check that if profile type has require roles, the user the profile is
+ // being added to has any of the required roles.
+ if ($entity->getEntityTypeId() == 'profile') {
+ $profile_roles = ProfileType::load($entity->bundle())->getRoles();
+ // Retrieve all user roles including locked roles.
+ $user_roles = $entity->getOwner()->getRoles();
+ if (!empty(array_filter($profile_roles)) && !array_intersect($user_roles, $profile_roles)) {
+ return AccessResult::forbidden();
+ }
+ }
+ elseif ($entity->getEntityTypeId() == 'profile_type') {
+ $profile_roles = $entity->getRoles();
+ // Retrieve all user roles including locked roles.
+ $user_roles = User::load($user_page->id())->getRoles();
+ if (!empty(array_filter($profile_roles)) && !array_intersect($user_roles, $profile_roles)) {
+ return AccessResult::forbidden();
+ }
+ }
+
+ if ($account->hasPermission('bypass profile access')) {
+ return AccessResult::allowed()->cachePerPermissions();
+ }
+ elseif (
+ (
+ $operation == 'add'
+ && (
+ (
+ $user_page->id() == $account->id()
+ && $account->hasPermission($operation . ' own ' . $entity->id() . ' profile')
+ )
+ || $account->hasPermission($operation . ' any ' . $entity->id() . ' profile')
+ )
+ ) || (
+ $operation != 'add'
+ && (
+ (
+ $entity->getOwnerId() == $account->id()
+ && $account->hasPermission($operation . ' own ' . $entity->getType() . ' profile')
+ )
+ || $account->hasPermission($operation . ' any ' . $entity->getType() . ' profile')
+ )
+ )
+ ){
+ return AccessResult::allowed()->cachePerPermissions();
+ }
+ else {
+ // No opinion.
+ return AccessResult::neutral()->cachePerPermissions();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
+ return AccessResult::allowedIfHasPermissions($account, [
+ 'add any ' . $entity_bundle . ' profile',
+ 'add own ' . $entity_bundle . ' profile',
+ ], 'OR');
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileHtmlRouteProvider.php b/sites/all/modules/contrib/users/profile/src/ProfileHtmlRouteProvider.php
new file mode 100644
index 000000000..8f1960dea
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileHtmlRouteProvider.php
@@ -0,0 +1,59 @@
+ '\Drupal\profile\Controller\ProfileController::userProfileForm',
+ '_title_callback' => '\Drupal\profile\Controller\ProfileController::addPageTitle'
+ ],
+ ['_profile_access_check' => 'add'],
+ [
+ 'parameters' => [
+ 'user' => ['type' => 'entity:user'],
+ 'profile_type' => ['type' => 'entity:profile_type'],
+ ],
+ ])
+ );
+ $collection->add("entity.profile.type.{$profile_type->id()}.user_profile_form", $route);
+
+ // If the profile type supports multiple, we need an additional route for
+ // adding new profiles.
+ if ($profile_type->getMultiple()) {
+ $route = (new Route(
+ "/user/{user}/{profile_type}/add",
+ ['_controller' => '\Drupal\profile\Controller\ProfileController::addProfile'],
+ ['_profile_access_check' => 'add'],
+ [
+ 'parameters' => [
+ 'user' => ['type' => 'entity:user'],
+ 'profile_type' => ['type' => 'entity:profile_type'],
+ ],
+ ])
+ );
+ $collection->add("entity.profile.type.{$profile_type->id()}.user_profile_form.add", $route);
+ }
+ }
+
+ return $collection;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileListBuilder.php b/sites/all/modules/contrib/users/profile/src/ProfileListBuilder.php
new file mode 100644
index 000000000..aef2e5d64
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileListBuilder.php
@@ -0,0 +1,140 @@
+dateFormatter = $date_formatter;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+ return new static(
+ $entity_type,
+ $container->get('entity.manager')->getStorage($entity_type->id()),
+ $container->get('date.formatter'),
+ $container->get('renderer')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildHeader() {
+ $header = [
+ 'label' => $this->t('Label'),
+ 'type' => [
+ 'data' => $this->t('Type'),
+ 'class' => [RESPONSIVE_PRIORITY_MEDIUM],
+ ],
+ 'owner' => [
+ 'data' => $this->t('Owner'),
+ 'class' => [RESPONSIVE_PRIORITY_LOW],
+ ],
+ 'status' => $this->t('Status'),
+ 'is_default' => $this->t('Default'),
+ 'changed' => [
+ 'data' => $this->t('Updated'),
+ 'class' => [RESPONSIVE_PRIORITY_LOW],
+ ],
+ ];
+ if (\Drupal::languageManager()->isMultilingual()) {
+ $header['language_name'] = [
+ 'data' => $this->t('Language'),
+ 'class' => [RESPONSIVE_PRIORITY_LOW],
+ ];
+ }
+ return $header + parent::buildHeader();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildRow(EntityInterface $entity) {
+ /** @var \Drupal\profile\Entity\ProfileInterface $entity */
+ $langcode = $entity->language()->getId();
+ $uri = $entity->toUrl();
+ $options = $uri->getOptions();
+ $options += ($langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED && isset($languages[$langcode]) ? ['language' => $languages[$langcode]] : []);
+ $uri->setOptions($options);
+ $row['label'] = $entity->toLink();
+ $row['type'] = $entity->getType();
+ $row['owner']['data'] = [
+ '#theme' => 'username',
+ '#account' => $entity->getOwner(),
+ ];
+ $row['status'] = $entity->isActive() ? $this->t('active') : $this->t('not active');
+ $row['is_default'] = $entity->isDefault() ? $this->t('default') : $this->t('not default');
+ $row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');
+ $language_manager = \Drupal::languageManager();
+ if ($language_manager->isMultilingual()) {
+ $row['language_name'] = $language_manager->getLanguageName($langcode);
+ }
+
+ return $row + parent::buildRow($entity);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOperations(EntityInterface $entity) {
+ $operations = parent::getOperations($entity);
+
+ if (!$entity->isDefault()) {
+ $operations['set_default'] = [
+ 'title' => $this->t('Mark as default'),
+ 'url' => $entity->toUrl('set-default'),
+ 'parameter' => $entity,
+ ];
+ }
+
+ return $operations;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfilePermissions.php b/sites/all/modules/contrib/users/profile/src/ProfilePermissions.php
new file mode 100644
index 000000000..22d87258d
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfilePermissions.php
@@ -0,0 +1,72 @@
+buildPermissions($type);
+ }
+
+ return $perms;
+ }
+
+ /**
+ * Builds a standard list of permissions for a given profile type.
+ *
+ * @param \Drupal\profile\Entity\ProfileType $profile_type
+ * The machine name of the profile type.
+ *
+ * @return array
+ * An array of permission names and descriptions.
+ */
+ protected function buildPermissions(ProfileType $profile_type) {
+ $type_id = $profile_type->id();
+ $type_params = ['%type' => $profile_type->label()];
+
+ return [
+ "add own $type_id profile" => [
+ 'title' => $this->t('%type: Add own profile', $type_params),
+ ],
+ "add any $type_id profile" => [
+ 'title' => $this->t('%type: Add any profile', $type_params),
+ ],
+ "view own $type_id profile" => [
+ 'title' => $this->t('%type: View own profile', $type_params),
+ ],
+ "view any $type_id profile" => [
+ 'title' => $this->t('%type: View any profile', $type_params),
+ ],
+ "edit own $type_id profile" => [
+ 'title' => $this->t('%type: Edit own profile', $type_params),
+ ],
+ "edit any $type_id profile" => [
+ 'title' => $this->t('%type: Edit any profile', $type_params),
+ ],
+ "delete own $type_id profile" => [
+ 'title' => $this->t('%type: Delete own profile', $type_params),
+ ],
+ "delete any $type_id profile" => [
+ 'title' => $this->t('%type: Delete any profile', $type_params),
+ ],
+ ];
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileStorage.php b/sites/all/modules/contrib/users/profile/src/ProfileStorage.php
new file mode 100644
index 000000000..b98694bc2
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileStorage.php
@@ -0,0 +1,51 @@
+loadByProperties([
+ 'uid' => $account->id(),
+ 'type' => $profile_type,
+ 'status' => $active,
+ ]);
+
+ return reset($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMultipleByUser(AccountInterface $account, $profile_type, $active = PROFILE_ACTIVE) {
+ return $this->loadByProperties([
+ 'uid' => $account->id(),
+ 'type' => $profile_type,
+ 'status' => $active,
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadDefaultByUser(AccountInterface $account, $profile_type) {
+ $result = $this->loadByProperties([
+ 'uid' => $account->id(),
+ 'type' => $profile_type,
+ 'status' => PROFILE_ACTIVE,
+ 'is_default' => TRUE,
+ ]);
+
+ return reset($result);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileStorageInterface.php b/sites/all/modules/contrib/users/profile/src/ProfileStorageInterface.php
new file mode 100644
index 000000000..18bea72b9
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileStorageInterface.php
@@ -0,0 +1,56 @@
+randomMachineName();
+ $label = !empty($label) ? $label : $this->randomMachineName();
+
+ $type = ProfileType::create([
+ 'id' => $id,
+ 'label' => $label,
+ 'registration' => $registration,
+ 'roles' => $roles,
+ ]);
+ $type->save();
+
+ return $type;
+ }
+
+ /**
+ * Create a user, and optionally a profile.
+ *
+ * @param \Drupal\profile\Entity\ProfileTypeInterface $profile_type
+ * A profile type for the created profile entity.
+ * @param \Drupal\user\UserInterface $user
+ * A user to create a profile.
+ *
+ * @return \Drupal\profile\Entity\ProfileInterface
+ * A profile for a user.
+ */
+ protected function createProfile(ProfileTypeInterface $profile_type, UserInterface $user) {
+ $profile = Profile::create([
+ 'type' => $profile_type->id(),
+ 'uid' => $user->id(),
+ ]);
+ $profile->save();
+ return $profile;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileTypeListBuilder.php b/sites/all/modules/contrib/users/profile/src/ProfileTypeListBuilder.php
new file mode 100644
index 000000000..836a5d2f4
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileTypeListBuilder.php
@@ -0,0 +1,63 @@
+toLink(NULL, 'edit-form');
+ $row['registration'] = $entity->getRegistration() ? t('Yes') : t('No');
+ $row['multiple'] = $entity->getMultiple() ? t('Yes') : t('No');
+ return $row + parent::buildRow($entity);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOperations(EntityInterface $entity) {
+ $operations = parent::getOperations($entity);
+ // Place the edit operation after the operations added by field_ui.module
+ // which have the weights 15, 20, 25.
+ if (isset($operations['edit'])) {
+ $operations['edit'] = [
+ 'title' => t('Edit'),
+ 'weight' => 30,
+ 'url' => $entity->toUrl('edit-form'),
+ ];
+ }
+ if (isset($operations['delete'])) {
+ $operations['delete'] = [
+ 'title' => t('Delete'),
+ 'weight' => 35,
+ 'url' => $entity->toUrl('delete-form'),
+ ];
+ }
+ // Sort the operations to normalize link order.
+ uasort($operations, [
+ 'Drupal\Component\Utility\SortArray',
+ 'sortByWeightElement',
+ ]);
+
+ return $operations;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/ProfileViewBuilder.php b/sites/all/modules/contrib/users/profile/src/ProfileViewBuilder.php
new file mode 100644
index 000000000..d5916f580
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/ProfileViewBuilder.php
@@ -0,0 +1,22 @@
+ t('Profile operations bulk form'),
+ 'help' => t('Add a form element that lets you run operations on multiple profiles.'),
+ 'field' => [
+ 'id' => 'profile_bulk_form',
+ ],
+ ];
+
+ return $data;
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileDefaultTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileDefaultTest.php
new file mode 100644
index 000000000..04b9d2412
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileDefaultTest.php
@@ -0,0 +1,411 @@
+adminUser = $this->drupalCreateUser([
+ 'access user profiles',
+ 'administer profiles',
+ 'administer profile types',
+ 'bypass profile access',
+ 'access administration pages',
+ ]);
+
+ $this->user1 = User::create([
+ 'name' => $this->randomMachineName(),
+ 'mail' => $this->randomMachineName() . '@example.com',
+ ]);
+ $this->user1->save();
+ $this->user2 = User::create([
+ 'name' => $this->randomMachineName(),
+ 'mail' => $this->randomMachineName() . '@example.com',
+ ]);
+ $this->user2->save();
+ }
+
+ /**
+ * Tests profiles are active by default.
+ */
+ public function testProfileActive() {
+ $profile_type = $this->createProfileType('test_defaults', 'test_defaults');
+
+ // Create new profiles.
+ $profile1 = Profile::create($expected = [
+ 'type' => $profile_type->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile1->save();
+
+ $this->assertTrue($profile1->isActive());
+
+ $profile1->setActive(PROFILE_NOT_ACTIVE);
+ $profile1->save();
+
+ $this->assertFalse($profile1->isActive());
+ }
+
+ /**
+ * Tests default profile functionality.
+ */
+ public function testDefaultProfile() {
+ $profile_type = $this->createProfileType('test_defaults', 'test_defaults');
+
+ // Create new profiles.
+ $profile1 = Profile::create($expected = [
+ 'type' => $profile_type->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile1->save();
+ $profile2 = Profile::create($expected = [
+ 'type' => $profile_type->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile2->setDefault(TRUE);
+ $profile2->save();
+
+ $this->assertFalse($profile1->isDefault());
+ $this->assertTrue($profile2->isDefault());
+
+ $profile1->setDefault(TRUE)->save();
+
+ $this->assertFalse(Profile::load($profile2->id())->isDefault());
+ $this->assertTrue(Profile::load($profile1->id())->isDefault());
+ }
+
+ /**
+ * Tests loading default from storage handler.
+ */
+ public function testLoadDefaultProfile() {
+ $profile_type = $this->createProfileType('test_defaults', 'test_defaults');
+
+ // Create new profiles.
+ $profile1 = Profile::create($expected = [
+ 'type' => $profile_type->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile1->setActive(TRUE);
+ $profile1->save();
+ $profile2 = Profile::create($expected = [
+ 'type' => $profile_type->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile2->setActive(TRUE);
+ $profile2->setDefault(TRUE);
+ $profile2->save();
+
+ /** @var \Drupal\profile\ProfileStorageInterface $storage */
+ $storage = \Drupal::entityTypeManager()->getStorage('profile');
+
+ $default_profile = $storage->loadDefaultByUser($this->user1, $profile_type->id());
+ $this->assertEqual($profile2->id(), $default_profile->id());
+ }
+
+ /**
+ * Tests mark as default action.
+ */
+ public function testDefaultAction() {
+ $types_data = [
+ 'profile_type_0' => [
+ 'label' => $this->randomMachineName(),
+ 'multiple' => TRUE,
+ ],
+ 'profile_type_1' => [
+ 'label' => $this->randomMachineName(),
+ 'multiple' => TRUE,
+ ],
+ ];
+
+ /** @var ProfileType[] $types */
+ $types = [];
+ foreach ($types_data as $id => $values) {
+ $types[$id] = $this->createProfileType($id, $values['label']);
+ }
+
+ $restricted_user = $this->drupalCreateUser([
+ 'administer profiles',
+ 'edit own ' . $types['profile_type_0']->id() . ' profile',
+ 'edit own ' . $types['profile_type_1']->id() . ' profile',
+ ]);
+
+ $admin_user = $this->drupalCreateUser([
+ 'administer profiles',
+ 'edit any ' . $types['profile_type_0']->id() . ' profile',
+ 'edit any ' . $types['profile_type_1']->id() . ' profile',
+ ]);
+
+ // Create new profiles.
+ $profile_profile_type_0_restricted_user = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $restricted_user->id(),
+ ]);
+ $profile_profile_type_0_restricted_user->setActive(TRUE);
+ $profile_profile_type_0_restricted_user->save();
+ $profile_profile_type_0_user1_1 = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile_profile_type_0_user1_1->setActive(TRUE);
+ $profile_profile_type_0_user1_1->save();
+ $profile_profile_type_0_user1_2 = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile_profile_type_0_user1_2->setActive(TRUE);
+ $profile_profile_type_0_user1_2->save();
+ $profile_profile_type_1_user1 = Profile::create($expected = [
+ 'type' => $types['profile_type_1']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile_profile_type_1_user1->setActive(TRUE);
+ $profile_profile_type_1_user1->save();
+ $profile_profile_type_1_user2 = Profile::create($expected = [
+ 'type' => $types['profile_type_1']->id(),
+ 'uid' => $this->user2->id(),
+ ]);
+ $profile_profile_type_1_user2->setActive(TRUE);
+ $profile_profile_type_1_user2->save();
+ $profile_profile_type_1_user1_inactive = Profile::create($expected = [
+ 'type' => $types['profile_type_1']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile_profile_type_1_user1_inactive->setActive(FALSE);
+ $profile_profile_type_1_user1_inactive->save();
+ $profile_profile_type_1_user1_active = Profile::create($expected = [
+ 'type' => $types['profile_type_1']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile_profile_type_1_user1_active->isActive(TRUE);
+ $profile_profile_type_1_user1_active->save();
+
+ // Make sure that $restricted_user is allowed to set default his own profile
+ // and not others'.
+ $this->drupalLogin($restricted_user);
+
+ $this->drupalGet('admin/config/people/profiles');
+
+ $this->clickLink('Mark as default', 0);
+ $this->assertTrue(Profile::load($profile_profile_type_0_restricted_user->id())->isDefault());
+ $this->clickLink('Mark as default', 1);
+ $this->assertResponse(403);
+ $this->drupalLogout();
+
+ $this->drupalLogin($admin_user);
+
+ $this->drupalGet('admin/config/people/profiles');
+
+ // Mark $profile_profile_type_0_user1_1 as default
+ // $profile_profile_type_0_user1_2 should stay not default.
+ $this->clickLink('Mark as default', 0);
+ $this->assertTrue(Profile::load($profile_profile_type_0_user1_1->id())->isDefault());
+ $this->assertFalse($profile_profile_type_0_user1_2->isDefault());
+
+ // Mark $profile_profile_type_0_user1_2 as default
+ // $profile_profile_type_0_user1_1 should become not default.
+ $profile_profile_type_0_user1_2->setDefault(TRUE);
+ $profile_profile_type_0_user1_2->save();
+ $this->assertTrue($profile_profile_type_0_user1_2->isDefault());
+ $this->assertFalse($profile_profile_type_0_user1_1->isDefault());
+
+ // Mark $profile_profile_type_1_user1 as default
+ // $profile_profile_type_1_user2 should stay not default.
+ $this->clickLink('Mark as default', 1);
+ $this->assertTrue(Profile::load($profile_profile_type_1_user1->id())->isDefault());
+ $this->assertFalse($profile_profile_type_1_user2->isDefault());
+
+ // Mark $profile_profile_type_1_user2 as default
+ // $profile_profile_type_1_user1 should stay default.
+ $profile_profile_type_1_user2->setDefault(TRUE);
+ $profile_profile_type_1_user2->save();
+ $this->assertTrue($profile_profile_type_1_user2->isDefault());
+ $this->assertTrue(Profile::load($profile_profile_type_1_user1->id())->isDefault());
+
+ // Mark $profile_profile_type_1_user1_inactive as default
+ // $profile_profile_type_1_user1_active should stay not default.
+ $this->clickLink('Mark as default', 2);
+ $this->assertTrue(Profile::load($profile_profile_type_1_user1_inactive->id())->isDefault());
+ $this->assertFalse($profile_profile_type_1_user1_active->isDefault());
+
+ // Mark $profile_profile_type_1_user1_active as default
+ // $profile_profile_type_1_user1_inactive should stay default.
+ $profile_profile_type_1_user1_active->setDefault(TRUE);
+ $profile_profile_type_1_user1_active->save();
+ $this->assertTrue($profile_profile_type_1_user1_active->isDefault());
+ $this->assertTrue(Profile::load($profile_profile_type_1_user1_inactive->id())->isDefault());
+ }
+
+ /**
+ * Tests whether profile default on edit is working.
+ */
+ public function testProfileEdit() {
+ $types_data = [
+ 'profile_type_0' => [
+ 'label' => $this->randomMachineName(),
+ 'multiple' => TRUE,
+ ],
+ ];
+
+ /** @var \Drupal\profile\Entity\ProfileTypeInterface[] $types */
+ $types = [];
+ foreach ($types_data as $id => $values) {
+ $types[$id] = $this->createProfileType($id, $values['label']);
+ }
+
+ $admin_user = $this->drupalCreateUser([
+ 'administer profiles',
+ 'administer users',
+ 'edit any ' . $types['profile_type_0']->id() . ' profile',
+ ]);
+
+ // Create new profiles.
+ $profile1 = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile1->save();
+ $profile2 = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile2->setDefault(TRUE);
+ $profile2->save();
+
+ $this->assertFalse($profile1->isDefault());
+ $this->assertTrue($profile2->isDefault());
+
+ $this->drupalLogin($admin_user);
+
+ $this->drupalPostForm("profile/{$profile1->id()}/edit", [], 'Save and make default');
+
+ \Drupal::entityTypeManager()->getStorage('profile')->resetCache([$profile1->id(), $profile2->id()]);
+ $this->assertTrue(Profile::load($profile1->id())->isDefault());
+ $this->assertFalse(Profile::load($profile2->id())->isDefault());
+ }
+
+ /**
+ * Tests administrative-only profiles.
+ */
+ public function testAdminOnlyProfiles() {
+ $id = $this->type->id();
+ $field_name = $this->field->getName();
+
+ // Create a test user account.
+ $web_user = $this->drupalCreateUser(['access user profiles']);
+ $uid = $web_user->id();
+ $value = $this->randomMachineName();
+
+ // Administratively enter profile field values for the new account.
+ $this->drupalLogin($this->adminUser);
+
+ $edit = [
+ "{$this->field->getName()}[0][value]" => $value,
+ ];
+ $this->drupalPostForm("user/$uid/$id", $edit, 'Save and make default');
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = \Drupal::entityTypeManager()
+ ->getStorage('profile')
+ ->loadByUser($web_user, $this->type->id());
+ $profile_id = $profile->id();
+
+ $this->assertEqual($profile->getType(), $this->type->id());
+
+ /*
+// Verify that the administrator can see the profile.
+$this->drupalGet("user/$uid");
+$this->assertText($this->type->label());
+$this->assertText($value);
+$this->drupalLogout();
+ // Verify that the user can not access, create or edit the profile.
+ $this->drupalLogin($web_user);
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+ $this->drupalGet("user/$uid/edit/profile/$id/$profile_id");
+ $this->assertResponse(403);
+
+ // Check edit link isn't displayed.
+ $this->assertNoLinkByHref("user/$uid/edit/profile/$id/$profile_id");
+ // Check delete link isn't displayed.
+ $this->assertNoLinkByHref("user/$uid/delete/profile/$id/$profile_id");
+
+
+ // Allow users to edit own profiles.
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ["edit own $id profile"]);
+
+ // Verify that the user is able to edit the own profile.
+ $value = $this->randomMachineName();
+ $edit = [
+ "{$field_name}[0][value]" => $value,
+ ];
+ $this->drupalPostForm("user/$uid/edit/profile/$id/$profile_id", $edit, t('Save'));
+ $this->assertText(new FormattableMarkup('profile has been updated.', []));
+
+
+ // Verify that the own profile is still not visible on the account page.
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+
+ // Allow users to view own profiles.
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ["view own $id profile"]);
+
+ // Verify that the own profile is visible on the account page.
+ $this->drupalGet("user/$uid");
+ $this->assertText($this->type->label());
+ $this->assertText($value);
+
+ // Allow users to delete own profiles.
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ["delete own $id profile"]);
+
+ // Verify that the user can delete the own profile.
+ $this->drupalGet("user/$uid/edit/profile/$id/$profile_id");
+ $this->clickLink(t('Delete'));
+ $this->drupalPostForm(NULL, [], t('Delete'));
+ $this->assertRaw(new FormattableMarkup('@label profile deleted.', ['@label' => $this->type->label()]));
+ $this->assertUrl("user/$uid");
+
+ // Verify that the profile is gone.
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($this->type->label());
+ $this->assertNoText($value);
+ $this->drupalGet("user/$uid/edit/profile/$id");
+ $this->assertNoText($value);
+*/
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileFieldAccessTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileFieldAccessTest.php
new file mode 100644
index 000000000..6c7a11bdb
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileFieldAccessTest.php
@@ -0,0 +1,91 @@
+adminUser = $this->drupalCreateUser([
+ 'access user profiles',
+ 'administer profile types',
+ 'administer profile fields',
+ 'administer profile display',
+ 'bypass profile access',
+ ]);
+
+ $user_permissions = [
+ 'access user profiles',
+ "add own {$this->type->id()} profile",
+ "edit own {$this->type->id()} profile",
+ "view own {$this->type->id()} profile",
+ ];
+
+ $this->webUser = $this->drupalCreateUser($user_permissions);
+ $this->otherUser = $this->drupalCreateUser($user_permissions);
+ }
+
+ /**
+ * Tests private profile field access.
+ */
+ public function testPrivateField() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create a private profile field.
+ $edit = [
+ 'new_storage_type' => 'string',
+ 'label' => 'Secret',
+ 'field_name' => 'secret',
+ ];
+ $this->drupalPostForm("admin/config/people/profiles/types/manage/{$this->type->id()}/fields/add-field", $edit, t('Save and continue'));
+ $this->drupalPostForm(NULL, [], t('Save field settings'));
+ $edit = [
+ 'profile_private' => 1,
+ ];
+ $this->drupalPostForm(NULL, $edit, t('Save settings'));
+
+ // Fill in a field value.
+ $this->drupalLogin($this->webUser);
+ $uid = $this->webUser->id();
+ $secret = $this->randomMachineName();
+ $edit = [
+ 'field_secret[0][value]' => $secret,
+ ];
+ $this->drupalPostForm("user/$uid/{$this->type->id()}", $edit, t('Save'));
+
+ // User cache page need to be cleared to see new profile.
+ Cache::invalidateTags([
+ 'user:' . $uid,
+ 'user_view',
+ ]);
+
+ // Verify that the private field value appears for the profile owner.
+ $this->drupalGet("user/$uid");
+ $this->assertText($secret);
+
+ // Verify that the private field value appears for the administrator.
+ $this->drupalLogin($this->adminUser);
+ $this->drupalGet("user/$uid");
+ $this->assertText($secret);
+
+ // Verify that the private field value does not appear for other users.
+ $this->drupalLogin($this->otherUser);
+ $this->drupalGet("user/$uid");
+ $this->assertNoText($secret);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileRoleAccessTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileRoleAccessTest.php
new file mode 100644
index 000000000..15b1a79ff
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileRoleAccessTest.php
@@ -0,0 +1,165 @@
+role1 = $this->drupalCreateRole([]);
+ $this->role2 = $this->drupalCreateRole([]);
+ $this->type2 = $this->createProfileType(NULL, NULL, FALSE, [$this->role2]);
+ $this->type3 = $this->createProfileType(NULL, NULL, FALSE, [$this->role1, $this->role2]);
+ }
+
+ /**
+ * Tests add profile form access for a profile type that does not require
+ * users to have a role.
+ */
+ public function testProfileWithNoRoles() {
+ // Create user with add own profile permissions.
+ $web_user1 = $this->drupalCreateUser(["add own {$this->type->id()} profile"]);
+ $this->drupalLogin($web_user1);
+
+ // Test user without role can access add profile form.
+ // Expected: User can access form.
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
+ $this->assertResponse(200);
+ }
+
+ public function testLockedRoles() {
+ $locked_role_type = $this->createProfileType(NULL, NULL, FALSE, [AccountInterface::AUTHENTICATED_ROLE]);
+
+ // Create user with add own profile permissions.
+ $web_user1 = $this->drupalCreateUser(["add own {$locked_role_type->id()} profile"]);
+ $this->drupalLogin($web_user1);
+
+ // Test user without role can access add profile form.
+ // Expected: User can access form.
+ $this->drupalGet("user/{$web_user1->id()}/{$locked_role_type->id()}");
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Tests add profile form access for a profile type that requires users to
+ * have a single role.
+ */
+ public function testProfileWithSingleRole() {
+ // Create user with add own profile permissions.
+ $web_user1 = $this->drupalCreateUser(["add own {$this->type2->id()} profile"]);
+ $this->drupalLogin($web_user1);
+
+ // Test user without role can access add profile form.
+ // Expected: User cannot access form.
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
+ $this->assertResponse(403);
+
+ // Test user with wrong role can access add profile form.
+ // Expected: User cannot access form.
+ $web_user1->addRole($this->role1);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
+ $this->assertResponse(403);
+
+ // Test user with correct role can access add profile form.
+ // Expected: User can access form.
+ $web_user1->removeRole($this->role1);
+ $web_user1->addRole($this->role2);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type2->id()}");
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Tests add profile form access for a profile type that requires users to
+ * have one of multiple roles.
+ */
+ public function testProfileWithAllRoles() {
+ // Create user with add own profile permissions.
+ $web_user1 = $this->drupalCreateUser(["add own {$this->type3->id()} profile"]);
+ $this->drupalLogin($web_user1);
+
+ // Test user without role can access add profile form.
+ // Expected: User cannot access form.
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
+ $this->assertResponse(403);
+
+ // Test user with role 1 can access add profile form.
+ // Expected: User can access form.
+ $web_user1->addRole($this->role1);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
+ $this->assertResponse(200);
+
+ // Test user with both roles can access add profile form.
+ // Expected: User can access form.
+ $web_user1->addRole($this->role2);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
+ $this->assertResponse(200);
+
+ // Test user with role 2 can access add profile form.
+ // Expected: User can access form.
+ $web_user1->removeRole($this->role1);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
+ $this->assertResponse(200);
+
+ // Test user without role can access add profile form.
+ // Expected: User cannot access form.
+ $web_user1->removeRole($this->role2);
+ $web_user1->save();
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type3->id()}");
+ $this->assertResponse(403);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileTabTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTabTest.php
new file mode 100644
index 000000000..637a17981
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTabTest.php
@@ -0,0 +1,135 @@
+adminUser = $this->drupalCreateUser([
+ 'access user profiles',
+ 'administer profiles',
+ 'administer profile types',
+ 'bypass profile access',
+ 'access administration pages',
+ ]);
+ }
+
+ /**
+ * Tests tabs in profile UI.
+ */
+ public function testProfileTabs() {
+ $types_data = [
+ 'profile_type_0' => ['label' => $this->randomMachineName()],
+ 'profile_type_1' => ['label' => $this->randomMachineName()],
+ ];
+
+ /** @var ProfileType[] $types */
+ $types = [];
+ foreach ($types_data as $id => $values) {
+ $types[$id] = ProfileType::create(['id' => $id] + $values);
+ $types[$id]->save();
+ }
+ $this->container->get('router.builder')->rebuild();
+
+ $this->user1 = User::create([
+ 'name' => $this->randomMachineName(),
+ 'mail' => $this->randomMachineName() . '@example.com',
+ ]);
+ $this->user1->save();
+ $this->user2 = User::create([
+ 'name' => $this->randomMachineName(),
+ 'mail' => $this->randomMachineName() . '@example.com',
+ ]);
+ $this->user2->save();
+
+ // Create new profiles.
+ $profile1 = Profile::create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $profile1->save();
+ $profile2 = Profile::create($expected = [
+ 'type' => $types['profile_type_1']->id(),
+ 'uid' => $this->user2->id(),
+ ]);
+ $profile2->save();
+
+ $this->drupalLogin($this->adminUser);
+
+ $this->drupalGet('admin/config');
+ $this->clickLink('User profiles');
+ $this->assertResponse(200);
+ $this->assertUrl('admin/config/people/profiles');
+
+ $this->assertLink($profile1->label());
+ $this->assertLinkByHref($profile2->toUrl('canonical')->toString());
+
+ $tasks = [
+ ['entity.profile.collection', []],
+ ['entity.profile_type.collection', []],
+ ];
+
+ $this->assertLocalTasks($tasks, 0);
+ }
+
+ /**
+ * Asserts local tasks in the page output.
+ *
+ * @param array $routes
+ * A list of expected local tasks, prepared as an array of route names and
+ * their associated route parameters, to assert on the page (in the given
+ * order).
+ * @param int $level
+ * (optional) The local tasks level to assert; 0 for primary, 1 for
+ * secondary. Defaults to 0.
+ */
+ protected function assertLocalTasks(array $routes, $level = 0) {
+ $elements = $this->xpath('//*[contains(@class, :class)]//a', array(
+ ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
+ ));
+ $this->assertTrue(count($elements), 'Local tasks found.');
+ foreach ($routes as $index => $route_info) {
+ list($route_name, $route_parameters) = $route_info;
+ $expected = Url::fromRoute($route_name, $route_parameters)->toString();
+ $method = ($elements[$index]['href'] == $expected ? 'pass' : 'fail');
+ $this->{$method}(new FormattableMarkup('Task @number href @value equals @expected.', [
+ '@number' => $index + 1,
+ '@value' => (string) $elements[$index]['href'],
+ '@expected' => $expected,
+ ]));
+ }
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileTestBase.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTestBase.php
new file mode 100644
index 000000000..3a91f994d
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTestBase.php
@@ -0,0 +1,139 @@
+drupalPlaceBlock('local_tasks_block');
+ $this->drupalPlaceBlock('local_actions_block');
+ $this->drupalPlaceBlock('page_title_block');
+
+ $this->type = $this->createProfileType('test', 'Test profile', TRUE);
+
+ $id = $this->type->id();
+
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => 'profile_fullname',
+ 'entity_type' => 'profile',
+ 'type' => 'text',
+ ]);
+ $field_storage->save();
+
+ $this->field = FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $this->type->id(),
+ 'label' => 'Full name',
+ ]);
+ $this->field->save();
+
+ // Configure the default display.
+ $this->display = EntityViewDisplay::load("profile.{$this->type->id()}.default");
+ if (!$this->display) {
+ $this->display = EntityViewDisplay::create([
+ 'targetEntityType' => 'profile',
+ 'bundle' => $this->type->id(),
+ 'mode' => 'default',
+ 'status' => TRUE,
+ ]);
+ $this->display->save();
+ }
+ $this->display
+ ->setComponent($this->field->getName(), ['type' => 'string'])
+ ->save();
+
+ // Configure rhe default form.
+ $this->form = EntityFormDisplay::load("profile.{$this->type->id()}.default");
+ if (!$this->form) {
+ $this->form = EntityFormDisplay::create([
+ 'targetEntityType' => 'profile',
+ 'bundle' => $this->type->id(),
+ 'mode' => 'default',
+ 'status' => TRUE,
+ ]);
+ $this->form->save();
+ }
+ $this->form
+ ->setComponent($this->field->getName(), [
+ 'type' => 'string_textfield',
+ ])->save();
+
+ $this->checkPermissions([
+ 'administer profile types',
+ "view own $id profile",
+ "view any $id profile",
+ "add own $id profile",
+ "add any $id profile",
+ "edit own $id profile",
+ "edit any $id profile",
+ "delete own $id profile",
+ "delete any $id profile",
+ ]);
+
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ['access user profiles']);
+ $this->adminUser = $this->drupalCreateUser([
+ 'administer profile types',
+ 'administer profiles',
+ "view any $id profile",
+ "add any $id profile",
+ "edit any $id profile",
+ "delete any $id profile",
+ ]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeCRUDTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeCRUDTest.php
new file mode 100644
index 000000000..3e40b4d18
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeCRUDTest.php
@@ -0,0 +1,103 @@
+adminUser = $this->drupalCreateUser([
+ 'access user profiles',
+ 'administer profile types',
+ 'administer profile fields',
+ 'administer profile display',
+ 'bypass profile access',
+ ]);
+ }
+
+ /**
+ * Verify that routes are created for the profile type.
+ */
+ public function testRoutes() {
+ $this->drupalLogin($this->adminUser);
+ $type = $this->createProfileType($this->randomMachineName());
+ \Drupal::service('router.builder')->rebuildIfNeeded();
+ $this->drupalGet("user/{$this->adminUser->id()}/{$type->id()}");
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Tests CRUD operations for profile types through the UI.
+ */
+ public function testCRUDUI() {
+ $this->drupalLogin($this->adminUser);
+
+ // Create a new profile type.
+ $this->drupalGet('admin/config/people/profiles/types');
+ $this->assertResponse(200);
+ $this->clickLink(t('Add profile type'));
+
+ $this->assertUrl('admin/config/people/profiles/types/add');
+ $id = Unicode::strtolower($this->randomMachineName());
+ $label = $this->randomString();
+ $edit = [
+ 'id' => $id,
+ 'label' => $label,
+ ];
+ $this->drupalPostForm(NULL, $edit, t('Save'));
+ $this->assertUrl('admin/config/people/profiles/types');
+ $this->assertRaw(new FormattableMarkup('%label profile type has been created.', ['%label' => $label]));
+ $this->assertLinkByHref("admin/config/people/profiles/types/manage/$id");
+ $this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/fields");
+ $this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/display");
+ $this->assertLinkByHref("admin/config/people/profiles/types/manage/$id/delete");
+
+ // Edit the new profile type.
+ $this->drupalGet("admin/config/people/profiles/types/manage/$id");
+ $this->assertRaw(new FormattableMarkup('Edit %label profile type', ['%label' => $label]));
+ $edit = [
+ 'registration' => 1,
+ ];
+ $this->drupalPostForm(NULL, $edit, t('Save'));
+ $this->assertUrl('admin/config/people/profiles/types');
+ $this->assertRaw(new FormattableMarkup('%label profile type has been updated.', ['%label' => $label]));
+
+ \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
+
+ // Add a field to the profile type.
+ $this->drupalGet("admin/config/people/profiles/types/manage/$id/fields/add-field");
+ $field_name = Unicode::strtolower($this->randomMachineName());
+ $field_label = $this->randomString();
+ $edit = [
+ 'new_storage_type' => 'string',
+ 'label' => $field_label,
+ 'field_name' => $field_name,
+ ];
+ $this->drupalPostForm(NULL, $edit, t('Save and continue'));
+ $this->drupalPostForm(NULL, [], t('Save field settings'));
+ $this->drupalPostForm(NULL, [], t('Save settings'));
+ $this->assertRaw(new FormattableMarkup('Saved %label configuration.', ['%label' => $field_label]));
+
+ // Verify that the field is still associated with it.
+ $this->drupalGet("admin/config/people/profiles/types/manage/$id/fields");
+ // @todo D8 core: This assertion fails for an unknown reason. Database
+ // contains the right values, so field_attach_rename_bundle() works
+ // correctly. The pre-existing field does not appear on the Manage
+ // fields page of the renamed bundle. Not even flushing all caches
+ // helps. Can be reproduced manually.
+ // $this->assertText(check_plain($field_label));
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeMultipleTest.php b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeMultipleTest.php
new file mode 100644
index 000000000..c21a1d335
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/src/Tests/ProfileTypeMultipleTest.php
@@ -0,0 +1,95 @@
+drupalLogin($this->adminUser);
+
+ $edit = [
+ 'multiple' => 1,
+ ];
+ $this->drupalPostForm("admin/config/people/profiles/types/manage/{$this->type->id()}", $edit, t('Save'));
+ $this->assertRaw(new FormattableMarkup('%type profile type has been updated.', [
+ '%type' => $this->type->label(),
+ ]));
+
+ $web_user1 = $this->drupalCreateUser(
+ [
+ "view own {$this->type->id()} profile",
+ "add own {$this->type->id()} profile",
+ "edit own {$this->type->id()} profile",
+ ]
+ );
+ $this->drupalLogin($web_user1);
+ $value = $this->randomMachineName();
+
+ $edit = [
+ "{$this->field->getName()}[0][value]" => $value,
+ ];
+ $this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, t('Save'));
+ $this->assertRaw(new FormattableMarkup('%type profile has been created.', [
+ '%type' => $this->type->label(),
+ ]));
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
+ $this->assertLinkByHref("user/{$web_user1->id()}/{$this->type->id()}/add");
+ $this->assertText($value);
+
+ $value2 = $this->randomMachineName();
+ $edit = [
+ "{$this->field->getName()}[0][value]" => $value2,
+ ];
+ $this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}/add", $edit, t('Save'));
+ $this->assertRaw(new FormattableMarkup('%type profile has been created.', [
+ '%type' => $this->type->label(),
+ ]));
+
+ Cache::invalidateTags(['profile_view']);
+
+ $this->drupalGet("user/{$web_user1->id()}/{$this->type->id()}");
+ $this->assertText($value2);
+ }
+
+ /**
+ * Tests the non-multiple profile type create and edit flow.
+ */
+ public function testProfileNotMultipleFlow() {
+ $web_user1 = $this->createUser([
+ "add own {$this->type->id()} profile",
+ "edit own {$this->type->id()} profile",
+ ]);
+ $this->drupalLogin($web_user1);
+
+ // Create the profile.
+ $edit = [
+ "{$this->field->getName()}[0][value]" => $this->randomString(),
+ ];
+ $this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, 'Save and make default');
+ $this->assertRaw(new FormattableMarkup('%type profile has been created.', [
+ '%type' => $this->type->label(),
+ ]));
+
+ // Update the profile.
+ $edit = [
+ "{$this->field->getName()}[0][value]" => $this->randomString(),
+ ];
+ $this->drupalPostForm("user/{$web_user1->id()}/{$this->type->id()}", $edit, t('Save'));
+ $this->assertRaw(new FormattableMarkup('%type profile has been updated.', [
+ '%type' => $this->type->label(),
+ ]));
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/templates/profile.html.twig b/sites/all/modules/contrib/users/profile/templates/profile.html.twig
new file mode 100644
index 000000000..91651ee0f
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/templates/profile.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ *
+ * Default theme implementation for profiles.
+ *
+ * Available variables:
+ * - content: Items for the content of the profile.
+ * Use 'content' to print them all, or print a subset such as
+ * 'content.title'. Use the following code to exclude the
+ * printing of a given child element:
+ * @code
+ * {{ content|without('title') }}
+ * @endcode
+ * - attributes: HTML attributes for the wrapper.
+ * - profile: The profile object.
+ * - url: The profile URL.
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {{ content }}
+
diff --git a/sites/all/modules/contrib/users/profile/tests/profile_test/profile_test.info.yml b/sites/all/modules/contrib/users/profile/tests/profile_test/profile_test.info.yml
new file mode 100644
index 000000000..4ecc56353
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/profile_test/profile_test.info.yml
@@ -0,0 +1,12 @@
+name: Profile
+type: module
+description: 'Test module for Profile.'
+# core: 8.x
+dependencies:
+ - profile
+
+# Information added by Drupal.org packaging script on 2016-08-31
+version: '8.x-1.0-alpha5+4-dev'
+core: '8.x'
+project: 'profile'
+datestamp: 1472611474
diff --git a/sites/all/modules/contrib/users/profile/tests/profile_test/test_views/views.view.users.yml b/sites/all/modules/contrib/users/profile/tests/profile_test/test_views/views.view.users.yml
new file mode 100644
index 000000000..87832c3ef
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/profile_test/test_views/views.view.users.yml
@@ -0,0 +1,243 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - profile
+ - user
+id: users
+label: Users
+module: views
+description: ''
+tag: ''
+base_table: users_field_data
+base_field: uid
+core: 8.x
+display:
+ default:
+ display_plugin: default
+ id: default
+ display_title: Master
+ position: 0
+ display_options:
+ access:
+ type: perm
+ options:
+ perm: 'access user profiles'
+ cache:
+ type: tag
+ options: { }
+ query:
+ type: views_query
+ options:
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_comment: ''
+ query_tags: { }
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ pager:
+ type: full
+ options:
+ items_per_page: 0
+ offset: 0
+ id: 0
+ total_pages: null
+ tags:
+ previous: '‹ Previous'
+ next: 'Next ›'
+ first: '« First'
+ last: 'Last »'
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 25, 50'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ quantity: 9
+ style:
+ type: default
+ row:
+ type: fields
+ options:
+ default_field_elements: true
+ inline: { }
+ separator: ''
+ hide_empty: false
+ fields:
+ name:
+ id: name
+ table: users_field_data
+ field: name
+ entity_type: user
+ entity_field: name
+ label: ''
+ alter:
+ alter_text: false
+ make_link: false
+ absolute: false
+ trim: false
+ word_boundary: false
+ ellipsis: false
+ strip_tags: false
+ html: false
+ hide_empty: false
+ empty_zero: false
+ plugin_id: field
+ relationship: none
+ group_type: group
+ admin_label: ''
+ exclude: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_alter_empty: true
+ click_sort_column: value
+ type: user_name
+ settings: { }
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ profile_id:
+ id: profile_id
+ table: profile
+ field: profile_id
+ relationship: profile
+ group_type: group
+ admin_label: ''
+ label: ''
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: false
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ click_sort_column: value
+ type: number_integer
+ settings:
+ thousand_separator: ''
+ prefix_suffix: true
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ entity_type: profile
+ entity_field: profile_id
+ plugin_id: field
+ filters:
+ status:
+ value: true
+ table: users_field_data
+ field: status
+ plugin_id: boolean
+ entity_type: user
+ entity_field: status
+ id: status
+ expose:
+ operator: ''
+ group: 1
+ sorts: { }
+ title: Users
+ header: { }
+ footer: { }
+ empty: { }
+ relationships:
+ profile:
+ id: profile
+ table: users_field_data
+ field: profile
+ relationship: none
+ group_type: group
+ admin_label: Profile
+ required: true
+ entity_type: user
+ plugin_id: standard
+ arguments: { }
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url.query_args
+ - user.permissions
+ tags: { }
+ page_1:
+ display_plugin: page
+ id: page_1
+ display_title: Page
+ position: 1
+ display_options:
+ display_extenders: { }
+ path: users
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url.query_args
+ - user.permissions
+ tags: { }
diff --git a/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileRegisterFormTest.php b/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileRegisterFormTest.php
new file mode 100644
index 000000000..10adc20a3
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileRegisterFormTest.php
@@ -0,0 +1,66 @@
+type->id();
+ $field_name = $this->field->getName();
+
+ $this->field->setRequired(TRUE);
+ $this->field->save();
+
+ // Allow registration without administrative approval and log in user
+ // directly after registering.
+ \Drupal::configFactory()->getEditable('user.settings')
+ ->set('register', USER_REGISTER_VISITORS)
+ ->set('verify_mail', 0)
+ ->save();
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ['view own test profile']);
+
+ // Verify that the additional profile field is attached and required.
+ $name = $this->randomMachineName();
+ $pass_raw = $this->randomMachineName();
+ $edit = [
+ 'name' => $name,
+ 'mail' => $this->randomMachineName() . '@example.com',
+ 'pass[pass1]' => $pass_raw,
+ 'pass[pass2]' => $pass_raw,
+ ];
+ $this->drupalPostForm('user/register', $edit, t('Create new account'));
+
+ $this->assertSession()->pageTextContains(new FormattableMarkup('@name field is required.', ['@name' => $this->field->getLabel()]));
+
+ // Verify that we can register.
+ $edit["entity_" . $id . "[$field_name][0][value]"] = $this->randomMachineName();
+ $this->drupalPostForm(NULL, $edit, t('Create new account'));
+ $this->assertSession()->pageTextContains(new FormattableMarkup('Registration successful. You are now logged in.', []));
+
+ $new_user = user_load_by_name($name);
+ $this->assertTrue($new_user->isActive(), 'New account is active after registration.');
+
+ // Verify that a new profile was created for the new user ID.
+ $profile = $this->container->get('entity_type.manager')
+ ->getStorage('profile')
+ ->loadByUser($new_user, $this->type->id());
+
+ $this->assertEquals($profile->get($field_name)->value, $edit["entity_" . $id . "[$field_name][0][value]"], 'Field value found in loaded profile.');
+
+ // Verify that the profile field value appears on the user account page.
+ $this->drupalGet('user');
+ $this->assertSession()->pageTextContains($edit["entity_" . $id . "[$field_name][0][value]"]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileTestBase.php b/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileTestBase.php
new file mode 100644
index 000000000..87070439d
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/src/Functional/ProfileTestBase.php
@@ -0,0 +1,140 @@
+drupalPlaceBlock('local_tasks_block');
+ $this->drupalPlaceBlock('local_actions_block');
+ $this->drupalPlaceBlock('page_title_block');
+
+ $this->type = $this->createProfileType('test', 'Test profile', TRUE);
+
+ $id = $this->type->id();
+
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => 'profile_fullname',
+ 'entity_type' => 'profile',
+ 'type' => 'text',
+ ]);
+ $field_storage->save();
+
+ $this->field = FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $this->type->id(),
+ 'label' => 'Full name',
+ ]);
+ $this->field->save();
+
+ // Configure the default display.
+ $this->display = EntityViewDisplay::load("profile.{$this->type->id()}.default");
+ if (!$this->display) {
+ $this->display = EntityViewDisplay::create([
+ 'targetEntityType' => 'profile',
+ 'bundle' => $this->type->id(),
+ 'mode' => 'default',
+ 'status' => TRUE,
+ ]);
+ $this->display->save();
+ }
+ $this->display
+ ->setComponent($this->field->getName(), ['type' => 'string'])
+ ->save();
+
+ // Configure rhe default form.
+ $this->form = EntityFormDisplay::load("profile.{$this->type->id()}.default");
+ if (!$this->form) {
+ $this->form = EntityFormDisplay::create([
+ 'targetEntityType' => 'profile',
+ 'bundle' => $this->type->id(),
+ 'mode' => 'default',
+ 'status' => TRUE,
+ ]);
+ $this->form->save();
+ }
+ $this->form
+ ->setComponent($this->field->getName(), [
+ 'type' => 'string_textfield',
+ ])->save();
+
+ $this->checkPermissions([
+ 'administer profile types',
+ "view own $id profile",
+ "view any $id profile",
+ "add own $id profile",
+ "add any $id profile",
+ "edit own $id profile",
+ "edit any $id profile",
+ "delete own $id profile",
+ "delete any $id profile",
+ ]);
+
+ user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, ['access user profiles']);
+ $this->adminUser = $this->drupalCreateUser([
+ 'administer profile types',
+ 'administer profiles',
+ "view any $id profile",
+ "add any $id profile",
+ "edit any $id profile",
+ "delete any $id profile",
+ ]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileAccessTest.php b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileAccessTest.php
new file mode 100644
index 000000000..c71476b9b
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileAccessTest.php
@@ -0,0 +1,210 @@
+installEntitySchema('profile');
+ $this->installEntitySchema('view');
+ $this->installConfig(['profile']);
+ $this->accessControlHandler = $this->container->get('entity_type.manager')->getAccessControlHandler('profile');
+ $this->accessManager = $this->container->get('access_manager');
+ $this->type = $this->createProfileType('test', 'Test profile', TRUE);
+ $this->createUser();
+ }
+
+ /**
+ * Tests profile create and permissions.
+ */
+ public function testProfileCreateAccess() {
+ // Test user without any permissions.
+ $web_user1 = $this->createUser();
+
+ // Verify user does not have access to create.
+ $access = $this->accessControlHandler->createAccess($this->type->id(), $web_user1);
+ $this->assertFalse($access);
+
+ // Verify access through route.
+ $this->assertFalse($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user1->id(), 'profile_type' => $this->type->id()],
+ $web_user1
+ ));
+
+
+ // Test user with permission to only add own profile.
+ $web_user2 = $this->createUser([], ["add own {$this->type->id()} profile"]);
+ $web_user1->addRole($web_user2->get('roles')->first()->target_id);
+ $web_user1->save();
+
+ // Verify user has access to add their own profile.
+ $access = $this->accessControlHandler->createAccess($this->type->id(), $web_user2);
+ $this->assertTrue($access);
+
+ // User 2 can create own profile of type.
+ $this->assertTrue($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
+ $web_user2
+ ));
+ // User 1 cannot create a profile for User 2.
+ $this->assertFalse($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
+ $web_user1
+ ));
+
+ // Create a new profile type.
+ $second_type = $this->createProfileType('test2', 'Test2 profile', TRUE);
+ $this->assertFalse($this->accessControlHandler->createAccess('test2', $web_user2));
+ $this->assertFalse($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user2->id(), 'profile_type' => $second_type->id()],
+ $web_user2
+ ));
+
+ // Test user with permission to only add any profile.
+ $web_user3 = $this->createUser([], ["add any {$this->type->id()} profile"]);
+ $this->assertTrue($this->accessControlHandler->createAccess($this->type->id(), $web_user3));
+
+ // Verify access through route.
+ $this->assertTrue($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user3->id(), 'profile_type' => $this->type->id()],
+ $web_user3
+ ));
+ $this->assertTrue($this->accessManager->checkNamedRoute(
+ 'entity.profile.add_form',
+ ['user' => $web_user2->id(), 'profile_type' => $this->type->id()],
+ $web_user3
+ ));
+ }
+
+ /**
+ * Tests profile view access.
+ */
+ public function testProfileViewAccess() {
+ // Setup users.
+ $web_user1 = $this->createUser([], [
+ 'access user profiles',
+ "view own {$this->type->id()} profile",
+ ]);
+ $web_user2 = $this->createUser([], [
+ 'access user profiles',
+ "view any {$this->type->id()} profile",
+ ]);
+
+ // Setup profiles.
+ $profile1 = Profile::create([
+ 'uid' => $web_user1->id(),
+ 'type' => $this->type->id(),
+ ]);
+ $profile1->save();
+ $profile2 = Profile::create([
+ 'uid' => $web_user2->id(),
+ 'type' => $this->type->id(),
+ ]);
+ $profile2->save();
+
+ // Test user1 can only view own profiles.
+ $this->assertTrue($profile1->access('view', $web_user1));
+ $this->assertFalse($profile2->access('view', $web_user1));
+
+ // Test user2 can view any profiles.
+ $this->assertTrue($profile1->access('view', $web_user2));
+ $this->assertTrue($profile2->access('view', $web_user2));
+
+ $user_view_builder = $this->container->get('entity_type.manager')->getViewBuilder('user');
+
+ $this->container->get('current_user')->setAccount($web_user1);
+ $user2_view = $user_view_builder->view($web_user2);
+ $this->render($user2_view);
+ $this->assertNoText($this->type->label());
+
+ $this->container->get('current_user')->setAccount($web_user2);
+ $user1_view = $user_view_builder->view($web_user1);
+ $this->render($user1_view);
+ $this->assertText($this->type->label());
+ }
+
+ /**
+ * Tests profile edit flow and permissions.
+ */
+ public function testProfileEditAccess() {
+ // Setup users.
+ $web_user1 = $this->createUser([], [
+ "add own {$this->type->id()} profile",
+ "edit own {$this->type->id()} profile",
+ ]);
+ $web_user2 = $this->createUser([], [
+ "add any {$this->type->id()} profile",
+ "edit any {$this->type->id()} profile",
+ ]);
+
+ // Setup profiles.
+ $profile1 = Profile::create([
+ 'uid' => $web_user1->id(),
+ 'type' => $this->type->id(),
+ ]);
+ $profile1->save();
+ $profile2 = Profile::create([
+ 'uid' => $web_user2->id(),
+ 'type' => $this->type->id(),
+ ]);
+ $profile2->save();
+
+ // Test user1 can only edit own profiles.
+ $this->assertTrue($profile1->access('edit', $web_user1));
+ $this->assertFalse($profile2->access('edit', $web_user1));
+
+ // Test user2 can edit any profiles.
+ $this->assertTrue($profile1->access('edit', $web_user2));
+ $this->assertTrue($profile2->access('edit', $web_user2));
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileTest.php b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileTest.php
new file mode 100644
index 000000000..878762a05
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileTest.php
@@ -0,0 +1,149 @@
+installEntitySchema('profile');
+ $this->installEntitySchema('view');
+ $this->installSchema('user', ['users_data']);
+ $this->installConfig(['profile', 'user']);
+ $this->profileStorage = $this->container->get('entity_type.manager')
+ ->getStorage('profile');
+ $this->user1 = $this->createUser();
+ $this->user2 = $this->createUser();
+ }
+
+ /**
+ * Tests the profile entity and its methods.
+ */
+ public function testProfile() {
+ $types_data = [
+ 'profile_type_0' => ['label' => $this->randomMachineName()],
+ 'profile_type_1' => ['label' => $this->randomMachineName()],
+ ];
+
+ /** @var \Drupal\profile\Entity\ProfileTypeInterface[] $types */
+ $types = [];
+ foreach ($types_data as $id => $values) {
+ $types[$id] = ProfileType::create(['id' => $id] + $values);
+ $types[$id]->save();
+ }
+
+
+ // Create a new profile.
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $this->profileStorage->create($expected = [
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+
+ $expected_label = new TranslatableMarkup('@type profile of @username (uid: @uid)', [
+ '@type' => $types['profile_type_0']->label(),
+ '@username' => $this->user1->getDisplayName(),
+ '@uid' => $this->user1->id(),
+ ]);
+
+ $this->assertEquals($expected_label, $profile->label());
+ $this->assertEquals($profile->getOwnerId(), $this->user1->id());
+ $this->assertEquals($profile->getCreatedTime(), REQUEST_TIME);
+ $this->assertEquals($profile->getChangedTime(), REQUEST_TIME);
+
+ // Save the profile.
+ $profile->save();
+ $this->assertEquals(REQUEST_TIME, $profile->getChangedTime());
+
+ // List profiles for the user and verify that the new profile appears.
+ $list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
+ $list_ids = array_keys($list);
+ $this->assertEquals($list_ids, [$profile->id()]);
+
+ // Create a second profile.
+ $user1_profile1 = $profile;
+ $user1_profile = $this->profileStorage->create([
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user1->id(),
+ ]);
+ $user1_profile->save();
+
+ // List profiles for the user and verify that both profiles appear.
+ $list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
+ $list_ids = array_keys($list);
+ $this->assertEquals($list_ids, [$user1_profile1->id(), $user1_profile->id()]);
+
+ // Delete the second profile and verify that the first still exists.
+ $user1_profile->delete();
+ $list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
+ $list_ids = array_keys($list);
+ $this->assertEquals($list_ids, [$user1_profile1->id()]);
+
+ // Create a profile for the second user.
+ $user2_profile1 = $this->profileStorage->create([
+ 'type' => $types['profile_type_0']->id(),
+ 'uid' => $this->user2->id(),
+ ]);
+ $user2_profile1->save();
+
+ // Delete the first user and verify that all of its profiles are deleted.
+ $this->user1->delete();
+ $list = $this->profileStorage->loadByProperties(['uid' => $this->user1->id()]);
+ $list_ids = array_keys($list);
+ $this->assertEquals($list_ids, []);
+
+ // List profiles for the second user and verify that they still exist.
+ $list = $this->profileStorage->loadByProperties(['uid' => $this->user2->id()]);
+ $list_ids = array_keys($list);
+ $this->assertEquals($list_ids, [$user2_profile1->id()]);
+ }
+
+}
diff --git a/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileViewTest.php b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileViewTest.php
new file mode 100644
index 000000000..e820b0808
--- /dev/null
+++ b/sites/all/modules/contrib/users/profile/tests/src/Kernel/ProfileViewTest.php
@@ -0,0 +1,106 @@
+installEntitySchema('user');
+ $this->installEntitySchema('profile');
+ $this->installEntitySchema('profile_type');
+
+ ViewTestData::createTestViews(get_class($this), ['profile_test']);
+ }
+
+ /**
+ * Tests views relationship with multiple referenced entities.
+ *
+ * Relationship is required, so only users with profiles will be listed.
+ */
+ public function testProfileRelationship() {
+ $profile_type = $this->createProfileType();
+
+ $user[0] = $user1 = User::create([
+ 'name' => Unicode::strtolower($this->randomMachineName()),
+ 'status' => TRUE,
+ ]);
+ $user1->save();
+ $user2 = User::create([
+ 'name' => Unicode::strtolower($this->randomMachineName()),
+ 'status' => TRUE,
+ ]);
+ $user2->save();
+ $user[1] = $user3 = User::create([
+ 'name' => Unicode::strtolower($this->randomMachineName()),
+ 'status' => TRUE,
+ ]);
+ $user3->save();
+ $user4 = User::create([
+ 'name' => Unicode::strtolower($this->randomMachineName()),
+ 'status' => TRUE,
+ ]);
+ $user4->save();
+
+ $profile[0] = $this->createProfile($profile_type, $user1);
+ $profile[1] = $this->createProfile($profile_type, $user3);
+
+ Views::viewsData()->clear();
+
+ // Check table relationship exists.
+ $views_data = Views::viewsData()->get('users_field_data');
+ $this->assertEquals($views_data['profile']['relationship']['base'], 'profile');
+ $this->assertEquals($views_data['profile']['relationship']['base field'], 'uid');
+
+ $view = Views::getView('users');
+ $this->executeView($view);
+
+ // Ensure values are populated for user and profiles.
+ foreach ($view->result as $index => $row) {
+ $this->assertEquals($row->uid, $user[$index]->id(), 'User ' . $user[$index]->id() . ' found on row: ' . $index);
+ $this->assertEquals($row->profile_users_field_data_profile_id, $profile[$index]->id(), 'Profile ' . $profile[$index]->id() . ' found on view: ' . $index);
+ }
+ }
+
+ /**
+ * Create a user, and optionally a profile.
+ *
+ * @return \Drupal\user\UserInterface
+ * A newly created user.
+ */
+ protected function createUser() {
+ $user = User::create([
+ 'name' => Unicode::strtolower($this->randomMachineName()),
+ 'status' => TRUE,
+ ]);
+ $user->save();
+ return $user;
+ }
+
+}