a few more base modules

This commit is contained in:
Bachir Soussi Chiadmi
2016-09-06 16:05:07 +02:00
parent c6cff46234
commit 027aa99b32
455 changed files with 43606 additions and 0 deletions
+19
View File
@@ -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)
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,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
@@ -0,0 +1,5 @@
services:
filter_perms.route_subscriber:
class: Drupal\filter_perms\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
@@ -0,0 +1,261 @@
<?php
namespace Drupal\filter_perms\Form;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\user\Form\UserPermissionsForm;
use Drupal\user\PermissionHandlerInterface;
use Drupal\user\RoleStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an enhanced user permissions administration form.
*/
class PermissionsForm extends UserPermissionsForm {
/**
* Indicates that all options should be user for filter.
*/
const ALL_OPTIONS = '-1';
/**
* The expirable key value store.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $keyValueExpirable;
/**
* Constructs a new PermissionsForm.
*
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
* The permission handler.
* @param \Drupal\user\RoleStorageInterface $role_storage
* The role storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable
* The key value expirable factory.
*/
public function __construct(PermissionHandlerInterface $permission_handler, RoleStorageInterface $role_storage, ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable) {
parent::__construct($permission_handler, $role_storage, $module_handler);
$this->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' => '<div class="permission"><span class="title">{{ title }}</span>{% if description or warning %}<div class="description">{% if warning %}<em class="permission-warning">{{ warning }}</em> {% endif %}{{ description }}</div>{% endif %}</div>',
'#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);
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\filter_perms\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RoleInterface;
/**
* Provides the user permissions administration form for a specific role.
*/
class PermissionsRoleSpecificForm extends PermissionsForm {
/**
* {@inheritdoc}
*
* @param \Drupal\user\RoleInterface $user_role
* The user role used for this form.
*/
public function buildForm(array $form, FormStateInterface $form_state, RoleInterface $user_role = NULL) {
$this->saveFilterSettings((array) $user_role->id(), (array) self::ALL_OPTIONS);
return $this->redirect('user.admin_permissions');
}
}
@@ -0,0 +1,25 @@
<?php
namespace Drupal\filter_perms\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
if ($route = $collection->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');
}
}
}
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,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 <weitzman at tejasa DOT com> http://www.acquia.com
- Hans Salvisberg <drupal at salvisberg DOT com>
- 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
@@ -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"
}
}
@@ -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'
@@ -0,0 +1,9 @@
id: devel
label: 'Development'
description: 'Links related to Devel module.'
langcode: en
locked: true
dependencies:
enforced:
module:
- devel
@@ -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'
@@ -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;
}
@@ -0,0 +1,25 @@
<?php
/**
* @file
* Hooks for the devel module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter devel dumper information declared by other modules.
*
* @param $info
* Devel dumper information to alter.
*/
function hook_devel_dumper_info_alter(&$info) {
$info['default']['label'] = 'Altered label';
}
/**
* @} End of "addtogroup hooks".
*/
@@ -0,0 +1,14 @@
type: module
name: Devel
description: 'Various blocks, pages, and functions for developers.'
package: Development
# core: 8.x
configure: devel.admin_settings
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
@@ -0,0 +1,30 @@
<?php
/**
* @file
* Install, update and uninstall functions for the devel module.
*/
/**
* Set the default devel dumper plugin.
*/
function devel_update_8001() {
$kint_enabled = \Drupal::moduleHandler()->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);
}
@@ -0,0 +1,5 @@
devel:
version: 0
css:
theme:
css/devel.css: {}
@@ -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
@@ -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
@@ -0,0 +1,626 @@
<?php
/**
* @file
* This module holds functions useful for Drupal development.
* Please contribute!
*/
define('DEVEL_ERROR_HANDLER_NONE', 0);
define('DEVEL_ERROR_HANDLER_STANDARD', 1);
define('DEVEL_ERROR_HANDLER_BACKTRACE_KINT', 2);
define('DEVEL_ERROR_HANDLER_BACKTRACE_DPM', 4);
define('DEVEL_MIN_TEXTAREA', 50);
use Drupal\comment\CommentInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Render\Element;
use Drupal\Core\Utility\Error;
/**
* Implements hook_help().
*/
function devel_help($route_name) {
switch ($route_name) {
case 'devel.reinstall':
$output = '<p>' . t('<strong>Warning</strong> - will delete your module tables and configuration.') . '</p>';
$output .= '<p>' . t('Uninstall and then install the selected modules. <code>hook_uninstall()</code> and <code>hook_install()</code> will be executed and the schema version number will be set to the most recent update number.') . '</p>';
return $output;
case 'devel/session':
return '<p>' . t('Here are the contents of your <code>$_SESSION</code> variable.') . '</p>';
case 'devel.state_system_page':
return '<p>' . t('This is a list of state variables and their values. For more information read online documentation of <a href=":documentation">State API in Drupal 8</a>.', array(':documentation' => "https://www.drupal.org/developing/api/8/state")) . '</p>';
}
}
/**
* 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'] = '<span title="' . $key . '">' . $form['modules'][$group][$key]['name']['#markup'] . '</span>';
}
}
}
}
}
}
/**
* 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);
}
}
@@ -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
@@ -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'
@@ -0,0 +1,24 @@
<?php
/**
* Implements hook_rules_action_info().
*/
function devel_rules_action_info() {
return array(
'devel_debug' => 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);
}
@@ -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 }
@@ -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.
@@ -0,0 +1,25 @@
<?php
/**
* Wrapping function for invoking the right
* method responsible for handle a given batch operation.
* Available for every DevelGeneratePlugin.
*/
function devel_generate_operation(Drupal\devel_generate\DevelGenerateBaseInterface $class, $method, $vars, &$context) {
return $class->$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);
}
@@ -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
@@ -0,0 +1,145 @@
<?php
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Component\Utility\Random;
use Drupal\devel_generate\DevelGenerateBase;
use Drupal\node\NodeInterface;
/**
* Implements hook_menu_links_discovered_alter().
*/
function devel_generate_menu_links_discovered_alter(&$links) {
$devel_generate_plugins = $devel_generate_manager = \Drupal::service('plugin.manager.develgenerate')->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();
}
@@ -0,0 +1,5 @@
administer devel_generate:
title: 'Administer devel_generate'
permission_callbacks:
- \Drupal\devel_generate\DevelGeneratePermissions::permissions
@@ -0,0 +1,2 @@
route_callbacks:
- '\Drupal\devel_generate\Routing\DevelGenerateRoutes::routes'
@@ -0,0 +1,4 @@
services:
plugin.manager.develgenerate:
class: Drupal\devel_generate\DevelGeneratePluginManager
parent: default_plugin_manager
@@ -0,0 +1,151 @@
<?php
namespace Unish;
if (class_exists('Unish\CommandUnishTestCase')) {
/**
* Tests for devel_generate drush commands.
*
* @group devel_generate
*/
class DevelGenerateUnishTest extends CommandUnishTestCase {
/**
* {@inheritdoc}
*/
public function setUp() {
if (UNISH_DRUPAL_MAJOR_VERSION < 8) {
$this->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;
}
}
}
@@ -0,0 +1,196 @@
<?php
/**
* @file
* Generate content, taxonomy, menu, and users via drush framework.
*/
use Drupal\devel_generate\DevelGenerateBaseInterface;
use Drupal\devel_generate\DevelGeneratePluginManager;
/**
* Implementation of hook_drush_command().
*/
function devel_generate_drush_command() {
$items['generate-users'] = array(
'callback' => '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);
}
@@ -0,0 +1,86 @@
<?php
namespace Drupal\devel_generate\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a DevelGenerate annotation object.
*
* DevelGenerate handle the bulk creation of entites.
*
* Additional annotation keys for DevelGenerate can be defined in
* hook_devel_generate_info_alter().
*
* @Annotation
*
* @see \Drupal\devel_generate\DevelGeneratePluginManager
* @see \Drupal\devel_generate\DevelGenerateBaseInterface
*/
class DevelGenerate extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the DevelGenerate type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* A short description of the DevelGenerate type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
/**
* A url to access the plugin settings form.
*
* @var string
*/
public $url;
/**
* The permission required to access the plugin settings form.
*
* @var string
*/
public $permission;
/**
* The name of the DevelGenerate class.
*
* This is not provided manually, it will be added by the discovery mechanism.
*
* @var string
*/
public $class;
/**
* An array whose keys are the names of the settings available to the
* DevelGenerate settingsForm, and whose values are the default values for those settings.
*
* @var array
*/
public $settings = array();
/**
* An array whose keys are the settings available to the
* DevelGenerate drush command: "suffix", "alias", "options" and "args".
*
* @var array
*/
public $drushSettings = array();
}
@@ -0,0 +1,168 @@
<?php
namespace Drupal\devel_generate;
use Drupal\Component\Utility\Random;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginBase;
/**
* Provides a base DevelGenerate plugin implementation.
*/
abstract class DevelGenerateBase extends PluginBase implements DevelGenerateBaseInterface {
/**
* The plugin settings.
*
* @var array
*/
protected $settings = array();
/**
* The random data generator.
*
* @var \Drupal\Component\Utility\Random
*/
protected $random;
/**
* {@inheritdoc}
*/
public function getSetting($key) {
// Merge defaults if we have no value for the key.
if (!array_key_exists($key, $this->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;
}
}
@@ -0,0 +1,77 @@
<?php
namespace Drupal\devel_generate;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Base interface definition for "DevelGenerate" plugins.
*
* This interface details base wrapping methods that most DevelGenerate implementations
* will want to directly inherit from Drupal\devel_generate\DevelGenerateBase.
*
* DevelGenerate impementationa plugins should developing settingsForm() and generateElements()
* to achieve its own behaviour.
*
*/
interface DevelGenerateBaseInterface extends PluginInspectionInterface {
/**
* Returns the array of settings, including defaults for missing settings.
*
* @return array
* The array of settings.
*/
function getSetting($key);
/**
* Returns the default settings for the plugin.
*
* @return array
* The array of default setting values, keyed by setting names.
*/
function getDefaultSettings();
/**
* Returns the current settings for the plugin.
*
* @return array
* The array of current setting values, keyed by setting names.
*/
function getSettings();
/**
* Returns the form for the plugin.
*
* @return array
* The array of default setting values, keyed by setting names.
*/
function settingsForm(array $form, FormStateInterface $form_state);
/**
* Form validation handler.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
function settingsFormValidate(array $form, FormStateInterface $form_state);
/**
* Execute the instructions in common for all DevelGenerate plugin
*
* @param array $values
* The input values from the settings form.
*/
function generate(array $values);
/**
* Responsible for validating Drush params.
*
* @Return an array of values ready to be used for generateElements()
*/
function validateDrushParams($args);
}
@@ -0,0 +1,12 @@
<?php
namespace Drupal\devel_generate;
use Drupal\Component\Plugin\Exception;
/**
* DevelGenerateException extending Generic Plugin exception class.
*/
class DevelGenerateException extends \Exception {
}
@@ -0,0 +1,62 @@
<?php
namespace Drupal\devel_generate;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic permissions of the filter module.
*/
class DevelGeneratePermissions implements ContainerInjectionInterface {
/**
* The plugin manager.
*
* @var \Drupal\devel_generate\DevelGeneratePluginManager;
*/
protected $develGeneratePluginManager;
/**
* Constructs a new DevelGeneratePermissions instance.
*
* @param \Drupal\devel_generate\DevelGeneratePluginManager $develGeneratePluginManager
* The plugin manager.
*/
public function __construct(DevelGeneratePluginManager $develGeneratePluginManager) {
$this->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;
}
}
@@ -0,0 +1,31 @@
<?php
namespace Drupal\devel_generate;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Plugin type manager for DevelGenerate plugins.
*/
Class DevelGeneratePluginManager extends DefaultPluginManager {
/**
* Constructs a DevelGeneratePluginManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/DevelGenerate', $namespaces, $module_handler, NULL, 'Drupal\devel_generate\Annotation\DevelGenerate');
$this->alterInfo('devel_generate_info');
$this->setCacheBackend($cache_backend, 'devel_generate_plugins');
}
}
@@ -0,0 +1,114 @@
<?php
namespace Drupal\devel_generate\Form;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\devel_generate\DevelGenerateException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a form that allows privileged users to generate entities.
*/
class DevelGenerateForm extends FormBase {
/**
* The manager to be used for instantiating plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $develGenerateManager;
/**
* Constructs a new DevelGenerateForm object.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $devel_generate_manager
* The manager to be used for instantiating plugins.
*/
public function __construct(PluginManagerInterface $devel_generate_manager) {
$this->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())));
}
}
}
@@ -0,0 +1,506 @@
<?php
namespace Drupal\devel_generate\Plugin\DevelGenerate;
use Drupal\comment\CommentManagerInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\devel_generate\DevelGenerateBase;
use Drupal\field\Entity\FieldConfig;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a ContentDevelGenerate plugin.
*
* @DevelGenerate(
* id = "content",
* label = @Translation("content"),
* description = @Translation("Generate a given number of content. Optionally delete current content."),
* url = "content",
* permission = "administer devel_generate",
* settings = {
* "num" = 50,
* "kill" = FALSE,
* "max_comments" = 0,
* "title_length" = 4
* }
* )
*/
class ContentDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeStorage;
/**
* The node type storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeTypeStorage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The comment manager service.
*
* @var \Drupal\comment\CommentManagerInterface
*/
protected $commentManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The url generator service.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param array $plugin_definition
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
* The node storage.
* @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
* The node type storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\comment\CommentManagerInterface $comment_manager
* The comment manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator service.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityStorageInterface $node_storage, EntityStorageInterface $node_type_storage, ModuleHandlerInterface $module_handler, CommentManagerInterface $comment_manager = NULL, LanguageManagerInterface $language_manager, UrlGeneratorInterface $url_generator, DateFormatterInterface $date_formatter) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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. <a href=":create-type">Go create a new content type</a>', 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('<strong>Delete all content</strong> 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;
}
}
@@ -0,0 +1,407 @@
<?php
namespace Drupal\devel_generate\Plugin\DevelGenerate;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\devel_generate\DevelGenerateBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a MenuDevelGenerate plugin.
*
* @DevelGenerate(
* id = "menu",
* label = @Translation("menus"),
* description = @Translation("Generate a given number of menus and menu links. Optionally delete current menus."),
* url = "menu",
* permission = "administer devel_generate",
* settings = {
* "num_menus" = 2,
* "num_links" = 50,
* "title_length" = 12,
* "max_width" = 6,
* "kill" = FALSE,
* }
* )
*/
class MenuDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
/**
* The menu tree service.
*
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
protected $menuLinkTree;
/**
* The menu storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuStorage;
/**
* The menu link storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuLinkContentStorage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a MenuDevelGenerate object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
* The menu tree service.
* @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
* The menu storage.
* @param \Drupal\Core\Entity\EntityStorageInterface $menu_link_storage
* The menu storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, EntityStorageInterface $menu_storage, EntityStorageInterface $menu_link_storage, ModuleHandlerInterface $module_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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:/<front>';
break;
default:
$link->devel_link_type = $link_type;
break;
}
$link->save();
$links[$link->id()] = $link->link_title;
}
return $links;
}
}
@@ -0,0 +1,267 @@
<?php
namespace Drupal\devel_generate\Plugin\DevelGenerate;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\devel_generate\DevelGenerateBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a TermDevelGenerate plugin.
*
* @DevelGenerate(
* id = "term",
* label = @Translation("terms"),
* description = @Translation("Generate a given number of terms. Optionally delete current terms."),
* url = "term",
* permission = "administer devel_generate",
* settings = {
* "num" = 10,
* "title_length" = 12,
* "kill" = FALSE,
* }
* )
*/
class TermDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
/**
* The vocabulary storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $vocabularyStorage;
/**
* The term storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $termStorage;
/**
* Constructs a new TermDevelGenerate object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $vocabulary_storage
* The vocabulary storage.
* @param \Drupal\Core\Entity\EntityStorageInterface $term_storage
* The term storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $vocabulary_storage, EntityStorageInterface $term_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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;
}
}
@@ -0,0 +1,191 @@
<?php
namespace Drupal\devel_generate\Plugin\DevelGenerate;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\devel_generate\DevelGenerateBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a UserDevelGenerate plugin.
*
* @DevelGenerate(
* id = "user",
* label = @Translation("users"),
* description = @Translation("Generate a given number of users. Optionally delete current users."),
* url = "user",
* permission = "administer devel_generate",
* settings = {
* "num" = 50,
* "kill" = FALSE,
* "pass" = ""
* }
* )
*/
class UserDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
/**
* The user storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $userStorage;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* Constructs a new UserDevelGenerate object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
* The user storage.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $entity_storage, DateFormatterInterface $date_formatter) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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 <em>authenticated user</em> 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;
}
}
@@ -0,0 +1,175 @@
<?php
namespace Drupal\devel_generate\Plugin\DevelGenerate;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\devel_generate\DevelGenerateBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a VocabularyDevelGenerate plugin.
*
* @DevelGenerate(
* id = "vocabulary",
* label = @Translation("vocabularies"),
* description = @Translation("Generate a given number of vocabularies. Optionally delete current vocabularies."),
* url = "vocabs",
* permission = "administer devel_generate",
* settings = {
* "num" = 1,
* "title_length" = 12,
* "kill" = FALSE
* }
* )
*/
class VocabularyDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
/**
* The vocabulary storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $vocabularyStorage;
/**
* Constructs a new VocabularyDevelGenerate object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $entity_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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;
}
}
@@ -0,0 +1,57 @@
<?php
namespace Drupal\devel_generate\Routing;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
/**
* Provides dynamic routes for devel_generate.
*/
class DevelGenerateRoutes implements ContainerInjectionInterface {
/**
* Constructs a new devel_generate route subscriber.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $devel_generate_manager
* The DevelGeneratePluginManager.
*/
public function __construct(PluginManagerInterface $devel_generate_manager) {
$this->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;
}
}
@@ -0,0 +1,175 @@
<?php
namespace Drupal\devel_generate\Tests;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\Language;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\node\Entity\Node;
use Drupal\simpletest\WebTestBase;
/**
* Tests the logic to generate data.
*
* @group devel_generate
*/
class DevelGenerateTest extends WebTestBase {
use CommentTestTrait;
use EntityReferenceTestTrait;
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('menu_ui', 'node', 'comment', 'taxonomy', 'path', 'devel_generate');
/**
* Prepares the testing environment
*/
public function setUp() {
parent::setUp();
// Create Basic page and Article node types.
if ($this->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.'));
}
}
@@ -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
@@ -0,0 +1,88 @@
<?php
namespace Drupal\devel_generate_example\Plugin\DevelGenerate;
use Drupal\devel_generate\DevelGenerateBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a ExampleDevelGenerate plugin.
*
* @DevelGenerate(
* id = "devel_generate_example",
* label = "Example",
* description = "Generate a given number of examples. Optionally delete current examples.",
* url = "devel_generate_example",
* permission = "administer devel_generate",
* settings = {
* "num" = 50,
* "kill" = FALSE
* }
* )
*/
class ExampleDevelGenerate extends DevelGenerateBase {
public function settingsForm(array $form, FormStateInterface $form_state) {
$form['num'] = array(
'#type' => '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;
}
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\Tests\devel_generate;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\devel_generate\DevelGeneratePluginManager;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Language\Language;
if (!defined('DRUPAL_ROOT')) {
//Looping to find drupal root folder.
$current_dir = dirname(__DIR__);
while (!file_exists("$current_dir/index.php")) {
$current_dir = dirname($current_dir);
}
define('DRUPAL_ROOT', $current_dir);
}
/**
* @coversDefaultClass \Drupal\devel_generate\DevelGeneratePluginManager
* @group devel_generate
*/
class DevelGenerateManagerTest extends UnitTestCase {
/**
* The plugin discovery.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $discovery;
/**
* A list of devel generate plugin definitions.
*
* @var array
*/
protected $definitions = array(
'devel_generate_example' => 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;
}
}
@@ -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
@@ -0,0 +1,3 @@
debug_mode: FALSE
by_user_mode: FALSE
user_ajax: FALSE
@@ -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'
@@ -0,0 +1,5 @@
div.devel-node-access-inline > div {
display: inline-block;
}
@@ -0,0 +1,81 @@
<?php
/**
* @file
* Hook provided by the Devel Node Access module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Explain your records in the {node_access} table.
*
* In order to help developers and administrators understand the forces
* that control access to any given node, the DNA module provides the
* Devel Node Access block, which lists all the grant records in the
* {node_access} table for that node.
*
* However, every Node Access module is free in how it defines and uses the
* 'realm' and 'gid' fields in its records in the {node_access} table, and
* it's often difficult to interpret them. This hook passes each record
* that DNA wants to display, and the owning module is expected to return
* an explanation of that record.
*
* The explanation should not be localized (not be passed through t()), so
* that administrators seeking help can present English explanations.
*
* @param $row
* The record from the {node_access} table, as object. The member fields are:
* nid, gid, realm, grant_view, grant_update, grant_delete.
*
* @return string|null
* A string with a (short!) explanation of the given {node_access} row,
* to be displayed in DNA's 'Devel Node Access' block. It will be displayed
* as HTML; any variable parts must already be sanitized.
*
* @see hook_node_access_records()
* @see devel_node_access_node_access_explain()
*
* @ingroup node_access
*/
function hook_node_access_explain($row) {
if ($row->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".
*/
@@ -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
@@ -0,0 +1,7 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Devel Node Access module.
*/
@@ -0,0 +1,64 @@
/**
* @file devel_node_access.js.
*/
(function ($) {
/**
* Perform the access by user ajax request.
*/
function devel_node_access_user_ajax(context, settings) {
// Get the cell ID for the first .dna-permission that isn't processed.
var cell = $('td.dna-permission', context)
.not('.ajax-processed', context)
.attr('id');
if (cell !== undefined) {
// Generate the URI from the basePath, path, data type, cell ID, and a
// random token to bypass caching.
var url = settings.path.basePath
+ "?q="
+ 'devel/node_access/by_user/json/'
+ cell
+ '/'
+ Math.floor((1000000000 * Math.random())).toString(16);
// Execute Ajax callback and handle the response.
$.getJSON(url, function(data) {
$('#' + cell, context).html(data).addClass('ajax-processed');
// Call this function again.
devel_node_access_user_ajax(context);
});
// Ajax fails silently on error, mark bad requests with an error message.
// If the request is just slow this will update when the request succeeds.
setTimeout(
function() {
if ($('#' + cell, context).hasClass('ajax-processed') == false) {
$('#' + cell, context)
.html(
'<span class="error">'
+ '<a href="' + url.replace('/json/', '/html/') + '">'
+ Drupal.t('Error: could not explain access')
+ '</a>'
+ '</span>'
)
.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);
@@ -0,0 +1,7 @@
devel_node_access:
version: 0
js:
devel_node_access.js: {}
css:
component:
css/devel_node_access.module.css: {}
@@ -0,0 +1,269 @@
<?php
/**
* @file
* This module gives developers feedback as to what their
* node_access table contains, and which nodes are protected or
* visible to the public.
*/
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\devel_node_access\Plugin\Block\DnaBlock;
define('DNA_ACCESS_VIEW', 'view devel_node_access information');
/**
* Implements hook_help().
*
* {@inheritdoc}
*/
function devel_node_access_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.devel_node_access':
$output = '<p>' . 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.') . "</p>\n";
$output .= '<p>' . 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.') . "</p>\n";
$output .= '<p>' . 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.') . "</p>\n";
$output .= '<p>' . 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).') . "</p>\n";
// $output .= '<p>' . t('This module also provides a <a href="@summary_page">summary page</a> 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'))
// ) . "</p>\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 <a href="' . url('admin/structure/block') . '">block administration</a> 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 .= '<h3>' . t('Access Granted to All Nodes (All Users)') . "</h3>\n";
$output .= '<p>' . 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.') . "</p>\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 .= '<h3>' . t('Legacy Nodes') . "</h3>\n";
$output .= '<p>' .
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')))
. "</p>\n";
if (!empty($access_granted_to_all_nodes)) {
$output .= '<p>' .
t('This issue may be masked by the one above, so look into the former first.')
. "</p>\n";
}
}
else {
$output .= '<h3>' . t('All Nodes Represented') . "</h3>\n";
$output .= '<p>' . t('All nodes are represented in the node_access table.') . "</p>\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 .= '<h3>' . t('Access Granted to All Nodes (Some Users)') . "</h3>\n";
$output .= '<p>' . 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.') . "</p>\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 .= '<h3>' . t('Access Granted to Some Nodes') . "</h3>\n";
$output .= '<p>' .
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.')
. "</p>\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 .= '<h3>' . t('Summary by Realm') . "</h3>\n";
$output .= '<p>' . t('The following realms grant limited access to some specific nodes.') . "</p>\n";
$headers = array(t('realm'), t('private nodes'));
$output .= theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Protected Nodes')));
}
return $output;
}
/**
* Implements hook_node_view().
*
* {@inheritdoc}
*/
function devel_node_access_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
// Remember this node, for display in our block.
DnaBlock::visibleNodes($entity->id());
}
/**
* Callback function for Node Access by User block ajax.
*/
function devel_node_access_user_ajax($data_type, $cell) {
list($op, $nid, $uid) = explode('-', $cell);
$node = node_load($nid);
$account = user_load($uid);
$output = array(
'#theme' => 'dna_permission',
'#permission' => DnaBlock::explainAccess($op, $node, $account),
);
// JSON output for Ajax callbacks.
if ($data_type == 'json') {
drupal_json_output(\Drupal::service('renderer')->render($output));
exit;
}
// HTML output for error message click-throughs.
if ($node) {
$node_title = l($node->title, 'node/' . $node->id());
}
else {
$node_title = t('unknown node %nid', array('%nid' => $nid));
}
drupal_set_title(
t(
'Devel Node Access: %op permission for !user on !node',
array(
'%op' => $op,
'!user' => theme('username', array('account' => $account)),
'!node' => $node_title,
)
),
PASS_THROUGH
);
return $output;
}
/**
* Implements hook_node_access_explain().
*
* {@inheritdoc}
*/
function devel_node_access_node_access_explain($row) {
if ($row->gid == 0 && $row->realm == 'all') {
foreach (array('view', 'update', 'delete') as $op) {
$gop = 'grant_' . $op;
if (!empty($row->$gop)) {
$ops[] = $op;
}
}
if (empty($ops)) {
return '(No access granted to ' . ($row->nid == 0 ? 'any nodes.)' : 'this node.)');
}
else {
return 'All users may ' . implode('/', $ops) . ($row->nid == 0 ? ' all nodes.' : ' this node.');
}
}
if (\Drupal::moduleHandler()->moduleExists('node_access_test')) {
switch ($row->realm) {
case 'node_access_test_author':
return "Author (uid=$row->gid) has full access.";
case 'node_access_test':
return "Users with the 'node test view' permission may view.";
}
}
return NULL;
}
/**
* Implements hook_theme().
*/
function devel_node_access_theme() {
return array(
'dna_permission' => array(
'variables' => array(
'permission' => NULL,
),
),
);
}
/**
* Indicates whether user has a permission or not.
*/
function theme_dna_permission($variables) {
$permission = &$variables['permission'];
return '<span class="' . ($permission[0] ? 'ok' : 'error') . '" title="' . $permission[2] . '">' . $permission[1] . '</span>';
}
@@ -0,0 +1,4 @@
view devel_node_access information:
title: 'Access DNA information'
description: 'View the node_access information block on node pages and the summary page.'
restrict access: TRUE
@@ -0,0 +1,135 @@
<?php
namespace Drupal\devel_node_access\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\devel_node_access\Plugin\Block\DnaBlock;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the form to change the DNA settings.
*/
class DnaForm extends FormBase {
/**
* Constructs a new DnaForm object.
*/
public function __construct() {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static();
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_dna_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#attached'] = array(
'library' => array(
'devel_node_access/devel_node_access'
),
);
$debug_mode = \Drupal::config('devel_node_access.settings')->get('debug_mode');
$by_user_mode = \Drupal::config('devel_node_access.settings')->get('by_user_mode');
$form['node_content'] = array(
'#prefix' => '<div id="devel-node-access-node-content-div">',
'#suffix' => '</div>',
);
$form['node_content'][0] = DnaBlock::buildNodeInfo($debug_mode);
$form['user_content'] = array(
'#prefix' => '<div id="devel-node-access-by-user-content-div">',
'#suffix' => '</div>',
);
if ($by_user_mode) {
$form['user_content'][0] = DnaBlock::buildByUserInfo();
}
$form['setup'] = array(
'#markup' => t('Enable:'),
'#prefix' => '<div class="devel-node-access-inline">',
'#suffix' => '</div>',
);
$form['setup']['debug_mode'] = array(
'#type' => 'checkbox',
'#value' => $debug_mode,
'#prefix' => ' &nbsp; &nbsp; ',
'#title' => t('Debug Mode'),
'#ajax' => array(
'callback' => '::toggleDebugMode',
'wrapper' => 'devel-node-access-node-content-div',
),
'#disabled' => TRUE,
);
$form['setup']['by_user_mode'] = array(
'#type' => 'checkbox',
'#value' => $by_user_mode,
'#prefix' => ' &nbsp; &nbsp; ',
'#title' => t('By-User Analysis (slow!)'),
'#ajax' => array(
'callback' => '::toggleByUserMode',
'wrapper' => 'devel-node-access-by-user-content-div',
),
'#disabled' => TRUE,
);
return $form;
}
/**
* AJAX handler for form_state.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
*/
public function toggleDebugMode($form, FormStateInterface $form_state) {
$debug_mode = $form_state->getUserInput();
$debug_mode = !empty($debug_mode['debug_mode']);
\Drupal::configFactory()->getEditable('devel_node_access.settings')->set('debug_mode', $debug_mode)->save(TRUE);
$form['node_content'][0] = DnaBlock::buildNodeInfo($debug_mode);
return $form['node_content'];
}
/**
* AJAX handler for by_user_state.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
*/
public function toggleByUserMode($form, FormStateInterface $form_state) {
$by_user_mode = $form_state->getUserInput();
$by_user_mode = !empty($by_user_mode['by_user_mode']);
\Drupal::configFactory()->getEditable('devel_node_access.settings')->set('by_user_mode', $by_user_mode)->save(TRUE);
$form['user_content'][0] = ($by_user_mode ? DnaBlock::buildByUserInfo() : []);
return $form['user_content'];
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}
@@ -0,0 +1,274 @@
<?php
/**
* @file
* Drush integration for the devel module.
*/
use Drupal\Component\Uuid\Php;
/**
* Implements hook_drush_command().
*/
function devel_drush_command() {
$items['devel-reinstall'] = array(
'description' => 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;
}
@@ -0,0 +1,38 @@
<?php
namespace Unish;
if (class_exists('Unish\CommandUnishTestCase')) {
/**
* PHPUnit Tests for devel. This uses Drush's own test framework, based on PHPUnit.
* To run the tests, use run-tests-drush.sh from the devel directory.
*/
class develCase extends CommandUnishTestCase {
public function testFnCommands() {
// Specify '8' just in case user has not set UNISH_DRUPAL_MAJOR_VERSION env variable.
$sites = $this->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);
}
}
}
@@ -0,0 +1,105 @@
<?php
/**
* @file
* Generate PhpStorm metadata file.
*/
/**
* Implements of hook_drush_command().
*/
function phpstorm_drush_command() {
$items = array();
$items['phpstorm-metadata'] = array(
'description' => '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 = '<?php
namespace PHPSTORM_META {
/** @noinspection PhpUnusedLocalVariableInspection */
/** @noinspection PhpIllegalArrayKeyTypeInspection */
$STATIC_METHOD_TYPES = [
';
foreach ($data as $method => $map) {
$file .= "\n";
$file .= " {$method} => [\n";
foreach ($map as $argument => $class) {
$file .= " '{$argument}' instanceof {$class},\n";
}
$file .= " ],";
$file .= "\n";
}
$file .= '
];
}
';
return $file;
}
@@ -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
@@ -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
@@ -0,0 +1,65 @@
<?php
use Drupal\Core\Render\Markup;
/**
* Alias of Kint::dump().
*
* Prints passed argument(s) using Kint debug tool.
*/
function kint() {
kint_require();
if (\Drupal::currentUser()->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';
}
@@ -0,0 +1,4 @@
access kint:
description: 'View developer debug output.'
title: 'Access kint information'
restrict access: TRUE
@@ -0,0 +1,5 @@
services:
kint.twig.kint_extension:
class: Drupal\kint\Twig\KintExtension
tags:
- { name: twig.extension }
@@ -0,0 +1,3 @@
/config.php
/.idea
@@ -0,0 +1,836 @@
<?php
/**
* Kint is a zero-setup debugging tool to output information about variables and stack traces prettily and comfortably.
*
* https://github.com/raveren/kint
*/
if ( defined( 'KINT_DIR' ) ) return;
define( 'KINT_DIR', dirname( __FILE__ ) . '/' );
define( 'KINT_PHP53', version_compare( PHP_VERSION, '5.3.0' ) >= 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 = '<span>' . sprintf( $format, $line ) . '</span> ' . $row;
if ( $line === $lineNumber ) {
# apply highlighting to this row
$row = '<div class="kint-highlight">' . $row . '</div>';
} else {
$row = '<div>' . $row . '</div>';
}
# 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:
# <parameters passed>); <the rest of the last read line>
# 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 & <?php tags from php code, makes for easier further parsing
*
* @param string $source
*
* @return string
*/
private static function _removeAllButCode( $source )
{
$commentTokens = array(
T_COMMENT => 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 "<pre>Kint: dd() is being deprecated, please use ddd() instead</pre>\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;
}
}
@@ -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.
@@ -0,0 +1,141 @@
# Kint - debugging helper for PHP developers
[![Total Downloads](https://poser.pugx.org/raveren/kint/downloads.png)](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.
![Screenshot](http://raveren.github.com/kint/img/preview.png)
## 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:
![Kint CLI output](http://i.imgur.com/6B9MCLw.png)
* **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*;
* <sup>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;</sup>
* 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:
![Kint displays data intelligently](http://i.imgur.com/9P57Ror.png)
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
<?php
require '/kint/Kint.class.php';
```
**Or, if you use Composer:**
```json
"require": {
"raveren/kint": "^1.0"
}
```
Or just run `composer require raveren/kint`
**That's it, you can now use Kint to debug your code:**
```php
########## DUMP VARIABLE ###########################
Kint::dump($GLOBALS, $_SERVER); // pass any number of parameters
// or simply use d() as a shorthand:
d($_SERVER);
########## DEBUG BACKTRACE #########################
Kint::trace();
// or via shorthand:
d(1);
############# BASIC OUTPUT #########################
# this will show a basic javascript-free display
s($GLOBALS);
######### WHITESPACE FORMATTED OUTPUT ##############
# this will be garbled if viewed in browser as it is whitespace-formatted only
~d($GLOBALS); // just prepend with the tilde
########## MISCELLANEOUS ###########################
# this will disable kint completely
Kint::enabled(false);
ddd('Get off my lawn!'); // no effect
Kint::enabled(true);
ddd( 'this line will stop the execution flow because Kint was just re-enabled above!' );
```
Note, that Kint *does* have configuration (like themes and IDE integration!), but it's in need of being rewritten, so I'm not documenting it yet.
## Tips & Tricks
* Kint is enabled by default, call `Kint::enabled(false);` to turn its funcionality completely off. The *best practice* is to enable Kint in DEVELOPMENT environment only (or for example `Kint::enabled($_SERVER['REMOTE_ADDR'] === '<your IP>');`) - 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 <kbd>D</kbd> 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 variable<sup>beta</sup>
```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 = '<theme name>';` where available options are: `'original'` (default), `'solarized'`, `'solarized-dark'` and `'aante-light'`. Here's an (outdated) preview:
![Kint themes](http://raveren.github.io/kint/img/theme-preview.png)
* 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)' );
```
![Kint profiling feature](http://i.imgur.com/tmHUMW4.png)
----
[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
@@ -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"
}
}
}
@@ -0,0 +1,94 @@
<?php
isset( $GLOBALS['_kint_settings'] ) or $GLOBALS['_kint_settings'] = array();
$_kintSettings = &$GLOBALS['_kint_settings'];
/** @var bool if set to false, kint will become silent, same as Kint::enabled(false) or Kint::$enabled = false */
$_kintSettings['enabled'] = true;
/**
* @var bool whether to display where kint was called from
*/
$_kintSettings['displayCalledFrom'] = true;
/**
* @var string format of the link to the source file in trace entries. Use %f for file path, %l for line number.
* Defaults to xdebug.file_link_format if not set.
*
* [!] EXAMPLE (works with for phpStorm and RemoteCall Plugin):
*
* $_kintSettings['fileLinkFormat'] = 'http://localhost:8091/?message=%f:%l';
*
*/
$_kintSettings['fileLinkFormat'] = ini_get( 'xdebug.file_link_format' );
/**
* @var array base directories of your application that will be displayed instead of the full path. Keys are paths,
* values are replacement strings
*
* Defaults to array( $_SERVER['DOCUMENT_ROOT'] => '&lt;ROOT&gt;' )
*
* [!] 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'] => '&lt;ROOT&gt;' )
: 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 );
@@ -0,0 +1,335 @@
<?php
class Kint_Decorators_Plain
{
private static $_enableColors;
private static $_cliEffects = array(
# effects
'bold' => '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(
"&#9484;", "&#9604;", "&#9488;",
"&#9474;",
"&#9492;", "&#9472;", "&#9496;",
);
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 = "<i>{$text}</i>";
break;
case 'type':
$text = "<b>{$text}</b>";
break;
case 'title':
$text = "<u>{$text}</u>";
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 '<pre class="-kint">';
}
return '';
}
public static function wrapEnd( $callee, $miniTrace, $prevCaller )
{
$lastLine = self::_colorize( self::_char( "", 80 ), 'title' );
$lastChar = Kint::enabled() === Kint::MODE_PLAIN ? '</pre>' : '';
if ( !Kint::$displayCalledFrom ) return $lastLine . $lastChar;
return $lastLine . self::_colorize( 'Called from ' . self::_buildCalleeString( $callee ), 'title' ) . $lastChar;
}
private static function _drawHeader( kintVariableData $kintVar )
{
$output = '';
if ( $kintVar->access ) {
$output .= ' ' . $kintVar->access;
}
if ( $kintVar->name !== null && $kintVar->name !== '' ) {
$output .= ' ' . kintParser::escape( $kintVar->name );
}
if ( $kintVar->operator ) {
$output .= ' ' . $kintVar->operator;
}
$output .= ' ' . self::_colorize( $kintVar->type, 'type', false );
if ( $kintVar->size !== null ) {
$output .= ' (' . $kintVar->size . ')';
}
if ( $kintVar->value !== null && $kintVar->value !== '' ) {
$output .= ' ' . self::_colorize(
$kintVar->value, # escape shell
'value',
false
);
}
return ltrim( $output );
}
private static function _buildCalleeString( $callee )
{
if ( Kint::enabled() === Kint::MODE_CLI ) { // todo win/nix
return "+{$callee['line']} {$callee['file']}";
}
$url = Kint::getIdeLink( $callee['file'], $callee['line'] );
$shortenedName = Kint::shortenPath( $callee['file'] ) . ':' . $callee['line'];
if ( Kint::enabled() === Kint::MODE_PLAIN ) {
if ( strpos( $url, 'http://' ) === 0 ) {
$calleeInfo = "<a href=\"#\"onclick=\""
. "X=new XMLHttpRequest;"
. "X.open('GET','{$url}');"
. "X.send();"
. "return!1\">{$shortenedName}</a>";
} else {
$calleeInfo = "<a href=\"{$url}\">{$shortenedName}</a>";
}
} else {
$calleeInfo = $shortenedName;
}
return $calleeInfo;
}
public static function init()
{
self::$_enableColors =
Kint::$cliColors
&& ( DIRECTORY_SEPARATOR === '/' || getenv( 'ANSICON' ) !== false || getenv( 'ConEmuANSI' ) === 'ON' );
return Kint::enabled() === Kint::MODE_PLAIN
? '<style>.-kint i{color:#d00;font-style:normal}.-kint u{color:#030;text-decoration:none;font-weight:bold}</style>'
: '';
}
}
@@ -0,0 +1,319 @@
<?php
class Kint_Decorators_Rich
{
# make calls to Kint::dump() from different places in source coloured differently.
private static $_usedColors = array();
public static function decorate( kintVariableData $kintVar )
{
$output = '<dl>';
$extendedPresent = $kintVar->extendedValue !== null || $kintVar->_alternatives !== null;
if ( $extendedPresent ) {
$class = 'kint-parent';
if ( Kint::$expandedByDefault ) {
$class .= ' kint-show';
}
$output .= '<dt class="' . $class . '">';
} else {
$output .= '<dt>';
}
if ( $extendedPresent ) {
$output .= '<span class="kint-popup-trigger" title="Open in new window">&rarr;</span><nav></nav>';
}
$output .= self::_drawHeader( $kintVar ) . $kintVar->value . '</dt>';
if ( $extendedPresent ) {
$output .= '<dd>';
}
if ( isset( $kintVar->extendedValue ) ) {
if ( is_array( $kintVar->extendedValue ) ) {
foreach ( $kintVar->extendedValue as $v ) {
$output .= self::decorate( $v );
}
} elseif ( is_string( $kintVar->extendedValue ) ) {
$output .= '<pre>' . $kintVar->extendedValue . '</pre>';
} else {
$output .= self::decorate( $kintVar->extendedValue ); //it's kint's container
}
} elseif ( isset( $kintVar->_alternatives ) ) {
$output .= "<ul class=\"kint-tabs\">";
foreach ( $kintVar->_alternatives as $k => $var ) {
$active = $k === 0 ? ' class="kint-active-tab"' : '';
$output .= "<li{$active}>" . self::_drawHeader( $var, false ) . '</li>';
}
$output .= "</ul><ul>";
foreach ( $kintVar->_alternatives as $var ) {
$output .= "<li>";
$var = $var->value;
if ( is_array( $var ) ) {
foreach ( $var as $v ) {
$output .= is_string( $v )
? '<pre>' . $v . '</pre>'
: self::decorate( $v );
}
} elseif ( is_string( $var ) ) {
$output .= '<pre>' . $var . '</pre>';
} elseif ( isset( $var ) ) {
throw new Exception(
'Kint has encountered an error, '
. 'please paste this report to https://github.com/raveren/kint/issues<br>'
. 'Error encountered at ' . basename( __FILE__ ) . ':' . __LINE__ . '<br>'
. ' variables: '
. htmlspecialchars( var_export( $kintVar->_alternatives, true ), ENT_QUOTES )
);
}
$output .= "</li>";
}
$output .= "</ul>";
}
if ( $extendedPresent ) {
$output .= '</dd>';
}
$output .= '</dl>';
return $output;
}
public static function decorateTrace( $traceData )
{
$output = '<dl class="kint-trace">';
foreach ( $traceData as $i => $step ) {
$class = 'kint-parent';
if ( Kint::$expandedByDefault ) {
$class .= ' kint-show';
}
$output .= '<dt class="' . $class . '">'
. '<b>' . ( $i + 1 ) . '</b> '
. '<nav></nav>'
. '<var>';
if ( isset( $step['file'] ) ) {
$output .= self::_ideLink( $step['file'], $step['line'] );
} else {
$output .= 'PHP internal call';
}
$output .= '</var>';
$output .= $step['function'];
if ( isset( $step['args'] ) ) {
$output .= '(' . implode( ', ', array_keys( $step['args'] ) ) . ')';
}
$output .= '</dt><dd>';
$firstTab = ' class="kint-active-tab"';
$output .= '<ul class="kint-tabs">';
if ( !empty( $step['source'] ) ) {
$output .= "<li{$firstTab}>Source</li>";
$firstTab = '';
}
if ( !empty( $step['args'] ) ) {
$output .= "<li{$firstTab}>Arguments</li>";
$firstTab = '';
}
if ( !empty( $step['object'] ) ) {
kintParser::reset();
$calleeDump = kintParser::factory( $step['object'] );
$output .= "<li{$firstTab}>Callee object [{$calleeDump->type}]</li>";
}
$output .= '</ul><ul>';
if ( !empty( $step['source'] ) ) {
$output .= "<li><pre class=\"kint-source\">{$step['source']}</pre></li>";
}
if ( !empty( $step['args'] ) ) {
$output .= "<li>";
foreach ( $step['args'] as $k => $arg ) {
kintParser::reset();
$output .= self::decorate( kintParser::factory( $arg, $k ) );
}
$output .= "</li>";
}
if ( !empty( $step['object'] ) ) {
$output .= "<li>" . self::decorate( $calleeDump ) . "</li>";
}
$output .= '</ul></dd>';
}
$output .= '</dl>';
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 "<div class=\"kint\">";
}
/**
* 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 '</div>';
}
$callingFunction = '';
$calleeInfo = '';
$traceDisplay = '';
if ( isset( $prevCaller['class'] ) ) {
$callingFunction = $prevCaller['class'];
}
if ( isset( $prevCaller['type'] ) ) {
$callingFunction .= $prevCaller['type'];
}
if ( isset( $prevCaller['function'] )
&& !in_array( $prevCaller['function'], array( 'include', 'include_once', 'require', 'require_once' ) )
) {
$callingFunction .= $prevCaller['function'] . '()';
}
$callingFunction and $callingFunction = " [{$callingFunction}]";
if ( isset( $callee['file'] ) ) {
$calleeInfo .= 'Called from ' . self::_ideLink( $callee['file'], $callee['line'] );
}
if ( !empty( $miniTrace ) ) {
$traceDisplay = '<ol>';
foreach ( $miniTrace as $step ) {
$traceDisplay .= '<li>' . self::_ideLink( $step['file'], $step['line'] ); // closing tag not required
if ( isset( $step['function'] )
&& !in_array( $step['function'], array( 'include', 'include_once', 'require', 'require_once' ) )
) {
$classString = ' [';
if ( isset( $step['class'] ) ) {
$classString .= $step['class'];
}
if ( isset( $step['type'] ) ) {
$classString .= $step['type'];
}
$classString .= $step['function'] . '()]';
$traceDisplay .= $classString;
}
}
$traceDisplay .= '</ol>';
$calleeInfo = '<nav></nav>' . $calleeInfo;
}
return "<footer>"
. '<span class="kint-popup-trigger" title="Open in new window">&rarr;</span> '
. "{$calleeInfo}{$callingFunction}{$traceDisplay}"
. "</footer></div>";
}
private static function _drawHeader( kintVariableData $kintVar, $verbose = true )
{
$output = '';
if ( $verbose ) {
if ( $kintVar->access !== null ) {
$output .= "<var>" . $kintVar->access . "</var> ";
}
if ( $kintVar->name !== null && $kintVar->name !== '' ) {
$output .= "<dfn>" . kintParser::escape( $kintVar->name ) . "</dfn> ";
}
if ( $kintVar->operator !== null ) {
$output .= $kintVar->operator . " ";
}
}
if ( $kintVar->type !== null ) {
if ( $verbose ) {
$output .= "<var>";
}
$output .= $kintVar->type;
if ( $verbose ) {
$output .= "</var>";
} else {
$output .= " ";
}
}
if ( $kintVar->size !== null ) {
$output .= "(" . $kintVar->size . ") ";
}
return $output;
}
private static function _ideLink( $file, $line )
{
$shortenedPath = Kint::shortenPath( $file );
if ( !Kint::$fileLinkFormat ) return $shortenedPath . ':' . $line;
$ideLink = Kint::getIdeLink( $file, $line );
$class = ( strpos( $ideLink, 'http://' ) === 0 ) ? 'class="kint-ide-link" ' : '';
return "<a {$class}href=\"{$ideLink}\">{$shortenedPath}:{$line}</a>";
}
/**
* produces css and js required for display. May be called multiple times, will only produce output once per
* pageload or until `-` or `@` modifier is used
*
* @return string
*/
public static function init()
{
$baseDir = KINT_DIR . 'view/compiled/';
if ( !is_readable( $cssFile = $baseDir . Kint::$theme . '.css' ) ) {
$cssFile = $baseDir . 'original.css';
}
return
'<script class="-kint-js">' . file_get_contents( $baseDir . 'kint.js' ) . '</script>'
. '<style class="-kint-css">' . file_get_contents( $cssFile ) . "</style>\n";
}
}
@@ -0,0 +1,176 @@
<?php
require( '../Kint.class.php' );
$selectedTheme = isset( $_GET['theme'] ) ? $_GET['theme'] : 'original';
$allowedThemes = array();
$dh = opendir( '../view/compiled' );
while ( ( $filename = readdir( $dh ) ) !== false ) {
if ( strpos( $filename, '.css' ) !== false ) {
$allowedThemes[] = str_replace( '.css', '', $filename );
}
}
sort( $allowedThemes );
if ( in_array( $selectedTheme, $allowedThemes ) ) {
Kint::$theme = $selectedTheme;
}
class BaseUser
{
/**
* @return string
*/
public function getFullName() { }
}
class User extends BaseUser
{
CONST DEFAULT_PATH = 'some/default/path';
CONST ROLE_DISALLOWED = 1;
CONST ROLE_ALLOWED = 2;
CONST ROLE_FORBIDDEN = 3;
public $additionalData;
private $username = 'demo_username';
private $password = 'demo_password';
private $createdDate;
public function __construct() { }
/**
* Check is user is equal to another user
*/
public function isEqualTo( BaseUser $user ) { }
/**
* Get data from this demo class
*
* @param string $username
*
* @return array
*/
public function setUsername( $username ) { }
/**
* Set additional data
*
* @array $data
*/
public function setAdditionalData( array $data ) { $this->additionalData = $data; }
/**
* @return \DateTime date object
*/
public function getCreatedDate() { }
/**
* @param \DateTime $date
*/
public function setCreatedDate( DateTime $date ) { $this->createdDate = $date; }
/**
* Dummy method that triggers trace
*/
public function ensure() { Kint::trace(); }
}
class UserManager
{
private $user;
/**
* Get user from manager
*/
public function getUser() { return $this->user; }
/**
* Debug specific user
*
* @param \User $user
*/
public function debugUser( $user )
{
$this->user = $user;
d( $this->getUser() );
}
/**
* Ensure user (triggers ensure() method on \User object that trace)
*
* @void
*/
public function ensureUser() { $this->user->ensure(); }
}
$user = new User;
$user->setAdditionalData( array(
'last_login' => new DateTime(),
'current_unix_timestamp' => time(),
'random_rgb_color_code' => '#FF9900',
'impressions' => 60,
'nickname' => 'Someuser',
)
);
$user->setCreatedDate( new DateTime( '2013-10-10' ) );
$userManager = new UserManager();
for ( $i = 1; $i < 6; $i++ ) {
$tabularData[] = array(
'date' => "2013-01-0{$i}",
'allowed' => $i % 3 == 0,
'action' => "action {$i}",
'clicks' => rand( 100, 50000 ),
'impressions' => rand( 10000, 500000 ),
);
if ( $i % 2 == 0 ) {
unset( $tabularData[ $i - 1 ]['clicks'] );
}
}
$nestedArray = array();
for ( $i = 1; $i < 6; $i++ ) {
$nestedArray["user group {$i}"] = array(
"user {$i}" => array(
'name' => "Name {$i}",
'surname' => "Surname {$i}"
),
'data' => array(
'conversions' => rand( 100, 5000 ),
'spent' => array( 'currency' => 'EUR', 'amount' => rand( 10000, 500000 ) )
),
);
}
?>
<html>
<head>
<title>Kint PHP debugging tool - overview</title>
</head>
<body>
<div>
<label style="float: right">Switch theme:
<select onchange="window.location = '?theme=' + this.value">
<?php $chosen = isset( $_GET['theme'] ) ? $_GET['theme'] : 'original' ?>
<?php foreach ( $allowedThemes as $theme ) : ?>
<option value="<?php echo $theme ?>"<?php echo $theme === $chosen ? ' selected' : '' ?>>
<?php echo ucfirst( str_replace( '-', ' ', $theme ) ) ?>
</option>
<?php endforeach ?>
</select>
</label>
<h2>Kint PHP debugging tool - overview</h2>
</div>
<h3>Debug variables</h3>
<?php
$userManager->debugUser( $user );
d( $userManager, $tabularData );
d( $nestedArray );
?>
<h3>Trace</h3>
<?php $userManager->ensureUser(); ?>
</body>
</html>
@@ -0,0 +1,19 @@
<?php
abstract class KintObject
{
/** @var string type of variable, can be set in inherited object or in static::parse() method */
public $name = 'NOT SET';
/** @var string quick variable value displayed inline */
public $value;
/**
* returns false or associative array - each key represents a tab in default view, values may be anything
*
* @param $variable
*
* @return mixed
*/
abstract public function parse( & $variable );
}
@@ -0,0 +1,608 @@
<?php
abstract class kintParser extends kintVariableData
{
private static $_level = 0;
private static $_customDataTypes;
private static $_objectParsers;
private static $_objects;
private static $_marker;
private static $_skipAlternatives = false;
private static $_placeFullStringInValue = false;
private static function _init()
{
$fh = opendir( KINT_DIR . 'parsers/custom/' );
while ( $fileName = readdir( $fh ) ) {
if ( substr( $fileName, -4 ) !== '.php' ) continue;
require KINT_DIR . 'parsers/custom/' . $fileName;
self::$_customDataTypes[] = substr( $fileName, 0, -4 );
}
$fh = opendir( KINT_DIR . 'parsers/objects/' );
while ( $fileName = readdir( $fh ) ) {
if ( substr( $fileName, -4 ) !== '.php' ) continue;
require KINT_DIR . 'parsers/objects/' . $fileName;
self::$_objectParsers[] = substr( $fileName, 0, -4 );
}
}
public static function reset()
{
self::$_level = 0;
self::$_objects = self::$_marker = null;
}
/**
* main and usually single method a custom parser must implement
*
* @param mixed $variable
*
* @return mixed [!!!] false is returned if the variable is not of current type
*/
abstract protected function _parse( & $variable );
/**
* the only public entry point to return a parsed representation of a variable
*
* @static
*
* @param $variable
* @param null $name
*
* @throws Exception
* @return \kintParser
*/
public final static function factory( & $variable, $name = null )
{
isset( self::$_customDataTypes ) or self::_init();
# save internal data to revert after dumping to properly handle recursions etc
$revert = array(
'level' => self::$_level,
'objects' => self::$_objects,
);
self::$_level++;
$varData = new kintVariableData;
$varData->name = $name;
# first parse the variable based on its type
$varType = gettype( $variable );
$varType === 'unknown type' and $varType = 'unknown'; # PHP 5.4 inconsistency
$methodName = '_parse_' . $varType;
# objects can be presented in a different way altogether, INSTEAD, not ALONGSIDE the generic parser
if ( $varType === 'object' ) {
foreach ( self::$_objectParsers as $parserClass ) {
$className = 'Kint_Objects_' . $parserClass;
/** @var $object KintObject */
$object = new $className;
if ( ( $alternativeTabs = $object->parse( $variable ) ) !== false ) {
self::$_skipAlternatives = true;
$alternativeDisplay = new kintVariableData;
$alternativeDisplay->type = $object->name;
$alternativeDisplay->value = $object->value;
$alternativeDisplay->name = $name;
foreach ( $alternativeTabs as $name => $values ) {
$alternative = kintParser::factory( $values );
$alternative->type = $name;
if ( Kint::enabled() === Kint::MODE_RICH ) {
empty( $alternative->value ) and $alternative->value = $alternative->extendedValue;
$alternativeDisplay->_alternatives[] = $alternative;
} else {
$alternativeDisplay->extendedValue[] = $alternative;
}
}
self::$_skipAlternatives = false;
self::$_level = $revert['level'];
self::$_objects = $revert['objects'];
return $alternativeDisplay;
}
}
}
# base type parser returning false means "stop processing further": e.g. recursion
if ( self::$methodName( $variable, $varData ) === false ) {
self::$_level--;
return $varData;
}
if ( Kint::enabled() === Kint::MODE_RICH && !self::$_skipAlternatives ) {
# if an alternative returns something that can be represented in an alternative way, don't :)
self::$_skipAlternatives = true;
# now check whether the variable can be represented in a different way
foreach ( self::$_customDataTypes as $parserClass ) {
$className = 'Kint_Parsers_' . $parserClass;
/** @var $parser kintParser */
$parser = new $className;
$parser->name = $name; # the parser may overwrite the name value, so set it first
if ( $parser->_parse( $variable ) !== false ) {
$varData->_alternatives[] = $parser;
}
}
# if alternatives exist, push extendedValue to their front and display it as one of alternatives
if ( !empty( $varData->_alternatives ) && isset( $varData->extendedValue ) ) {
$_ = new kintVariableData;
$_->value = $varData->extendedValue;
$_->type = 'contents';
$_->size = null;
array_unshift( $varData->_alternatives, $_ );
$varData->extendedValue = null;
}
self::$_skipAlternatives = false;
}
self::$_level = $revert['level'];
self::$_objects = $revert['objects'];
if ( strlen( $varData->name ) > 80 ) {
$varData->name =
self::_substr( $varData->name, 0, 37 )
. '...'
. self::_substr( $varData->name, -38, null );
}
return $varData;
}
private static function _checkDepth()
{
return Kint::$maxLevels != 0 && self::$_level >= Kint::$maxLevels;
}
private static function _isArrayTabular( array $variable )
{
if ( Kint::enabled() !== Kint::MODE_RICH ) return false;
$arrayKeys = array();
$keys = null;
$closeEnough = false;
foreach ( $variable as $row ) {
if ( !is_array( $row ) || empty( $row ) ) return false;
foreach ( $row as $col ) {
if ( !empty( $col ) && !is_scalar( $col ) ) return false; // todo add tabular "tolerance"
}
if ( isset( $keys ) && !$closeEnough ) {
# let's just see if the first two rows have same keys, that's faster and has the
# positive side effect of easily spotting missing keys in later rows
if ( $keys !== array_keys( $row ) ) return false;
$closeEnough = true;
} else {
$keys = array_keys( $row );
}
$arrayKeys = array_unique( array_merge( $arrayKeys, $keys ) );
}
return $arrayKeys;
}
private static function _decorateCell( kintVariableData $kintVar )
{
if ( $kintVar->extendedValue !== null || !empty( $kintVar->_alternatives ) ) {
return '<td>' . Kint_Decorators_Rich::decorate( $kintVar ) . '</td>';
}
$output = '<td';
if ( $kintVar->value !== null ) {
$output .= ' title="' . $kintVar->type;
if ( $kintVar->size !== null ) {
$output .= " (" . $kintVar->size . ")";
}
$output .= '">' . $kintVar->value;
} else {
$output .= '>';
if ( $kintVar->type !== 'NULL' ) {
$output .= '<u>' . $kintVar->type;
if ( $kintVar->size !== null ) {
$output .= "(" . $kintVar->size . ")";
}
$output .= '</u>';
} else {
$output .= '<u>NULL</u>';
}
}
return $output . '</td>';
}
public static function escape( $value, $encoding = null )
{
if ( empty( $value ) ) return $value;
if ( Kint::enabled() === Kint::MODE_CLI ) {
$value = str_replace( "\x1b", "\\x1b", $value );
}
if ( Kint::enabled() === Kint::MODE_CLI || Kint::enabled() === Kint::MODE_WHITESPACE ) return $value;
$encoding or $encoding = self::_detectEncoding( $value );
$value = htmlspecialchars( $value, ENT_NOQUOTES, $encoding === 'ASCII' ? 'UTF-8' : $encoding );
if ( $encoding === 'UTF-8' ) {
// todo we could make the symbols hover-title show the code for the invisible symbol
# when possible force invisible characters to have some sort of display (experimental)
$value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '?', $value );
}
# this call converts all non-ASCII characters into html chars of format
if ( function_exists( 'mb_encode_numericentity' ) ) {
$value = mb_encode_numericentity(
$value,
array( 0x80, 0xffff, 0, 0xffff, ),
$encoding
);
}
return $value;
}
private static $_dealingWithGlobals = false;
private static function _parse_array( &$variable, kintVariableData $variableData )
{
isset( self::$_marker ) or self::$_marker = "\x00" . uniqid();
# naturally, $GLOBALS variable is an intertwined recursion nightmare, use black magic
$globalsDetector = false;
if ( array_key_exists( 'GLOBALS', $variable ) && is_array( $variable['GLOBALS'] ) ) {
$globalsDetector = "\x01" . uniqid();
$variable['GLOBALS'][ $globalsDetector ] = true;
if ( isset( $variable[ $globalsDetector ] ) ) {
unset( $variable[ $globalsDetector ] );
self::$_dealingWithGlobals = true;
} else {
unset( $variable['GLOBALS'][ $globalsDetector ] );
$globalsDetector = false;
}
}
$variableData->type = 'array';
$variableData->size = count( $variable );
if ( $variableData->size === 0 ) {
return;
}
if ( isset( $variable[ self::$_marker ] ) ) { # recursion; todo mayhaps show from where
if ( self::$_dealingWithGlobals ) {
$variableData->value = '*RECURSION*';
} else {
unset( $variable[ self::$_marker ] );
$variableData->value = self::$_marker;
}
return false;
}
if ( self::_checkDepth() ) {
$variableData->extendedValue = "*DEPTH TOO GREAT*";
return false;
}
$isSequential = self::_isSequential( $variable );
if ( $variableData->size > 1 && ( $arrayKeys = self::_isArrayTabular( $variable ) ) !== false ) {
$variable[ self::$_marker ] = true; # this must be AFTER _isArrayTabular
$firstRow = true;
$extendedValue = '<table class="kint-report"><thead>';
foreach ( $variable as $rowIndex => & $row ) {
# display strings in their full length
self::$_placeFullStringInValue = true;
if ( $rowIndex === self::$_marker ) continue;
if ( isset( $row[ self::$_marker ] ) ) {
$variableData->value = "*RECURSION*";
return false;
}
$extendedValue .= '<tr>';
if ( $isSequential ) {
$output = '<td>' . '#' . ( $rowIndex + 1 ) . '</td>';
} else {
$output = self::_decorateCell( kintParser::factory( $rowIndex ) );
}
if ( $firstRow ) {
$extendedValue .= '<th>&nbsp;</th>';
}
# 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 .= '<th>' . self::escape( $key ) . '</th>';
}
if ( !array_key_exists( $key, $row ) ) {
$output .= '<td class="kint-empty"></td>';
continue;
}
$var = kintParser::factory( $row[ $key ] );
if ( $var->value === self::$_marker ) {
$variableData->value = '*RECURSION*';
return false;
} elseif ( $var->value === '*RECURSION*' ) {
$output .= '<td class="kint-empty"><u>*RECURSION*</u></td>';
} else {
$output .= self::_decorateCell( $var );
}
unset( $var );
}
if ( $firstRow ) {
$extendedValue .= '</tr></thead><tr>';
$firstRow = false;
}
$extendedValue .= $output . '</tr>';
}
self::$_placeFullStringInValue = false;
$variableData->extendedValue = $extendedValue . '</table>';
} else {
$variable[ self::$_marker ] = true;
$extendedValue = array();
foreach ( $variable as $key => & $val ) {
if ( $key === self::$_marker ) continue;
$output = kintParser::factory( $val );
if ( $output->value === self::$_marker ) {
$variableData->value = "*RECURSION*"; // recursion occurred on a higher level, thus $this is recursion
return false;
}
if ( !$isSequential ) {
$output->operator = '=>';
}
$output->name = $isSequential ? null : "'" . $key . "'";
$extendedValue[] = $output;
}
$variableData->extendedValue = $extendedValue;
}
if ( $globalsDetector ) {
self::$_dealingWithGlobals = false;
}
unset( $variable[ self::$_marker ] );
}
private static function _parse_object( &$variable, kintVariableData $variableData )
{
if ( function_exists( 'spl_object_hash' ) ) {
$hash = spl_object_hash( $variable );
} else {
ob_start();
var_dump( $variable );
preg_match( '[#(\d+)]', ob_get_clean(), $match );
$hash = $match[1];
}
$castedArray = (array) $variable;
$variableData->type = get_class( $variable );
$variableData->size = count( $castedArray );
if ( isset( self::$_objects[ $hash ] ) ) {
$variableData->value = '*RECURSION*';
return false;
}
if ( self::_checkDepth() ) {
$variableData->extendedValue = "*DEPTH TOO GREAT*";
return false;
}
# ArrayObject (and maybe ArrayIterator, did not try yet) unsurprisingly consist of mainly dark magic.
# What bothers me most, var_dump sees no problem with it, and ArrayObject also uses a custom,
# undocumented serialize function, so you can see the properties in internal functions, but
# can never iterate some of them if the flags are not STD_PROP_LIST. Fun stuff.
if ( $variableData->type === 'ArrayObject' || is_subclass_of( $variable, 'ArrayObject' ) ) {
$arrayObjectFlags = $variable->getFlags();
$variable->setFlags( ArrayObject::STD_PROP_LIST );
}
self::$_objects[ $hash ] = true; // todo store reflectorObject here for alternatives cache
$reflector = new ReflectionObject( $variable );
# add link to definition of userland objects
if ( Kint::enabled() === Kint::MODE_RICH && Kint::$fileLinkFormat && $reflector->isUserDefined() ) {
$url = Kint::getIdeLink( $reflector->getFileName(), $reflector->getStartLine() );
$class = ( strpos( $url, 'http://' ) === 0 ) ? 'class="kint-ide-link" ' : '';
$variableData->type = "<a {$class}href=\"{$url}\">{$variableData->type}</a>";
}
$variableData->size = 0;
$extendedValue = array();
$encountered = array();
# copy the object as an array as it provides more info than Reflection (depends)
foreach ( $castedArray as $key => $value ) {
/* casting object to array:
* integer properties are inaccessible;
* private variables have the class name prepended to the variable name;
* protected variables have a '*' prepended to the variable name.
* These prepended values have null bytes on either side.
* http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
*/
if ( $key{0} === "\x00" ) {
$access = $key{1} === "*" ? "protected" : "private";
// Remove the access level from the variable name
$key = substr( $key, strrpos( $key, "\x00" ) + 1 );
} else {
$access = "public";
}
$encountered[ $key ] = true;
$output = kintParser::factory( $value, self::escape( $key ) );
$output->access = $access;
$output->operator = '->';
$extendedValue[] = $output;
$variableData->size++;
}
foreach ( $reflector->getProperties() as $property ) {
$name = $property->name;
if ( $property->isStatic() || isset( $encountered[ $name ] ) ) continue;
if ( $property->isProtected() ) {
$property->setAccessible( true );
$access = "protected";
} elseif ( $property->isPrivate() ) {
$property->setAccessible( true );
$access = "private";
} else {
$access = "public";
}
$value = $property->getValue( $variable );
$output = kintParser::factory( $value, self::escape( $name ) );
$output->access = $access;
$output->operator = '->';
$extendedValue[] = $output;
$variableData->size++;
}
if ( isset( $arrayObjectFlags ) ) {
$variable->setFlags( $arrayObjectFlags );
}
if ( $variableData->size ) {
$variableData->extendedValue = $extendedValue;
}
}
private static function _parse_boolean( &$variable, kintVariableData $variableData )
{
$variableData->type = 'bool';
$variableData->value = $variable ? 'TRUE' : 'FALSE';
}
private static function _parse_double( &$variable, kintVariableData $variableData )
{
$variableData->type = 'float';
$variableData->value = $variable;
}
private static function _parse_integer( &$variable, kintVariableData $variableData )
{
$variableData->type = 'integer';
$variableData->value = $variable;
}
private static function _parse_null( &$variable, kintVariableData $variableData )
{
$variableData->type = 'NULL';
}
private static function _parse_resource( &$variable, kintVariableData $variableData )
{
$resourceType = get_resource_type( $variable );
$variableData->type = "resource ({$resourceType})";
if ( $resourceType === 'stream' && $meta = stream_get_meta_data( $variable ) ) {
if ( isset( $meta['uri'] ) ) {
$file = $meta['uri'];
if ( function_exists( 'stream_is_local' ) ) {
// Only exists on PHP >= 5.2.4
if ( stream_is_local( $file ) ) {
$file = Kint::shortenPath( $file );
}
}
$variableData->value = $file;
}
}
}
private static function _parse_string( &$variable, kintVariableData $variableData )
{
$variableData->type = 'string';
$encoding = self::_detectEncoding( $variable );
if ( $encoding !== 'ASCII' ) {
$variableData->type .= ' ' . $encoding;
}
$variableData->size = self::_strlen( $variable, $encoding );
if ( Kint::enabled() !== Kint::MODE_RICH ) {
$variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
return;
}
if ( !self::$_placeFullStringInValue ) {
$strippedString = preg_replace( '[\s+]', ' ', $variable );
if ( Kint::$maxStrLength && $variableData->size > Kint::$maxStrLength ) {
// encode and truncate
$variableData->value = '"'
. self::escape( self::_substr( $strippedString, 0, Kint::$maxStrLength, $encoding ), $encoding )
. '&hellip;"';
$variableData->extendedValue = self::escape( $variable, $encoding );
return;
} elseif ( $variable !== $strippedString ) { // omit no data from display
$variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
$variableData->extendedValue = self::escape( $variable, $encoding );
return;
}
}
$variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
}
private static function _parse_unknown( &$variable, kintVariableData $variableData )
{
$type = gettype( $variable );
$variableData->type = "UNKNOWN" . ( !empty( $type ) ? " ({$type})" : '' );
$variableData->value = var_export( $variable, true );
}
}
@@ -0,0 +1,102 @@
<?php
class kintVariableData
{
/** @var string */
public $type;
/** @var string */
public $access;
/** @var string */
public $name;
/** @var string */
public $operator;
/** @var int */
public $size;
/**
* @var kintVariableData[] array of kintVariableData objects or strings; displayed collapsed, each element from
* the array is a separate possible representation of the dumped var
*/
public $extendedValue;
/** @var string inline value */
public $value;
/** @var kintVariableData[] array of alternative representations for same variable, don't use in custom parsers */
public $_alternatives;
/* *******************************************
* HELPERS
*/
private static $_supportedCharsets = array(
'UTF-8',
'Windows-1252', # Western; includes iso-8859-1
'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
);
protected static function _detectEncoding( $value )
{
$ret = null;
if ( function_exists( 'mb_detect_encoding' ) ) {
$mbDetected = mb_detect_encoding( $value );
if ( $mbDetected === 'ASCII' ) return 'ASCII';
}
if ( !function_exists( 'iconv' ) ) {
return !empty( $mbDetected ) ? $mbDetected : 'UTF-8';
}
$md5 = md5( $value );
foreach ( Kint::$charEncodings as $encoding ) {
# fuck knows why, //IGNORE and //TRANSLIT still throw notice
if ( md5( @iconv( $encoding, $encoding, $value ) ) === $md5 ) {
return $encoding;
}
}
return 'ASCII';
}
/**
* returns whether the array:
* 1) is numeric and
* 2) in sequence starting from zero
*
* @param array $array
*
* @return bool
*/
protected static function _isSequential( array $array )
{
return array_keys( $array ) === range( 0, count( $array ) - 1 );
}
protected static function _strlen( $string, $encoding = null )
{
if ( function_exists( 'mb_strlen' ) ) {
$encoding or $encoding = self::_detectEncoding( $string );
return mb_strlen( $string, $encoding );
} else {
return strlen( $string );
}
}
protected static function _substr( $string, $start, $end, $encoding = null )
{
if ( function_exists( 'mb_substr' ) ) {
$encoding or $encoding = self::_detectEncoding( $string );
return mb_substr( $string, $start, $end, $encoding );
} else {
return substr( $string, $start, $end );
}
}
}
@@ -0,0 +1,150 @@
<?php
class Kint_Parsers_ClassMethods extends kintParser
{
private static $cache = array();
protected function _parse( &$variable )
{
if ( !KINT_PHP53 || !is_object( $variable ) ) return false;
$className = get_class( $variable );
# assuming class definition will not change inside one request
if ( !isset( self::$cache[ $className ] ) ) {
$reflection = new ReflectionClass( $variable );
$public = $private = $protected = array();
// Class methods
foreach ( $reflection->getMethods() as $method ) {
$params = array();
// Access type
$access = implode( ' ', Reflection::getModifierNames( $method->getModifiers() ) );
// Method parameters
foreach ( $method->getParameters() as $param ) {
$paramString = '';
if ( $param->isArray() ) {
$paramString .= 'array ';
} else {
try {
if ( $paramClassName = $param->getClass() ) {
$paramString .= $paramClassName->name . ' ';
}
} catch ( ReflectionException $e ) {
preg_match( '/\[\s\<\w+?>\s([\w]+)/s', $param->__toString(), $matches );
$paramClassName = isset( $matches[1] ) ? $matches[1] : '';
$paramString .= ' UNDEFINED CLASS (' . $paramClassName . ') ';
}
}
$paramString .= ( $param->isPassedByReference() ? '&' : '' ) . '$' . $param->getName();
if ( $param->isDefaultValueAvailable() ) {
if ( is_array( $param->getDefaultValue() ) ) {
$arrayValues = array();
foreach ( $param->getDefaultValue() as $key => $value ) {
$arrayValues[] = $key . ' => ' . $value;
}
$defaultValue = 'array(' . implode( ', ', $arrayValues ) . ')';
} elseif ( $param->getDefaultValue() === null ) {
$defaultValue = 'NULL';
} elseif ( $param->getDefaultValue() === false ) {
$defaultValue = 'false';
} elseif ( $param->getDefaultValue() === true ) {
$defaultValue = 'true';
} elseif ( $param->getDefaultValue() === '' ) {
$defaultValue = '""';
} else {
$defaultValue = $param->getDefaultValue();
}
$paramString .= ' = ' . $defaultValue;
}
$params[] = $paramString;
}
$output = new kintVariableData;
// Simple DocBlock parser, look for @return
if ( ( $docBlock = $method->getDocComment() ) ) {
$matches = array();
if ( preg_match_all( '/@(\w+)\s+(.*)\r?\n/m', $docBlock, $matches ) ) {
$lines = array_combine( $matches[1], $matches[2] );
if ( isset( $lines['return'] ) ) {
$output->operator = '->';
# since we're outputting code, assumption that the string is utf8 is most likely correct
# and saves resources
$output->type = self::escape( $lines['return'], 'UTF-8' );
}
}
}
$output->name = ( $method->returnsReference() ? '&' : '' ) . $method->getName() . '('
. implode( ', ', $params ) . ')';
$output->access = $access;
if ( is_string( $docBlock ) ) {
$lines = array();
foreach ( explode( "\n", $docBlock ) as $line ) {
$line = trim( $line );
if ( in_array( $line, array( '/**', '/*', '*/' ) ) ) {
continue;
} elseif ( strpos( $line, '*' ) === 0 ) {
$line = substr( $line, 1 );
}
$lines[] = self::escape( trim( $line ), 'UTF-8' );
}
$output->extendedValue = implode( "\n", $lines ) . "\n\n";
}
$declaringClass = $method->getDeclaringClass();
$declaringClassName = $declaringClass->getName();
if ( $declaringClassName !== $className ) {
$output->extendedValue .= "<small>Inherited from <i>{$declaringClassName}</i></small>\n";
}
$fileName = Kint::shortenPath( $method->getFileName() ) . ':' . $method->getStartLine();
$output->extendedValue .= "<small>Defined in {$fileName}</small>";
$sortName = $access . $method->getName();
if ( $method->isPrivate() ) {
$private[ $sortName ] = $output;
} elseif ( $method->isProtected() ) {
$protected[ $sortName ] = $output;
} else {
$public[ $sortName ] = $output;
}
}
if ( !$private && !$protected && !$public ) {
self::$cache[ $className ] = false;
}
ksort( $public );
ksort( $protected );
ksort( $private );
self::$cache[ $className ] = $public + $protected + $private;
}
if ( count( self::$cache[ $className ] ) === 0 ) {
return false;
}
$this->value = self::$cache[ $className ];
$this->type = 'Available methods';
$this->size = count( self::$cache[ $className ] );
}
}
@@ -0,0 +1,49 @@
<?php
class Kint_Parsers_ClassStatics extends kintParser
{
protected function _parse( & $variable )
{
if ( !KINT_PHP53 || !is_object( $variable ) ) return false;
$extendedValue = array();
$reflection = new ReflectionClass( $variable );
// first show static values
foreach ( $reflection->getProperties( ReflectionProperty::IS_STATIC ) as $property ) {
if ( $property->isPrivate() ) {
if ( !method_exists( $property, 'setAccessible' ) ) {
break;
}
$property->setAccessible( true );
$access = "private";
} elseif ( $property->isProtected() ) {
$property->setAccessible( true );
$access = "protected";
} else {
$access = 'public';
}
$_ = $property->getValue();
$output = kintParser::factory( $_, '$' . $property->getName() );
$output->access = $access;
$output->operator = '::';
$extendedValue[] = $output;
}
foreach ( $reflection->getConstants() as $constant => $val ) {
$output = kintParser::factory( $val, $constant );
$output->access = 'constant';
$output->operator = '::';
$extendedValue[] = $output;
}
if ( empty( $extendedValue ) ) return false;
$this->value = $extendedValue;
$this->type = 'Static class properties';
$this->size = count( $extendedValue );
}
}
@@ -0,0 +1,400 @@
<?php
class Kint_Parsers_Color extends kintParser
{
private static $_css3Named = array(
'aliceblue'=>'#f0f8ff','antiquewhite'=>'#faebd7','aqua'=>'#00ffff','aquamarine'=>'#7fffd4','azure'=>'#f0ffff',
'beige'=>'#f5f5dc','bisque'=>'#ffe4c4','black'=>'#000000','blanchedalmond'=>'#ffebcd','blue'=>'#0000ff',
'blueviolet'=>'#8a2be2','brown'=>'#a52a2a','burlywood'=>'#deb887','cadetblue'=>'#5f9ea0','chartreuse'=>'#7fff00',
'chocolate'=>'#d2691e','coral'=>'#ff7f50','cornflowerblue'=>'#6495ed','cornsilk'=>'#fff8dc','crimson'=>'#dc143c',
'cyan'=>'#00ffff','darkblue'=>'#00008b','darkcyan'=>'#008b8b','darkgoldenrod'=>'#b8860b','darkgray'=>'#a9a9a9',
'darkgrey'=>'#a9a9a9','darkgreen'=>'#006400','darkkhaki'=>'#bdb76b','darkmagenta'=>'#8b008b',
'darkolivegreen'=>'#556b2f','darkorange'=>'#ff8c00','darkorchid'=>'#9932cc','darkred'=>'#8b0000',
'darksalmon'=>'#e9967a','darkseagreen'=>'#8fbc8f','darkslateblue'=>'#483d8b','darkslategray'=>'#2f4f4f',
'darkslategrey'=>'#2f4f4f','darkturquoise'=>'#00ced1','darkviolet'=>'#9400d3','deeppink'=>'#ff1493',
'deepskyblue'=>'#00bfff','dimgray'=>'#696969','dimgrey'=>'#696969','dodgerblue'=>'#1e90ff',
'firebrick'=>'#b22222','floralwhite'=>'#fffaf0','forestgreen'=>'#228b22','fuchsia'=>'#ff00ff',
'gainsboro'=>'#dcdcdc','ghostwhite'=>'#f8f8ff','gold'=>'#ffd700','goldenrod'=>'#daa520','gray'=>'#808080',
'grey'=>'#808080','green'=>'#008000','greenyellow'=>'#adff2f','honeydew'=>'#f0fff0','hotpink'=>'#ff69b4',
'indianred'=>'#cd5c5c','indigo'=>'#4b0082','ivory'=>'#fffff0','khaki'=>'#f0e68c','lavender'=>'#e6e6fa',
'lavenderblush'=>'#fff0f5','lawngreen'=>'#7cfc00','lemonchiffon'=>'#fffacd','lightblue'=>'#add8e6',
'lightcoral'=>'#f08080','lightcyan'=>'#e0ffff','lightgoldenrodyellow'=>'#fafad2','lightgray'=>'#d3d3d3',
'lightgrey'=>'#d3d3d3','lightgreen'=>'#90ee90','lightpink'=>'#ffb6c1','lightsalmon'=>'#ffa07a',
'lightseagreen'=>'#20b2aa','lightskyblue'=>'#87cefa','lightslategray'=>'#778899','lightslategrey'=>'#778899',
'lightsteelblue'=>'#b0c4de','lightyellow'=>'#ffffe0','lime'=>'#00ff00','limegreen'=>'#32cd32','linen'=>'#faf0e6',
'magenta'=>'#ff00ff','maroon'=>'#800000','mediumaquamarine'=>'#66cdaa','mediumblue'=>'#0000cd',
'mediumorchid'=>'#ba55d3','mediumpurple'=>'#9370d8','mediumseagreen'=>'#3cb371','mediumslateblue'=>'#7b68ee',
'mediumspringgreen'=>'#00fa9a','mediumturquoise'=>'#48d1cc','mediumvioletred'=>'#c71585',
'midnightblue'=>'#191970','mintcream'=>'#f5fffa','mistyrose'=>'#ffe4e1','moccasin'=>'#ffe4b5',
'navajowhite'=>'#ffdead','navy'=>'#000080','oldlace'=>'#fdf5e6','olive'=>'#808000','olivedrab'=>'#6b8e23',
'orange'=>'#ffa500','orangered'=>'#ff4500','orchid'=>'#da70d6','palegoldenrod'=>'#eee8aa','palegreen'=>'#98fb98',
'paleturquoise'=>'#afeeee','palevioletred'=>'#d87093','papayawhip'=>'#ffefd5','peachpuff'=>'#ffdab9',
'peru'=>'#cd853f','pink'=>'#ffc0cb','plum'=>'#dda0dd','powderblue'=>'#b0e0e6','purple'=>'#800080',
'red'=>'#ff0000','rosybrown'=>'#bc8f8f','royalblue'=>'#4169e1','saddlebrown'=>'#8b4513','salmon'=>'#fa8072',
'sandybrown'=>'#f4a460','seagreen'=>'#2e8b57','seashell'=>'#fff5ee','sienna'=>'#a0522d','silver'=>'#c0c0c0',
'skyblue'=>'#87ceeb','slateblue'=>'#6a5acd','slategray'=>'#708090','slategrey'=>'#708090','snow'=>'#fffafa',
'springgreen'=>'#00ff7f','steelblue'=>'#4682b4','tan'=>'#d2b48c','teal'=>'#008080','thistle'=>'#d8bfd8',
'tomato'=>'#ff6347','turquoise'=>'#40e0d0','violet'=>'#ee82ee','wheat'=>'#f5deb3','white'=>'#ffffff',
'whitesmoke'=>'#f5f5f5','yellow'=>'#ffff00','yellowgreen'=>'#9acd32'
);
protected function _parse( & $variable )
{
if ( !self::_fits( $variable ) ) return false;
$this->type = 'CSS color';
$variants = self::_convert( $variable );
$this->value =
"<div style=\"background:{$variable}\" class=\"kint-color-preview\">{$variable}</div>"
. "<strong>hex :</strong> {$variants['hex']}\n"
. "<strong>rgb :</strong> {$variants['rgb']}\n"
. ( isset( $variants['name'] ) ? "<strong>name:</strong> {$variants['name']}\n" : '' )
. "<strong>hsl :</strong> {$variants['hsl']}";
}
private static function _fits( $variable )
{
if ( !is_string( $variable ) ) return false;
$var = strtolower( trim( $variable ) );
return isset( self::$_css3Named[$var] )
|| preg_match(
'/^(?:#[0-9A-Fa-f]{3}|#[0-9A-Fa-f]{6}|(?:rgb|hsl)a?\s*\((?:\s*[0-9.%]+\s*,?){3,4}\))$/',
$var
);
}
private static function _convert( $color )
{
$color = strtolower( $color );
$decimalColors = array();
$variants = array(
'hex' => null,
'rgb' => null,
'name' => null,
'hsl' => null,
);
if ( isset( self::$_css3Named[ $color ] ) ) {
$variants['name'] = $color;
$color = self::$_css3Named[ $color ];
}
if ( $color{0} === '#' ) {
$variants['hex'] = $color;
$color = substr( $color, 1 );
if ( strlen( $color ) === 6 ) {
$colors = str_split( $color, 2 );
} else {
$colors = array(
$color{0} . $color{0},
$color{1} . $color{1},
$color{2} . $color{2},
);
}
$decimalColors = array_map( 'hexdec', $colors );
} elseif ( substr( $color, 0, 3 ) === 'rgb' ) {
$variants['rgb'] = $color;
preg_match_all( '#([0-9.%]+)#', $color, $matches );
$decimalColors = $matches[1];
foreach ( $decimalColors as &$color ) {
if ( strpos( $color, '%' ) !== false ) {
$color = str_replace( '%', '', $color ) * 2.55;
}
}
} elseif ( substr( $color, 0, 3 ) === 'hsl' ) {
$variants['hsl'] = $color;
preg_match_all( '#([0-9.%]+)#', $color, $matches );
$colors = $matches[1];
$colors[0] /= 360;
$colors[1] = str_replace( '%', '', $colors[1] ) / 100;
$colors[2] = str_replace( '%', '', $colors[2] ) / 100;
$decimalColors = self::_HSLtoRGB( $colors );
if ( isset( $colors[3] ) ) {
$decimalColors[] = $colors[3];
}
}
if ( isset( $decimalColors[3] ) ) {
$alpha = $decimalColors[3];
unset( $decimalColors[3] );
} else {
$alpha = null;
}
foreach ( $variants as $type => &$variant ) {
if ( isset( $variant ) ) continue;
switch ( $type ) {
case 'hex':
$variant = '#';
foreach ( $decimalColors as &$color ) {
$variant .= str_pad( dechex( $color ), 2, "0", STR_PAD_LEFT );
}
$variant .= isset( $alpha ) ? ' (alpha omitted)' : '';
break;
case 'rgb':
$rgb = $decimalColors;
if ( isset( $alpha ) ) {
$rgb[] = $alpha;
$a = 'a';
} else {
$a = '';
}
$variant = "rgb{$a}( " . implode( ', ', $rgb ) . " )";
break;
case 'hsl':
$rgb = self::_RGBtoHSL( $decimalColors );
if ( $rgb === null ) {
unset( $variants[ $type ] );
break;
}
if ( isset( $alpha ) ) {
$rgb[] = $alpha;
$a = 'a';
} else {
$a = '';
}
$variant = "hsl{$a}( " . implode( ', ', $rgb ) . " )";
break;
case 'name':
// [!] name in initial variants array must go after hex
if ( ( $key = array_search( $variants['hex'], self::$_css3Named, true ) ) !== false ) {
$variant = $key;
} else {
unset( $variants[ $type ] );
}
break;
}
}
return $variants;
}
private static function _HSLtoRGB( array $hsl )
{
list( $h, $s, $l ) = $hsl;
$m2 = ( $l <= 0.5 ) ? $l * ( $s + 1 ) : $l + $s - $l * $s;
$m1 = $l * 2 - $m2;
return array(
round( self::_hue2rgb( $m1, $m2, $h + 0.33333 ) * 255 ),
round( self::_hue2rgb( $m1, $m2, $h ) * 255 ),
round( self::_hue2rgb( $m1, $m2, $h - 0.33333 ) * 255 ),
);
}
/**
* Helper function for _color_hsl2rgb().
*/
private static function _hue2rgb( $m1, $m2, $h )
{
$h = ( $h < 0 ) ? $h + 1 : ( ( $h > 1 ) ? $h - 1 : $h );
if ( $h * 6 < 1 ) return $m1 + ( $m2 - $m1 ) * $h * 6;
if ( $h * 2 < 1 ) return $m2;
if ( $h * 3 < 2 ) return $m1 + ( $m2 - $m1 ) * ( 0.66666 - $h ) * 6;
return $m1;
}
private static function _RGBtoHSL( array $rgb )
{
list( $clrR, $clrG, $clrB ) = $rgb;
$clrMin = min( $clrR, $clrG, $clrB );
$clrMax = max( $clrR, $clrG, $clrB );
$deltaMax = $clrMax - $clrMin;
$L = ( $clrMax + $clrMin ) / 510;
if ( 0 == $deltaMax ) {
$H = 0;
$S = 0;
} else {
if ( 0.5 > $L ) {
$S = $deltaMax / ( $clrMax + $clrMin );
} else {
$S = $deltaMax / ( 510 - $clrMax - $clrMin );
}
if ( $clrMax == $clrR ) {
$H = ( $clrG - $clrB ) / ( 6.0 * $deltaMax );
} else if ( $clrMax == $clrG ) {
$H = 1 / 3 + ( $clrB - $clrR ) / ( 6.0 * $deltaMax );
} else {
$H = 2 / 3 + ( $clrR - $clrG ) / ( 6.0 * $deltaMax );
}
if ( 0 > $H ) $H += 1;
if ( 1 < $H ) $H -= 1;
}
return array(
round( $H * 360 ),
round( $S * 100 ) . '%',
round( $L * 100 ) . '%'
);
}
}
/* *************
* TEST DATA
*
dd(array(
'hsl(0, 100%,50%)',
'hsl(30, 100%,50%)',
'hsl(60, 100%,50%)',
'hsl(90, 100%,50%)',
'hsl(120,100%,50%)',
'hsl(150,100%,50%)',
'hsl(180,100%,50%)',
'hsl(210,100%,50%)',
'hsl(240,100%,50%)',
'hsl(270,100%,50%)',
'hsl(300,100%,50%)',
'hsl(330,100%,50%)',
'hsl(360,100%,50%)',
'hsl(120,100%,25%)',
'hsl(120,100%,50%)',
'hsl(120,100%,75%)',
'hsl(120,100%,50%)',
'hsl(120, 67%,50%)',
'hsl(120, 33%,50%)',
'hsl(120, 0%,50%)',
'hsl(120, 60%,70%)',
'#f03',
'#F03',
'#ff0033',
'#FF0033',
'rgb(255,0,51)',
'rgb(255, 0, 51)',
'rgb(100%,0%,20%)',
'rgb(100%, 0%, 20%)',
'hsla(240,100%,50%,0.05)',
'hsla(240,100%,50%, 0.4)',
'hsla(240,100%,50%, 0.7)',
'hsla(240,100%,50%, 1)',
'rgba(255,0,0,0.1)',
'rgba(255,0,0,0.4)',
'rgba(255,0,0,0.7)',
'rgba(255,0,0, 1)',
'black',
'silver',
'gray',
'white',
'maroon',
'red',
'purple',
'fuchsia',
'green',
'lime',
'olive',
'yellow',
'navy',
'blue',
'teal',
'aqua',
'orange',
'aliceblue',
'antiquewhite',
'aquamarine',
'azure',
'beige',
'bisque',
'blanchedalmond',
'blueviolet',
'brown',
'burlywood',
'cadetblue',
'chartreuse',
'chocolate',
'coral',
'cornflowerblue',
'cornsilk',
'crimson',
'darkblue',
'darkcyan',
'darkgoldenrod',
'darkgray',
'darkgreen',
'darkgrey',
'darkkhaki',
'darkmagenta',
'darkolivegreen',
'darkorange',
'darkorchid',
'darkred',
'darksalmon',
'darkseagreen',
'darkslateblue',
'darkslategray',
'darkslategrey',
'darkturquoise',
'darkviolet',
'deeppink',
'deepskyblue',
'dimgray',
'dimgrey',
'dodgerblue',
'firebrick',
'floralwhite',
'forestgreen',
'gainsboro',
'ghostwhite',
'gold',
'goldenrod',
'greenyellow',
'grey',
'honeydew',
'hotpink',
'indianred',
'indigo',
'ivory',
'khaki',
'lavender',
'lavenderblush',
'lawngreen',
'lemonchiffon',
'lightblue',
'lightcoral',
'lightcyan',
'lightgoldenrodyellow',
'lightgray',
'lightgreen',
'lightgrey',
'lightpink',
'lightsalmon',
'lightseagreen',
'lightskyblue',
'lightslategray',
'lightslategrey',
'lightsteelblue',
'lightyellow',
'limegreen',
'linen',
'mediumaquamarine',
'mediumblue',
'mediumorchid',
'mediumpurple',
'mediumseagreen',
'mediumslateblue',
'mediumspringgreen',
'mediumturquoise',
'mediumvioletred',
'midnightblue',
'mintcream',
'mistyrose',
'moccasin',
'navajowhite',
'oldlace',
'olivedrab',
));*/
@@ -0,0 +1,69 @@
<?php
class Kint_Parsers_FsPath extends kintParser
{
protected function _parse( & $variable )
{
if ( !KINT_PHP53
|| !is_string( $variable )
|| strlen( $variable ) > 2048
|| preg_match( '[[:?<>"*|]]', $variable )
|| !@is_readable( $variable ) # f@#! PHP and its random warnings
) return false;
try {
$fileInfo = new SplFileInfo( $variable );
$flags = array();
$perms = $fileInfo->getPerms();
if ( ( $perms & 0xC000 ) === 0xC000 ) {
$type = 'File socket';
$flags[] = 's';
} elseif ( ( $perms & 0xA000 ) === 0xA000 ) {
$type = 'File symlink';
$flags[] = 'l';
} elseif ( ( $perms & 0x8000 ) === 0x8000 ) {
$type = 'File';
$flags[] = '-';
} elseif ( ( $perms & 0x6000 ) === 0x6000 ) {
$type = 'Block special file';
$flags[] = 'b';
} elseif ( ( $perms & 0x4000 ) === 0x4000 ) {
$type = 'Directory';
$flags[] = 'd';
} elseif ( ( $perms & 0x2000 ) === 0x2000 ) {
$type = 'Character special file';
$flags[] = 'c';
} elseif ( ( $perms & 0x1000 ) === 0x1000 ) {
$type = 'FIFO pipe file';
$flags[] = 'p';
} else {
$type = 'Unknown file';
$flags[] = 'u';
}
// owner
$flags[] = ( ( $perms & 0x0100 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0080 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0040 ) ? ( ( $perms & 0x0800 ) ? 's' : 'x' ) : ( ( $perms & 0x0800 ) ? 'S' : '-' ) );
// group
$flags[] = ( ( $perms & 0x0020 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0010 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0008 ) ? ( ( $perms & 0x0400 ) ? 's' : 'x' ) : ( ( $perms & 0x0400 ) ? 'S' : '-' ) );
// world
$flags[] = ( ( $perms & 0x0004 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0002 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0001 ) ? ( ( $perms & 0x0200 ) ? 't' : 'x' ) : ( ( $perms & 0x0200 ) ? 'T' : '-' ) );
$this->type = $type;
$this->size = sprintf( '%.2fK', $fileInfo->getSize() / 1024 );
$this->value = implode( $flags );
} catch ( Exception $e ) {
return false;
}
}
}
@@ -0,0 +1,19 @@
<?php
class Kint_Parsers_Json extends kintParser
{
protected function _parse( & $variable )
{
if ( !KINT_PHP53
|| !is_string( $variable )
|| !isset( $variable{0} ) || ( $variable{0} !== '{' && $variable{0} !== '[' )
|| ( $json = json_decode( $variable, true ) ) === null
) return false;
$val = (array) $json;
if ( empty( $val ) ) return false;
$this->value = kintParser::factory( $val )->extendedValue;
$this->type = 'JSON';
}
}
@@ -0,0 +1,60 @@
<?php
class Kint_Parsers_Microtime extends kintParser
{
private static $_times = array();
private static $_laps = array();
protected function _parse( & $variable )
{
if ( !is_string( $variable ) || !preg_match( '[0\.[0-9]{8} [0-9]{10}]', $variable ) ) {
return false;
}
list( $usec, $sec ) = explode( " ", $variable );
$time = (float) $usec + (float) $sec;
if ( KINT_PHP53 ) {
$size = memory_get_usage( true );
}
# '@' is used to prevent the dreaded timezone not set error
$this->value = @date( 'Y-m-d H:i:s', $sec ) . '.' . substr( $usec, 2, 4 );
$numberOfCalls = count( self::$_times );
if ( $numberOfCalls > 0 ) { # meh, faster than count($times) > 1
$lap = $time - end( self::$_times );
self::$_laps[] = $lap;
$this->value .= "\n<b>SINCE LAST CALL:</b> <b class=\"kint-microtime\">" . round( $lap, 4 ) . '</b>s.';
if ( $numberOfCalls > 1 ) {
$this->value .= "\n<b>SINCE START:</b> " . round( $time - self::$_times[0], 4 ) . 's.';
$this->value .= "\n<b>AVERAGE DURATION:</b> "
. round( array_sum( self::$_laps ) / $numberOfCalls, 4 ) . 's.';
}
}
$unit = array( 'B', 'KB', 'MB', 'GB', 'TB' );
if ( KINT_PHP53 ) {
$this->value .= "\n<b>MEMORY USAGE:</b> " . $size . " bytes ("
. round( $size / pow( 1024, ( $i = floor( log( $size, 1024 ) ) ) ), 3 ) . ' ' . $unit[ $i ] . ")";
}
self::$_times[] = $time;
$this->type = 'Stats';
}
/*
function test() {
d( 'start', microtime() );
for ( $i = 0; $i < 10; $i++ ) {
d(
$duration = mt_rand( 0, 200000 ), // the reported duration will be larger because of Kint overhead
usleep( $duration ),
microtime()
);
}
dd( );
}
*/
}
@@ -0,0 +1,22 @@
<?php
class Kint_Parsers_objectIterateable extends kintParser
{
protected function _parse( & $variable )
{
if ( !KINT_PHP53
|| !is_object( $variable )
|| !$variable instanceof Traversable
|| stripos( get_class( $variable ), 'zend' ) !== false // zf2 PDO wrapper does not play nice
) return false;
$arrayCopy = iterator_to_array( $variable, true );
if ( $arrayCopy === false ) return false;
$this->value = kintParser::factory( $arrayCopy )->extendedValue;
$this->type = 'Iterator contents';
$this->size = count( $arrayCopy );
}
}
@@ -0,0 +1,24 @@
<?php
class Kint_Parsers_SplObjectStorage extends kintParser
{
protected function _parse( & $variable )
{
if ( !is_object( $variable ) || !$variable instanceof SplObjectStorage ) return false;
/** @var $variable SplObjectStorage */
$count = $variable->count();
if ( $count === 0 ) return false;
$variable->rewind();
while ( $variable->valid() ) {
$current = $variable->current();
$this->value[] = kintParser::factory( $current );
$variable->next();
}
$this->type = 'Storage contents';
$this->size = $count;
}
}
@@ -0,0 +1,29 @@
<?php
class Kint_Parsers_Timestamp extends kintParser
{
private static function _fits( $variable )
{
if ( !is_string( $variable ) && !is_int( $variable ) ) return false;
$len = strlen( (int) $variable );
return
(
$len === 9 || $len === 10 # a little naive
|| ( $len === 13 && substr( $variable, -3 ) === '000' ) # also handles javascript micro timestamps
)
&& ( (string) (int) $variable == $variable );
}
protected function _parse( & $variable )
{
if ( !self::_fits( $variable ) ) return false;
$var = strlen( $variable ) === 13 ? substr( $variable, 0, -3 ) : $variable;
$this->type = 'timestamp';
# avoid dreaded "Timezone must be set" error
$this->value = @date( 'Y-m-d H:i:s', $var );
}
}
@@ -0,0 +1,25 @@
<?php
class Kint_Parsers_Xml extends kintParser
{
protected function _parse( & $variable )
{
try {
if ( is_string( $variable ) && substr( $variable, 0, 5 ) === '<?xml' ) {
$e = libxml_use_internal_errors( true );
$xml = simplexml_load_string( $variable );
libxml_use_internal_errors( $e );
if ( empty( $xml ) ) {
return false;
}
} else {
return false;
}
} catch ( Exception $e ) {
return false;
}
$this->value = kintParser::factory( $xml )->extendedValue;
$this->type = 'XML';
}
}
@@ -0,0 +1,47 @@
<?php
class Kint_Objects_Closure extends KintObject
{
public function parse( & $variable )
{
if ( !$variable instanceof Closure ) return false;
$this->name = 'Closure';
$reflection = new ReflectionFunction( $variable );
$ret = array(
'Parameters' => array()
);
if ( $val = $reflection->getParameters() ) {
foreach ( $val as $parameter ) {
// todo http://php.net/manual/en/class.reflectionparameter.php
$ret['Parameters'][] = $parameter->name;
}
}
if ( $val = $reflection->getStaticVariables() ) {
$ret['Uses'] = $val;
}
if ( method_exists($reflection, 'getClousureThis') && $val = $reflection->getClosureThis() ) {
$ret['Uses']['$this'] = $val;
}
if ( $val = $reflection->getFileName() ) {
$this->value = Kint::shortenPath( $val ) . ':' . $reflection->getStartLine();
}
return $ret;
}
public function isDefaultValueAvailable()
{
if ( PHP_VERSION_ID === 50316 ) { // PHP bug #62988
try {
$this->getDefaultValue();
return true;
} catch ( \ReflectionException $e ) {
return false;
}
}
return parent::isDefaultValueAvailable();
}
}
@@ -0,0 +1,35 @@
<?php
class Kint_Objects_Smarty extends KintObject
{
public function parse( & $variable )
{
if ( !$variable instanceof Smarty
|| !defined( 'Smarty::SMARTY_VERSION' ) # lower than 3.x
) return false;
$this->name = 'object Smarty (v' . substr( Smarty::SMARTY_VERSION, 7 ) . ')'; # trim 'Smarty-'
$assigned = $globalAssigns = array();
foreach ( $variable->tpl_vars as $name => $var ) {
$assigned[ $name ] = $var->value;
}
foreach ( Smarty::$global_tpl_vars as $name => $var ) {
if ( $name === 'SCRIPT_NAME' ) continue;
$globalAssigns[ $name ] = $var->value;
}
return array(
'Assigned' => $assigned,
'Assigned globally' => $globalAssigns,
'Configuration' => array(
'Compiled files stored in' => isset($variable->compile_dir)
? $variable->compile_dir
: $variable->getCompileDir(),
)
);
}
}
@@ -0,0 +1,70 @@
<?php
class Kint_Objects_SplFileInfo extends KintObject
{
public function parse( & $variable )
{
if ( !KINT_PHP53 || !is_object( $variable ) || !$variable instanceof SplFileInfo ) return false;
$this->name = 'SplFileInfo';
$this->value = $variable->getBasename();
$flags = array();
$perms = $variable->getPerms();
if ( ( $perms & 0xC000 ) === 0xC000 ) {
$type = 'File socket';
$flags[] = 's';
} elseif ( ( $perms & 0xA000 ) === 0xA000 ) {
$type = 'File symlink';
$flags[] = 'l';
} elseif ( ( $perms & 0x8000 ) === 0x8000 ) {
$type = 'File';
$flags[] = '-';
} elseif ( ( $perms & 0x6000 ) === 0x6000 ) {
$type = 'Block special file';
$flags[] = 'b';
} elseif ( ( $perms & 0x4000 ) === 0x4000 ) {
$type = 'Directory';
$flags[] = 'd';
} elseif ( ( $perms & 0x2000 ) === 0x2000 ) {
$type = 'Character special file';
$flags[] = 'c';
} elseif ( ( $perms & 0x1000 ) === 0x1000 ) {
$type = 'FIFO pipe file';
$flags[] = 'p';
} else {
$type = 'Unknown file';
$flags[] = 'u';
}
// owner
$flags[] = ( ( $perms & 0x0100 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0080 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0040 ) ? ( ( $perms & 0x0800 ) ? 's' : 'x' ) : ( ( $perms & 0x0800 ) ? 'S' : '-' ) );
// group
$flags[] = ( ( $perms & 0x0020 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0010 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0008 ) ? ( ( $perms & 0x0400 ) ? 's' : 'x' ) : ( ( $perms & 0x0400 ) ? 'S' : '-' ) );
// world
$flags[] = ( ( $perms & 0x0004 ) ? 'r' : '-' );
$flags[] = ( ( $perms & 0x0002 ) ? 'w' : '-' );
$flags[] = ( ( $perms & 0x0001 ) ? ( ( $perms & 0x0200 ) ? 't' : 'x' ) : ( ( $perms & 0x0200 ) ? 'T' : '-' ) );
$size = sprintf( '%.2fK', $variable->getSize() / 1024 );
$flags = implode( $flags );
$path = $variable->getRealPath();
return array(
'File information' => array(
'Full path' => $path,
'Type' => $type,
'Size' => $size,
'Flags' => $flags
)
);
}
}
@@ -0,0 +1,437 @@
/**
java -jar compiler.jar --js $FileName$ --js_output_file ../inc/kint.js --compilation_level ADVANCED_OPTIMIZATIONS --output_wrapper "(function(){%output%})()"
*/
if ( typeof kintInitialized === 'undefined' ) {
kintInitialized = 1;
var kint = {
visiblePluses : [], // all visible toggle carets
currentPlus : -1, // currently selected caret
selectText : function( element ) {
var selection = window.getSelection(),
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
},
each : function( selector, callback ) {
Array.prototype.slice.call(document.querySelectorAll(selector), 0).forEach(callback)
},
hasClass : function( target, className ) {
if ( !target.classList ) return false;
if ( typeof className === 'undefined' ) {
className = 'kint-show';
}
return target.classList.contains(className);
},
addClass : function( target, className ) {
if ( typeof className === 'undefined' ) {
className = 'kint-show';
}
target.classList.add(className);
},
removeClass : function( target, className ) {
if ( typeof className === 'undefined' ) {
className = 'kint-show';
}
target.classList.remove(className);
return target;
},
next : function( element ) {
do {
element = element.nextElementSibling;
} while ( element.nodeName.toLowerCase() !== 'dd' );
return element;
},
toggle : function( element, hide ) {
var parent = kint.next(element);
if ( typeof hide === 'undefined' ) {
hide = kint.hasClass(element);
}
if ( hide ) {
kint.removeClass(element);
} else {
kint.addClass(element);
}
if ( parent.childNodes.length === 1 ) {
parent = parent.childNodes[0].childNodes[0]; // reuse variable cause I can
// parent is checked in case of empty <pre> when array("\n") is dumped
if ( parent && kint.hasClass(parent, 'kint-parent') ) {
kint.toggle(parent, hide)
}
}
},
toggleChildren : function( element, hide ) {
var parent = kint.next(element)
, nodes = parent.getElementsByClassName('kint-parent')
, i = nodes.length;
if ( typeof hide === 'undefined' ) {
hide = kint.hasClass(element);
}
while ( i-- ) {
kint.toggle(nodes[i], hide);
}
kint.toggle(element, hide);
},
toggleAll : function( caret ) {
var elements = document.getElementsByClassName('kint-parent')
, i = elements.length
, visible = kint.hasClass(caret.parentNode);
while ( i-- ) {
kint.toggle(elements[i], visible);
}
},
switchTab : function( target ) {
var lis, el = target, index = 0;
target.parentNode.getElementsByClassName('kint-active-tab')[0].className = '';
target.className = 'kint-active-tab';
// take the index of clicked title tab and make the same n-th content tab visible
while ( el = el.previousSibling ) el.nodeType === 1 && index++;
lis = target.parentNode.nextSibling.childNodes;
for ( var i = 0; i < lis.length; i++ ) {
if ( i === index ) {
lis[i].style.display = 'block';
if ( lis[i].childNodes.length === 1 ) {
el = lis[i].childNodes[0].childNodes[0];
if ( kint.hasClass(el, 'kint-parent') ) {
kint.toggle(el, false)
}
}
} else {
lis[i].style.display = 'none';
}
}
},
isSibling : function( el ) {
for ( ; ; ) {
el = el.parentNode;
if ( !el || kint.hasClass(el, 'kint') ) break;
}
return !!el;
},
fetchVisiblePluses : function() {
kint.visiblePluses = [];
kint.each('.kint nav, .kint-tabs>li:not(.kint-active-tab)', function( el ) {
if ( el.offsetWidth !== 0 || el.offsetHeight !== 0 ) {
kint.visiblePluses.push(el)
}
});
},
openInNewWindow : function( kintContainer ) {
var newWindow;
if ( newWindow = window.open() ) {
newWindow.document.open();
newWindow.document.write(
'<html>'
+ '<head>'
+ '<title>Kint (' + new Date().toISOString() + ')</title>'
+ '<meta charset="utf-8">'
+ document.getElementsByClassName('-kint-js')[0].outerHTML
+ document.getElementsByClassName('-kint-css')[0].outerHTML
+ '</head>'
+ '<body>'
+ '<input style="width: 100%" placeholder="Take some notes!">'
+ '<div class="kint">'
+ kintContainer.parentNode.outerHTML
+ '</div></body>'
);
newWindow.document.close();
}
},
sortTable : function( table, column ) {
var tbody = table.tBodies[0];
var format = function( s ) {
var n = column === 1 ? s.replace(/^#/, '') : s;
if ( isNaN(n) ) {
return s.trim().toLocaleLowerCase();
} else {
n = parseFloat(n);
return isNaN(n) ? s.trim() : n;
}
};
[].slice.call(table.tBodies[0].rows)
.sort(function( a, b ) {
a = format(a.cells[column].textContent);
b = format(b.cells[column].textContent);
if ( a < b ) return -1;
if ( a > b ) return 1;
return 0;
})
.forEach(function( el ) {
tbody.appendChild(el);
});
},
keyCallBacks : {
cleanup : function( i ) {
var focusedClass = 'kint-focused';
var prevElement = document.querySelector('.' + focusedClass);
prevElement && kint.removeClass(prevElement, focusedClass);
if ( i !== -1 ) {
var el = kint.visiblePluses[i];
kint.addClass(el, focusedClass);
var offsetTop = function( el ) {
return el.offsetTop + ( el.offsetParent ? offsetTop(el.offsetParent) : 0 );
};
var top = offsetTop(el) - (window.innerHeight / 2 );
window.scrollTo(0, top);
}
kint.currentPlus = i;
},
moveCursor : function( up, i ) {
// todo make the first VISIBLE plus active
if ( up ) {
if ( --i < 0 ) {
i = kint.visiblePluses.length - 1;
}
} else {
if ( ++i >= kint.visiblePluses.length ) {
i = 0;
}
}
kint.keyCallBacks.cleanup(i);
return false;
}
}
};
window.addEventListener("click", function( e ) {
var target = e.target
, nodeName = target.nodeName.toLowerCase();
if ( !kint.isSibling(target) ) return;
// auto-select name of variable
if ( nodeName === 'dfn' ) {
kint.selectText(target);
target = target.parentNode;
} else if ( nodeName === 'var' ) { // stupid workaround for misc elements
target = target.parentNode; // to not stop event from further propagating
nodeName = target.nodeName.toLowerCase()
} else if ( nodeName === 'th' ) {
if ( !e.ctrlKey ) {
kint.sortTable(target.parentNode.parentNode.parentNode, target.cellIndex)
}
return false;
}
// switch tabs
if ( nodeName === 'li' && target.parentNode.className === 'kint-tabs' ) {
if ( target.className !== 'kint-active-tab' ) {
kint.switchTab(target);
if ( kint.currentPlus !== -1 ) kint.fetchVisiblePluses();
}
return false;
}
// handle clicks on the navigation caret
if ( nodeName === 'nav' ) {
// special case for nav in footer
if ( target.parentNode.nodeName.toLowerCase() === 'footer' ) {
target = target.parentNode;
if ( kint.hasClass(target) ) {
kint.removeClass(target)
} else {
kint.addClass(target)
}
} else {
// ensure doubleclick has different behaviour, see below
setTimeout(function() {
var timer = parseInt(target.kintTimer, 10);
if ( timer > 0 ) {
target.kintTimer--;
} else {
kint.toggleChildren(target.parentNode); // <dt>
if ( kint.currentPlus !== -1 ) kint.fetchVisiblePluses();
}
}, 300);
}
e.stopPropagation();
return false;
} else if ( kint.hasClass(target, 'kint-parent') ) {
kint.toggle(target);
if ( kint.currentPlus !== -1 ) kint.fetchVisiblePluses();
return false;
} else if ( kint.hasClass(target, 'kint-ide-link') ) {
e.preventDefault();
var ajax = new XMLHttpRequest(); // add ajax call to contact editor but prevent link default action
ajax.open('GET', target.href);
ajax.send(null);
return false;
} else if ( kint.hasClass(target, 'kint-popup-trigger') ) {
var kintContainer = target.parentNode;
if ( kintContainer.nodeName.toLowerCase() === 'footer' ) {
kintContainer = kintContainer.previousSibling;
} else {
while ( kintContainer && !kint.hasClass(kintContainer, 'kint-parent') ) {
kintContainer = kintContainer.parentNode;
}
}
kint.openInNewWindow(kintContainer);
} else if ( nodeName === 'pre' && e.detail === 3 ) { // triple click pre to select it all
kint.selectText(target);
}
}, false);
window.addEventListener("dblclick", function( e ) {
var target = e.target;
if ( !kint.isSibling(target) ) return;
if ( target.nodeName.toLowerCase() === 'nav' ) {
target.kintTimer = 2;
kint.toggleAll(target);
if ( kint.currentPlus !== -1 ) kint.fetchVisiblePluses();
e.stopPropagation();
}
}, false);
// keyboard navigation
window.onkeydown = function( e ) { // direct assignment is used to have priority over ex FAYT
// do nothing if alt/ctrl key is pressed or if we're actually typing somewhere
if ( e.target !== document.body || e.altKey || e.ctrlKey ) return;
var keyCode = e.keyCode
, shiftKey = e.shiftKey
, i = kint.currentPlus;
if ( keyCode === 68 ) { // 'd' : toggles navigation on/off
if ( i === -1 ) {
kint.fetchVisiblePluses();
return kint.keyCallBacks.moveCursor(false, i);
} else {
kint.keyCallBacks.cleanup(-1);
return false;
}
} else {
if ( i === -1 ) return;
if ( keyCode === 9 ) { // TAB : moves up/down depending on shift key
return kint.keyCallBacks.moveCursor(shiftKey, i);
} else if ( keyCode === 38 ) { // ARROW UP : moves up
return kint.keyCallBacks.moveCursor(true, i);
} else if ( keyCode === 40 ) { // ARROW DOWN : down
return kint.keyCallBacks.moveCursor(false, i);
}
}
var kintNode = kint.visiblePluses[i];
if ( kintNode.nodeName.toLowerCase() === 'li' ) { // we're on a trace tab
if ( keyCode === 32 || keyCode === 13 ) { // SPACE/ENTER
kint.switchTab(kintNode);
kint.fetchVisiblePluses();
return kint.keyCallBacks.moveCursor(true, i);
} else if ( keyCode === 39 ) { // arrows
return kint.keyCallBacks.moveCursor(false, i);
} else if ( keyCode === 37 ) {
return kint.keyCallBacks.moveCursor(true, i);
}
}
kintNode = kintNode.parentNode; // simple dump
if ( keyCode === 32 || keyCode === 13 ) { // SPACE/ENTER : toggles
kint.toggle(kintNode);
kint.fetchVisiblePluses();
return false;
} else if ( keyCode === 39 || keyCode === 37 ) { // ARROW LEFT/RIGHT : respectively hides/shows and traverses
var visible = kint.hasClass(kintNode);
var hide = keyCode === 37;
if ( visible ) {
kint.toggleChildren(kintNode, hide); // expand/collapse all children if immediate ones are showing
} else {
if ( hide ) { // LEFT
// traverse to parent and THEN hide
do {kintNode = kintNode.parentNode} while ( kintNode && kintNode.nodeName.toLowerCase() !== 'dd' );
if ( kintNode ) {
kintNode = kintNode.previousElementSibling;
i = -1;
var parentPlus = kintNode.querySelector('nav');
while ( parentPlus !== kint.visiblePluses[++i] ) {}
kint.keyCallBacks.cleanup(i)
} else { // we are at root
kintNode = kint.visiblePluses[i].parentNode;
}
}
kint.toggle(kintNode, hide);
}
kint.fetchVisiblePluses();
return false;
}
};
window.addEventListener("load", function( e ) { // colorize microtime results relative to others
var elements = Array.prototype.slice.call(document.querySelectorAll('.kint-microtime'), 0);
elements.forEach(function( el ) {
var value = parseFloat(el.innerHTML)
, min = Infinity
, max = -Infinity
, ratio;
elements.forEach(function( el ) {
var val = parseFloat(el.innerHTML);
if ( min > val ) min = val;
if ( max < val ) max = val;
});
ratio = 1 - (value - min) / (max - min);
el.style.background = 'hsl(' + Math.round(ratio * 120) + ',60%,70%)';
});
});
}
// debug purposes only, removed in minified source
function clg( i ) {
if ( !window.console )return;
var l = arguments.length, o = 0;
while ( o < l )console.log(arguments[o++])
}

Some files were not shown because too many files have changed in this diff Show More