updated core and modules

This commit is contained in:
Bachir Soussi Chiadmi
2017-09-09 16:27:51 +02:00
parent 027aa99b32
commit 41863f872c
7810 changed files with 318922 additions and 83631 deletions
@@ -1,28 +1,92 @@
!! README.md is a work in progress !!
#Dependencies:
# Dependencies
- d3.js: Webprofiler module requires D3 library 3.x (not 4.x) to render data.
Download https://github.com/mbostock/d3 into /libraries/d3/d3.min.js
- highlight.js: Webprofiler module requires highlight library to syntax highlight collected queries.
Download http://highlightjs.org into /libraries/highlight
#IDE link:
- highlight.js: Webprofiler module requires highlight 9.7.x library to syntax highlight collected queries.
Every class name discovered while profiling (controller class, event class) are linked to an url for directly open in
an IDE, you can configure the url of those link based on the IDE you are using:
## Install using Composer (recommended)
- Sublime text (2 and 3): see https://github.com/dhoulb/subl for Mac OS X
- Textmate: should be supported by default, use txmt://open?url=file://@file&line=@line as link
- PhpStorm 8+: use phpstorm://open?file=@file&line=@line as link
If you use Composer to manage dependencies, edit `/composer.json` as follows.
#Timeline:
1\. Run `composer require --prefer-dist composer/installers` to ensure that you have the `composer/installers` package installed. This package facilitates the installation of packages into directories other than `/vendor` (e.g. `/libraries`) using Composer.
Now it is possible to also collect the time needed to instantiate every single service used in a request, to make it
work you need to add these two lines to settings.php (or, event better, to settings.local.php):
2\. Add the following to the "installer-paths" section of `composer.json`:
```
"libraries/{$name}": ["type:drupal-library"],
```
3\. Add the following to the "repositories" section of `composer.json`:
```
"d3": {
"type": "package",
"package": {
"name": "d3/d3",
"version": "v3.5.17",
"type": "drupal-library",
"dist": {
"url": "https://github.com/d3/d3/archive/v3.5.17.zip",
"type": "zip"
}
}
},
"highlightjs": {
"type": "package",
"package": {
"name": "components/highlightjs",
"version": "9.7.0",
"type": "drupal-library",
"dist": {
"url": "https://github.com/components/highlightjs/archive/9.7.0.zip",
"type": "zip"
}
}
}
```
4\. Run `composer require --prefer-dist d3/d3:3.5.* components/highlightjs:9.7.*` - you should find that new directories have been created
under `/libraries`
## Install manually
- d3.js:
- Create a `/libraries/d3/` directory below your Drupal root directory
- Download https://d3js.org/d3.v3.min.js
- Rename it to `/libraries/d3/d3.min.js`
For further details on how to obtain D3.js, see https://github.com/d3/d3/
- highlight.js:
- Create `/libraries/highlightjs/` directory below your Drupal root directory
- Download the library and CSS from http://highlightjs.org into it
# IDE link
Each class name discovered while profiling (controller class, event class) is specially linked to open the class in
an IDE. You can configure the URLs for these links to work for your IDE.
## Sublime text (2 and 3) - macOS
See https://github.com/dhoulb/subl
## Textmate
Use txmt://open?url=file://@file&line=@line
## PhpStorm 8+
Use phpstorm://open?file=@file&line=@line
# Timeline
It is also possible to collect the time needed to instantiate every single service used in a request.
Add the following two lines to `settings.php` (or, even better, to `settings.local.php`):
```
$class_loader->addPsr4('Drupal\\webprofiler\\', [ __DIR__ . '/../../modules/contrib/devel/webprofiler/src']);
$settings['container_base_class'] = '\Drupal\webprofiler\DependencyInjection\TraceableContainer';
```
Check if the path from the Webprofiler module in your `settings.php` file matches the location of the installed Webprofiler module in your project.
@@ -2,17 +2,14 @@
"name": "drupal/webprofiler",
"description": "Drupal Web Profiler.",
"type": "drupal-module",
"license": "GPL-2.0+",
"require": {
"oomphinc/composer-installers-extender" : "v1.1.*",
"mbostock/d3": "v3.5.*",
"components/highlightjs": "v9.5.*",
"symfony/stopwatch": "2.4.*"
},
"extra": {
"installer-types": ["library", "component"],
"installer-paths": {
"libraries/{$name}": ["mbostock/d3", "components/highlightjs"]
"authors": [
{
"name": "Luca Lusso",
"email": "lussoluca@gmail.com",
"homepage": "https://github.com/lussoluca",
"role": "Maintainer"
}
}
],
"license": "GPL-2.0+",
"require": {}
}
@@ -1,54 +0,0 @@
commands:
webprofiler:
export:
description: Exports Webprofiler profile/s to file.
arguments:
id: Profile id.
options:
directory: Destination directory to store exported file/s.
messages:
success: Succesfully exported to %s
exported_count: <info>Exported %s profiles</info>
error_writing: Error writing file %s
error_no_profile: No profile with id %s
progress:
exporting: Exporting profiles...
archive: Create archive...
delete_tmp: Delete temp files...
done: Done.
list:
description: Lists Webprofiler profiles.
options:
ip: Filter by IP.
url: Filter by URL.
method: Filter by HTTP method.
limit: Limit printed profiles.
rows:
time: D, m/d/Y - H:i:s
header:
token: Token
ip: IP
method: Method
url: URL
time: Time
benchmark:
description: Benchmark an url.
arguments:
url: Url to benchmark.
options:
runs: Number of runs.
file: Save results as file.
cache-rebuild: Rebuild cache before start benchmark.
messages:
not_git: Not in a git repository.
error_login: Impossibile to login in the user.
progress:
cache_rebuild: Rebuilding cache...
login: Login user...
get: Http request...
compute_avg: Compute average...
compute_median: Compute median...
compute_95percentile: Compute 95 percentile...
git_hash: Compute GIT hash...
yaml: Generate output...
done: Done.
@@ -1,54 +0,0 @@
commands:
webprofiler:
export:
description: Exportar Webprofiler perfil(es) a un archivo.
arguments:
id: Profile id.
options:
directory: Directorio de destiono para almacenar archivo(s) exportados.
messages:
success: Satisfactoriamente exportado en %s
exported_count: <info>Exportados %s perfiles</info>
error_writing: Error escribiendo el archivo %s
error_no_profile: No fue encontrado un perfil con id %s
progress:
exporting: Exportando perfiles...
archive: Creando archivos...
delete_tmp: Borrando archivos temporales...
done: Terminado.
list:
description: Listar perfiles Webprofiler.
options:
ip: Filtrar por IP.
url: Filtrar por URL.
method: Filtrar por método HTTP.
limit: Limitar perfiles a mostrar.
rows:
time: D, d/m/Y - H:i:s
header:
token: Token
ip: IP
method: Método
url: URL
time: Fecha
benchmark:
description: Benchmark una URL.
arguments:
url: Url para efectuar un benchmark.
options:
runs: Número de ejecuciones.
file: Guardar los resultados en un archivo.
cache-rebuild: Reconstruir cache antes de iniciar el benchmark.
messages:
not_git: No en un repositorio git.
error_login: Error ingresando al sistema.
progress:
cache_rebuild: Reconstruyendo cache...
login: Ingresando al sistema...
get: Solicitud Http...
compute_avg: Calculando promedio...
compute_median: Calculando media...
compute_95percentile: Calculando percentile 95...
git_hash: Calculando GIT hash...
yaml: Generando salida...
done: Terminado.
@@ -0,0 +1,14 @@
services:
webprofiler.list:
class: Drupal\webprofiler\Command\ListCommand
tags:
- { name: drupal.command }
lazy: true
webprofiler.export:
class: Drupal\webprofiler\Command\ExportCommand
tags:
- { name: drupal.command }
webprofiler.benchmark:
class: Drupal\webprofiler\Command\BenchmarkCommand
tags:
- { name: drupal.command }
@@ -0,0 +1,20 @@
description: Benchmark an url.
arguments:
url: Url to benchmark.
options:
runs: Number of runs.
file: Save results as file.
cache-rebuild: Rebuild cache before start benchmark.
messages:
not_git: Not in a git repository.
error_login: Impossibile to login in the user.
progress:
cache_rebuild: Rebuilding cache...
login: Login user...
get: Http request...
compute_avg: Compute average...
compute_median: Compute median...
compute_95percentile: Compute 95 percentile...
git_hash: Compute GIT hash...
yaml: Generate output...
done: Done.
@@ -0,0 +1,15 @@
description: Exports Webprofiler profile/s to file.
arguments:
id: Profile id.
options:
directory: Destination directory to store exported file/s.
messages:
success: Succesfully exported to %s
exported_count: <info>Exported %s profiles</info>
error_writing: Error writing file %s
error_no_profile: No profile with id %s
progress:
exporting: Exporting profiles...
archive: Create archive...
delete_tmp: Delete temp files...
done: Done.
@@ -0,0 +1,14 @@
description: Lists Webprofiler profiles.
options:
ip: Filter by IP.
url: Filter by URL.
method: Filter by HTTP method.
limit: Limit printed profiles.
rows:
time: D, m/d/Y - H:i:s
header:
token: Token
ip: IP
method: Method
url: URL
time: Time
@@ -0,0 +1,20 @@
description: Benchmark una URL.
arguments:
url: Url para efectuar un benchmark.
options:
runs: Número de ejecuciones.
file: Guardar los resultados en un archivo.
cache-rebuild: Reconstruir cache antes de iniciar el benchmark.
messages:
not_git: No en un repositorio git.
error_login: Error ingresando al sistema.
progress:
cache_rebuild: Reconstruyendo cache...
login: Ingresando al sistema...
get: Solicitud Http...
compute_avg: Calculando promedio...
compute_median: Calculando media...
compute_95percentile: Calculando percentile 95...
git_hash: Calculando GIT hash...
yaml: Generando salida...
done: Terminado.
@@ -0,0 +1,15 @@
description: Exportar Webprofiler perfil(es) a un archivo.
arguments:
id: Profile id.
options:
directory: Directorio de destiono para almacenar archivo(s) exportados.
messages:
success: Satisfactoriamente exportado en %s
exported_count: <info>Exportados %s perfiles</info>
error_writing: Error escribiendo el archivo %s
error_no_profile: No fue encontrado un perfil con id %s
progress:
exporting: Exportando perfiles...
archive: Creando archivos...
delete_tmp: Borrando archivos temporales...
done: Terminado.
@@ -0,0 +1,14 @@
description: Listar perfiles Webprofiler.
options:
ip: Filtrar por IP.
url: Filtrar por URL.
method: Filtrar por método HTTP.
limit: Limitar perfiles a mostrar.
rows:
time: D, d/m/Y - H:i:s
header:
token: Token
ip: IP
method: Método
url: URL
time: Fecha
@@ -9,6 +9,9 @@
},
repl = function replaceAll(string, find, replace) {
if (typeof string != 'string') {
return '';
}
return string.replace(new RegExp(escapeRx(find), 'g'), replace);
},
@@ -4,12 +4,13 @@ namespace Drupal\webprofiler\Cache;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\webprofiler\DataCollector\CacheDataCollector;
/**
* Wraps an existing cache backend to track calls to the cache backend.
*/
class CacheBackendWrapper implements CacheBackendInterface {
class CacheBackendWrapper implements CacheBackendInterface, CacheTagsInvalidatorInterface {
/**
* The data collector to register the calls.
@@ -55,7 +56,7 @@ class CacheBackendWrapper implements CacheBackendInterface {
$cache = $this->cacheBackend->get($cid, $allow_invalid);
if ($cache) {
$cacheCopy = new \StdClass();
$cacheCopy = new \stdClass();
$cacheCopy->cid = $cache->cid;
$cacheCopy->expire = $cache->expire;
$cacheCopy->tags = $cache->tags;
@@ -81,7 +82,7 @@ class CacheBackendWrapper implements CacheBackendInterface {
$this->cacheDataCollector->registerCacheMiss($this->bin, $cid);
}
else {
$cacheCopy = new \StdClass();
$cacheCopy = new \stdClass();
$cacheCopy->cid = $cache[$cid]->cid;
$cacheCopy->expire = $cache[$cid]->expire;
$cacheCopy->tags = $cache[$cid]->tags;
@@ -121,13 +122,6 @@ class CacheBackendWrapper implements CacheBackendInterface {
return $this->cacheBackend->deleteMultiple($cids);
}
/**
* {@inheritdoc}
*/
public function deleteTags(array $tags) {
return $this->cacheBackend->deleteTags($tags);
}
/**
* {@inheritdoc}
*/
@@ -153,7 +147,9 @@ class CacheBackendWrapper implements CacheBackendInterface {
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
return $this->cacheBackend->invalidateTags($tags);
if ($this->cacheBackend instanceof CacheTagsInvalidatorInterface) {
$this->cacheBackend->invalidateTags($tags);
}
}
/**
@@ -176,4 +172,5 @@ class CacheBackendWrapper implements CacheBackendInterface {
public function removeBin() {
return $this->cacheBackend->removeBin();
}
}
@@ -9,15 +9,24 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Drupal\Console\Command\ContainerAwareCommand;
use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;
use Drupal\Console\Annotations\DrupalCommand;
/**
* Class BenchmarkCommand
*
* @DrupalCommand (
* extension="webprofiler",
* extensionType="module"
* )
*/
class BenchmarkCommand extends ContainerAwareCommand {
class BenchmarkCommand extends Command {
use ContainerAwareCommandTrait;
/**
* {@inheritdoc}
@@ -56,7 +65,7 @@ class BenchmarkCommand extends ContainerAwareCommand {
}
/** @var \Drupal\Core\Http\Client $client */
$client = $this->getContainer()->get('http_client');
$client = $this->container->get('http_client');
$progress = new ProgressBar($output, $runs + $steps);
$progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
@@ -152,7 +161,7 @@ class BenchmarkCommand extends ContainerAwareCommand {
$token = $response->getHeader('X-Debug-Token');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
$profiler = $this->container->get('profiler');
/** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
$profile = $profiler->loadProfile($token);
@@ -2,19 +2,28 @@
namespace Drupal\webprofiler\Command;
use Drupal\Console\Command\ContainerAwareCommand;
use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\webprofiler\Profiler\Profiler;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Drupal\Console\Annotations\DrupalCommand;
/**
* Class ExportCommand
*
* @DrupalCommand (
* extension="webprofiler",
* extensionType="module"
* )
*/
class ExportCommand extends ContainerAwareCommand {
class ExportCommand extends Command {
use ContainerAwareCommandTrait;
/** @var string */
private $filename;
@@ -38,7 +47,7 @@ class ExportCommand extends ContainerAwareCommand {
$directory = $input->getOption('directory');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
$profiler = $this->container->get('profiler');
try {
if ($id) {
@@ -2,16 +2,25 @@
namespace Drupal\webprofiler\Command;
use Drupal\Console\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
use Drupal\Console\Annotations\DrupalCommand;
/**
* Class ListCommand
**
* @DrupalCommand (
* extension="webprofiler",
* extensionType="module"
* )
*/
class ListCommand extends ContainerAwareCommand {
class ListCommand extends Command {
use ContainerAwareCommandTrait;
/**
* {@inheritdoc}
@@ -36,7 +45,7 @@ class ListCommand extends ContainerAwareCommand {
$limit = $input->getOption('limit');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
$profiler = $this->container->get('profiler');
$profiles = $profiler->find($ip, $url, $limit, $method, '', '');
$rows = [];
@@ -1,30 +0,0 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Class EventPass.
*/
class EventPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$definition = $container->findDefinition('http_kernel.basic');
$definition->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver'));
// Replace the regular event_dispatcher service with the debug one.
$definition = $container->findDefinition('event_dispatcher');
$definition->setPublic(FALSE);
$container->setDefinition('webprofiler.debug.event_dispatcher.default', $definition);
$container->register('event_dispatcher', 'Drupal\webprofiler\TraceableEventDispatcher')
->addArgument(new Reference('webprofiler.debug.event_dispatcher.default'))
->addArgument(new Reference('stopwatch'))
->setProperty('_serviceId', 'event_dispatcher');
}
}
@@ -50,7 +50,6 @@ class ServicePass implements CompilerPassInterface {
$inEdges[] = [
'id' => $edge->getSourceNode()->getId(),
'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
];
}
@@ -63,7 +62,6 @@ class ServicePass implements CompilerPassInterface {
$outEdges[] = [
'id' => $edge->getDestNode()->getId(),
'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
];
}
}
@@ -124,7 +124,7 @@ class BlocksDataCollector extends DataCollector implements DrupalDataCollectorIn
/** @var \Drupal\block\BlockInterface $block */
foreach ($decorator->getEntities() as $block) {
/** @var Block $entity */
if ($entity = $storage->load($block->get('id'))) {
if (null !== $block && $entity = $storage->load($block->get('id'))) {
$route = '';
if ($entity->hasLinkTemplate('edit-form')) {
@@ -1,14 +1,12 @@
<?php
/**
* Contains \Drupal\webprofiler\DataCollector\DevelDataCollector;
*/
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Link;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\devel\Plugin\Menu\DestinationMenuLink;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -21,30 +19,18 @@ class DevelDataCollector extends DataCollector implements DrupalDataCollectorInt
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
private $menuLinkTree;
/**
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menuLinkTree
*/
public function __construct(MenuLinkTreeInterface $menuLinkTree) {
$this->menuLinkTree = $menuLinkTree;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['links'] = $this->develMenuLinks();
$this->data['original_url'] = $request->getPathInfo();
}
/**
* @return string
*/
public function getLinks() {
return $this->data['links'];
return $this->develMenuLinks($this->data['original_url']);
}
/**
@@ -68,36 +54,6 @@ class DevelDataCollector extends DataCollector implements DrupalDataCollectorInt
return FALSE;
}
/**
* @return array
* Array containing Devel Menu links
*/
protected function develMenuLinks() {
$parameters = new MenuTreeParameters();
$parameters->setMaxDepth(1)->onlyEnabledLinks();
$tree = $this->menuLinkTree->load('devel', $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $this->menuLinkTree->transform($tree, $manipulators);
$links = array();
foreach ($tree as $item) {
if ($item->access->isAllowed()) {
$links[] = array(
'title' => $item->link->getTitle(),
'description' => $item->link->getDescription(),
'url' => $item->link->getUrlObject()->toString(),
);
}
}
return $links;
}
/**
* Returns the collector icon in base64 format.
*
@@ -107,4 +63,41 @@ class DevelDataCollector extends DataCollector implements DrupalDataCollectorInt
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAYAAABlL09dAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2RTdCREU2NUVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2RTdCREU2NkVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZFN0JERTYzRUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZFN0JERTY0RUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+6sNOKAAAAPVJREFUeNpi/P//PwMtABMDjcCowbQ3mNHe3h6XnBUQTwJiTRzy14E4+8CBAyexSbLgsZQbiI2BOBCH/HqoGgZSDYaBDXjk/uMzuBaLzeVI7FV4DN7n4OAAojvRxL+CwvgnkMFG5bj7BUoVv4lUfBWIrxGp9jcoKM4DsQABhZ+A2BPqs61AzEVA/XuQwT1AzElA4Q8g/gJN951EBN13kMFLgJiHgMJvQCwNxOxAvJwIg7+wEBnGv4D4M1TtLyIMBocxMQUyKExnAzEzEHMQof4/C5GxDHJh4qAp3VhpYC4rKCieAbEQlQ1+yzhamY4aTD+DAQIMAFv+MFaJEyYhAAAAAElFTkSuQmCC';
}
/**
* @param string $original_url
*
* @return array Array containing Devel Menu links
* Array containing Devel Menu links
*/
protected function develMenuLinks($original_url) {
// We cannot use injected services here because at this point this
// class is deserialized from a storage and not constructed.
$menuLinkTreeService = \Drupal::service('menu.link_tree');
$rendererService = \Drupal::service('renderer');
$parameters = new MenuTreeParameters();
$parameters->setMaxDepth(1)->onlyEnabledLinks();
$tree = $menuLinkTreeService->load('devel', $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $menuLinkTreeService->transform($tree, $manipulators);
$links = array();
foreach ($tree as $item) {
/** @var DestinationMenuLink $link */
$link = $item->link;
$renderable = Link::fromTextAndUrl($link->getTitle(), $link->getUrlObject())
->toRenderable();
$rendered = $rendererService->renderPlain($renderable);
$linkString = preg_replace('/\/profiler\/(.*)&/', $original_url . '&', $rendered);
$links[] = Markup::create($linkString);;
}
return $links;
}
}
@@ -2,69 +2,123 @@
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpKernel\DataCollector\EventDataCollector as BaseEventDataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\EventDispatcher\EventDispatcherTraceableInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
/**
* Class EventsDataCollector
*/
class EventsDataCollector extends BaseEventDataCollector implements DrupalDataCollectorInterface {
class EventsDataCollector extends DataCollector implements DrupalDataCollectorInterface, LateDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $eventDispatcher;
/**
* EventsDataCollector constructor.
*
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
*/
public function __construct(EventDispatcherInterface $event_dispatcher) {
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data = [
'called_listeners' => [],
'called_listeners_count' => 0,
'not_called_listeners' => [],
'not_called_listeners_count' => 0,
];
}
/**
* {@inheritdoc}
*/
public function lateCollect() {
if ($this->eventDispatcher instanceof EventDispatcherTraceableInterface) {
$countCalled = 0;
$calledListeners = $this->eventDispatcher->getCalledListeners();
foreach ($calledListeners as &$events) {
foreach ($events as &$priority) {
foreach ($priority as &$listener) {
$countCalled++;
$listener['clazz'] = $this->getMethodData($listener['class'], $listener['method']);
}
}
}
$countNotCalled = 0;
$notCalledListeners = $this->eventDispatcher->getNotCalledListeners();
foreach ($notCalledListeners as $events) {
foreach ($events as $priority) {
foreach ($priority as $listener) {
$countNotCalled++;
}
}
}
$this->data = [
'called_listeners' => $calledListeners,
'called_listeners_count' => $countCalled,
'not_called_listeners' => $notCalledListeners,
'not_called_listeners_count' => $countNotCalled,
];
}
}
/**
* @return array
*/
public function getCalledListeners() {
return $this->data['called_listeners'];
}
/**
* @return array
*/
public function getNotCalledListeners() {
return $this->data['not_called_listeners'];
}
/**
* @return int
*/
public function getCalledListenersCount() {
return count($this->getCalledListeners());
return $this->data['called_listeners_count'];
}
/**
* @return int
*/
public function getNotCalledListenersCount() {
return count($this->getNotCalledListeners());
return $this->data['not_called_listeners_count'];
}
/**
* {@inheritdoc}
*/
public function setCalledListeners(array $listeners) {
$listeners = $this->computePriority($listeners);
$this->data['called_listeners'] = $listeners;
public function getName() {
return 'events';
}
/**
* Adds the priority value to the $listeners array.
*
* @param array $listeners
* @return array
* @return mixed
*/
private function computePriority(array $listeners) {
foreach ($listeners as &$listener) {
foreach ($listener['class']::getSubscribedEvents() as $event => $methods) {
if (is_string($methods)) {
$methods = [[$methods], 0];
}
else {
if (is_string($methods[0])) {
$methods = [$methods];
}
}
foreach ($methods as $method) {
if ($listener['event'] === $event) {
if ($listener['method'] === $method[0]) {
$listener['priority'] = isset($method[1]) ? $method[1] : 0;
}
}
}
}
}
return $listeners;
public function getData() {
return $this->data;
}
/**
@@ -78,7 +132,7 @@ class EventsDataCollector extends BaseEventDataCollector implements DrupalDataCo
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Called listeners: @listeners', ['@listeners' => count($this->getCalledListeners())]);
return $this->t('Called listeners: @listeners', ['@listeners' => $this->getCalledListenersCount()]);
}
/**
@@ -63,7 +63,7 @@ class HttpDataCollector extends DataCollector implements DrupalDataCollectorInte
'request_target' => $request->getRequestTarget(),
'stats' => [
'transferTime' => $stats->getTransferTime(),
'handlerStats' => $stats->getHandlerStats()
'handlerStats' => $stats->getHandlerStats(),
],
],
'response' => [
@@ -71,7 +71,7 @@ class HttpDataCollector extends DataCollector implements DrupalDataCollectorInte
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'protocol' => $response->getProtocolVersion(),
]
],
];
}
@@ -82,7 +82,7 @@ class HttpDataCollector extends DataCollector implements DrupalDataCollectorInte
$response = $data['response'];
$uri = $request->getUri();
$this->data['failed'][] = [
$failureData = [
'request' => [
'method' => $request->getMethod(),
'uri' => [
@@ -97,13 +97,18 @@ class HttpDataCollector extends DataCollector implements DrupalDataCollectorInte
'protocol' => $request->getProtocolVersion(),
'request_target' => $request->getRequestTarget(),
],
'response' => [
];
if ($response) {
$failureData['response'] = [
'phrase' => $response->getReasonPhrase(),
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'protocol' => $response->getProtocolVersion(),
]
];
];
}
$this->data['failed'][] = $failureData;
}
}
@@ -153,10 +158,12 @@ class HttpDataCollector extends DataCollector implements DrupalDataCollectorInte
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Completed @completed, error @error', [
return $this->t(
'Completed @completed, error @error', [
'@completed' => $this->getCompletedRequestsCount(),
'@error' => $this->getFailedRequestsCount()
]);
'@error' => $this->getFailedRequestsCount(),
]
);
}
/**
@@ -5,7 +5,7 @@ namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DependencyInjection\TraceableContainer;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
@@ -18,15 +18,15 @@ class ServicesDataCollector extends DataCollector implements DrupalDataCollector
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface
* @var \Symfony\Component\DependencyInjection\ContainerInterface
* $container
*/
private $container;
/**
* @param IntrospectableContainerInterface $container
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
public function __construct(IntrospectableContainerInterface $container) {
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
@@ -49,7 +49,7 @@ class TranslationsDataCollector extends DataCollector implements DrupalDataColle
$this->data['translations']['translated'] = $this->translation->getTranslated();
$this->data['translations']['untranslated'] = $this->translation->getUntranslated();
}
$data['user_interface_translations_path'] = $this->urlGenerator->generateFromRoute('locale.translate_page');
$this->data['user_interface_translations_path'] = $this->urlGenerator->generateFromRoute('locale.translate_page');
}
/**
@@ -15,42 +15,42 @@ class ShortcutSetStorageDecorator extends ConfigEntityStorageDecorator implement
* {@inheritdoc}
*/
public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
// TODO: Implement assignUser() method.
$this->getOriginalObject()->assignUser($shortcut_set, $account);
}
/**
* {@inheritdoc}
*/
public function unassignUser($account) {
// TODO: Implement unassignUser() method.
return $this->getOriginalObject()->unassignUser($account);
}
/**
* {@inheritdoc}
*/
public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
// TODO: Implement deleteAssignedShortcutSets() method.
$this->getOriginalObject()->deleteAssignedShortcutSets($entity);
}
/**
* {@inheritdoc}
*/
public function getAssignedToUser($account) {
// TODO: Implement getAssignedToUser() method.
return $this->getOriginalObject()->getAssignedToUser($account);
}
/**
* {@inheritdoc}
*/
public function countAssignedUsers(ShortcutSetInterface $shortcut_set) {
// TODO: Implement countAssignedUsers() method.
return $this->getOriginalObject()->countAssignedUsers($shortcut_set);
}
/**
* {@inheritdoc}
*/
public function getDefaultSet(AccountInterface $account) {
// TODO: Implement getDefaultSet() method.
return $this->getOriginalObject()->getDefaultSet($account);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Drupal\webprofiler\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
interface EventDispatcherTraceableInterface extends EventDispatcherInterface {
/**
* @return array
*/
public function getCalledListeners();
/**
* @return mixed
*/
public function getNotCalledListeners();
}
@@ -0,0 +1,195 @@
<?php
namespace Drupal\webprofiler\EventDispatcher;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Drupal\webprofiler\Stopwatch;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class TraceableEventDispatcher
*/
class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements EventDispatcherTraceableInterface {
/**
* @var \Drupal\webprofiler\Stopwatch
* The stopwatch service.
*/
protected $stopwatch;
/**
* @var array
*/
protected $calledListeners;
/**
* @var array
*/
protected $notCalledListeners;
/**
* @param \Drupal\webprofiler\Stopwatch $stopwatch
*/
public function setStopwatch(Stopwatch $stopwatch) {
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function dispatch($event_name, Event $event = NULL) {
if ($event === NULL) {
$event = new Event();
}
$this->preDispatch($event_name, $event);
$e = $this->stopwatch->start($event_name, 'section');
if (isset($this->listeners[$event_name])) {
// Sort listeners if necessary.
if (isset($this->unsorted[$event_name])) {
krsort($this->listeners[$event_name]);
unset($this->unsorted[$event_name]);
}
// Invoke listeners and resolve callables if necessary.
foreach ($this->listeners[$event_name] as $priority => &$definitions) {
foreach ($definitions as $key => &$definition) {
if (!isset($definition['callable'])) {
$definition['callable'] = [
$this->container->get($definition['service'][0]),
$definition['service'][1],
];
}
$definition['callable']($event, $event_name, $this);
$this->addCalledListener($definition, $event_name, $priority);
if ($event->isPropagationStopped()) {
return $event;
}
}
}
}
if ($e->isStarted()) {
$e->stop();
}
$this->postDispatch($event_name, $event);
return $event;
}
/**
* {@inheritdoc}
*/
public function getCalledListeners() {
return $this->calledListeners;
}
/**
* {@inheritdoc}
*/
public function getNotCalledListeners() {
return $this->notCalledListeners;
}
/**
* Called before dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function preDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::VIEW:
case KernelEvents::RESPONSE:
// stop only if a controller has been executed
if ($this->stopwatch->isStarted('controller')) {
$this->stopwatch->stop('controller');
}
break;
}
}
/**
* Called after dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function postDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::CONTROLLER:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
}
catch (\LogicException $e) {
}
break;
case KernelEvents::TERMINATE:
// In the special case described in the `preDispatch` method above, the `$token` section
// does not exist, then closing it throws an exception which must be caught.
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
}
catch (\LogicException $e) {
}
break;
}
}
/**
* @param $definition
* @param $event_name
* @param $priority
*/
private function addCalledListener($definition, $event_name, $priority) {
$this->calledListeners[$event_name][$priority][] = [
'class' => get_class($definition['callable'][0]),
'method' => $definition['callable'][1],
];
// Remove this listener from the $notCalledListeners array.
if (!$this->notCalledListeners) {
$this->notCalledListeners = $this->cloneListeners($this->listeners);
}
foreach ($this->notCalledListeners[$event_name][$priority] as $key => $listener) {
if ($listener['service'][0] == $definition['service'][0] && $listener['service'][1] == $definition['service'][1]) {
unset($this->notCalledListeners[$event_name][$priority][$key]);
}
}
}
/**
* @param $listeners
*
* @return array
*/
private function cloneListeners($listeners) {
$clone = [];
foreach ($listeners as $eventName => $events) {
foreach ($events as $priorityValue => $priorities) {
foreach ($priorities as $key => $listener) {
$clone[$eventName][$priorityValue][$key]['service'] = [
$listener['service'][0],
$listener['service'][1]
];
}
}
}
return $clone;
}
}
@@ -3,7 +3,6 @@
namespace Drupal\webprofiler\Routing;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\webprofiler\Profiler\Profiler;
use Symfony\Component\Routing\Route;
/**
@@ -11,29 +10,22 @@ use Symfony\Component\Routing\Route;
*/
class TokenConverter implements ParamConverterInterface {
/**
* @var \Drupal\webprofiler\Profiler\Profiler
*/
private $profiler;
/**
* Constructs a new WebprofilerController.
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
*/
public function __construct(Profiler $profiler) {
$this->profiler = $profiler;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
if (NULL === $this->profiler) {
// "profiler" service isn't injected to prevent circular reference when
// more than one language is active and "Account administration pages" is
// enabled on admin/config/regional/language/detection. See #2710787 for
// more information.
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = \Drupal::service('profiler');
if (NULL === $profiler) {
return NULL;
}
$profile = $this->profiler->loadProfile($value);
$profile = $profiler->loadProfile($value);
if (NULL === $profile) {
return NULL;
@@ -2,27 +2,36 @@
namespace Drupal\webprofiler\State;
use Drupal\Core\Cache\CacheCollector;
use Drupal\Core\State\StateInterface;
use Drupal\webprofiler\DataCollector\StateDataCollector;
/**
* Class StateWrapper
* Class StateWrapper.
*/
class StateWrapper implements StateInterface {
class StateWrapper extends CacheCollector implements StateInterface {
/**
* The system state.
*
* @var \Drupal\Core\State\StateInterface
*/
private $state;
/**
* The state data collector.
*
* @var \Drupal\webprofiler\DataCollector\StateDataCollector
*/
private $dataCollector;
/**
* StateWrapper constructor.
*
* @param \Drupal\Core\State\StateInterface $state
* The system state.
* @param \Drupal\webprofiler\DataCollector\StateDataCollector $dataCollector
* The state data collector.
*/
public function __construct(StateInterface $state, StateDataCollector $dataCollector) {
$this->state = $state;
@@ -83,4 +92,34 @@ class StateWrapper implements StateInterface {
public function resetCache() {
$this->state->resetCache();
}
/**
* {@inheritdoc}
*/
protected function resolveCacheMiss($key) {
return $this->state->resolveCacheMiss($key);
}
/**
* {@inheritdoc}
*/
public function destruct() {
$this->updateCache();
}
/**
* Passes through all non-tracked calls onto the decorated object.
*
* @param string $method
* The called method.
* @param mixed $args
* The passed in arguments.
*
* @return mixed
* The return argument of the call.
*/
public function __call($method, $args) {
return call_user_func_array([$this->state, $method], $args);
}
}
@@ -19,7 +19,25 @@ class ThemeNegotiatorWrapper extends ThemeNegotiator {
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
foreach ($this->getSortedNegotiators() as $negotiator) {
// This method has changed in Drupal 8.4.x, to maintain compatibility with
// Drupal 8.3.x we check the existence or not of the classResolver
// property.
// TODO: remove this logic when we decide to drop Drupal 8.3.x support.
if (property_exists($this, 'classResolver')) {
$classResolver = $this->classResolver;
$negotiators = $this->negotiators;
} else {
$classResolver = \Drupal::classResolver();
$negotiators = $this->getSortedNegotiators();
}
foreach ($negotiators as $negotiator_id) {
if (property_exists($this, 'classResolver')) {
$negotiator = $classResolver->getInstanceFromDefinition($negotiator_id);
} else {
$negotiator = $negotiator_id;
}
if ($negotiator->applies($route_match)) {
$theme = $negotiator->determineActiveTheme($route_match);
if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
@@ -36,4 +54,27 @@ class ThemeNegotiatorWrapper extends ThemeNegotiator {
public function getNegotiator() {
return $this->negotiator;
}
/**
* Returns the sorted array of theme negotiators.
*
* @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[]
* An array of theme negotiator objects.
*
* TODO: remove this method when we decide to drop Drupal 8.3.x support.
*/
protected function getSortedNegotiators() {
if (!isset($this->sortedNegotiators)) {
// Sort the negotiators according to priority.
krsort($this->negotiators);
// Merge nested negotiators from $this->negotiators into
// $this->sortedNegotiators.
$this->sortedNegotiators = [];
foreach ($this->negotiators as $builders) {
$this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
}
}
return $this->sortedNegotiators;
}
}
@@ -1,70 +0,0 @@
<?php
namespace Drupal\webprofiler;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\Event;
/**
* Class TraceableEventDispatcher
*/
class TraceableEventDispatcher extends BaseTraceableEventDispatcher {
/**
* {@inheritdoc}
*/
protected function preDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::VIEW:
case KernelEvents::RESPONSE:
// stop only if a controller has been executed
if ($this->stopwatch->isStarted('controller')) {
$this->stopwatch->stop('controller');
}
break;
}
}
/**
* {@inheritdoc}
*/
protected function postDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::CONTROLLER:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {
}
break;
case KernelEvents::TERMINATE:
// In the special case described in the `preDispatch` method above, the `$token` section
// does not exist, then closing it throws an exception which must be caught.
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {
}
break;
}
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener) {
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (FALSE !== ($key = array_search($listener, $listeners, TRUE))) {
return $priority;
}
}
}
}
@@ -3,6 +3,7 @@
namespace Drupal\webprofiler\Views;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\HttpFoundation\Response;
/**
@@ -58,6 +59,7 @@ class TraceableViewExecutable extends ViewExecutable {
return;
}
/** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
$exposed_form = $this->display_handler->getPlugin('exposed_form');
$exposed_form->preRender($this->result);
@@ -70,11 +72,11 @@ class TraceableViewExecutable extends ViewExecutable {
$themes[] = $active_theme->getName();
// Check for already-cached output.
/** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
if (!empty($this->live_preview)) {
$cache = FALSE;
$cache = Views::pluginManager('cache')->createInstance('none');
}
else {
/** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
$cache = $this->display_handler->getPlugin('cache');
}
@@ -130,9 +132,7 @@ class TraceableViewExecutable extends ViewExecutable {
$exposed_form->postRender($this->display_handler->output);
if ($cache) {
$cache->postRender($this->display_handler->output);
}
$cache->postRender($this->display_handler->output);
// Let modules modify the view output after it is rendered.
$module_handler->invokeAll('views_post_render', [
@@ -145,7 +145,7 @@ class TraceableViewExecutable extends ViewExecutable {
foreach ($themes as $theme_name) {
$function = $theme_name . '_views_post_render';
if (function_exists($function)) {
$function($this);
$function($this, $this->display_handler->output, $cache);
}
}
@@ -28,7 +28,6 @@ class WebprofilerServiceProvider extends ServiceProviderBase {
$container->addCompilerPass(new StoragePass());
$container->addCompilerPass(new ServicePass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new EventPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new DecoratorPass(), PassConfig::TYPE_AFTER_REMOVING);
$modules = $container->getParameter('container.modules');
@@ -57,7 +56,7 @@ class WebprofilerServiceProvider extends ServiceProviderBase {
'priority' => 78,
]);
}
// Add TranslationsDataCollector only if Locale module is enabled.
if (isset($modules['locale'])) {
$container->register('webprofiler.translations', 'Drupal\webprofiler\DataCollector\TranslationsDataCollector')
@@ -105,5 +104,14 @@ class WebprofilerServiceProvider extends ServiceProviderBase {
// Replace the regular string_translation service with a traceable one.
$container->getDefinition('string_translation')
->setClass('Drupal\webprofiler\StringTranslation\TranslationManagerWrapper');
// Replace the regular event_dispatcher service with a traceable one.
$container->getDefinition('event_dispatcher')
->setClass('Drupal\webprofiler\EventDispatcher\TraceableEventDispatcher')
->addMethodCall('setStopwatch', [new Reference('stopwatch')]);
$container->getDefinition('http_kernel.basic')
->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver'));
}
}
@@ -10,7 +10,7 @@
{% spaceless %}
{% for item in collector.links %}
<div class="sf-toolbar-info-piece">
<span><a href="{{ item.url }}" title="{{ item.description|default(item.title) }}">{{ item.title }}</a></span>
<span>{{ item }}</span>
</div>
{% endfor %}
{% endspaceless %}
@@ -1,21 +1,22 @@
{% block toolbar %}
{% set icon %}
<a href="{{ url("webprofiler.dashboard", {profile: token}, {fragment: 'events'}) }}" title="{{ 'Events'|t }}">
<img width="20" height="28" alt="{{ 'Events'|t }}"
src="data:image/png;base64,{{ collector.icon }}">
<span class="sf-toolbar-info-piece-additional sf-toolbar-status">{{ collector.getCalledListenersCount }}</span>
</a>
<a href="{{ url("webprofiler.dashboard", {profile: token}, {fragment: 'events'}) }}" title="{{ 'Events'|t }}">
<img width="20" height="28" alt="{{ 'Events'|t }}"
src="data:image/png;base64,{{ collector.icon }}">
<span class="sf-toolbar-info-piece-additional sf-toolbar-status">{{ collector.getCalledListenersCount }}</span>
</a>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>{{ 'Triggered'|t }}</b>
<span>{{ collector.getCalledListenersCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>{{ 'Not triggered'|t }}</b>
<span>{{ collector.getNotCalledListenersCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>{{ 'Called'|t }}</b>
<span>{{ collector.getCalledListenersCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>{{ 'Not called'|t }}</b>
<span>{{ collector.getNotCalledListenersCount }}</span>
</div>
{% endset %}
<div class="sf-toolbar-block">
@@ -36,44 +37,46 @@
<th>{{ 'Class'|t }}</th>
<th>{{ 'Priority'|t }}</th>
</thead>
<tbody> <% _.each( data.called_listeners, function( item ){ %>
<tr>
<td><%= item.event %></td>
<% if(item.type == 'Method') { %>
<td>
<%= Drupal.webprofiler.helpers.classLink(item) %>
</td>
<% } else { %>
<td>{{ 'Closure'|t }}</td>
<% } %>
<td><%= item.priority %></td>
</tr>
<tbody>
<% _.each( data.called_listeners, function( events, event_name ){ %>
<% _.each( events, function( priority, priority_value ){ %>
<% _.each( priority, function( listener ){ %>
<tr>
<td><%= event_name %></td>
<% if( listener.clazz ) { %>
<td><%= Drupal.webprofiler.helpers.classLink(listener.clazz) %></td>
<% } else { %>
<td><%= listener.service[0] %>::<%= listener.service[1] %></td>
<% } %>
<td><%= priority_value %></td>
</tr>
<% }); %>
<% }); %>
<% }); %>
</tbody>
</table>
</div>
<div class="panel__container">
<table class="table--duo">
<thead>
<th>{{ 'Non called listeners'|t }}</th>
<th>{{ 'Class'|t }}</th>
<th>{{ 'Not called listeners'|t }}</th>
<th>{{ 'Service'|t }}</th>
<th>{{ 'Priority'|t }}</th>
</thead>
<tbody>
<% _.each( data.not_called_listeners, function( item ){ %>
<% _.each( data.not_called_listeners, function( events, event_name ){ %>
<% _.each( events, function( priority, priority_value ){ %>
<% _.each( priority, function( listener ){ %>
<tr>
<td><%= item.event %></td>
<% if(item.type == 'Method') { %>
<td>
<%= Drupal.webprofiler.helpers.classLink(item) %>
</td>
<% } else { %>
<td>{{ 'Closure'|t }}</td>
<% } %>
<td><%= event_name %></td>
<td><%= listener.service[0] %>::<%= listener.service[1] %></td>
<td><%= priority_value %></td>
</tr>
<% }); %>
<% }); %>
<% }); %>
</tbody>
</table>
</div>
</script>
{% endblock %}
@@ -0,0 +1,79 @@
<?php
namespace Drupal\Tests\webprofiler\FunctionalJavascript;
/**
* Tests the JavaScript functionality of webprofiler.
*
* @group webprofiler
*/
class ToolbarTest extends WebprofilerTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['webprofiler', 'node'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
\Drupal::configFactory()->getEditable('system.site')->set('page.front', '/node')->save(TRUE);
}
/**
* Tests if the toolbar appears on front page.
*/
public function testToolbarOnFrontPage() {
$this->loginForToolbar();
$this->drupalGet('<front>');
$this->waitForToolbar();
$assert = $this->assertSession();
$assert->pageTextContains(\Drupal::VERSION);
$assert->pageTextContains('Configure Webprofiler');
$assert->pageTextContains('View latest reports');
$assert->pageTextContains('Drupal Documentation');
$assert->pageTextContains('Get involved!');
}
/**
* Tests the toolbar report page.
*/
public function testToolbarReportPage() {
$this->loginForDashboard();
$this->drupalGet('<front>');
$token = $this->waitForToolbar();
$this->drupalGet('admin/reports/profiler/list');
$assert = $this->assertSession();
$assert->pageTextContains($token);
}
/**
* Tests the toolbar not appears on excluded path.
*/
public function testToolbarNotAppearsOnExcludedPath() {
$this->loginForDashboard();
$this->drupalGet('admin/config/development/devel');
$token = $this->waitForToolbar();
$assert = $this->assertSession();
$assert->pageTextContains($token);
$assert->pageTextContains('Configure Webprofiler');
$this->config('webprofiler.config')
->set('exclude', '/admin/config/development/devel')
->save();
$this->drupalGet('admin/config/development/devel');
$this->assertSession()->pageTextNotContains('sf-toolbar');
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\webprofiler\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use PHPUnit_Framework_AssertionFailedError;
/**
* Class WebprofilerTestBase.
*
* @group webprofiler
*/
abstract class WebprofilerTestBase extends JavascriptTestBase {
/**
* Wait until the toolbar is present on page.
*/
protected function waitForToolbar() {
$session = $this->getSession();
$token = $this->getToken();
$page = $session->getPage();
$toolbar = $page->findById('webprofiler' . $token);
$this->assertTrue($toolbar->hasClass('sf-toolbar'), 'Toolbar loader is present in page');
$session->wait(1000, 'null !== document.getElementById(\'sfToolbarMainContent-' . $token . '\')');
return $token;
}
/**
* Return the Webprofiler token.
*
* @return null|string
* The page token
*/
protected function getToken() {
$token = $this->getSession()->getResponseHeader('X-Debug-Token');
if (NULL === $token) {
throw new PHPUnit_Framework_AssertionFailedError();
}
return $token;
}
/**
* Login with a user that can see the toolbar.
*/
protected function loginForToolbar() {
$admin_user = $this->drupalCreateUser(
[
'view webprofiler toolbar',
]
);
$this->drupalLogin($admin_user);
}
/**
* Login with a user that can see the toolbar and the dashboard.
*/
protected function loginForDashboard() {
$admin_user = $this->drupalCreateUser(
[
'view webprofiler toolbar',
'access webprofiler',
]
);
$this->drupalLogin($admin_user);
}
/**
* Flush cache.
*/
protected function flushCache() {
$module_handler = \Drupal::moduleHandler();
$module_handler->invokeAll('cache_flush');
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\Tests\webprofiler\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Class DecoratorTest.
*
* @group webprofiler
*/
class DecoratorTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'views'];
/**
* Tests the Entity Type Manager service decoration.
*
* @param string $service
* The service name.
* @param string $original
* The original class.
* @param string $decorated
* The decorated class.
*
* @dataProvider decorators
*/
public function testEntityTypeDecorator($service, $original, $decorated) {
$entityTypeManagerOriginal = $this->container->get($service);
$this->assertInstanceOf($original, $entityTypeManagerOriginal);
$this->container->get('module_installer')->install(['webprofiler']);
$entityTypeManagerDecorated = $this->container->get($service);
$this->assertInstanceOf($decorated, $entityTypeManagerDecorated);
}
/**
* DataProvider for testEntityTypeDecorator.
*
* @return array
* The array of values to run tests on.
*/
public function decorators() {
return array(
array('entity_type.manager', 'Drupal\Core\Entity\EntityTypeManager', 'Drupal\webprofiler\Entity\EntityManagerWrapper'),
array('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory', 'Drupal\webprofiler\Cache\CacheFactoryWrapper'),
array('asset.css.collection_renderer', 'Drupal\Core\Asset\CssCollectionRenderer', 'Drupal\webprofiler\Asset\CssCollectionRendererWrapper'),
array('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer', 'Drupal\webprofiler\Asset\JsCollectionRendererWrapper'),
array('state', 'Drupal\Core\State\State', 'Drupal\webprofiler\State\StateWrapper'),
array('views.executable', 'Drupal\views\ViewExecutableFactory', 'Drupal\webprofiler\Views\ViewExecutableFactoryWrapper'),
array('form_builder', 'Drupal\Core\Form\FormBuilder', 'Drupal\webprofiler\Form\FormBuilderWrapper'),
array('access_manager', 'Drupal\Core\Access\AccessManager', 'Drupal\webprofiler\Access\AccessManagerWrapper'),
array('theme.negotiator', 'Drupal\Core\Theme\ThemeNegotiator', 'Drupal\webprofiler\Theme\ThemeNegotiatorWrapper'),
array('config.factory', 'Drupal\Core\Config\ConfigFactory', 'Drupal\webprofiler\Config\ConfigFactoryWrapper'),
array('string_translation', 'Drupal\Core\StringTranslation\TranslationManager', 'Drupal\webprofiler\StringTranslation\TranslationManagerWrapper'),
);
}
}
@@ -0,0 +1,68 @@
<?php
namespace Drupal\Tests\webprofiler\Unit\DataCollector;
use Drupal\webprofiler\Asset\CssCollectionRendererWrapper;
use Drupal\webprofiler\DataCollector\AssetsDataCollector;
/**
* @coversDefaultClass \Drupal\webprofiler\DataCollector\AssetsDataCollector
*
* @group webprofiler
*/
class AssetsDataCollectorTest extends DataCollectorBaseTest {
/**
* @var \Drupal\webprofiler\DataCollector\AssetsDataCollector
*/
private $assetDataCollector;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $assetCollectionRendererInterface;
const ROOT = 'test_root';
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->assetDataCollector = new AssetsDataCollector(AssetsDataCollectorTest::ROOT);
$this->assetCollectionRendererInterface = $this->getMock('Drupal\Core\Asset\AssetCollectionRendererInterface');
}
/**
* Tests the Assets data collector.
*/
public function testCSS() {
$css = [
'core/assets/vendor/normalize-css/normalize.css' => [
'weight' => -219.944,
'group' => 0,
'type' => 'file',
'data' => 'core\/assets\/vendor\/normalize-css\/normalize.css',
'version' => '3.0.3',
'media' => 'all',
'preprocess' => TRUE,
'browsers' => [
'IE' => TRUE,
'!IE' => TRUE,
],
],
];
$cssCollectionRendererWrapper = new CssCollectionRendererWrapper($this->assetCollectionRendererInterface, $this->assetDataCollector);
$cssCollectionRendererWrapper->render($css);
$this->assertEquals(1, $this->assetDataCollector->getCssCount());
$this->assetDataCollector->collect($this->request, $this->response, $this->exception);
$data = $this->assetDataCollector->getData();
$this->assertEquals(AssetsDataCollectorTest::ROOT . '/', $data['assets']['installation_path']);
}
}
@@ -1,16 +1,16 @@
<?php
namespace Drupal\Tests\webprofiler\Unit\Cache;
namespace Drupal\Tests\webprofiler\Unit\DataCollector;
use Drupal\Tests\UnitTestCase;
use Drupal\webprofiler\Cache\CacheBackendWrapper;
use Drupal\webprofiler\DataCollector\CacheDataCollector;
/**
* @coversDefaultClass \Drupal\webprofiler\DataCollector\CacheDataCollector
*
* @group webprofiler
*/
class CacheDataCollectorTest extends UnitTestCase {
class CacheDataCollectorTest extends DataCollectorBaseTest {
/**
* @var \Drupal\webprofiler\DataCollector\CacheDataCollector
@@ -50,7 +50,7 @@ class CacheDataCollectorTest extends UnitTestCase {
* Tests the collection of a cache hit.
*/
public function testCacheCollectorHit() {
$cache = new \StdClass();
$cache = new \stdClass();
$cache->cid = 'cache_id';
$cache->expire = 1;
$cache->tags = ['tag1', 'tag2'];
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\webprofiler\Unit\DataCollector;
use Drupal\Tests\UnitTestCase;
/**
* Class DataCollectorBaseTest.
*
* @group webprofiler
*/
abstract class DataCollectorBaseTest extends UnitTestCase {
/**
* @var
*/
protected $request;
/**
* @var
*/
protected $response;
/**
* @var
*/
protected $exception;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->request = $this->getMock('Symfony\Component\HttpFoundation\Request');
$this->response = $this->getMock('Symfony\Component\HttpFoundation\Response');
$this->exception = $this->getMock('Exception');
}
}
@@ -9,8 +9,8 @@ tags:
dependencies:
- devel
# Information added by Drupal.org packaging script on 2016-09-03
version: '8.x-1.0-alpha1+23-dev'
# Information added by Drupal.org packaging script on 2017-08-14
version: '8.x-1.0'
core: '8.x'
project: 'devel'
datestamp: 1472897643
datestamp: 1502732047
@@ -97,10 +97,9 @@ function webprofiler_requirements($phase) {
if (!$has_d3) {
$requirements['d3js']['severity'] = REQUIREMENT_WARNING;
$d3_url = Url::fromUri('https://github.com/mbostock/d3', ['attributes' => ['target' => '_blank']]);
$requirements['d3js']['description'] = [
'#prefix' => ' ',
'#markup' => t('Webprofiler module requires D3.js library to properly render data. <a href=":d3_link">Download D3.js</a> into /libraries/d3/d3.min.js.', [':d3_link' => $d3_url->toUriString()])
'#markup' => t('Webprofiler module requires D3.js library to properly render data. Composer based install recommended, see README.md file for instructions.'),
];
}
@@ -112,10 +111,9 @@ function webprofiler_requirements($phase) {
if (!$has_highlight) {
$requirements['highlightjs']['severity'] = REQUIREMENT_WARNING;
$highlight_url = Url::fromUri('http://highlightjs.org', ['attributes' => ['target' => '_blank']]);
$requirements['highlightjs']['description'] = [
'#prefix' => ' ',
'#markup' => t('Webprofiler module requires highlight.js library to syntax highlight collected queries. <a href=":highlight_link">Download highlight.js</a> assets into /libraries/highlight.', [':highlight_link' => $highlight_url->toUriString()])
'#markup' => t('Webprofiler module requires highlight.js library to syntax highlight collected queries. Composer based install recommended, see README.md file for instructions.'),
];
}
}
@@ -1,24 +1,24 @@
d3:
remote: https://github.com/mbostock/d3
version: 3.4.4
remote: https://github.com/d3/d3
version: 3.5.17
license:
name: BSD-3
url: https://github.com/mbostock/d3/blob/master/LICENSE
url: https://github.com/d3/d3/blob/master/LICENSE
gpl-compatible: true
js:
libraries/d3/d3.min.js: {}
highlight:
/libraries/d3/d3.min.js: {}
highlightjs:
remote: https://github.com/isagalaev/highlight.js
version: 8.0
version: 9.7.0
license:
name: BSD-3
url: https://github.com/isagalaev/highlight.js/blob/master/LICENSE
gpl-compatible: true
js:
libraries/highlightjs/highlight.pack.js: {}
/libraries/highlightjs/highlight.pack.js: {}
css:
component:
libraries/highlightjs/styles/idea.css: {}
/libraries/highlightjs/styles/idea.css: {}
dashboard:
version: 2.0
js:
@@ -48,7 +48,7 @@ database:
- core/jquery
- core/drupal
- core/drupalSettings
- webprofiler/highlight
- webprofiler/highlightjs
timeline:
version: 2.0
js:
@@ -2,11 +2,11 @@ core: "8.x"
api: 2
libraries:
highlight:
highlightjs:
download:
type: "git"
url: "git@github.com:mbostock/d3.git"
url: "https://github.com/isagalaev/highlight.js.git"
d3:
download:
type: "git"
url: "git@github.com:mbostock/d3.git"
url: "https://github.com/d3/d3.git"
@@ -22,7 +22,7 @@ webprofiler.frontend.save:
type: 'webprofiler:token'
methods: [POST]
requirements:
_permission: 'access webprofiler'
_permission: 'view webprofiler toolbar'
# view profile
webprofiler.dashboard:
@@ -47,7 +47,7 @@ services:
# event subscribers
webprofiler.profiler_listener:
class: Symfony\Component\HttpKernel\EventListener\ProfilerListener
arguments: ['@profiler', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%', '@request_stack']
arguments: ['@profiler', '@request_stack', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%']
tags:
- { name: event_subscriber }
@@ -187,13 +187,9 @@ services:
stopwatch:
class: Drupal\webprofiler\Stopwatch
webprofiler.debug.event_dispatcher.default:
class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
arguments: ['@service_container']
webprofiler.debug.plugin.manager.mail.default:
class: Drupal\Core\Mail\MailManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation']
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation', '@renderer']
webprofiler.debug.controller_resolver:
class: Symfony\Component\HttpKernel\Controller\TraceableControllerResolver
@@ -253,7 +249,6 @@ services:
# parameter converter service for profile token
webprofiler.token_converter:
class: Drupal\webprofiler\Routing\TokenConverter
arguments: ['@profiler']
tags:
- { name: paramconverter }