diff --git a/composer.json b/composer.json index 59a6b18bb0f5243f7f42eda9dcad912b4d7d4534..0f8ba0f8d0ad681134ab6c9efac8d9b1cd30058e 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "drupal/crop": "2.0-rc1", "drupal/ctools": "3.0", "drupal/ctools_views": "3.0", - "drupal/devel": "1.0-rc2", + "drupal/devel": "1.2", "drupal/draggableviews": "1.0", "drupal/dropzonejs": "2.0-alpha3", "drupal/editor_advanced_link": "1.4", diff --git a/composer.lock b/composer.lock index d3672b8c41207cb7eab5538a0637ea4c0030d8a3..fb3a413bab737598c98224684ed0c520baf21fff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "78820edba785cf9a24da44d934c1f8c6", + "content-hash": "d5a4707da1701314441aa4c244af663c", "packages": [ { "name": "alchemy/zippy", @@ -2832,17 +2832,17 @@ }, { "name": "drupal/devel", - "version": "1.0.0-rc2", + "version": "1.2.0", "source": { "type": "git", "url": "https://git.drupal.org/project/devel", - "reference": "8.x-1.0-rc2" + "reference": "8.x-1.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.0-rc2.zip", - "reference": "8.x-1.0-rc2", - "shasum": "011322bf9c263dab29f94640829f4b654e9c5632" + "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.2.zip", + "reference": "8.x-1.2", + "shasum": "01f3349ef75f6e21fceef24be9d3d6506ca29647" }, "require": { "drupal/core": "~8.0" @@ -2856,11 +2856,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc2", - "datestamp": "1502732044", + "version": "8.x-1.2", + "datestamp": "1507197844", "security-coverage": { - "status": "not-covered", - "message": "RC releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index c1baf9703b2bb77a10edb935ce9245a58cd759c6..84383c0426aef3ed1f3cf6ec0ca6f4b44d8ee0c3 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2918,18 +2918,18 @@ }, { "name": "drupal/devel", - "version": "1.0.0-rc2", - "version_normalized": "1.0.0.0-RC2", + "version": "1.2.0", + "version_normalized": "1.2.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/devel", - "reference": "8.x-1.0-rc2" + "reference": "8.x-1.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.0-rc2.zip", - "reference": "8.x-1.0-rc2", - "shasum": "011322bf9c263dab29f94640829f4b654e9c5632" + "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.2.zip", + "reference": "8.x-1.2", + "shasum": "01f3349ef75f6e21fceef24be9d3d6506ca29647" }, "require": { "drupal/core": "~8.0" @@ -2943,11 +2943,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc2", - "datestamp": "1502732044", + "version": "8.x-1.2", + "datestamp": "1507197844", "security-coverage": { - "status": "not-covered", - "message": "RC releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, diff --git a/web/modules/devel/css/devel.toolbar.css b/web/modules/devel/css/devel.toolbar.css index 4f4ac976a2b76586fead5c3be4f7a33181d3bea4..c9871bb7bd3de694c7c9b7643ae97b9d4e94d353 100644 --- a/web/modules/devel/css/devel.toolbar.css +++ b/web/modules/devel/css/devel.toolbar.css @@ -18,10 +18,10 @@ float: left; } -.toolbar .toolbar-tray-horizontal .menu { +.toolbar .toolbar-tray-horizontal .toolbar-menu { float: left; /* LTR */ } -[dir="rtl"] .toolbar .toolbar-tray-horizontal .menu { +[dir="rtl"] .toolbar .toolbar-tray-horizontal .toolbar-menu { float: right; } diff --git a/web/modules/devel/devel.info.yml b/web/modules/devel/devel.info.yml index f0fae7c4e4f150894a5735d90d6c419835e932a5..fa7fbf8946a71f71ee9aa5bd38b0b3bf5f6e4601 100644 --- a/web/modules/devel/devel.info.yml +++ b/web/modules/devel/devel.info.yml @@ -7,8 +7,8 @@ configure: devel.admin_settings tags: - developer -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/devel.install b/web/modules/devel/devel.install index a65d61b4bc387cec2562630a8d1d7aafd4a926af..b27e9b97b75def17fcdbec1c9fd4f8edbedbe9fe 100644 --- a/web/modules/devel/devel.install +++ b/web/modules/devel/devel.install @@ -16,7 +16,7 @@ function devel_requirements($phase) { // https://www.drupal.org/node/2834400. $requirements['devel'] = [ 'title' => t('Devel module enabled'), - 'description' => t('The module provide the access to debug informations, therefore is recommended to disable the Devel module on production sites.'), + 'description' => t('The Devel module provides access to internal debugging information; therefore it\'s recommended to disable this module on sites in production.'), 'severity' => REQUIREMENT_INFO, ]; } diff --git a/web/modules/devel/devel.module b/web/modules/devel/devel.module index 1b37d13b568d3c16004a5ca83154f8d9e76e9df3..08b93e22f36587f5e53004f50e37745f8ca11981 100644 --- a/web/modules/devel/devel.module +++ b/web/modules/devel/devel.module @@ -26,6 +26,7 @@ use Drupal\Core\Utility\Error; use Drupal\devel\EntityTypeInfo; use Drupal\devel\ToolbarHandler; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Implements hook_help(). @@ -74,6 +75,11 @@ function devel_help($route_name, RouteMatchInterface $route_match) { 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>'; + case 'devel.layout_info': + $output = ''; + $output .= '<p>' . t('Displays layouts available to the site. For a complete overview of the layout system, see the <a href=":url">Layout API documentation</a>.', [':url' => 'https://www.drupal.org/docs/8/api/layout-api']) . '</p>'; + return $output; + } } @@ -104,6 +110,21 @@ function devel_toolbar() { ->toolbar(); } +/** + * Implements hook_menu_links_discovered_alter(). + */ +function devel_menu_links_discovered_alter(&$links) { + // Conditionally add the Layouts info menu link. + if (\Drupal::moduleHandler()->moduleExists('layout_discovery')) { + $links['devel.layout_info'] = [ + 'title' => new TranslatableMarkup('Layouts Info'), + 'route_name' => 'devel.layout_info', + 'description' => new TranslatableMarkup('Overview of layouts available to the site.'), + 'menu_name' => 'devel', + ]; + } +} + /** * Implements hook_local_tasks_alter(). */ @@ -531,46 +552,6 @@ function ddebug_backtrace($return = FALSE, $pop = 0, $options = DEBUG_BACKTRACE_ } } -/* - * 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(). * diff --git a/web/modules/devel/devel.routing.yml b/web/modules/devel/devel.routing.yml index 6b91709c4fbcbdd3e91bf93b6480a1663efcc5f6..8d20e90d1bb963eb2d169d4b2b5a24b8befe5259 100644 --- a/web/modules/devel/devel.routing.yml +++ b/web/modules/devel/devel.routing.yml @@ -7,7 +7,7 @@ devel.admin_settings: _permission: 'administer site configuration' devel.toolbar.settings_form: - path: 'admin/config/development/devel/toolbar' + path: '/admin/config/development/devel/toolbar' defaults: _form: '\Drupal\devel\Form\ToolbarSettingsForm' _title: 'Devel Toolbar Settings' @@ -50,7 +50,7 @@ devel.config_edit: path: '/devel/config/edit/{config_name}' defaults: _form: '\Drupal\devel\Form\ConfigEditor' - _title: 'Edit configuration object: !config_name' + _title: 'Edit configuration object: @config_name' options: _admin_route: TRUE requirements: @@ -86,16 +86,6 @@ devel.theme_registry: 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: @@ -138,16 +128,6 @@ devel.switch: _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: @@ -242,3 +222,57 @@ devel.event_info: _admin_route: TRUE requirements: _permission: 'access devel information' + +# Layouts info +devel.layout_info: + path: '/devel/layouts' + defaults: + _controller: '\Drupal\devel\Controller\LayoutInfoController::layoutInfoPage' + _title: 'Layouts' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + _module_dependencies: 'layout_discovery' + +# Element info +devel.elements_page: + path: '/devel/elements' + defaults: + _controller: '\Drupal\devel\Controller\ElementInfoController::elementList' + _title: 'Element Info' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.elements_page.detail: + path: '/devel/elements/{element_name}' + defaults: + _controller: '\Drupal\devel\Controller\ElementInfoController::elementDetail' + _title: 'Element @element_name' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +# Entity type info +devel.entity_info_page: + path: '/devel/entity/info' + defaults: + _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeList' + _title: 'Entity info' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.entity_info_page.detail: + path: '/devel/entity/info/{entity_type_id}' + defaults: + _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeDetail' + _title: 'Entity type @entity_type_id' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' diff --git a/web/modules/devel/devel_generate/devel_generate.info.yml b/web/modules/devel/devel_generate/devel_generate.info.yml index 76f572b7758917cdd8ed127f5bb8ad1b29654182..890261d985b50740aed20635c9572e3b0c314986 100644 --- a/web/modules/devel/devel_generate/devel_generate.info.yml +++ b/web/modules/devel/devel_generate/devel_generate.info.yml @@ -6,8 +6,8 @@ package: Development tags: - developer -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/devel_generate/drush.services.yml b/web/modules/devel/devel_generate/drush.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..e008b6c172b02e45d64005f8edf2cb34f0eb4b9c --- /dev/null +++ b/web/modules/devel/devel_generate/drush.services.yml @@ -0,0 +1,7 @@ +services: + develgenerate.command: + class: Drupal\devel_generate\Commands\DevelGenerateCommands + arguments: ['@plugin.manager.develgenerate'] + tags: + - { name: drush.command } + diff --git a/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php b/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php deleted file mode 100644 index 128c2773c19503a9cfdac218b5e72ef525bf11fc..0000000000000000000000000000000000000000 --- a/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php +++ /dev/null @@ -1,151 +0,0 @@ -<?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; - } - - } - -} diff --git a/web/modules/devel/devel_generate/drush/devel_generate.drush.inc b/web/modules/devel/devel_generate/drush/devel_generate.drush8.inc similarity index 98% rename from web/modules/devel/devel_generate/drush/devel_generate.drush.inc rename to web/modules/devel/devel_generate/drush/devel_generate.drush8.inc index 33ae8a267482bc560411d6e445bb16d285f95f4e..e28226d83a484d10e8905325a3e504a0471ce9b1 100644 --- a/web/modules/devel/devel_generate/drush/devel_generate.drush.inc +++ b/web/modules/devel/devel_generate/drush/devel_generate.drush8.inc @@ -2,7 +2,7 @@ /** * @file - * Generate content, taxonomy, menu, and users via drush framework. + * Integration with Drush8. Drush9 commands are in src/Commands. */ use Drupal\devel_generate\DevelGenerateBaseInterface; use Drupal\devel_generate\DevelGeneratePluginManager; diff --git a/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php b/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php new file mode 100644 index 0000000000000000000000000000000000000000..493023f64a09d756270718072eacf5c043f3096f --- /dev/null +++ b/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php @@ -0,0 +1,191 @@ +<?php +namespace Drupal\devel_generate\Commands; + +use Consolidation\AnnotatedCommand\CommandData; +use Drupal\devel_generate\DevelGenerateBaseInterface; +use Drush\Commands\DrushCommands; + +/** + * For commands that are parts of modules, Drush expects to find commandfiles in + * __MODULE__/src/Commands, and the namespace is Drupal/__MODULE__/Commands. + * + * In addition to a commandfile like this one, you need to add a drush.services.yml + * in root of your module like this module does. + */ +class DevelGenerateCommands extends DrushCommands { + + /** + * @var DevelGenerateBaseInterface $manager + */ + protected $manager; + + /** + * The plugin instance. + * + * @var DevelGenerateBaseInterface $instance + */ + protected $pluginInstance; + + /** + * The Generate plugin parameters. + * + * @var array $parameters + */ + protected $parameters; + + /** + * DevelGenerateCommands constructor. + * @param $manager + */ + public function __construct($manager) { + parent::__construct(); + $this->setManager($manager); + } + + /** + * @return \Drupal\devel_generate\DevelGenerateBaseInterface + */ + public function getManager() { + return $this->manager; + } + + /** + * @param \Drupal\devel_generate\DevelGenerateBaseInterface $manager + */ + public function setManager($manager) { + $this->manager = $manager; + } + + /** + * @return mixed + */ + public function getPluginInstance() { + return $this->pluginInstance; + } + + /** + * @param mixed $pluginInstance + */ + public function setPluginInstance($pluginInstance) { + $this->pluginInstance = $pluginInstance; + } + + /** + * @return array + */ + public function getParameters() { + return $this->parameters; + } + + /** + * @param array $parameters + */ + public function setParameters($parameters) { + $this->parameters = $parameters; + } + + /** + * Create users. + * + * @command devel-generate-users + * @pluginId user + * @param $num Number of users to generate. + * @option kill Delete all users before generating new ones. + * @option roles A comma delimited list of role IDs for new users. Don't specify 'authenticated'. + * @option pass Specify a password to be set for all generated users. + * @aliases genu + */ + public function users($num = 50, $options = ['kill' => FALSE, 'roles' => '']) { + // @todo pass $options to the plugins. + $this->generate(); + } + + /** + * Create terms in specified vocabulary. + * + * @command devel-generate-terms + * @pluginId term + * @param $machine_name Vocabulary machine name into which new terms will be inserted. + * @param $num Number of terms to generate. + * @option kill Delete all terms before generating new ones. + * @option feedback An integer representing interval for insertion rate logging. + * @validate-entity-load taxonomy_vocabulary machine_name + * @aliases gent + */ + public function terms($machine_name, $num = 50, $options = ['feedback' => 1000]) { + $this->generate(); + } + + /** + * Create vocabularies. + * + * @command devel-generate-vocabs + * @pluginId vocabulary + * @param $num Number of vocabularies to generate. + * @option kill Delete all vocabs before generating new ones. + * @aliases genv + * @validate-module-enabled taxonomy + */ + public function vocabs($num = 1, $options = ['kill' => FALSE]) { + $this->generate(); + } + + /** + * Create menus. + * + * @command devel-generate-menus + * @pluginId menu + * @param $number_menus Number of menus to generate. + * @param $number_links Number of links to generate. + * @param $max_depth Max link depth. + * @param $max_width Max width of first level of links. + * @option kill Delete all content before generating new content. + * @aliases genm + * @validate-module-enabled menu_link_content + */ + public function menus($number_menus = 2, $number_links = 50, $max_depth = 3, $max_width = 8, $options = ['kill' => FALSE]) { + $this->generate(); + } + + /** + * Create content. + * + * @command devel-generate-content + * @pluginId content + * @param $num Number of nodes to generate. + * @param $max_comments Maximum number of comments to generate. + * @option kill Delete all content before generating new content. + * @option types A comma delimited list of content types to create. Defaults to page,article. + * @option feedback An integer representing interval for insertion rate logging. + * @option skip-fields A comma delimited list of fields to omit when generating random values + * @option languages A comma-separated list of language codes + * @aliases genc + * @validate-module-enabled node + */ + public function content($num = 50, $max_comments = 0, $options = ['kill' => FALSE, 'types' => 'page,article', 'feedback' => 1000]) { + $this->generate(); + drush_backend_batch_process(); + } + + + /** + * @hook validate + * @param \Consolidation\AnnotatedCommand\CommandData $commandData + * @return \Consolidation\AnnotatedCommand\CommandError|null + */ + public function validate(CommandData $commandData) { + $manager = $this->getManager(); + $args = $commandData->input()->getArguments(); + $commandName = array_shift($args); + /** @var DevelGenerateBaseInterface $instance */ + $instance = $manager->createInstance($commandData->annotationData()->get('pluginId'), array()); + $this->setPluginInstance($instance); + $parameters = $instance->validateDrushParams($args, $commandData->input()->getOptions()); + $this->setParameters($parameters); + } + + public function generate() { + $instance = $this->getPluginInstance(); + $instance->generate($this->getParameters()); + } +} diff --git a/web/modules/devel/devel_generate/src/DevelGenerateBase.php b/web/modules/devel/devel_generate/src/DevelGenerateBase.php index c2df234de32261c0e03f569001563da9bf8c6690..1d38a352089398d21468b8814e5d6f8ea8d942ea 100644 --- a/web/modules/devel/devel_generate/src/DevelGenerateBase.php +++ b/web/modules/devel/devel_generate/src/DevelGenerateBase.php @@ -165,4 +165,8 @@ protected function getRandom() { } return $this->random; } + + protected function isDrush8() { + return function_exists('drush_drupal_load_autoloader'); + } } diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php index 11d55bff349b6dccb4217259c995c9c6cfad5025..34e3e71a6e0a7e05e87df577c792913b4417d6c8 100644 --- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php +++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php @@ -14,6 +14,7 @@ use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\devel_generate\DevelGenerateBase; use Drupal\field\Entity\FieldConfig; +use Drush\Utils\StringUtils; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -315,7 +316,7 @@ private function generateContent($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) { + if ($this->isDrush8() && 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; @@ -343,7 +344,7 @@ private function generateBatchContent($values) { $operations[] = array('devel_generate_operation', array($this, 'batchContentAddNode', $values)); } - // Start the batch. + // Set the batch. $batch = array( 'title' => $this->t('Generating Content'), 'operations' => $operations, @@ -371,8 +372,8 @@ public function batchContentKill($vars, &$context) { /** * {@inheritdoc} */ - public function validateDrushParams($args) { - $add_language = drush_get_option('languages'); + public function validateDrushParams($args, $options = []) { + $add_language = $this->isDrush8() ? drush_get_option('languages') : $options['languages']; if (!empty($add_language)) { $add_language = explode(',', str_replace(' ', '', $add_language)); // Intersect with the enabled languages to make sure the language args @@ -380,33 +381,33 @@ public function validateDrushParams($args) { $values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL))); } - $values['kill'] = drush_get_option('kill'); + $values['kill'] = $this->isDrush8() ? drush_get_option('kill') : $options['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 ($this->isDrush8()) { + $selected_types = _convert_csv_to_array(drush_get_option('types', $default_types)); + } + else { + $selected_types = StringUtils::csvToArray($options['types'] ?: $default_types); } if (empty($selected_types)) { - return drush_set_error('DEVEL_GENERATE_NO_CONTENT_TYPES', dt('No content types available')); + throw new \Exception(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.')); + throw new \Exception(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')); + throw new \Exception(dt('One or more content types have been entered that don\'t exist on this site')); } return $values; @@ -437,7 +438,6 @@ protected function contentKill($values) { protected function develGenerateContentPreNode(&$results) { // Get user id. $users = $this->getUsers(); - $users = array_merge($users, array('0')); $results['users'] = $users; } @@ -492,7 +492,7 @@ protected function getLangcode($results) { } /** - * Retrive 50 uids from the database. + * Retrieve 50 uids from the database. */ protected function getUsers() { $users = array(); diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php index 7c961a6c397c864d1c17e7c713f9fd44df9c23ae..6ccacc295423aaf44543bd4441952f52adafcf11 100644 --- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php +++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php @@ -221,14 +221,14 @@ public function generateElements(array $values) { /** * {@inheritdoc} */ - public function validateDrushParams($args) { + public function validateDrushParams($args, $options = []) { $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'), + 'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['kill'], + 'pipe' => $this->isDrush8() ? drush_get_option('pipe') : $options['pipe'], 'link_types' => array_combine($link_types, $link_types), ); @@ -240,16 +240,16 @@ public function validateDrushParams($args) { $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')); + throw new \Exception(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')); + throw new \Exception(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')); + throw new \Exception(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.')); + throw new \Exception(dt('Invalid maximum menu width. Use a positive numeric value.')); } return $values; @@ -300,19 +300,17 @@ protected function deleteMenus() { 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))); + 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 = $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(); - } + $menu->save(); + $menus[$menu->id()] = $menu->label(); } return $menus; diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php index f7aa39b8073dda83f18f0972f68d6c3467124c67..61e6dd4110ba9df18212949b82487debd1fc3fe2 100644 --- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php +++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php @@ -210,15 +210,6 @@ protected function generateTerms($records, $vocabs, $maxlength = 12) { $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(); @@ -233,7 +224,7 @@ protected function generateTerms($records, $vocabs, $maxlength = 12) { /** * {@inheritdoc} */ - public function validateDrushParams($args) { + public function validateDrushParams($args, $options = []) { $vocabulary_name = array_shift($args); $number = array_shift($args); @@ -242,21 +233,21 @@ public function validateDrushParams($args) { } if (!$vocabulary_name) { - return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide a vocabulary machine name.')); + throw new \Exception(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))); + throw new \Exception(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))); + throw new \Exception(dt('Invalid vocabulary name: @name', array('@name' => $vocabulary_name))); } $values = [ 'num' => $number, - 'kill' => drush_get_option('kill'), + 'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['kill'], 'title_length' => 12, 'vids' => [$vocabulary->id()], ]; diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php index 420ef8ff5fff677f2023de7677b43c5382f83bb2..4e7344b89e80eef28f16f6bace387f0f8b15ad5e 100644 --- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php +++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php @@ -7,6 +7,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\devel_generate\DevelGenerateBase; +use Drush\Utils\StringUtils; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -177,14 +178,26 @@ protected function generateElements(array $values) { /** * {@inheritdoc} */ - public function validateDrushParams($args) { + public function validateDrushParams($args, $options = []) { $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, ); + + if ($this->isDrush8()) { + $values += [ + 'roles' => explode(',', drush_get_option('roles', '')), + 'kill' => drush_get_option('kill'), + 'pass' => drush_get_option('pass', NULL), + ]; + } + else { + $values += [ + 'roles' => StringUtils::csvToArray($options['roles']), + 'kill' => $options['kill'], + 'pass' => $options['pass'], + ]; + } return $values; } diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php index a34a2ff4cf84c8ecfa7eb830a97b5316b820e801..1a037465aee877fe7459bbc027ce213e16f10fd1 100644 --- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php +++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php @@ -158,15 +158,15 @@ protected function generateVocabularies($records, $maxlength = 12) { /** * {@inheritdoc} */ - public function validateDrushParams($args) { + public function validateDrushParams($args, $options = []) { $values = array( 'num' => array_shift($args), - 'kill' => drush_get_option('kill'), + 'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['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']))); + throw new \Exception(dt('Invalid number of vocabularies: @num.', array('@num' => $values['num']))); } return $values; diff --git a/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml b/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml index b0f5c0abdf8ad9fbfaa6afe9b423fde0752b3b22..b4acf0a6d1ddd24e1755f1e5fa7fdb1463a931c6 100644 --- a/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml +++ b/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml @@ -7,8 +7,8 @@ configure: admin/config/development/generate tags: - developer -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/drush.services.yml b/web/modules/devel/drush.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..1c8ea0bc52b5fe98b7ea71d746670b11bf18deb8 --- /dev/null +++ b/web/modules/devel/drush.services.yml @@ -0,0 +1,6 @@ +services: + devel.command: + class: Drupal\devel\Commands\DevelCommands + arguments: ['@token', '@service_container', '@event_dispatcher', '@module_handler'] + tags: + - { name: drush.command } diff --git a/web/modules/devel/drush/devel.drush.inc b/web/modules/devel/drush/devel.drush8.inc similarity index 98% rename from web/modules/devel/drush/devel.drush.inc rename to web/modules/devel/drush/devel.drush8.inc index b38cf9aef4468100639d3bb6bf3ad72f04793644..651578d3e5e86d81fdedec13d595c1b550d911ce 100644 --- a/web/modules/devel/drush/devel.drush.inc +++ b/web/modules/devel/drush/devel.drush8.inc @@ -2,7 +2,8 @@ /** * @file - * Drush integration for the devel module. + * This file is only used by Drush8. Drush9 discovers its commands via tagged + * service(s) in devel.services.yml. Also see classes in src/Commands. */ use Drupal\Component\Uuid\Php; @@ -160,6 +161,7 @@ function drush_devel_fn_event($event = NULL) { drush_log(dt('No implementations.'), 'ok'); } } + /** * Command handler. Show source code of specified function or method. */ diff --git a/web/modules/devel/drush/phpstorm.drush.inc b/web/modules/devel/drush/phpstorm.drush.inc deleted file mode 100644 index 416bdd63d25d52391f026abbe004a7190f704cc3..0000000000000000000000000000000000000000 --- a/web/modules/devel/drush/phpstorm.drush.inc +++ /dev/null @@ -1,105 +0,0 @@ -<?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; -} diff --git a/web/modules/devel/kint/kint.info.yml b/web/modules/devel/kint/kint.info.yml index 658553d44e8c1a2f93271b551c2b1938a3a8bd08..0b20df64b7d0dcef45f82f5bd35c1911e8ae029b 100644 --- a/web/modules/devel/kint/kint.info.yml +++ b/web/modules/devel/kint/kint.info.yml @@ -6,8 +6,8 @@ package: Development tags: - developer -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/log.json b/web/modules/devel/log.json deleted file mode 100644 index e9987b4b28adc460c22f938fc700b9be978d0799..0000000000000000000000000000000000000000 --- a/web/modules/devel/log.json +++ /dev/null @@ -1,51 +0,0 @@ -{"level":"debug","msg":"main.go started","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:33:53+01:00"} -{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:33:53+01:00"} -{"level":"debug","msg":"app.Stop(drupal8)","time":"2017-03-24T19:33:53+01:00"} -{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"} -{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"} -{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"} -{"level":"debug","msg":"main.go started","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:33:58+01:00"} -{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:33:58+01:00"} -{"level":"debug","msg":"app.Stop(d8)","time":"2017-03-24T19:33:58+01:00"} -{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:13+01:00"} -{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:13+01:00"} -{"level":"debug","msg":"app.Start(drupal8)","time":"2017-03-24T19:44:13+01:00"} -{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"} -{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"} -{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"} -{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:17+01:00"} -{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:17+01:00"} -{"level":"debug","msg":"app.Start(drupal8builder)","time":"2017-03-24T19:44:17+01:00"} -{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"} -{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"} -{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"} -{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:25+01:00"} -{"level":"debug","msg":"app.Start(d8builder)","time":"2017-03-24T19:44:25+01:00"} -{"level":"info","msg":"- Project:d8builderstatus is: STARTED","time":"2017-03-24T19:44:28+01:00"} -{"level":"debug","msg":"Show(d8builder)","time":"2017-03-24T19:44:28+01:00"} diff --git a/web/modules/devel/src/Commands/DevelCommands.php b/web/modules/devel/src/Commands/DevelCommands.php new file mode 100644 index 0000000000000000000000000000000000000000..cda3e1a01fa02dbc3759d6cd404beb1e30f28bf8 --- /dev/null +++ b/web/modules/devel/src/Commands/DevelCommands.php @@ -0,0 +1,264 @@ +<?php +namespace Drupal\devel\Commands; +use Consolidation\OutputFormatters\StructuredData\RowsOfFields; +use Drupal\Component\Uuid\Php; +use Drupal\Core\Utility\Token; +use Drush\Commands\DrushCommands; +use Drush\Exceptions\UserAbortException; +use Drush\Utils\StringUtils; +use Symfony\Component\Console\Input\Input; +use Symfony\Component\Console\Output\Output; + +/** + * For commands that are parts of modules, Drush expects to find commandfiles in + * __MODULE__/src/Commands, and the namespace is Drupal/__MODULE__/Commands. + * + * In addition to a commandfile like this one, you need to add a drush.services.yml + * in root of your module like this module does. + */ +class DevelCommands extends DrushCommands { + + protected $token; + + protected $container; + + protected $eventDispatcher; + + protected $moduleHandler; + + public function __construct(Token $token, $container, $eventDispatcher, $moduleHandler) { + parent::__construct(); + $this->token = $token; + $this->container = $container; + $this->eventDispatcher = $eventDispatcher; + $this->moduleHandler = $moduleHandler; + } + + /** + * @return \Drupal\Core\Extension\ModuleHandlerInterface + */ + public function getModuleHandler() { + return $this->moduleHandler; + } + + /** + * @return mixed + */ + public function getEventDispatcher() { + return $this->eventDispatcher; + } + + /** + * @return mixed + */ + public function getContainer() { + return $this->container; + } + + /** + * @return Token + */ + public function getToken() { + return $this->token; + } + + /** + * Uninstall, and Install modules. + + * @command devel:reinstall + * @param $modules A comma-separated list of module names. + * @aliases dre,devel-reinstall + * @allow-additional-options pm-uninstall,pm-enable + */ + public function reinstall($modules) { + $modules = StringUtils::csvToArray($modules); + + $modules_str = implode(',', $modules); + drush_invoke_process('@self', 'pm:uninstall', [$modules_str], []); + drush_invoke_process('@self', 'pm:enable', [$modules_str], []); + } + + /** + * List implementations of a given hook and optionally edit one. + * + * @command devel:hook + * @param $hook The name of the hook to explore. + * @param $implementation The name of the implementation to edit. Usually omitted. + * @usage devel-hook cron + * List implementations of hook_cron(). + * @aliases fnh,fn-hook,hook,devel-hook + * @optionset_get_editor + */ + function hook($hook, $implementation) { + // Get implementations in the .install files as well. + include_once './core/includes/install.inc'; + drupal_load_updates(); + $info = $this->codeLocate($implementation . "_$hook"); + $exec = drush_get_editor(); + drush_shell_exec_interactive($exec, $info['file']); + } + + /** + * @hook interact hook + */ + public function hookInteract(Input $input, Output $output) { + if (!$input->getArgument('implementation')) { + if ($hook_implementations = $this->getModuleHandler()->getImplementations($input->getArgument('hook'))) { + if (!$choice = $this->io()->choice('Enter the number of the hook implementation you wish to view.', array_combine($hook_implementations, $hook_implementations))) { + throw new UserAbortException(); + } + $input->setArgument('implementation', $choice); + } + else { + throw new \Exception(dt('No implementations')); + } + } + } + + /** + * List implementations of a given event and optionally edit one. + * + * @command devel:event + * @param $event The name of the event to explore. If omitted, a list of events is shown. + * @param $implementation The name of the implementation to show. Usually omitted. + * @usage devel-event + * Pick a Kernel event, then pick an implementation, and then view its source code. + * @usage devel-event kernel.terminate + * Pick a terminate subscribers implementation and view its source code. + * @aliases fne,fn-event,event + */ + function event($event, $implementation) { + $info= $this->codeLocate($implementation); + $exec = drush_get_editor(); + drush_shell_exec_interactive($exec, $info['file']); + } + + /** + * @hook interact devel:event + */ + public function interactEvent(Input $input, Output $output) { + $dispatcher = $this->getEventDispatcher(); + if (!$input->getArgument('event')) { + // @todo Expand this list. + $events = array('kernel.controller', 'kernel.exception', 'kernel.request', 'kernel.response', 'kernel.terminate', 'kernel.view'); + $events = array_combine($events, $events); + if (!$event = $this->io()->choice('Enter the event you wish to explore.', $events)) { + throw new UserAbortException(); + } + $input->setArgument('event', $event); + } + if ($implementations = $dispatcher->getListeners($event)) { + foreach ($implementations as $implementation) { + $callable = get_class($implementation[0]) . '::' . $implementation[1]; + $choices[$callable] = $callable; + } + if (!$choice = $this->io()->choice('Enter the number of the implementation you wish to view.', $choices)) { + throw new UserAbortException(); + } + $input->setArgument('implementation', $choice); + } + else { + throw new \Exception(dt('No implementations.')); + } + } + + /** + * List available tokens. + * + * @command devel:token + * @aliases token,devel-token + * @field-labels + * group: Group + * token: Token + * name: Name + * @default-fields group,token,name + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function token($options = ['format' => 'table']) { + $all = $this->getToken()->getInfo(); + foreach ($all['tokens'] as $group => $tokens) { + foreach ($tokens as $key => $token) { + $rows[] = [ + 'group' => $group, + 'token' => $key, + 'name' => $token['name'], + ]; + } + } + return new RowsOfFields($rows); + } + + /** + * Generate a UUID. + * + * @command devel:uuid + * @aliases uuid,devel-uuid + * @usage drush devel-uuid + * Outputs a Universally Unique Identifier. + * + * @return string + */ + public function uuid() { + $uuid = new Php(); + return $uuid->generate(); + } + + + /** + * Get source code line for specified function or method. + */ + function codeLocate($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)) { + throw new \Exception(dt('Function not found')); + } + $reflect = new \ReflectionFunction($function_name); + } + else { + list($class, $method) = explode('::', $function_name); + if (!method_exists($class, $method)) { + throw new \Exception(dt('Method not found')); + } + $reflect = new \ReflectionMethod($class, $method); + } + return array('file' => $reflect->getFileName(), 'startline' => $reflect->getStartLine(), 'endline' => $reflect->getEndLine()); + + } + + /** + * Get a list of available container services. + * + * @command devel:services + * @param $prefix A prefix to filter the service list by. + * @aliases devel-container-services,dcs,devel-services + * @usage drush devel-services + * Gets a list of all available container services + * @usage drush dcs plugin.manager + * Get all services containing "plugin.manager" + * + * @return array + */ + public function services($prefix = NULL, $options = ['format' => 'yaml']) { + $container = $this->getContainer(); + + // 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)) { + throw new \Exception(dt('No container services found.')); + } + + sort($services); + return $services; + } +} \ No newline at end of file diff --git a/web/modules/devel/src/Controller/DevelController.php b/web/modules/devel/src/Controller/DevelController.php index 92f59270dc35501d84d973cb5d3c6912aa748dc1..2c3a7b4def1819e2c6747c0d7b39c63b0d91ae66 100644 --- a/web/modules/devel/src/Controller/DevelController.php +++ b/web/modules/devel/src/Controller/DevelController.php @@ -53,25 +53,6 @@ public function themeRegistry() { return $this->dumper->exportAsRenderable($hooks); } - /** - * Builds the elements info overview page. - * - * @return array - * Array of page elements to render. - */ - public function elementsPage() { - $element_info_manager = \Drupal::service('element_info'); - - $elements_info = array(); - foreach ($element_info_manager->getDefinitions() as $element_type => $definition) { - $elements_info[$element_type] = $definition + $element_info_manager->getInfo($element_type); - } - - ksort($elements_info); - - return $this->dumper->exportAsRenderable($elements_info); - } - /** * Builds the fields info overview page. * @@ -106,18 +87,6 @@ public function fieldInfoPage() { return $output; } - /** - * Builds the entity types overview page. - * - * @return array - * Array of page elements to render. - */ - public function entityInfoPage() { - $types = $this->entityTypeManager()->getDefinitions(); - ksort($types); - return $this->dumper->exportAsRenderable($types); - } - /** * Builds the state variable overview page. * diff --git a/web/modules/devel/src/Controller/ElementInfoController.php b/web/modules/devel/src/Controller/ElementInfoController.php new file mode 100644 index 0000000000000000000000000000000000000000..a0440c32769d563740c801697a4ea22d63d17f9d --- /dev/null +++ b/web/modules/devel/src/Controller/ElementInfoController.php @@ -0,0 +1,162 @@ +<?php + +namespace Drupal\devel\Controller; + +use Drupal\Component\Serialization\Json; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\Core\Url; +use Drupal\devel\DevelDumperManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Provides route responses for the element info page. + */ +class ElementInfoController extends ControllerBase { + + /** + * Element info manager service. + * + * @var \Drupal\Core\Render\ElementInfoManagerInterface + */ + protected $elementInfo; + + /** + * The dumper service. + * + * @var \Drupal\devel\DevelDumperManagerInterface + */ + protected $dumper; + + /** + * EventInfoController constructor. + * + * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info + * Element info manager service. + * @param \Drupal\devel\DevelDumperManagerInterface $dumper + * The dumper service. + */ + public function __construct(ElementInfoManagerInterface $element_info, DevelDumperManagerInterface $dumper) { + $this->elementInfo = $element_info; + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('element_info'), + $container->get('devel.dumper') + ); + } + + /** + * Builds the element overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function elementList() { + $headers = [ + $this->t('Name'), + $this->t('Provider'), + $this->t('Class'), + $this->t('Operations'), + ]; + + $rows = []; + + foreach ($this->elementInfo->getDefinitions() as $element_type => $definition) { + $row['name'] = [ + 'data' => $element_type, + 'class' => 'table-filter-text-source', + ]; + $row['provider'] = [ + 'data' => $definition['provider'], + 'class' => 'table-filter-text-source', + ]; + $row['class'] = [ + 'data' => $definition['class'], + 'class' => 'table-filter-text-source', + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_type]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$element_type] = $row; + } + + ksort($rows); + + $output['#attached']['library'][] = 'system/drupal.system.modules'; + + $output['filters'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['table-filter', 'js-show'], + ], + ]; + $output['filters']['text'] = [ + '#type' => 'search', + '#title' => $this->t('Search'), + '#size' => 30, + '#placeholder' => $this->t('Enter element id, provider or class'), + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => '.devel-filter-text', + 'autocomplete' => 'off', + 'title' => $this->t('Enter a part of the element id, provider or class to filter by.'), + ], + ]; + $output['elements'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No elements found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-element-list', 'devel-filter-text'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the element. + * + * @param string $element_name + * The name of the element to retrieve. + * + * @return array + * A render array containing the element. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested element is not defined. + */ + public function elementDetail($element_name) { + if (!$element = $this->elementInfo->getDefinition($element_name, FALSE)) { + throw new NotFoundHttpException(); + } + + $element += $this->elementInfo->getInfo($element_name); + return $this->dumper->exportAsRenderable($element, $element_name); + } + +} diff --git a/web/modules/devel/src/Controller/EntityTypeInfoController.php b/web/modules/devel/src/Controller/EntityTypeInfoController.php new file mode 100644 index 0000000000000000000000000000000000000000..f8bac6dc00fde2c08f03a2a3d80de6d0de23d408 --- /dev/null +++ b/web/modules/devel/src/Controller/EntityTypeInfoController.php @@ -0,0 +1,154 @@ +<?php + +namespace Drupal\devel\Controller; + +use Drupal\Component\Serialization\Json; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Url; +use Drupal\devel\DevelDumperManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Provides route responses for the entity types info page. + */ +class EntityTypeInfoController extends ControllerBase { + + /** + * The dumper service. + * + * @var \Drupal\devel\DevelDumperManagerInterface + */ + protected $dumper; + + /** + * EntityTypeInfoController constructor. + * + * @param \Drupal\devel\DevelDumperManagerInterface $dumper + * The dumper service. + */ + public function __construct(DevelDumperManagerInterface $dumper) { + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('devel.dumper') + ); + } + + /** + * Builds the entity types overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function entityTypeList() { + $headers = [ + $this->t('ID'), + $this->t('Name'), + $this->t('Provider'), + $this->t('Class'), + $this->t('Operations'), + ]; + + $rows = []; + + foreach ($this->entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) { + $row['id'] = [ + 'data' => $entity_type->id(), + 'class' => 'table-filter-text-source', + ]; + $row['name'] = [ + 'data' => $entity_type->getLabel(), + 'class' => 'table-filter-text-source', + ]; + $row['provider'] = [ + 'data' => $entity_type->getProvider(), + 'class' => 'table-filter-text-source', + ]; + $row['class'] = [ + 'data' => $entity_type->getClass(), + 'class' => 'table-filter-text-source', + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$entity_type_id] = $row; + } + + ksort($rows); + + $output['#attached']['library'][] = 'system/drupal.system.modules'; + + $output['filters'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['table-filter', 'js-show'], + ], + ]; + $output['filters']['text'] = [ + '#type' => 'search', + '#title' => $this->t('Search'), + '#size' => 30, + '#placeholder' => $this->t('Enter entity type id, provider or class'), + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => '.devel-filter-text', + 'autocomplete' => 'off', + 'title' => $this->t('Enter a part of the entity type id, provider or class to filter by.'), + ], + ]; + $output['entities'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No entity types found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-entity-type-list', 'devel-filter-text'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the entity type. + * + * @param string $entity_type_id + * The name of the entity type to retrieve. + * + * @return array + * A render array containing the entity type. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested entity type is not defined. + */ + public function entityTypeDetail($entity_type_id) { + if (!$entity_type = $this->entityTypeManager()->getDefinition($entity_type_id, FALSE)) { + throw new NotFoundHttpException(); + } + + return $this->dumper->exportAsRenderable($entity_type, $entity_type_id); + } + +} diff --git a/web/modules/devel/src/Controller/LayoutInfoController.php b/web/modules/devel/src/Controller/LayoutInfoController.php new file mode 100644 index 0000000000000000000000000000000000000000..4a2cff2c1e8fd9d3ef1fc9fe81cb1de8d956884f --- /dev/null +++ b/web/modules/devel/src/Controller/LayoutInfoController.php @@ -0,0 +1,91 @@ +<?php + +namespace Drupal\devel\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Layout\LayoutPluginManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Returns response for Layout Info route. + */ +class LayoutInfoController extends ControllerBase { + + /** + * The Layout Plugin Manager. + * + * @var Drupal\Core\Layout\LayoutPluginManagerInterface + */ + protected $layoutPluginManager; + + /** + * LayoutInfoController constructor. + * + * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $pluginManagerLayout + * The layout manager. + */ + public function __construct(LayoutPluginManagerInterface $pluginManagerLayout) { + $this->layoutPluginManager = $pluginManagerLayout; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.core.layout') + ); + } + + /** + * Builds the Layout Info page. + * + * @return array + * Array of page elements to render. + */ + public function layoutInfoPage() { + $definedLayouts = []; + $layouts = $this->layoutPluginManager->getDefinitions(); + foreach ($layouts as $layout) { + // @todo Revisit once https://www.drupal.org/node/2660124 gets in, getting + // the image should be as simple as $layout->getIcon(). + $image = NULL; + if ($layout->getIconPath() != NULL) { + $image = [ + 'data' => [ + '#theme' => 'image', + '#uri' => $layout->getIconPath(), + '#alt' => $layout->getLabel(), + '#height' => '65', + ] + ]; + } + $definedLayouts[] = [ + $image, + $layout->getLabel(), + $layout->getDescription(), + $layout->getCategory(), + implode(', ', $layout->getRegionLabels()), + $layout->getProvider(), + ]; + } + + return [ + '#theme' => 'table', + '#header' => [ + $this->t('Icon'), + $this->t('Label'), + $this->t('Description'), + $this->t('Category'), + $this->t('Regions'), + $this->t('Provider'), + ], + '#rows' => $definedLayouts, + '#empty' => $this->t('No layouts available.'), + '#attributes' => [ + 'class' => ['devel-layout-list'], + ], + ]; + } + +} diff --git a/web/modules/devel/src/DevelDumperBase.php b/web/modules/devel/src/DevelDumperBase.php index 41713e9b13321dda0b89deef209751d5816ef7f4..5cfe70dd4ef153f12f02d32df59149bac957f080 100644 --- a/web/modules/devel/src/DevelDumperBase.php +++ b/web/modules/devel/src/DevelDumperBase.php @@ -2,8 +2,8 @@ namespace Drupal\devel; -use Drupal\Core\Render\Markup; use Drupal\Core\Plugin\PluginBase; +use Drupal\devel\Render\FilteredMarkup; /** * Defines a base devel dumper implementation. @@ -39,7 +39,7 @@ public function exportAsRenderable($input, $name = NULL) { * The unaltered input value. */ protected function setSafeMarkup($input) { - return Markup::create($input); + return FilteredMarkup::create($input); } } diff --git a/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php b/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php index a0be6f17154b8f8adb72eb27f12a95b6b7082007..596e709b4a14e8b245b46e4e4495a7695f9aee51 100644 --- a/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php +++ b/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php @@ -75,6 +75,10 @@ public function rebuildThemeInfo(GetResponseEvent $event) { drupal_theme_rebuild(); // Refresh theme data. $this->themeHandler->refreshInfo(); + // Resets the internal state of the theme handler and clear the 'system + // list' cache; this allow to properly register, if needed, PSR-4 + // namespaces for theme extensions after refreshing the info data. + $this->themeHandler->reset(); // Notify the user that the theme info are rebuilt on every request. $this->triggerWarningIfNeeded($event->getRequest()); } diff --git a/web/modules/devel/src/Form/ConfigEditor.php b/web/modules/devel/src/Form/ConfigEditor.php index f8c9858672b4fae95b224485792a6854e1e2a91b..e4fe88cc2f5085510ec41c2504cbf017d93a3236 100644 --- a/web/modules/devel/src/Form/ConfigEditor.php +++ b/web/modules/devel/src/Form/ConfigEditor.php @@ -32,7 +32,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_n return; } - $data = $config->get(); + $data = $config->getOriginal(); if (empty($data)) { drupal_set_message(t('Config @name exists but has no data.', array('@name' => $config_name)), 'warning'); diff --git a/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php b/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php index 54e8aaea433c711a486cc7986251f2e31f25571e..9df1aec5cf24623daa7d5b2fbd308aeb001a971d 100644 --- a/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php +++ b/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php @@ -3,6 +3,7 @@ namespace Drupal\devel\Plugin\Devel\Dumper; use Doctrine\Common\Util\Debug; +use Drupal\Component\Utility\Xss; use Drupal\devel\DevelDumperBase; /** @@ -28,6 +29,10 @@ public function export($input, $name = NULL) { $dump = ob_get_contents(); ob_end_clean(); + // Run Xss::filterAdmin on the resulting string to prevent + // cross-site-scripting (XSS) vulnerabilities. + $dump = Xss::filterAdmin($dump); + $dump = '<pre>' . $name . $dump . '</pre>'; return $this->setSafeMarkup($dump); diff --git a/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php b/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php index 3f38d15f25a7b44f918bc75cf293d582051f7583..a1561e3e96241a40c3557f083ca6a57b99e065bb 100644 --- a/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php +++ b/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php @@ -3,6 +3,7 @@ namespace Drupal\devel\Plugin\Devel\Dumper; use Drupal\Component\Utility\Variable; +use Drupal\Component\Utility\Xss; use Drupal\devel\DevelDumperBase; /** @@ -21,7 +22,11 @@ class DrupalVariable extends DevelDumperBase { */ public function export($input, $name = NULL) { $name = $name ? $name . ' => ' : ''; - $dump = '<pre>' . $name . Variable::export($input) . '</pre>'; + $dump = Variable::export($input); + // Run Xss::filterAdmin on the resulting string to prevent + // cross-site-scripting (XSS) vulnerabilities. + $dump = Xss::filterAdmin($dump); + $dump = '<pre>' . $name . $dump . '</pre>'; return $this->setSafeMarkup($dump); } diff --git a/web/modules/devel/src/Render/FilteredMarkup.php b/web/modules/devel/src/Render/FilteredMarkup.php new file mode 100644 index 0000000000000000000000000000000000000000..d43c0d5d8ae2461008454cff729b174df1696097 --- /dev/null +++ b/web/modules/devel/src/Render/FilteredMarkup.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\devel\Render; + +use Drupal\Component\Render\MarkupInterface; +use Drupal\Component\Render\MarkupTrait; + +/** + * Defines an object that passes safe strings through the Devel system. + * + * This object should only be constructed with a known safe string. If there is + * any risk that the string contains user-entered data that has not been + * filtered first, it must not be used. + * + * @internal + * This object is marked as internal because it should only be used in the + * Devel module. + * @see \Drupal\Core\Render\Markup + */ +final class FilteredMarkup implements MarkupInterface, \Countable { + use MarkupTrait; + +} \ No newline at end of file diff --git a/web/modules/devel/src/ToolbarHandler.php b/web/modules/devel/src/ToolbarHandler.php index 0c890de38f9211f817b32b5a5bf6d6088454cdc9..0d7cbcdf0a3cd869ba4f8a23b3405d760a1ea568 100644 --- a/web/modules/devel/src/ToolbarHandler.php +++ b/web/modules/devel/src/ToolbarHandler.php @@ -61,7 +61,7 @@ public function __construct(MenuLinkTreeInterface $menu_link_tree, ConfigFactory */ public static function create(ContainerInterface $container) { return new static( - $container->get('menu.link_tree'), + $container->get('toolbar.menu_tree'), $container->get('config.factory'), $container->get('current_user') ); diff --git a/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml b/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml index b7a5c43e961d4a56787e9fc6d8aa110ba217e8a5..265aeb347f0c29f407dafa8bed8252dd3e9882fb 100644 --- a/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml +++ b/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml @@ -5,8 +5,8 @@ package: Testing # version: VERSION # core: 8.x -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml b/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml index 4f5e5ce2ca86f79837ec390361a6a0301f04c36c..4b01554cfb28c6a116fc20efb11ce429ab831453 100644 --- a/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml +++ b/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml @@ -9,8 +9,8 @@ dependencies: - text - entity_test -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/tests/modules/devel_test/devel_test.info.yml b/web/modules/devel/tests/modules/devel_test/devel_test.info.yml index 78000b513bde6d725d546e41f61133379c576d46..191c8255571a48d22c67f5a0dcbddc28a05ef66b 100644 --- a/web/modules/devel/tests/modules/devel_test/devel_test.info.yml +++ b/web/modules/devel/tests/modules/devel_test/devel_test.info.yml @@ -5,8 +5,8 @@ package: Testing # version: VERSION # core: 8.x -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/src/Tests/DevelControllerTest.php b/web/modules/devel/tests/src/Functional/DevelControllerTest.php similarity index 89% rename from web/modules/devel/src/Tests/DevelControllerTest.php rename to web/modules/devel/tests/src/Functional/DevelControllerTest.php index 770c2b98d0170c9c221165039a8e093f864b969a..0fc375afbda65391fa1bc8ebb6fe45d181e6e9c0 100644 --- a/web/modules/devel/src/Tests/DevelControllerTest.php +++ b/web/modules/devel/tests/src/Functional/DevelControllerTest.php @@ -1,22 +1,22 @@ <?php -namespace Drupal\devel\Tests; +namespace Drupal\Tests\devel\Functional; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests Devel controller. * * @group devel */ -class DevelControllerTest extends WebTestBase { +class DevelControllerTest extends BrowserTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('devel', 'node', 'entity_test', 'devel_entity_test', 'block'); + public static $modules = ['devel', 'node', 'entity_test', 'devel_entity_test', 'block']; /** * {@inheritdoc} @@ -26,35 +26,35 @@ protected function setUp() { // Create a test entity. $random_label = $this->randomMachineName(); - $data = array('type' => 'entity_test', 'name' => $random_label); + $data = ['type' => 'entity_test', 'name' => $random_label]; $this->entity = entity_create('entity_test', $data); $this->entity->save(); // Create a test entity with only canonical route. $random_label = $this->randomMachineName(); - $data = array('type' => 'devel_entity_test_canonical', 'name' => $random_label); + $data = ['type' => 'devel_entity_test_canonical', 'name' => $random_label]; $this->entity_canonical = entity_create('devel_entity_test_canonical', $data); $this->entity_canonical->save(); // Create a test entity with only edit route. $random_label = $this->randomMachineName(); - $data = array('type' => 'devel_entity_test_edit', 'name' => $random_label); + $data = ['type' => 'devel_entity_test_edit', 'name' => $random_label]; $this->entity_edit = entity_create('devel_entity_test_edit', $data); $this->entity_edit->save(); // Create a test entity with no routes. $random_label = $this->randomMachineName(); - $data = array('type' => 'devel_entity_test_no_links', 'name' => $random_label); + $data = ['type' => 'devel_entity_test_no_links', 'name' => $random_label]; $this->entity_no_links = entity_create('devel_entity_test_no_links', $data); $this->entity_no_links->save(); $this->drupalPlaceBlock('local_tasks_block'); - $web_user = $this->drupalCreateUser(array( + $web_user = $this->drupalCreateUser([ 'view test entity', 'administer entity_test content', 'access devel information', - )); + ]); $this->drupalLogin($web_user); } diff --git a/web/modules/devel/src/Tests/DevelDumperTest.php b/web/modules/devel/tests/src/Functional/DevelDumperTest.php similarity index 69% rename from web/modules/devel/src/Tests/DevelDumperTest.php rename to web/modules/devel/tests/src/Functional/DevelDumperTest.php index bf214a0ca9d22933f34def39ba14a04e138426f2..a4ef9b48fc773ece2c348ae4d2a2139cd9cbd583 100644 --- a/web/modules/devel/src/Tests/DevelDumperTest.php +++ b/web/modules/devel/tests/src/Functional/DevelDumperTest.php @@ -1,16 +1,16 @@ <?php -namespace Drupal\devel\Tests; +namespace Drupal\Tests\devel\Functional; use Drupal\Component\Render\FormattableMarkup; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests pluggable dumper feature. * * @group devel */ -class DevelDumperTest extends WebTestBase { +class DevelDumperTest extends BrowserTestBase { /** * Modules to enable. @@ -36,36 +36,42 @@ public function testDumpersConfiguration() { $this->drupalGet('admin/config/development/devel'); // Ensures that the dumper input is present on the config page. - $this->assertFieldByName('dumper'); + $this->assertSession()->fieldExists('dumper'); // Ensures that the 'default' dumper is enabled by default. - $this->assertFieldChecked('edit-dumper-default'); + $this->assertSession()->checkboxChecked('edit-dumper-default'); // Ensures that all dumpers declared by devel are present on the config page // and that only the available dumpers are selectable. - $dumpers = ['default', 'drupal_variable', 'firephp', 'chromephp', 'var_dumper']; + $dumpers = [ + 'default', + 'drupal_variable', + 'firephp', + 'chromephp', + 'var_dumper', + ]; $available_dumpers = ['default', 'drupal_variable']; foreach ($dumpers as $dumper) { - $this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper, new FormattableMarkup('Radio button for @dumper found.', ['@dumper' => $dumper])); + $this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper); if (in_array($dumper, $available_dumpers)) { - $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper, new FormattableMarkup('Dumper @dumper is available.', ['@dumper' => $dumper])); + $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper); } else { - $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper, new FormattableMarkup('Dumper @dumper is disabled.', ['@dumper' => $dumper])); + $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper); } } // Ensures that dumper plugins declared by other modules are present on the // config page and that only the available dumpers are selectable. - $this->assertFieldByName('dumper', 'available_test_dumper'); - $this->assertText('Available test dumper.', 'Available dumper label is present'); - $this->assertText('Drupal dumper for testing purposes (available).', 'Available dumper description is present'); + $this->assertFieldByXPath('//input[@name="dumper"]', 'available_test_dumper'); + $this->assertSession()->pageTextContains('Available test dumper.'); + $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (available).'); $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', 'available_test_dumper', 'Available dumper input not is disabled.'); - $this->assertFieldByName('dumper', 'not_available_test_dumper'); - $this->assertText('Not available test dumper.', 'Non available dumper label is present'); - $this->assertText('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.', 'Non available dumper description is present'); + $this->assertFieldByXPath('//input[@name="dumper"]', 'not_available_test_dumper'); + $this->assertSession()->pageTextContains('Not available test dumper.'); + $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.'); $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', 'not_available_test_dumper', 'Non available dumper input is disabled.'); // Ensures that saving of the dumpers configuration works as expected. @@ -73,10 +79,10 @@ public function testDumpersConfiguration() { 'dumper' => 'drupal_variable', ]; $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration')); - $this->assertText(t('The configuration options have been saved.')); + $this->assertSession()->pageTextContains(t('The configuration options have been saved.')); $config = \Drupal::config('devel.settings')->get('devel_dumper'); - $this->assertEqual('drupal_variable', $config, 'The configuration options have been properly saved'); + $this->assertEquals('drupal_variable', $config, 'The configuration options have been properly saved'); // Ensure that if the chosen dumper is not available (e.g. the module that // provide it is uninstalled) the 'default' dumper appears selected in the @@ -84,22 +90,22 @@ public function testDumpersConfiguration() { \Drupal::service('module_installer')->install(['kint']); $this->drupalGet('admin/config/development/devel'); - $this->assertFieldByName('dumper', 'kint'); + $this->assertFieldByXPath('//input[@name="dumper"]', 'kint'); $edit = [ 'dumper' => 'kint', ]; $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration')); - $this->assertText(t('The configuration options have been saved.')); + $this->assertSession()->pageTextContains(t('The configuration options have been saved.')); $config = \Drupal::config('devel.settings')->get('devel_dumper'); - $this->assertEqual('kint', $config, 'The configuration options have been properly saved'); + $this->assertEquals('kint', $config, 'The configuration options have been properly saved'); \Drupal::service('module_installer')->uninstall(['kint']); $this->drupalGet('admin/config/development/devel'); - $this->assertNoFieldByName('dumper', 'kint'); - $this->assertFieldChecked('edit-dumper-default'); + $this->assertNoFieldByXPath('//input[@name="dumper"]', 'kint'); + $this->assertSession()->checkboxChecked('edit-dumper-default'); } /** @@ -110,7 +116,7 @@ function testDumpersOutput() { 'dumper' => 'available_test_dumper', ]; $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration')); - $this->assertText(t('The configuration options have been saved.')); + $this->assertSession()->pageTextContains(t('The configuration options have been saved.')); $this->drupalGet('devel_dumper_test/dump'); $elements = $this->xpath('//body/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::dump() Test output']); @@ -129,8 +135,8 @@ function testDumpersOutput() { $this->assertTrue(!empty($elements), 'Dumped message is present.'); // Ensures that plugins can add libraries to the page when the // ::exportAsRenderable() method is used. - $this->assertRaw('devel_dumper_test/css/devel_dumper_test.css'); - $this->assertRaw('devel_dumper_test/js/devel_dumper_test.js'); + $this->assertSession()->responseContains('devel_dumper_test/css/devel_dumper_test.css'); + $this->assertSession()->responseContains('devel_dumper_test/js/devel_dumper_test.js'); $debug_filename = file_directory_temp() . '/drupal_debug.txt'; @@ -140,7 +146,7 @@ function testDumpersOutput() { <pre>AvailableTestDumper::export() Test output</pre> EOF; - $this->assertEqual($file_content, $expected, 'Dumped message is present.'); + $this->assertEquals($file_content, $expected, 'Dumped message is present.'); // Ensures that the DevelDumperManager::debug() is not access checked and // that the dump is written in the debug file even if the user has not the @@ -153,7 +159,7 @@ function testDumpersOutput() { <pre>AvailableTestDumper::export() Test output</pre> EOF; - $this->assertEqual($file_content, $expected, 'Dumped message is present.'); + $this->assertEquals($file_content, $expected, 'Dumped message is present.'); } } diff --git a/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php b/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2698e24da3bff57fbed1233725989f6ba799cb3d --- /dev/null +++ b/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php @@ -0,0 +1,151 @@ +<?php + +namespace Drupal\Tests\devel\Functional; + +use Behat\Mink\Element\NodeElement; +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests element info pages and links. + * + * @group devel + */ +class DevelElementInfoTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['devel', 'block']; + + /** + * The user for the test. + * + * @var \Drupal\user\UserInterface + */ + protected $develUser; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + + $this->develUser = $this->drupalCreateUser(['access devel information']); + $this->drupalLogin($this->develUser); + } + + /** + * Tests element info menu link. + */ + public function testElementInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the element info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Element Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/elements'); + $this->assertSession()->pageTextContains('Element Info'); + } + + /** + * Tests element list page. + */ + public function testElementList() { + $this->drupalGet('/devel/elements'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Element Info'); + + $page = $this->getSession()->getPage(); + + // Ensures that the element list table is found. + $table = $page->find('css', 'table.devel-element-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(4, count($headers)); + + $expected_headers = ['Name', 'Provider', 'Class', 'Operations']; + $actual_headers = array_map(function (NodeElement $element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Tests the presence of some (arbitrarily chosen) elements in the table. + $expected_elements = [ + 'button' => [ + 'class' => 'Drupal\Core\Render\Element\Button', + 'provider' => 'core', + ], + 'form' => [ + 'class' => 'Drupal\Core\Render\Element\Form', + 'provider' => 'core', + ], + 'html' => [ + 'class' => 'Drupal\Core\Render\Element\Html', + 'provider' => 'core', + ], + ]; + + foreach ($expected_elements as $element_name => $element) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $element_name)); + $this->assertNotNull($row); + + /** @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(4, count($cells)); + + $cell = $cells[0]; + $this->assertEquals($element_name, $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[1]; + $this->assertEquals($element['provider'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[2]; + $this->assertEquals($element['class'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[3]; + $actual_href = $cell->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_name])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/elements'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests element detail page. + */ + public function testElementDetail() { + $element_name = 'button'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/elements/$element_name"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Element $element_name"); + + // Ensures that the page returns a 404 error if the requested element is + // not defined. + $this->drupalGet('/devel/elements/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("/devel/elements/$element_name"); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php b/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3a708e5155baf75d01ba0707ea2271489c4bf740 --- /dev/null +++ b/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php @@ -0,0 +1,158 @@ +<?php + +namespace Drupal\Tests\devel\Functional; + +use Behat\Mink\Element\NodeElement; +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests entity type info pages and links. + * + * @group devel + */ +class DevelEntityTypeInfoTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['devel', 'block']; + + /** + * The user for the test. + * + * @var \Drupal\user\UserInterface + */ + protected $develUser; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + + $this->develUser = $this->drupalCreateUser(['access devel information']); + $this->drupalLogin($this->develUser); + } + + /** + * Tests entity info menu link. + */ + public function testEntityInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the entity type info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Entity Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/entity/info'); + $this->assertSession()->pageTextContains('Entity Info'); + } + + /** + * Tests entity type list page. + */ + public function testEntityTypeList() { + $this->drupalGet('/devel/entity/info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Entity Info'); + + $page = $this->getSession()->getPage(); + + // Ensures that the entity type list table is found. + $table = $page->find('css', 'table.devel-entity-type-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(5, count($headers)); + + $expected_headers = ['ID', 'Name', 'Provider', 'Class', 'Operations']; + $actual_headers = array_map(function (NodeElement $element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Tests the presence of some (arbitrarily chosen) entity types in the table. + $expected_types = [ + 'date_format' => [ + 'name' => 'Date format', + 'class' => 'Drupal\Core\Datetime\Entity\DateFormat', + 'provider' => 'core', + ], + 'block' => [ + 'name' => 'Block', + 'class' => 'Drupal\block\Entity\Block', + 'provider' => 'block', + ], + 'entity_view_mode' => [ + 'name' => 'View mode', + 'class' => 'Drupal\Core\Entity\Entity\EntityViewMode', + 'provider' => 'core', + ], + ]; + + foreach ($expected_types as $entity_type_id => $entity_type) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $entity_type_id)); + $this->assertNotNull($row); + + /** @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(5, count($cells)); + + $cell = $cells[0]; + $this->assertEquals($entity_type_id, $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[1]; + $this->assertEquals($entity_type['name'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[2]; + $this->assertEquals($entity_type['provider'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[3]; + $this->assertEquals($entity_type['class'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[4]; + $actual_href = $cell->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/entity/info'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests entity type detail page. + */ + public function testEntityTypeDetail() { + $entity_type_id = 'date_format'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/entity/info/$entity_type_id"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Entity type $entity_type_id"); + + // Ensures that the page returns a 404 error if the requested entity type is + // not defined. + $this->drupalGet('/devel/entity/info/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("/devel/entity/info/$entity_type_id"); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php b/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6277de1927e7d503d5b91e3b2ebfeca332404c43 --- /dev/null +++ b/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php @@ -0,0 +1,151 @@ +<?php + +namespace Drupal\Tests\devel\Functional; + +use Drupal\Tests\BrowserTestBase; + +/** + * Tests layout info pages and links. + * + * @group devel + */ +class DevelLayoutInfoTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['devel', 'block', 'layout_discovery']; + + /** + * The user for the test. + * + * @var \Drupal\user\UserInterface + */ + protected $develUser; + + /** + * {@inheritdoc} + */ + protected function setUp() { + // TODO find a cleaner way to skip layout info tests when running tests on + // Drupal branch < 8.3.x. + if (version_compare(\Drupal::VERSION, '8.3', '<')) { + $this->markTestSkipped('Devel Layout Info Tests only available on version 8.3.x+.'); + } + + parent::setUp(); + + $this->drupalPlaceBlock('page_title_block'); + + $this->develUser = $this->drupalCreateUser(['access devel information']); + $this->drupalLogin($this->develUser); + } + + /** + * Tests layout info menu link. + */ + public function testLayoutsInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the layout info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Layouts Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/layouts'); + $this->assertSession()->pageTextContains('Layout'); + } + + /** + * Tests layout info page. + */ + public function testLayoutList() { + $this->drupalGet('/devel/layouts'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Layouts'); + + $page = $this->getSession()->getPage(); + + // Ensures that the layout table is found. + $table = $page->find('css', 'table.devel-layout-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + /** @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(6, count($headers)); + + $expected_headers = ['Icon', 'Label', 'Description', 'Category', 'Regions', 'Provider']; + $actual_headers = array_map(function ($element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Ensures that all the layouts are listed in the table. + $layout_manager = \Drupal::service('plugin.manager.core.layout'); + $layouts = $layout_manager->getDefinitions(); + $table_rows = $table->findAll('css', 'tbody tr'); + $this->assertEquals(count($layouts), count($table_rows)); + + $index = 0; + foreach ($layouts as $layout) { + $cells = $table_rows[$index]->findAll('css', 'td'); + $this->assertEquals(6, count($cells)); + + $cell_layout_icon = $cells[0]; + if (empty($layout->getIconPath())) { + // @todo test that the icon path image is set correctly + } + else { + $this->assertNull($cell_layout_icon->getText()); + } + + $cell_layout_label = $cells[1]; + $this->assertEquals($cell_layout_label->getText(), $layout->getLabel()); + + $cell_layout_description = $cells[2]; + $this->assertEquals($cell_layout_description->getText(), $layout->getDescription()); + + $cell_layout_category = $cells[3]; + $this->assertEquals($cell_layout_category->getText(), $layout->getCategory()); + + $cell_layout_regions = $cells[4]; + $this->assertEquals($cell_layout_regions->getText(), implode(', ', $layout->getRegionLabels())); + + $cell_layout_provider = $cells[5]; + $this->assertEquals($cell_layout_provider->getText(), $layout->getProvider()); + + $index++; + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/layouts'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests the dependency with layout_discovery module. + */ + public function testLayoutDiscoveryDependency() { + $this->container->get('module_installer')->uninstall(['layout_discovery']); + $this->drupalPlaceBlock('system_menu_block:devel'); + + // Ensures that the layout info link is not present on the devel menu. + $this->drupalGet(''); + $this->assertSession()->linkNotExists('Layouts Info'); + + // Ensures that the layouts info page is not available. + $this->drupalGet('/devel/layouts'); + $this->assertSession()->statusCodeEquals(404); + + // Check a few other devel pages to verify devel module stil works. + $this->drupalGet('/devel/events'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('devel/routes'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('/devel/container/service'); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/web/modules/devel/src/Tests/DevelMenuLinksTest.php b/web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php similarity index 95% rename from web/modules/devel/src/Tests/DevelMenuLinksTest.php rename to web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php index 1183745f70170ee1aef6203038b25f00a123f356..e806453addda8fdbbcde886d014602cb38b58e66 100644 --- a/web/modules/devel/src/Tests/DevelMenuLinksTest.php +++ b/web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php @@ -1,16 +1,16 @@ <?php -namespace Drupal\devel\Tests; +namespace Drupal\Tests\devel\Functional; use Drupal\Core\Url; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests devel menu links. * * @group devel */ -class DevelMenuLinksTest extends WebTestBase { +class DevelMenuLinksTest extends BrowserTestBase { /** * Modules to enable. diff --git a/web/modules/devel/src/Tests/DevelReinstallTest.php b/web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php similarity index 67% rename from web/modules/devel/src/Tests/DevelReinstallTest.php rename to web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php index 9ff5efa03fd26898930b9aa36b63fc95571d2801..5068f2cab5a3aa74e269e89f518001cb925fa4d5 100644 --- a/web/modules/devel/src/Tests/DevelReinstallTest.php +++ b/web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php @@ -1,22 +1,22 @@ <?php -namespace Drupal\devel\Tests; +namespace Drupal\Tests\devel\Functional; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests reinstall modules. * * @group devel */ -class DevelReinstallTest extends WebTestBase { +class DevelModulesReinstallTest extends BrowserTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('devel'); + public static $modules = ['devel']; /** * The profile to install as a basis for testing. @@ -31,7 +31,7 @@ class DevelReinstallTest extends WebTestBase { protected function setUp() { parent::setUp(); - $web_user = $this->drupalCreateUser(array('administer site configuration')); + $web_user = $this->drupalCreateUser(['administer site configuration']); $this->drupalLogin($web_user); } @@ -40,7 +40,7 @@ protected function setUp() { */ public function testDevelReinstallModules() { // Minimal profile enables only dblog, block and node. - $modules = array('dblog', 'block'); + $modules = ['dblog', 'block']; // Needed for compare correctly the message. sort($modules); @@ -48,13 +48,13 @@ public function testDevelReinstallModules() { $this->drupalGet('devel/reinstall'); // Prepare field data in an associative array - $edit = array(); + $edit = []; foreach ($modules as $module) { $edit["reinstall[$module]"] = TRUE; } $this->drupalPostForm('devel/reinstall', $edit, t('Reinstall')); - $this->assertText(t('Uninstalled and installed: @names.', array('@names' => implode(', ', $modules)))); + $this->assertText(t('Uninstalled and installed: @names.', ['@names' => implode(', ', $modules)])); } } diff --git a/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php b/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php index faa6bf1f52178b8bd4733b8d3f06803b991eb8f1..876a5ff4f7e8a1e8185789506bcbdb871310f27e 100644 --- a/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php +++ b/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php @@ -27,7 +27,7 @@ public function testStatusPage() { $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContains('Devel module enabled'); - $this->assertSession()->pageTextContains('The module provide the access to debug informations, therefore is recommended to disable the Devel module on production sites.'); + $this->assertSession()->pageTextContains('The Devel module provides access to internal debugging information; therefore it\'s recommended to disable this module on sites in production.'); } } diff --git a/web/modules/devel/src/Tests/DevelSwitchUserTest.php b/web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php similarity index 92% rename from web/modules/devel/src/Tests/DevelSwitchUserTest.php rename to web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php index 68f6136db61f5f61f284636febac5d4b5feec5bd..a0c89daa945fdd3cf197959157c13c658bd90a73 100644 --- a/web/modules/devel/src/Tests/DevelSwitchUserTest.php +++ b/web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php @@ -1,16 +1,16 @@ <?php -namespace Drupal\devel\Tests; +namespace Drupal\Tests\devel\Functional; use Drupal\Component\Render\FormattableMarkup; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests switch user. * * @group devel */ -class DevelSwitchUserTest extends WebTestBase { +class DevelSwitchUserTest extends BrowserTestBase { /** * Modules to enable. @@ -70,7 +70,7 @@ public function testSwitchUser() { $this->assertNoText($this->block->label(), 'Block title was not found.'); // Ensure that a token is required to switch user. - $this->drupalGet('/devel/switch/' . $this->webUser->getUsername()); + $this->drupalGet('/devel/switch/' . $this->webUser->getDisplayName()); $this->assertResponse(403); $this->drupalLogin($this->develUser); @@ -83,22 +83,22 @@ public function testSwitchUser() { $this->assertResponse(403); // Ensure that a token is required to switch user. - $this->drupalGet('/devel/switch/' . $this->switchUser->getUsername()); + $this->drupalGet('/devel/switch/' . $this->switchUser->getDisplayName()); $this->assertResponse(403); // Switch to another user account. $this->drupalGet('/user/' . $this->switchUser->id()); - $this->clickLink($this->switchUser->getUsername()); + $this->clickLink($this->switchUser->getDisplayName()); $this->assertSessionByUid($this->switchUser->id()); $this->assertNoSessionByUid($this->develUser->id()); // Switch back to initial account. - $this->clickLink($this->develUser->getUsername()); + $this->clickLink($this->develUser->getDisplayName()); $this->assertNoSessionByUid($this->switchUser->id()); $this->assertSessionByUid($this->develUser->id()); // Use the search form to switch to another account. - $edit = ['userid' => $this->switchUser->getUsername()]; + $edit = ['userid' => $this->switchUser->getDisplayName()]; $this->drupalPostForm(NULL, $edit, t('Switch')); $this->assertSessionByUid($this->switchUser->id()); $this->assertNoSessionByUid($this->develUser->id()); @@ -166,22 +166,22 @@ public function testSwitchUserListItems() { // Ensure that user with 'switch users' permission are prioritized. $this->assertSwitchUserListCount(2); - $this->assertSwitchUserListContainsUser($this->develUser->getUsername()); - $this->assertSwitchUserListContainsUser($this->switchUser->getUsername()); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); + $this->assertSwitchUserListContainsUser($this->switchUser->getDisplayName()); // Ensure that blocked users are not shown in the list. $this->switchUser->set('status', 0)->save(); $this->drupalGet(''); $this->assertSwitchUserListCount(2); - $this->assertSwitchUserListContainsUser($this->develUser->getUsername()); - $this->assertSwitchUserListContainsUser($this->webUser->getUsername()); - $this->assertSwitchUserListNoContainsUser($this->switchUser->getUsername()); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); + $this->assertSwitchUserListContainsUser($this->webUser->getDisplayName()); + $this->assertSwitchUserListNoContainsUser($this->switchUser->getDisplayName()); // Ensure that anonymous user are prioritized if include_anon is set to true. $this->setBlockConfiguration('include_anon', TRUE); $this->drupalGet(''); $this->assertSwitchUserListCount(2); - $this->assertSwitchUserListContainsUser($this->develUser->getUsername()); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); $this->assertSwitchUserListContainsUser($anonymous); // Ensure that the switch user block works properly even if no prioritized @@ -192,7 +192,7 @@ public function testSwitchUserListItems() { $this->drupalLogin($this->rootUser); $this->drupalGet(''); $this->assertSwitchUserListCount(2); - $this->assertSwitchUserListContainsUser($this->rootUser->getUsername()); + $this->assertSwitchUserListContainsUser($this->rootUser->getDisplayName()); $this->assertSwitchUserListContainsUser($anonymous); // Ensure that the switch user block works properly even if no roles have @@ -202,7 +202,7 @@ public function testSwitchUserListItems() { $this->drupalGet(''); $this->assertSwitchUserListCount(2); - $this->assertSwitchUserListContainsUser($this->rootUser->getUsername()); + $this->assertSwitchUserListContainsUser($this->rootUser->getDisplayName()); $this->assertSwitchUserListContainsUser($anonymous); } @@ -276,12 +276,12 @@ protected function setBlockConfiguration($key, $value) { */ protected function assertSessionByUid($uid) { $query = \Drupal::database()->select('sessions'); - $query->fields('sessions', array('uid')); + $query->fields('sessions', ['uid']); $query->condition('uid', $uid); $result = $query->execute()->fetchAll(); if (empty($result)) { - $this->fail(new FormattableMarkup('No session found for uid @uid', array('@uid' => $uid))); + $this->fail(new FormattableMarkup('No session found for uid @uid', ['@uid' => $uid])); } elseif (count($result) > 1) { // If there is more than one session, then that must be unexpected. @@ -304,7 +304,7 @@ protected function assertSessionByUid($uid) { */ protected function assertNoSessionByUid($uid) { $query = \Drupal::database()->select('sessions'); - $query->fields('sessions', array('uid')); + $query->fields('sessions', ['uid']); $query->condition('uid', $uid); $result = $query->execute()->fetchAll(); $this->assert(empty($result), "No session for uid $uid found."); diff --git a/web/modules/devel/tests/src/Functional/DevelToolbarTest.php b/web/modules/devel/tests/src/Functional/DevelToolbarTest.php index aa2271dbe175914ebef9ff69193f8c31509d8ff2..26293f896eb3cabde0edc37478564bfaa3f92c67 100644 --- a/web/modules/devel/tests/src/Functional/DevelToolbarTest.php +++ b/web/modules/devel/tests/src/Functional/DevelToolbarTest.php @@ -182,11 +182,11 @@ public function testToolbarIntegration() { $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); $devel_menu_items = $this->getMenuLinkInfos(); - $toolbar_items = $toolbar_tray->findAll('css', 'ul.menu a'); + $toolbar_items = $toolbar_tray->findAll('css', 'ul.toolbar-menu a'); $this->assertCount(count($devel_menu_items), $toolbar_items); foreach ($devel_menu_items as $link) { - $item_selector = sprintf('ul.menu a:contains("%s")', $link['title']); + $item_selector = sprintf('ul.toolbar-menu a:contains("%s")', $link['title']); $item = $this->assertSession()->elementExists('css', $item_selector, $toolbar_tray); // TODO: find a more correct way to test link url. $this->assertContains(strtok($link['url'], '?'), $item->getAttribute('href')); @@ -206,7 +206,7 @@ public function testToolbarIntegration() { $this->drupalGet(''); $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); - $item = $this->assertSession()->elementExists('css', sprintf('ul.menu a:contains("%s")', 'Events Info'), $toolbar_tray); + $item = $this->assertSession()->elementExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray); $this->assertFalse($item->hasClass('toolbar-horizontal-item-hidden')); // Ensures that disabling a menu link it will not more shown in the toolbar @@ -216,7 +216,7 @@ public function testToolbarIntegration() { $this->drupalGet(''); $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); - $this->assertSession()->elementNotExists('css', sprintf('ul.menu a:contains("%s")', 'Events Info'), $toolbar_tray); + $this->assertSession()->elementNotExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray); } /** diff --git a/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php b/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php index 725e4cf5906388bf6a3caa52f296bb5ef9955345..4050b41036338463090edb2ae5e9b6546b2882c6 100644 --- a/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php +++ b/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php @@ -56,7 +56,7 @@ public function get($cid, $allow_invalid = FALSE) { $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; @@ -82,7 +82,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) { $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; diff --git a/web/modules/devel/webprofiler/src/Compiler/EventPass.php b/web/modules/devel/webprofiler/src/Compiler/EventPass.php deleted file mode 100644 index 5f009afdba22558eebe61a29d207d8ef0d2faee1..0000000000000000000000000000000000000000 --- a/web/modules/devel/webprofiler/src/Compiler/EventPass.php +++ /dev/null @@ -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'); - } -} diff --git a/web/modules/devel/webprofiler/src/Compiler/ServicePass.php b/web/modules/devel/webprofiler/src/Compiler/ServicePass.php index df6b87907f1d5f6a9f2e3103d77d34a337a9c9c9..4928418c63f63cc06e1d4044eacd9db310d0d246 100644 --- a/web/modules/devel/webprofiler/src/Compiler/ServicePass.php +++ b/web/modules/devel/webprofiler/src/Compiler/ServicePass.php @@ -50,7 +50,6 @@ private function extractData(ContainerBuilder $container, ServiceReferenceGraph $inEdges[] = [ 'id' => $edge->getSourceNode()->getId(), 'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL, - 'strict' => $edgeValue ? $edgeValue->isStrict() : NULL, ]; } @@ -63,7 +62,6 @@ private function extractData(ContainerBuilder $container, ServiceReferenceGraph $outEdges[] = [ 'id' => $edge->getDestNode()->getId(), 'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL, - 'strict' => $edgeValue ? $edgeValue->isStrict() : NULL, ]; } } diff --git a/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php index bdcf49ceb947ee9141725c34e0192fb4ef712b87..2c766a4150562b473dde34c6eef08e3b237a511e 100644 --- a/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php +++ b/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php @@ -56,7 +56,7 @@ public function collect(Request $request, Response $response, \Exception $except unset($query['caller']['args']); // Remove query args element if empty. - if (empty($query['args'])) { + if (isset($query['args']) && empty($query['args'])) { unset($query['args']); } @@ -223,8 +223,11 @@ public function getData() { $query['type'] = $type; $quoted = []; - foreach ((array) $query['args'] as $key => $val) { - $quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val); + + if (isset($query['args'])) { + foreach ((array) $query['args'] as $key => $val) { + $quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val); + } } $query['query_args'] = strtr($query['query'], $quoted); diff --git a/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php index 79493a3f3cbad4a0a4a213debb9a595563af8a3f..6374ca962fdbdebc7b1b4942400ae7c643bea7dd 100644 --- a/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php +++ b/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php @@ -2,74 +2,123 @@ namespace Drupal\webprofiler\DataCollector; -use Drupal\webprofiler\DrupalDataCollectorInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -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; /** - * @return int + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - public function getCalledListenersCount() { - return count($this->getCalledListeners()); - } + private $eventDispatcher; /** - * @return int + * EventsDataCollector constructor. + * + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ - public function getNotCalledListenersCount() { - return count($this->getNotCalledListeners()); + public function __construct(EventDispatcherInterface $event_dispatcher) { + $this->eventDispatcher = $event_dispatcher; } /** * {@inheritdoc} */ - public function setCalledListeners(array $listeners) { - $listeners = $this->computePriority($listeners); - $this->data['called_listeners'] = $listeners; + 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, + ]; } /** - * Adds the priority value to the $listeners array. - * - * @param array $listeners - * @return array + * {@inheritdoc} */ - private function computePriority(array $listeners) { - foreach ($listeners as &$listener) { - if (is_subclass_of($listener['class'], EventSubscriberInterface::class)) { - foreach ($listener['class']::getSubscribedEvents() as $event => $methods) { - - if (is_string($methods)) { - $methods = [[$methods], 0]; - } - else { - if (is_string($methods[0])) { - $methods = [$methods]; - } + 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']); } + } + } - foreach ($methods as $method) { - if ($listener['event'] === $event) { - if ($listener['method'] === $method[0]) { - $listener['priority'] = isset($method[1]) ? $method[1] : 0; - } - } + $countNotCalled = 0; + $notCalledListeners = $this->eventDispatcher->getNotCalledListeners(); + foreach ($notCalledListeners as $events) { + foreach ($events as $priority) { + foreach ($priority as $listener) { + $countNotCalled++; } } - } else { - $listener['priority'] = isset($listener['priority']) ? $listener['priority'] : 0; } + + $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 $this->data['called_listeners_count']; + } - return $listeners; + /** + * @return int + */ + public function getNotCalledListenersCount() { + return $this->data['not_called_listeners_count']; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'events'; + } + + /** + * @return mixed + */ + public function getData() { + return $this->data; } /** @@ -83,7 +132,7 @@ public function getTitle() { * {@inheritdoc} */ public function getPanelSummary() { - return $this->t('Called listeners: @listeners', ['@listeners' => count($this->getCalledListeners())]); + return $this->t('Called listeners: @listeners', ['@listeners' => $this->getCalledListenersCount()]); } /** diff --git a/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php index b7ecb1095588da0ba0e84aa74df413c4e67d4bfd..ef653f8051a5e28713cc54403ee11acde53ac21c 100644 --- a/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php +++ b/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php @@ -5,7 +5,7 @@ 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; } diff --git a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php index 168b739c598d03cb63913b8d5a0f04ef95318eed..b9a06f55282193ec3e63f2a53afff355581a6857 100644 --- a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php +++ b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php @@ -108,6 +108,13 @@ public function save(EntityInterface $entity) { return $this->getOriginalObject()->save($entity); } + /** + * {@inheritdoc} + */ + public function hasData() { + return $this->getOriginalObject()->hasData(); + } + /** * {@inheritdoc} */ diff --git a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php index 1371009d8265b1e85c7b420eb71f686fb896ce39..7248ba1ce7b6da6212cb5abd17c1ea61e52b505f 100644 --- a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php +++ b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php @@ -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); } } diff --git a/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php b/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..dbad1515385ac419ac834d2151589b770b85fb63 --- /dev/null +++ b/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php @@ -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(); + +} diff --git a/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php b/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..b3451fb508eec7bafa2c9e8fbf983daec04b8967 --- /dev/null +++ b/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php @@ -0,0 +1,193 @@ +<?php + +namespace Drupal\webprofiler\EventDispatcher; + +use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Drupal\webprofiler\Stopwatch; +use Symfony\Component\DependencyInjection\ContainerInterface; +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; + + /** + * {@inheritdoc} + */ + public function __construct(ContainerInterface $container, array $listeners = []) { + parent::__construct($container, $listeners); + $this->notCalledListeners = $listeners; + } + + /** + * {@inheritdoc} + */ + public function addListener($event_name, $listener, $priority = 0) { + parent::addListener($event_name, $listener, $priority); + $this->notCalledListeners[$event_name][$priority][] = ['callable' => $listener]; + } + + /** + * {@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; + } + + /** + * @param \Drupal\webprofiler\Stopwatch $stopwatch + */ + public function setStopwatch(Stopwatch $stopwatch) { + $this->stopwatch = $stopwatch; + } + + /** + * 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], + ]; + + foreach ($this->notCalledListeners[$event_name][$priority] as $key => $listener) { + if (isset($listener['service'])) { + if ($listener['service'][0] == $definition['service'][0] && $listener['service'][1] == $definition['service'][1]) { + unset($this->notCalledListeners[$event_name][$priority][$key]); + } + } + else { + if (get_class($listener['callable'][0]) == get_class($definition['callable'][0]) && $listener['callable'][1] == $definition['callable'][1]) { + unset($this->notCalledListeners[$event_name][$priority][$key]); + } + } + + } + } + +} diff --git a/web/modules/devel/webprofiler/src/Form/ConfigForm.php b/web/modules/devel/webprofiler/src/Form/ConfigForm.php index ae7c6012687ea0be0e8f39b77471a03dfc9ee99b..e99e2168cade40ab147501fca573df7d2ccf9649 100644 --- a/web/modules/devel/webprofiler/src/Form/ConfigForm.php +++ b/web/modules/devel/webprofiler/src/Form/ConfigForm.php @@ -134,7 +134,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#states' => array( 'visible' => array( array( - ':input[name="active_toolbar_items[database]' => array('checked' => TRUE), + 'input[name="active_toolbar_items[database]"]' => array('checked' => TRUE), ), ), ), diff --git a/web/modules/devel/webprofiler/src/State/StateWrapper.php b/web/modules/devel/webprofiler/src/State/StateWrapper.php index f42e66f358894c9d3d4531acad741b0bd14cb029..23f930ad40ed1fbd9b5547f15d314c33279601d1 100644 --- a/web/modules/devel/webprofiler/src/State/StateWrapper.php +++ b/web/modules/devel/webprofiler/src/State/StateWrapper.php @@ -2,13 +2,14 @@ namespace Drupal\webprofiler\State; +use Drupal\Core\Cache\CacheCollector; use Drupal\Core\State\StateInterface; use Drupal\webprofiler\DataCollector\StateDataCollector; /** * Class StateWrapper. */ -class StateWrapper implements StateInterface { +class StateWrapper extends CacheCollector implements StateInterface { /** * The system state. @@ -92,6 +93,20 @@ 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. * diff --git a/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php b/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php index 7b427f180448d49e3d7a69325a0c524afb1e1375..8a96b08df1ad1c684eeef02202cc191d879df2e2 100644 --- a/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php +++ b/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php @@ -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 @@ public function determineActiveTheme(RouteMatchInterface $route_match) { 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; + } + } diff --git a/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php b/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php deleted file mode 100644 index 44c5cf3dfc4eeb63e63ee782a157b7ee063f195d..0000000000000000000000000000000000000000 --- a/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php +++ /dev/null @@ -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; - } - } - } - -} diff --git a/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php b/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php index 3580392da1e4c489e5f3f89a89c2e4203dc0b1aa..afd6e39170f7de3e1db74e3a2fecffcd04da1b6f 100644 --- a/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php +++ b/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php @@ -31,7 +31,7 @@ public function getBuildTime() { * @return float */ public function getExecuteTime() { - return $this->execute_time; + return property_exists($this, 'execute_time') ? $this->execute_time : 0.0; } /** diff --git a/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php b/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php index 20b67f0b53c11e826cc6523dc90e0000a3f40aa2..3ba1ff0682a33db24ee06ed76250451f6104503d 100644 --- a/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php +++ b/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php @@ -28,7 +28,6 @@ public function register(ContainerBuilder $container) { $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 @@ public function register(ContainerBuilder $container) { '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 @@ public function alter(ContainerBuilder $container) { // 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')); } + } diff --git a/web/modules/devel/webprofiler/templates/Collector/events.html.twig b/web/modules/devel/webprofiler/templates/Collector/events.html.twig index 9167413af1df7e68febdb90cdd475c6c4b06258f..17087e1bff172f0353a93f10f29dac32eea21617 100644 --- a/web/modules/devel/webprofiler/templates/Collector/events.html.twig +++ b/web/modules/devel/webprofiler/templates/Collector/events.html.twig @@ -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 %} diff --git a/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php new file mode 100644 index 0000000000000000000000000000000000000000..84c7d820f3a324e86d445e0b8eeb872577ce8630 --- /dev/null +++ b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php @@ -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'); + } + +} diff --git a/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..b38a1f21e910967f04d5dd6f6dcbf9861e162e13 --- /dev/null +++ b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php @@ -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'); + } + +} diff --git a/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php b/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a557c08f47ff778143be6c91f7c527394e710ecf --- /dev/null +++ b/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php @@ -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'), + ); + } + +} diff --git a/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..37d6ac3ee5626734c1a5121e4d69e098005742e8 --- /dev/null +++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php @@ -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']); + } + +} diff --git a/web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php similarity index 91% rename from web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php rename to web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php index f4673eea7dc2c643f7d6d4f9c3b91a4b258cbacb..d51e604dd676ef6903deb49ba55848ab78fd851a 100644 --- a/web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php +++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php @@ -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 @@ public function testCacheCollectorMiss() { * 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']; diff --git a/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8d62e7809e86430ae3956f99500f349a2a146083 --- /dev/null +++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php @@ -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'); + } + +} diff --git a/web/modules/devel/webprofiler/webprofiler.info.yml b/web/modules/devel/webprofiler/webprofiler.info.yml index 903971555a2f0a4b9e49e8b90d4dc4977b363b76..8aebae23136e74127e1a2ccbc6eb7ae526cac70e 100644 --- a/web/modules/devel/webprofiler/webprofiler.info.yml +++ b/web/modules/devel/webprofiler/webprofiler.info.yml @@ -9,8 +9,8 @@ tags: dependencies: - devel -# Information added by Drupal.org packaging script on 2017-04-23 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2017-10-05 +version: '8.x-1.2' core: '8.x' project: 'devel' -datestamp: 1492989248 +datestamp: 1507197848 diff --git a/web/modules/devel/webprofiler/webprofiler.routing.yml b/web/modules/devel/webprofiler/webprofiler.routing.yml index 200bc39734943ddab27ccf9e6e6e065c4731685a..11543ecb90fac4b38e8c02cce8c6d2a19f8e9bd7 100644 --- a/web/modules/devel/webprofiler/webprofiler.routing.yml +++ b/web/modules/devel/webprofiler/webprofiler.routing.yml @@ -22,7 +22,7 @@ webprofiler.frontend.save: type: 'webprofiler:token' methods: [POST] requirements: - _permission: 'access webprofiler' + _permission: 'view webprofiler toolbar' # view profile webprofiler.dashboard: diff --git a/web/modules/devel/webprofiler/webprofiler.services.yml b/web/modules/devel/webprofiler/webprofiler.services.yml index 8d6e618e53f09632304d75ba894c35de286ac9ab..6332fef9260b578bd7ddbfaefa365fafa957b93a 100644 --- a/web/modules/devel/webprofiler/webprofiler.services.yml +++ b/web/modules/devel/webprofiler/webprofiler.services.yml @@ -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,10 +187,6 @@ 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', '@renderer']