diff --git a/composer.json b/composer.json index 59a6b18bb0f5243f7f42eda9dcad912b4d7d4534..0ff5930bec8d121efc9cb1a208eeb21400fcfdbf 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ "drupal/admin_toolbar": "1.24", "drupal/administerusersbyrole": "2.0-alpha6", "drupal/allowed_formats": "1.1", - "drupal/better_exposed_filters": "3.0-alpha3", + "drupal/better_exposed_filters": "3.0-alpha4", "drupal/block_permissions": "^1.0", "drupal/block_region_permissions": "^1.2", "drupal/bootstrap": "3.5", @@ -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", @@ -99,7 +99,7 @@ "drupal/entity_reference_revisions": "1.3", "drupal/externalauth": "1.0", "drupal/features": "3.8", - "drupal/field_group": "1.0-rc6", + "drupal/field_group": "1.0", "drupal/field_permissions": "1.0-beta1", "drupal/file_browser": "1.1", "drupal/focal_point": "1.0-beta6", @@ -107,7 +107,7 @@ "drupal/google_analytics": "2.2", "drupal/honeypot": "^1.28", "drupal/image_popup": "1.1", - "drupal/inline_entity_form": "1.0-beta1", + "drupal/inline_entity_form": "1.0-rc1", "drupal/link_attributes": "1.0", "drupal/linkit": "5.0-beta6", "drupal/magnific_popup": "1.3", @@ -118,7 +118,7 @@ "drupal/migrate_tools": "4.0", "drupal/paragraphs": "1.3", "drupal/pathauto": "1.0", - "drupal/redis": "1.0-rc2", + "drupal/redis": "1.0", "drupal/roleassign": "^1.0@alpha", "drupal/scheduler": "1.0", "drupal/search_api": "1.1", @@ -126,17 +126,17 @@ "drupal/search_api_glossary": "3.4", "drupal/simple_megamenu": "1.0-beta3", "drupal/simplesamlphp_auth": "3.0", - "drupal/smtp": "1.0-beta3", + "drupal/smtp": "1.0-beta4", "drupal/social_media_links": "^2.6", "drupal/superfish": "1.2", "drupal/svg_image": "1.8", "drupal/token": "1.0", "drupal/userprotect": "1.0", "drupal/video_embed_field": "2.0", - "drupal/views_accordion": "1.0-beta2", + "drupal/views_accordion": "1.1", "drupal/views_ajax_history": "^1.0", "drupal/views_autocomplete_filters": "1.1", - "drupal/views_bootstrap": "3.x-dev", + "drupal/views_bootstrap": "3.1", "drupal/views_bulk_operations": "2.4", "drupal/views_fieldsets": "3.3", "drupal/views_infinite_scroll": "1.5", diff --git a/composer.lock b/composer.lock index d3672b8c41207cb7eab5538a0637ea4c0030d8a3..1da72fe096e4c4e1f7398efcc5ee75975dbdecb2 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": "5eb47f8a36f62539ff7f81a51b48209d", "packages": [ { "name": "alchemy/zippy", @@ -1564,17 +1564,17 @@ }, { "name": "drupal/better_exposed_filters", - "version": "3.0.0-alpha3", + "version": "3.0.0-alpha4", "source": { "type": "git", "url": "https://git.drupal.org/project/better_exposed_filters", - "reference": "8.x-3.0-alpha3" + "reference": "8.x-3.0-alpha4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/better_exposed_filters-8.x-3.0-alpha3.zip", - "reference": "8.x-3.0-alpha3", - "shasum": "0268a42ea5a4ab170c8f4aefecd26b6f3c7448d2" + "url": "https://ftp.drupal.org/files/projects/better_exposed_filters-8.x-3.0-alpha4.zip", + "reference": "8.x-3.0-alpha4", + "shasum": "f42013d1fc36d45212d582e18afc59bc2c364719" }, "require": { "drupal/core": "*" @@ -1585,7 +1585,7 @@ "dev-3.x": "3.x-dev" }, "drupal": { - "version": "8.x-3.0-alpha3", + "version": "8.x-3.0-alpha4", "datestamp": "1525044484", "security-coverage": { "status": "not-covered", @@ -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" } } }, @@ -3648,17 +3648,17 @@ }, { "name": "drupal/field_group", - "version": "1.0.0-rc6", + "version": "1.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/field_group", - "reference": "8.x-1.0-rc6" + "reference": "8.x-1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/field_group-8.x-1.0-rc6.zip", - "reference": "8.x-1.0-rc6", - "shasum": "1ce3c6387e745a04a1099956a5bf39bb50096d44" + "url": "https://ftp.drupal.org/files/projects/field_group-8.x-1.0.zip", + "reference": "8.x-1.0", + "shasum": "e8aa3fae5c3c5dec84644bb577996938d638a611" }, "require": { "drupal/core": "*" @@ -3669,11 +3669,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc6", + "version": "8.x-1.0", "datestamp": "1510352885", "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" } } }, @@ -4094,17 +4094,17 @@ }, { "name": "drupal/inline_entity_form", - "version": "1.0.0-beta1", + "version": "1.0.0-rc1", "source": { "type": "git", "url": "https://git.drupal.org/project/inline_entity_form", - "reference": "8.x-1.0-beta1" + "reference": "8.x-1.0-rc1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/inline_entity_form-8.x-1.0-beta1.zip", - "reference": "8.x-1.0-beta1", - "shasum": "185ffc28a7b68d19cce057855d1c111f1741a3ea" + "url": "https://ftp.drupal.org/files/projects/inline_entity_form-8.x-1.0-rc1.zip", + "reference": "8.x-1.0-rc1", + "shasum": "898789fb6a0662fc2572b87f8d0654a0241473f9" }, "require": { "drupal/core": "~8.0" @@ -4118,11 +4118,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta1", + "version": "8.x-1.0-rc1", "datestamp": "1527030784", "security-coverage": { "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "message": "RC releases are not covered by Drupal security advisories." } } }, @@ -4781,17 +4781,17 @@ }, { "name": "drupal/redis", - "version": "1.0.0-rc2", + "version": "1.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/redis", - "reference": "8.x-1.0-rc2" + "reference": "8.x-1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/redis-8.x-1.0-rc2.zip", - "reference": "8.x-1.0-rc2", - "shasum": "7eeaa7078e9df6f4bbea1ca90dbc43aa044da346" + "url": "https://ftp.drupal.org/files/projects/redis-8.x-1.0.zip", + "reference": "8.x-1.0", + "shasum": "98cf460db5c9aaacf3d5c02ac51e0c9e8e85edbb" }, "require": { "drupal/core": "~8.0" @@ -4805,11 +4805,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc2", + "version": "8.x-1.0", "datestamp": "1527699484", "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" } } }, @@ -5224,17 +5224,17 @@ }, { "name": "drupal/smtp", - "version": "1.0.0-beta3", + "version": "1.0.0-beta4", "source": { "type": "git", "url": "https://git.drupal.org/project/smtp", - "reference": "8.x-1.0-beta3" + "reference": "8.x-1.0-beta4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/smtp-8.x-1.0-beta3.zip", - "reference": "8.x-1.0-beta3", - "shasum": "a351dac3765b9570933fd07f1e029aa22e5c69b0" + "url": "https://ftp.drupal.org/files/projects/smtp-8.x-1.0-beta4.zip", + "reference": "8.x-1.0-beta4", + "shasum": "80a4df4b2fd2d1b2dc653552d232ce98cb54bb9d" }, "require": { "drupal/core": "~8.0" @@ -5245,7 +5245,7 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta3", + "version": "8.x-1.0-beta4", "datestamp": "1527598380", "security-coverage": { "status": "not-covered", @@ -5621,17 +5621,17 @@ }, { "name": "drupal/views_accordion", - "version": "1.0.0-beta2", + "version": "1.1.0", "source": { "type": "git", "url": "https://git.drupal.org/project/views_accordion", - "reference": "8.x-1.0-beta2" + "reference": "8.x-1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_accordion-8.x-1.0-beta2.zip", - "reference": "8.x-1.0-beta2", - "shasum": "943ab7b2d530e3885fcaf5cc30817e956c6759ef" + "url": "https://ftp.drupal.org/files/projects/views_accordion-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "2e5e1f758141bf271b26f1e4631211dfaa5dae27" }, "require": { "drupal/core": "*" @@ -5642,11 +5642,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta2", - "datestamp": "1494784384", + "version": "8.x-1.1", + "datestamp": "1521988085", "security-coverage": { - "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, @@ -5779,11 +5779,17 @@ }, { "name": "drupal/views_bootstrap", - "version": "dev-3.x", + "version": "3.1.0", "source": { "type": "git", "url": "https://git.drupal.org/project/views_bootstrap", - "reference": "8a8d1add3a575842e33c51a3733b43c24c4bc41f" + "reference": "8.x-3.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/views_bootstrap-8.x-3.1.zip", + "reference": "8.x-3.1", + "shasum": "7fd556457f028fa736d1422a9b5a763566fd7d2e" }, "require": { "drupal/core": "*" @@ -5794,11 +5800,11 @@ "dev-3.x": "3.x-dev" }, "drupal": { - "version": "8.x-3.0+4-dev", - "datestamp": "1523236084", + "version": "8.x-3.1", + "datestamp": "1537360681", "security-coverage": { - "status": "not-covered", - "message": "Dev releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, @@ -5831,8 +5837,7 @@ ], "support": { "source": "http://cgit.drupalcode.org/views_bootstrap" - }, - "time": "2018-05-17T13:28:26+00:00" + } }, { "name": "drupal/views_bulk_operations", @@ -10078,8 +10083,7 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "drupal/roleassign": 15, - "drupal/views_bootstrap": 20 + "drupal/roleassign": 15 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 9b1cd40d36b5c6f1666b83a80eff607690d6a401..553274385b82ace565bbf5e2beb03e2694061d07 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -2472,6 +2472,7 @@ 'Drupal\\redis\\Lock\\PhpRedis' => $baseDir . '/web/modules/redis/src/Lock/PhpRedis.php', 'Drupal\\redis\\Lock\\Predis' => $baseDir . '/web/modules/redis/src/Lock/Predis.php', 'Drupal\\redis\\PersistentLock\\PhpRedis' => $baseDir . '/web/modules/redis/src/PersistentLock/PhpRedis.php', + 'Drupal\\redis\\PersistentLock\\Predis' => $baseDir . '/web/modules/redis/src/PersistentLock/Predis.php', 'Drupal\\redis\\Queue\\PhpRedis' => $baseDir . '/web/modules/redis/src/Queue/PhpRedis.php', 'Drupal\\redis\\Queue\\Predis' => $baseDir . '/web/modules/redis/src/Queue/Predis.php', 'Drupal\\redis\\Queue\\QueueBase' => $baseDir . '/web/modules/redis/src/Queue/QueueBase.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index dc9d97e8d7b788a9c703954a0743be1395225489..d3091d1dd22de5f34081598b89a4522cab751bc5 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -2992,6 +2992,7 @@ class ComposerStaticInit5c689ffcd54b9e495ed983fdce09b530 'Drupal\\redis\\Lock\\PhpRedis' => __DIR__ . '/../..' . '/web/modules/redis/src/Lock/PhpRedis.php', 'Drupal\\redis\\Lock\\Predis' => __DIR__ . '/../..' . '/web/modules/redis/src/Lock/Predis.php', 'Drupal\\redis\\PersistentLock\\PhpRedis' => __DIR__ . '/../..' . '/web/modules/redis/src/PersistentLock/PhpRedis.php', + 'Drupal\\redis\\PersistentLock\\Predis' => __DIR__ . '/../..' . '/web/modules/redis/src/PersistentLock/Predis.php', 'Drupal\\redis\\Queue\\PhpRedis' => __DIR__ . '/../..' . '/web/modules/redis/src/Queue/PhpRedis.php', 'Drupal\\redis\\Queue\\Predis' => __DIR__ . '/../..' . '/web/modules/redis/src/Queue/Predis.php', 'Drupal\\redis\\Queue\\QueueBase' => __DIR__ . '/../..' . '/web/modules/redis/src/Queue/QueueBase.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index c1baf9703b2bb77a10edb935ce9245a58cd759c6..c834fb162f9e2587df64987e3bb5848846b98042 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1615,18 +1615,18 @@ }, { "name": "drupal/better_exposed_filters", - "version": "3.0.0-alpha3", - "version_normalized": "3.0.0.0-alpha3", + "version": "3.0.0-alpha4", + "version_normalized": "3.0.0.0-alpha4", "source": { "type": "git", "url": "https://git.drupal.org/project/better_exposed_filters", - "reference": "8.x-3.0-alpha3" + "reference": "8.x-3.0-alpha4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/better_exposed_filters-8.x-3.0-alpha3.zip", - "reference": "8.x-3.0-alpha3", - "shasum": "0268a42ea5a4ab170c8f4aefecd26b6f3c7448d2" + "url": "https://ftp.drupal.org/files/projects/better_exposed_filters-8.x-3.0-alpha4.zip", + "reference": "8.x-3.0-alpha4", + "shasum": "f42013d1fc36d45212d582e18afc59bc2c364719" }, "require": { "drupal/core": "*" @@ -1637,7 +1637,7 @@ "dev-3.x": "3.x-dev" }, "drupal": { - "version": "8.x-3.0-alpha3", + "version": "8.x-3.0-alpha4", "datestamp": "1525044484", "security-coverage": { "status": "not-covered", @@ -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" } } }, @@ -3757,18 +3757,18 @@ }, { "name": "drupal/field_group", - "version": "1.0.0-rc6", - "version_normalized": "1.0.0.0-RC6", + "version": "1.0.0", + "version_normalized": "1.0.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/field_group", - "reference": "8.x-1.0-rc6" + "reference": "8.x-1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/field_group-8.x-1.0-rc6.zip", - "reference": "8.x-1.0-rc6", - "shasum": "1ce3c6387e745a04a1099956a5bf39bb50096d44" + "url": "https://ftp.drupal.org/files/projects/field_group-8.x-1.0.zip", + "reference": "8.x-1.0", + "shasum": "e8aa3fae5c3c5dec84644bb577996938d638a611" }, "require": { "drupal/core": "*" @@ -3779,11 +3779,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc6", + "version": "8.x-1.0", "datestamp": "1510352885", "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" } } }, @@ -4219,18 +4219,18 @@ }, { "name": "drupal/inline_entity_form", - "version": "1.0.0-beta1", - "version_normalized": "1.0.0.0-beta1", + "version": "1.0.0-rc1", + "version_normalized": "1.0.0.0-RC1", "source": { "type": "git", "url": "https://git.drupal.org/project/inline_entity_form", - "reference": "8.x-1.0-beta1" + "reference": "8.x-1.0-rc1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/inline_entity_form-8.x-1.0-beta1.zip", - "reference": "8.x-1.0-beta1", - "shasum": "185ffc28a7b68d19cce057855d1c111f1741a3ea" + "url": "https://ftp.drupal.org/files/projects/inline_entity_form-8.x-1.0-rc1.zip", + "reference": "8.x-1.0-rc1", + "shasum": "898789fb6a0662fc2572b87f8d0654a0241473f9" }, "require": { "drupal/core": "~8.0" @@ -4244,11 +4244,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta1", + "version": "8.x-1.0-rc1", "datestamp": "1527030784", "security-coverage": { "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "message": "RC releases are not covered by Drupal security advisories." } } }, @@ -4928,18 +4928,18 @@ }, { "name": "drupal/redis", - "version": "1.0.0-rc2", - "version_normalized": "1.0.0.0-RC2", + "version": "1.0.0", + "version_normalized": "1.0.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/redis", - "reference": "8.x-1.0-rc2" + "reference": "8.x-1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/redis-8.x-1.0-rc2.zip", - "reference": "8.x-1.0-rc2", - "shasum": "7eeaa7078e9df6f4bbea1ca90dbc43aa044da346" + "url": "https://ftp.drupal.org/files/projects/redis-8.x-1.0.zip", + "reference": "8.x-1.0", + "shasum": "98cf460db5c9aaacf3d5c02ac51e0c9e8e85edbb" }, "require": { "drupal/core": "~8.0" @@ -4953,11 +4953,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-rc2", + "version": "8.x-1.0", "datestamp": "1527699484", "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" } } }, @@ -5386,18 +5386,18 @@ }, { "name": "drupal/smtp", - "version": "1.0.0-beta3", - "version_normalized": "1.0.0.0-beta3", + "version": "1.0.0-beta4", + "version_normalized": "1.0.0.0-beta4", "source": { "type": "git", "url": "https://git.drupal.org/project/smtp", - "reference": "8.x-1.0-beta3" + "reference": "8.x-1.0-beta4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/smtp-8.x-1.0-beta3.zip", - "reference": "8.x-1.0-beta3", - "shasum": "a351dac3765b9570933fd07f1e029aa22e5c69b0" + "url": "https://ftp.drupal.org/files/projects/smtp-8.x-1.0-beta4.zip", + "reference": "8.x-1.0-beta4", + "shasum": "80a4df4b2fd2d1b2dc653552d232ce98cb54bb9d" }, "require": { "drupal/core": "~8.0" @@ -5408,7 +5408,7 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta3", + "version": "8.x-1.0-beta4", "datestamp": "1527598380", "security-coverage": { "status": "not-covered", @@ -5797,18 +5797,18 @@ }, { "name": "drupal/views_accordion", - "version": "1.0.0-beta2", - "version_normalized": "1.0.0.0-beta2", + "version": "1.1.0", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/views_accordion", - "reference": "8.x-1.0-beta2" + "reference": "8.x-1.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_accordion-8.x-1.0-beta2.zip", - "reference": "8.x-1.0-beta2", - "shasum": "943ab7b2d530e3885fcaf5cc30817e956c6759ef" + "url": "https://ftp.drupal.org/files/projects/views_accordion-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "2e5e1f758141bf271b26f1e4631211dfaa5dae27" }, "require": { "drupal/core": "*" @@ -5819,11 +5819,11 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.0-beta2", - "datestamp": "1494784384", + "version": "8.x-1.1", + "datestamp": "1521988085", "security-coverage": { - "status": "not-covered", - "message": "Beta releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, @@ -5961,12 +5961,18 @@ }, { "name": "drupal/views_bootstrap", - "version": "dev-3.x", - "version_normalized": "dev-3.x", + "version": "3.1.0", + "version_normalized": "3.1.0.0", "source": { "type": "git", "url": "https://git.drupal.org/project/views_bootstrap", - "reference": "8a8d1add3a575842e33c51a3733b43c24c4bc41f" + "reference": "8.x-3.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/views_bootstrap-8.x-3.1.zip", + "reference": "8.x-3.1", + "shasum": "7fd556457f028fa736d1422a9b5a763566fd7d2e" }, "require": { "drupal/core": "*" @@ -5977,11 +5983,11 @@ "dev-3.x": "3.x-dev" }, "drupal": { - "version": "8.x-3.0+4-dev", - "datestamp": "1523236084", + "version": "8.x-3.1", + "datestamp": "1537360681", "security-coverage": { - "status": "not-covered", - "message": "Dev releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } } }, diff --git a/web/modules/better_exposed_filters/better_exposed_filters.info.yml b/web/modules/better_exposed_filters/better_exposed_filters.info.yml index f2d1d3e77d2d57cd6374a4114b155f260096b768..b5e40eb0a92416a78edb74a30408d963b8254cb9 100644 --- a/web/modules/better_exposed_filters/better_exposed_filters.info.yml +++ b/web/modules/better_exposed_filters/better_exposed_filters.info.yml @@ -4,10 +4,10 @@ description: Provides advanced options (such as links, checkboxes, or jQueryUI w type: module package: Views dependencies: - - views + - drupal:views -# Information added by Drupal.org packaging script on 2017-07-28 -version: '8.x-3.0-alpha3' +# Information added by Drupal.org packaging script on 2018-04-29 +version: '8.x-3.0-alpha4' core: '8.x' project: 'better_exposed_filters' -datestamp: 1501274345 +datestamp: 1525044488 diff --git a/web/modules/better_exposed_filters/better_exposed_filters.module b/web/modules/better_exposed_filters/better_exposed_filters.module index ee7648c1e6dc1f9dd4d7f41da4fe9d00263ffcf1..bf29b7869f642382b3442b71445180786a114c00 100644 --- a/web/modules/better_exposed_filters/better_exposed_filters.module +++ b/web/modules/better_exposed_filters/better_exposed_filters.module @@ -296,8 +296,9 @@ function _bef_preprocess_nested_elements(array &$variables) { */ function bef_sort_combine_submit($form, FormStateInterface $form_state) { $sortBy = $sortOrder = ''; - if ($form_state->hasValue('sort_bef_combine')) { - list($sortBy, $sortOrder) = explode(' ', $form_state->getValue('sort_bef_combine')); + $combined = $form_state->getValue('sort_bef_combine'); + if (!empty($combined)) { + list($sortBy, $sortOrder) = explode(' ', $combined); } $form_state->setValue('sort_by', $sortBy); $form_state->setValue('sort_order', $sortOrder); diff --git a/web/modules/better_exposed_filters/better_exposed_filters.theme b/web/modules/better_exposed_filters/better_exposed_filters.theme index 8d3f4426f9ba1e1e3ab7307f712a28e064c1f3a7..d51a79d5b9afc5bd4795442ef480ed6b9d03c621 100644 --- a/web/modules/better_exposed_filters/better_exposed_filters.theme +++ b/web/modules/better_exposed_filters/better_exposed_filters.theme @@ -87,7 +87,8 @@ function theme_select_as_hidden($vars) { // Check for Taxonomy-based filters. if (is_object($elem)) { $slice = array_slice($elem->option, 0, 1, TRUE); - list($option, $elem) = each($slice); + $option = key($slice); + $elem = current($slice); } // Check for optgroups. Put subelements in the $element_set array and add a @@ -212,7 +213,8 @@ function theme_select_as_tree($vars) { // Check for Taxonomy-based filters. if (is_object($option_label)) { $slice = array_slice($option_label->option, 0, 1, TRUE); - list($option_value, $option_label) = each($slice); + $option_value = key($slice); + $option_label = current($slice); } // Check for optgroups -- which is basically a two-level deep tree. @@ -391,7 +393,8 @@ function theme_select_as_links($vars) { // Check for Taxonomy-based filters. if (is_object($elem)) { $slice = array_slice($elem->option, 0, 1, TRUE); - list($option, $elem) = each($slice); + $option = key($slice); + $elem = current($slice); } // Check for optgroups. Put subelements in the $element_set array and add diff --git a/web/modules/better_exposed_filters/config/schema/better_exposed_filters.exposed_form.schema.yml b/web/modules/better_exposed_filters/config/schema/better_exposed_filters.exposed_form.schema.yml index 167f9d8e39a19f75237c93bb2e19ddc506d4d9c8..2bc504662f3967e5d4b0d877d21fb56189a7a26e 100644 --- a/web/modules/better_exposed_filters/config/schema/better_exposed_filters.exposed_form.schema.yml +++ b/web/modules/better_exposed_filters/config/schema/better_exposed_filters.exposed_form.schema.yml @@ -12,6 +12,9 @@ better_exposed_filters.general: autosubmit: type: boolean label: 'Autosubmit' + autosubmit_exclude_textfield: + type: boolean + label: 'Exclude Textfield' autosubmit_hide: type: boolean label: 'Hide submit button' diff --git a/web/modules/better_exposed_filters/js/bef_select_all_none.js b/web/modules/better_exposed_filters/js/bef_select_all_none.js index f855042a3ed2485d89887214d2b64ad3760d80eb..30d6313c205c73af35d56669493932e8b2f7f610 100644 --- a/web/modules/better_exposed_filters/js/bef_select_all_none.js +++ b/web/modules/better_exposed_filters/js/bef_select_all_none.js @@ -68,7 +68,7 @@ // If all checkboxes are already checked by default then switch to Select None if ($('input:checkbox:checked', this).length == $('input:checkbox', this).length) { - newLink.click(); + newLink.text(selNone); } }) ; diff --git a/web/modules/better_exposed_filters/src/Plugin/views/exposed_form/BetterExposedFilters.php b/web/modules/better_exposed_filters/src/Plugin/views/exposed_form/BetterExposedFilters.php index 72348d0c25323282c0dbff0700d1adc12e47894c..9e52626a7e8d610338204c53a81bd8fe6afa0478 100644 --- a/web/modules/better_exposed_filters/src/Plugin/views/exposed_form/BetterExposedFilters.php +++ b/web/modules/better_exposed_filters/src/Plugin/views/exposed_form/BetterExposedFilters.php @@ -88,6 +88,18 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#default_value' => $existing['general']['autosubmit'], ); + $bef_options['general']['autosubmit_exclude_textfield'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Exclude Textfield'), + '#description' => $this->t('Exclude Textfield from autosubmit. User will have to press enter key or click submit.'), + '#default_value' => $existing['general']['autosubmit_exclude_textfield'], + '#states' => array( + 'visible' => array( + ':input[name="exposed_form_options[bef][general][autosubmit]"]' => array('checked' => TRUE), + ), + ), + ); + $bef_options['general']['autosubmit_hide'] = array( '#type' => 'checkbox', '#title' => $this->t('Hide submit button'), @@ -644,6 +656,14 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { $form['actions']['submit']['#attributes']['data-bef-auto-submit-click'] = ''; $form['#attached']['library'][] = 'better_exposed_filters/auto_submit'; + if (!empty($settings['general']['autosubmit_exclude_textfield'])) { + foreach ($form as &$element) { + if (isset($element['#type']) && $element['#type'] == 'textfield') { + $element['#attributes'] = ['data-bef-auto-submit-exclude' => '']; + } + } + } + if (!empty($settings['general']['autosubmit_hide'])) { $form['actions']['submit']['#attributes']['class'][] = 'js-hide'; } @@ -719,7 +739,7 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // the view results appear on. This can cause problems with // select_as_links options as they will use the wrong path. We // provide a hint for theme functions to correct this. - $form['sort_bef_combine']['#bef_path'] = $this->displayHandler->getUrl(); + $form['sort_bef_combine']['#bef_path'] = $this->getExposedFormActionUrl(); break; case 'default': @@ -747,12 +767,14 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // Leave sort_by and sort_order as separate elements. if ('bef' == $settings['sort']['bef_format']) { foreach (['sort_by', 'sort_order'] as $field) { - $form[$field]['#theme'] = 'bef_radios'; - $form[$field]['#type'] = 'radios'; - if (empty($form[$field]['#process'])) { - $form[$field]['#process'] = array(); + if (!empty($form[$field])) { + $form[$field]['#theme'] = 'bef_radios'; + $form[$field]['#type'] = 'radios'; + if (empty($form[$field]['#process'])) { + $form[$field]['#process'] = array(); + } + $form[$field]['#process'][] = ['\Drupal\Core\Render\Element\Radios', 'processRadios']; } - $form[$field]['#process'][] = ['\Drupal\Core\Render\Element\Radios', 'processRadios']; } } elseif ('bef_links' == $settings['sort']['bef_format']) { @@ -765,9 +787,9 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // view results appear on. This can cause problems with // select_as_links options as they will use the wrong path. We provide // a hint for theme functions to correct this. - $form['sort_by']['#bef_path'] = $this->displayHandler->getUrl(); + $form['sort_by']['#bef_path'] = $this->getExposedFormActionUrl(); if(!empty($form['sort_order'])) { - $form['sort_order']['#bef_path'] = $this->displayHandler->getUrl(); + $form['sort_order']['#bef_path'] = $this->getExposedFormActionUrl(); } } @@ -834,7 +856,7 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // the view results appear on. This can cause problems with // select_as_links options as they will use the wrong path. We // provide a hint for theme functions to correct this. - $form['items_per_page']['#bef_path'] = $this->displayHandler->getUrl(); + $form['items_per_page']['#bef_path'] = $this->getExposedFormActionUrl(); } break; } @@ -1082,7 +1104,7 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // the view results appear on. This can cause problems with // select_as_links options as they will use the wrong path. We provide // a hint for theme functions to correct this. - $form[$field_id]['#bef_path'] = $this->displayHandler->getUrl(); + $form[$field_id]['#bef_path'] = $this->getExposedFormActionUrl(); break; case 'bef_single': @@ -1090,8 +1112,36 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { // Use filter label as checkbox label. $form[$field_id]['#title'] = $filters[$label]->options['expose']['label']; - $form[$field_id]['#return_value'] = 1; $form[$field_id]['#type'] = 'checkbox'; + // Views populates missing values in $form_state['input'] with the + // defaults and a checkbox does not appear in $_GET (or $_POST) so it + // will appear to be missing when a user submits a form. Because of + // this, instead of unchecking the checkbox value will revert to the + // default. More, the default value for select values is reused which + // results in the checkbox always checked. So we need to add a form + // element to see whether the form is submitted or not and then we + // need to look at $_GET directly to see whether the checkbox is + // there. For security reasons, we must not copy the $_GET value. + + // First, let's figure out a short name for the signal element and + // then add it. + if (empty($signal)) { + for ($signal = 'a'; isset($form[$signal]); $signal++); + // This is all the signal element needs. + $form[$signal]['#type'] = 'hidden'; + } + $input = $form_state->getUserInput(); + $value = \Drupal::request()->query->get($field_id); + $checked = isset($input[$signal]) ? isset($value) : $form[$field_id]['#default_value']; + // For security, we check if value is valid and exist. + if ($checked) { + if (!in_array($value, array_keys($form[$field_id]['#options']))) { + $checked = FALSE; + } + } + // Now we know whether the checkbox is checked or not, set #value + // accordingly. + $form[$field_id]['#value'] = $checked ? $value : 0; break; case 'bef': @@ -1167,9 +1217,11 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { $form['#info']["filter-$label"]['description'] = ''; // Check if the operator is exposed for this filter. - if ($this->view->getHandlers('filter')[$field_id]['expose']['use_operator']) { + if (isset($this->view->getHandlers('filter')[$label]) + && $this->view->getHandlers('filter')[$label]['expose']['use_operator'] + ) { // Include the exposed operator with the filter. - $operator_id = $this->view->getHandlers('filter')[$field_id]['expose']['operator_id']; + $operator_id = $this->view->getHandlers('filter')[$label]['expose']['operator_id']; $form[$field_id][$operator_id] = $form[$operator_id]; unset($form[$operator_id]); } @@ -1322,7 +1374,8 @@ protected function rewriteOptions(array $options, $rewriteSettings, $reorder = F } else { if ($return[$index] instanceof \stdClass) { - list($tid, $text) = each($return[$index]->option); + $tid = key($return[$index]->option); + $text = current($return[$index]->option); $return[$index]->option[$tid] = $rewrites[$text]; } else { @@ -1357,7 +1410,8 @@ protected function cleanOptions(array $options) { // need to be converted to text. if (is_object($value) && !is_a($value, 'Drupal\Core\StringTranslation\TranslatableMarkup')) { reset($value->option); - list($key, $val) = each($value->option); + $key = key($value->option); + $val = current($value->option); $clean[$key] = $val; } else { @@ -1471,6 +1525,7 @@ protected function getSettings() { 'allow_secondary' => FALSE, 'secondary_label' => $this->t('Advanced options'), 'autosubmit' => FALSE, + 'autosubmit_exclude_textfield' => FALSE, 'autosubmit_hide' => FALSE, ), 'sort' => array( @@ -1542,4 +1597,22 @@ protected function getSettings() { return $settings; } + /** + * Returns exposed form action URL object. + * + * @return \Drupal\Core\Url + * Url object. + */ + protected function getExposedFormActionUrl() { + if ($this->displayHandler->getRoutedDisplay()) { + return $this->displayHandler->getUrl(); + } + + $request = \Drupal::request(); + $url = Url::createFromRequest($request); + $url->setAbsolute(); + + return $url; + } + } diff --git a/web/modules/better_exposed_filters/tests/bef_test/bef_test.info.yml b/web/modules/better_exposed_filters/tests/bef_test/bef_test.info.yml index 8eefb0ca9e43584dd1650d23545cce1944a1af32..0005b22879d1f6b3a7545fa66cc79ee751f23cce 100644 --- a/web/modules/better_exposed_filters/tests/bef_test/bef_test.info.yml +++ b/web/modules/better_exposed_filters/tests/bef_test/bef_test.info.yml @@ -10,8 +10,8 @@ dependencies: - options - user -# Information added by Drupal.org packaging script on 2017-07-28 -version: '8.x-3.0-alpha3' +# Information added by Drupal.org packaging script on 2018-04-29 +version: '8.x-3.0-alpha4' core: '8.x' project: 'better_exposed_filters' -datestamp: 1501274345 +datestamp: 1525044488 diff --git a/web/modules/better_exposed_filters/tests/bef_test/bef_test.install b/web/modules/better_exposed_filters/tests/bef_test/bef_test.install index e4b21ff30a7830a885ab3d91e277048b36b8d9c6..e43bc07da7f6154a0c554e9c106a8c99337d0ce9 100644 --- a/web/modules/better_exposed_filters/tests/bef_test/bef_test.install +++ b/web/modules/better_exposed_filters/tests/bef_test/bef_test.install @@ -69,7 +69,7 @@ function bef_test_install() { * TID of the newly created term. */ function _bef_test_add_term($name, $parent = 0) { - $term = \Drupal::entityManager()->getStorage('taxonomy_term')->create([ + $term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->create([ 'vid' => 'bef_test_location', 'name' => $name, 'parent' => [$parent], diff --git a/web/modules/better_exposed_filters/tests/src/Unit/BetterExposedFiltersTest.php b/web/modules/better_exposed_filters/tests/src/Unit/BetterExposedFiltersTest.php index 244ce73386709ca38f7a84fd0509415835e2ecb4..cade986a7b1d6e1479c8294eb839541db27c7669 100644 --- a/web/modules/better_exposed_filters/tests/src/Unit/BetterExposedFiltersTest.php +++ b/web/modules/better_exposed_filters/tests/src/Unit/BetterExposedFiltersTest.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Contains \Drupal\better_exposed_filters\Tests\BetterExposedFiltersTest. - */ - namespace Drupal\better_exposed_filters\Tests; use Drupal\better_exposed_filters\Plugin\views\exposed_form\BetterExposedFilters; 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'] diff --git a/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml b/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml index b9c0a03c5fe582f16a54ae6fb75af837ecde6b9e..53f8c6e63f067949f09b67a223b49741bed68da5 100644 --- a/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml +++ b/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml @@ -7,8 +7,8 @@ package: Migrate dependencies: - field_group:field_group -# Information added by Drupal.org packaging script on 2016-11-28 -version: '8.x-1.0-rc6' +# Information added by Drupal.org packaging script on 2017-11-10 +version: '8.x-1.0' core: '8.x' project: 'field_group' -datestamp: 1480365490 +datestamp: 1510352889 diff --git a/web/modules/field_group/contrib/field_group_migrate/tests/src/Kernel/Migrate/d7/MigrateFieldGroupTest.php b/web/modules/field_group/contrib/field_group_migrate/tests/src/Kernel/Migrate/d7/MigrateFieldGroupTest.php index 9a9cbfc132e29b71ea3999d87c4f0fa9da3ed823..ba8e92a54ad79153e3de5daf3f7994e419212b9a 100644 --- a/web/modules/field_group/contrib/field_group_migrate/tests/src/Kernel/Migrate/d7/MigrateFieldGroupTest.php +++ b/web/modules/field_group/contrib/field_group_migrate/tests/src/Kernel/Migrate/d7/MigrateFieldGroupTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\field_group_migrate\Kernel\Migrate\d7; -use Drupal\Core\Entity\Display\EntityDisplayInterface; use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; /** @@ -12,7 +11,10 @@ */ class MigrateFieldGroupTest extends MigrateDrupal7TestBase { - static $modules = [ + /** + * {@inheritdoc} + */ + public static $modules = [ 'field_group', 'field_group_migrate', 'comment', @@ -23,6 +25,8 @@ class MigrateFieldGroupTest extends MigrateDrupal7TestBase { 'taxonomy', 'telephone', 'text', + 'taxonomy', + 'menu_ui', ]; /** @@ -37,6 +41,7 @@ protected function setUp() { $this->executeMigrations([ 'd7_node_type', 'd7_comment_type', + 'd7_taxonomy_vocabulary', 'd7_view_modes', 'd7_field', 'd7_field_instance', @@ -68,13 +73,14 @@ protected function setUp() { * The expected parent name. */ protected function assertEntity($id, $type, $group_name, $expected_label, $expected_weight = 0, $expected_format_settings = [], $expected_format_type = 'tabs', $expected_children = [], $expected_parent_name = '') { - /** @var EntityDisplayInterface $entity */ + /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $entity */ $entity = \Drupal::entityTypeManager() ->getStorage($type) ->load($id); $field_group_settings = $entity->getThirdPartySettings('field_group'); $this->assertNotEmpty($field_group_settings); $this->assertArrayHasKey($group_name, $field_group_settings); + $field_group = $field_group_settings[$group_name]; $this->assertEquals($expected_label, $field_group['label']); $this->assertEquals($expected_format_settings, $field_group['format_settings']); @@ -94,7 +100,7 @@ public function testFieldGroup() { $this->assertEntity('node.article.teaser', 'entity_view_display', 'group_article', 'htab group', 2, ['classes' => 'htab-group'], 'tab', ['field_image']); // Check an entity_view_display without a field group. - /** @var EntityDisplayInterface $entity */ + /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $entity */ $entity = \Drupal::entityTypeManager() ->getStorage('entity_view_display') ->load('node.page.teaser'); diff --git a/web/modules/field_group/contrib/field_group_migrate/tests/src/Unit/Migrate/d7/FieldGroupTest.php b/web/modules/field_group/contrib/field_group_migrate/tests/src/Unit/Migrate/d7/FieldGroupTest.php index 34449fbdf20065024761e6096e88b6df69aa9b55..1150015080b62a101be0168c7b117885f4d07ca6 100644 --- a/web/modules/field_group/contrib/field_group_migrate/tests/src/Unit/Migrate/d7/FieldGroupTest.php +++ b/web/modules/field_group/contrib/field_group_migrate/tests/src/Unit/Migrate/d7/FieldGroupTest.php @@ -91,4 +91,12 @@ protected function setUp() { parent::setUp(); } + /** + * {@inheritdoc} + */ + public function providerSource() { + // @TODO FIX. + return []; + } + } diff --git a/web/modules/field_group/field_group.info.yml b/web/modules/field_group/field_group.info.yml index e747db2d7c1367e6214acb73378c5cc0f71939a4..5654d7f35f14003a3626acea8d9ebf83352a04ca 100644 --- a/web/modules/field_group/field_group.info.yml +++ b/web/modules/field_group/field_group.info.yml @@ -6,8 +6,8 @@ package : Fields dependencies: - drupal:field -# Information added by Drupal.org packaging script on 2016-11-28 -version: '8.x-1.0-rc6' +# Information added by Drupal.org packaging script on 2017-11-10 +version: '8.x-1.0' core: '8.x' project: 'field_group' -datestamp: 1480365490 +datestamp: 1510352889 diff --git a/web/modules/field_group/field_group.module b/web/modules/field_group/field_group.module index fdf4c6d6394e955653a8e88f72876c336a80dc7a..e85c489e252ccf2500cd4371e1e1ce2a6255e0c5 100644 --- a/web/modules/field_group/field_group.module +++ b/web/modules/field_group/field_group.module @@ -7,7 +7,6 @@ use Drupal\Core\Entity\ContentEntityFormInterface; use Drupal\Core\Entity\Display\EntityDisplayInterface; -use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\EntityInterface; @@ -154,13 +153,16 @@ function field_group_field_info_max_weight($entity_type, $bundle, $context, $con */ function field_group_form_alter(array &$form, FormStateInterface $form_state) { - if ($form_state->getFormObject() instanceof ContentEntityFormInterface && !$form_state->getFormObject() instanceof ConfirmFormInterface) { + $form_object = $form_state->getFormObject(); + if ($form_object instanceof ContentEntityFormInterface && !$form_object instanceof ConfirmFormInterface) { /** * @var EntityFormDisplayInterface $form_display */ - if ($form_display = $form_state->getStorage()['form_display']) { - $entity = $form_state->getFormObject()->getEntity(); + $storage = $form_state->getStorage(); + if (!empty($storage['form_display'])) { + $form_display = $storage['form_display']; + $entity = $form_object->getEntity(); $context = array( 'entity_type' => $entity->getEntityTypeId(), @@ -189,7 +191,7 @@ function field_group_inline_entity_form_entity_form_alter(&$entity_form, FormSta 'bundle' => $entity_form['#entity']->bundle(), 'entity' => $entity_form['#entity'], 'display_context' => 'form', - 'mode' => 'default', + 'mode' => isset($entity_form['#form_mode']) ? $entity_form['#form_mode'] : 'default', ]; field_group_attach_groups($entity_form, $context); @@ -348,7 +350,7 @@ function field_group_attach_groups(&$element, $context) { * @see field_group_theme_registry_alter * @see field_group_fields_nest() * @param $vars preprocess vars or form element - * @param $context The display context (form or view) + * @param $context The display context (entity type, form or view) * @return $element Array with re-arranged fields in groups. */ function field_group_build_entity_groups(&$vars, $context = 'view') { @@ -365,7 +367,6 @@ function field_group_build_entity_groups(&$vars, $context = 'view') { $element = &$vars['content']; } else { - if ($context === 'eck_entity') { $element = &$vars['entity']; } @@ -383,7 +384,7 @@ function field_group_build_entity_groups(&$vars, $context = 'view') { } // Nest the fields in the corresponding field groups. - field_group_fields_nest($element, $nest_vars); + field_group_fields_nest($element, $nest_vars, $context); // Allow others to alter the pre_rendered build. Drupal::moduleHandler()->alter('field_group_build_pre_render', $element); @@ -401,11 +402,9 @@ function field_group_build_entity_groups(&$vars, $context = 'view') { // Put groups inside content if we are rendering an entity_view. foreach ($element['#fieldgroups'] as $group) { if (!empty($element[$group->group_name])) { - if (isset($vars['content'])) { - $vars['content'][$group->group_name] = $element[$group->group_name]; - } - elseif (isset($vars['user_profile'])) { - $vars['user_profile'][$group->group_name] = $element[$group->group_name]; + $key = field_group_get_content_element_key($context); + if (isset($vars[$key])) { + $vars[$key][$group->group_name] = $element[$group->group_name]; } } } @@ -423,8 +422,10 @@ function field_group_build_entity_groups(&$vars, $context = 'view') { * The current element to analyse for grouping. * @param Array $vars * Rendering vars from the entity being viewed. + * @param Array $context + * The display context (entity type, form or view). */ -function field_group_fields_nest(&$element, &$vars = NULL) { +function field_group_fields_nest(&$element, &$vars = NULL, $context = NULL) { // Create all groups and keep a flat list of references to these groups. $group_references = array(); @@ -454,14 +455,11 @@ function field_group_fields_nest(&$element, &$vars = NULL) { // Entity being viewed if ($vars) { - // If not a group, check vars['content'] for empty field. - if (!isset($element['#fieldgroups'][$child_name]) && isset($vars['content'][$child_name])) { - $group_references[$parent_name][$child_name] = $vars['content'][$child_name]; - unset($vars['content'][$child_name]); - } - elseif (!isset($element['#fieldgroups'][$child_name]) && isset($vars['user_profile'][$child_name])) { - $group_references[$parent_name][$child_name] = $vars['user_profile'][$child_name]; - unset($vars['user_profile'][$child_name]); + // If not a group, check the content variable for empty field. + $key = field_group_get_content_element_key($context); + if (!isset($element['#fieldgroups'][$child_name]) && isset($vars[$key][$child_name])) { + $group_references[$parent_name][$child_name] = $vars[$key][$child_name]; + unset($vars[$key][$child_name]); } // If this is a group, we have to use a reference to keep the reference // list intact (but if it is a field we don't mind). @@ -531,16 +529,46 @@ function field_group_pre_render(& $element, $group, & $rendering_object) { } +/** + * Provides the content element key for a display context. + * + * This allows entity modules to specify their content element for field group + * support, or other modules to add entity module support. + * + * @param $context + * The display context (entity type, form or view). + */ +function field_group_get_content_element_key($context = 'default') { + $keys = &drupal_static('field_group_content_elements'); + if (!isset($keys)) { + $keys['default'] = 'content'; + // Allow other modules to alter the array. + Drupal::moduleHandler()->alter('field_group_content_element_keys', $keys); + } + + // Check if we have a specific content element key for this entity type. + $key = $keys['default']; + if (isset($keys[$context])) { + $key = $keys[$context]; + } + return $key; +} + + /** * Saves a group definition. - * This function is called by ctools export when calls are made - * through ctools_export_crud_save(). * - * @param $group + * This function is called by ctools export when calls are made through + * ctools_export_crud_save(). It's also used as an api method to add groups to a + * display. + * + * @param \stdClass $group * A group definition. - * @param $display + * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display * The display to update if known. - * @return EntityDisplayInterface || NULL + * + * @return \Drupal\Core\Entity\Display\EntityDisplayInterface|NULL + * The updated entity display. */ function field_group_group_save($group, $display = NULL) { if ($display === NULL) { @@ -554,7 +582,6 @@ function field_group_group_save($group, $display = NULL) { // If no display was found. It doesn't exist yet, create it. if (!isset($display)) { - if ($group->context == 'form') { $display = EntityFormDisplay::create(array( 'targetEntityType' => $group->entity_type, @@ -572,9 +599,6 @@ function field_group_group_save($group, $display = NULL) { } - /** - * @var $display \Drupal\Core\Entity\Display\EntityDisplayInterface - */ if (isset($display)) { $data = (array) $group; unset($data['group_name'], $data['entity_type'], $data['bundle'], $data['mode'], $data['form'], $data['context']); @@ -638,7 +662,7 @@ function field_group_info_groups($entity_type, $bundle, $context, $mode) { $data = $display->getThirdPartySettings('field_group'); } $groups = array(); - if (isset($data)) { + if (isset($data) && is_array($data)) { foreach ($data as $group_name => $definition) { $definition += array( 'group_name' => $group_name, diff --git a/web/modules/field_group/formatters/html_element/html-element.js b/web/modules/field_group/formatters/html_element/html-element.js index 070606744db87e5dd917307087f9db3b6dc1356e..d1f1922a92874794f44f0f07c44bad8f185f460a 100644 --- a/web/modules/field_group/formatters/html_element/html-element.js +++ b/web/modules/field_group/formatters/html_element/html-element.js @@ -31,7 +31,6 @@ // Turn the legend into a clickable link, but retain span.field-group-format-toggler // for CSS positioning. - var $toggler = $('.field-group-toggler:first', $wrapper); var $link = $('<a class="field-group-title" href="#"></a>'); $link.prepend($toggler.contents()); diff --git a/web/modules/field_group/includes/helpers.inc b/web/modules/field_group/includes/helpers.inc index 7d3b1b7c715657e4ac47bb03da27928cf5b1b4ce..a6735f094d99d292df70acb218fd976d6766ae83 100644 --- a/web/modules/field_group/includes/helpers.inc +++ b/web/modules/field_group/includes/helpers.inc @@ -46,7 +46,7 @@ function field_group_validate_css_class($element, FormStateInterface $form_state $form_state_values = $form_state->getValues(); $plugin_name = $form_state->get('plugin_settings_edit'); if (!empty($form_state_values['fields'][$plugin_name]['settings_edit_form']['settings']['classes']) && !preg_match('!^[A-Za-z0-9-_ ]+$!', $form_state_values['fields'][$plugin_name]['settings_edit_form']['settings']['classes'])) { - Drupal::formBuilder()->setError($element, $form_state, t('The css class must include only letters, numbers, underscores and dashes.')); + $form_state->setError($element, $form_state, t('The css class must include only letters, numbers, underscores and dashes.')); } } diff --git a/web/modules/field_group/src/Element/HtmlElement.php b/web/modules/field_group/src/Element/HtmlElement.php index 43f34ab5d1a99715841586665fa4d4f760edf44e..e58d6e1150b32df523e185ba4dde00cbddc234cd 100644 --- a/web/modules/field_group/src/Element/HtmlElement.php +++ b/web/modules/field_group/src/Element/HtmlElement.php @@ -44,8 +44,10 @@ public static function processHtmlElement(&$element, FormStateInterface $form_st if (!empty($element['#effect']) && $element['#effect'] !== 'none') { $element['#attached']['library'][] = 'field_group/formatter.html_element'; + $element['#attached']['library'][] = 'field_group/core'; // Add the required classes for the js. + $element['#attributes']['class'][] = 'field-group-html-element'; $element['#attributes']['class'][] = 'fieldgroup-collapsible'; $element['#attributes']['class'][] = 'effect-' . $element['#effect']; if (!empty($element['#speed'])) { diff --git a/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/Fieldset.php b/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/Fieldset.php index f31e9c8df90b7c8f53adca56f58d07cc495aeffe..ecbf217dacc81815d00933fac0a7a6a8c7479c64 100644 --- a/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/Fieldset.php +++ b/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/Fieldset.php @@ -36,6 +36,12 @@ public function preRender(&$element, $rendering_object) { $element += array( '#description' => $this->getSetting('description'), ); + + // When a fieldset has a description, an id is required. + if (!$this->getSetting('id')) { + $element['#id'] = Html::getId($this->group->group_name); + } + } if ($this->getSetting('id')) { diff --git a/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/HtmlElement.php b/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/HtmlElement.php index bd87df1cd027d3179cb61199deac7bde9164e469..41f2d7e4e37f1aae5bc659c6051b4584ed8ae457 100644 --- a/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/HtmlElement.php +++ b/web/modules/field_group/src/Plugin/field_group/FieldGroupFormatter/HtmlElement.php @@ -75,6 +75,7 @@ public function preRender(&$element, $rendering_object) { \Drupal\field_group\Element\HtmlElement::processHtmlElement($element, $form_state); if ($this->getSetting('required_fields')) { + $element['#attributes']['class'][] = 'field-group-html-element'; $element['#attached']['library'][] = 'field_group/formatter.html_element'; $element['#attached']['library'][] = 'field_group/core'; } diff --git a/web/modules/field_group/src/Tests/ManageDisplayTest.php b/web/modules/field_group/src/Tests/ManageDisplayTest.php deleted file mode 100644 index 8e6155cde6c79962674e20e5a223abc935c37bbe..0000000000000000000000000000000000000000 --- a/web/modules/field_group/src/Tests/ManageDisplayTest.php +++ /dev/null @@ -1,148 +0,0 @@ -<?php - -namespace Drupal\field_group\Tests; - -use Drupal\Component\Utility\Unicode; -use Drupal\Core\Entity\Entity\EntityFormDisplay; -use Drupal\simpletest\WebTestBase; - -/** - * Tests for managing display of entities. - * - * @group field_group - */ -class ManageDisplayTest extends WebTestBase { - - use FieldGroupTestTrait; - - /** - * Modules to enable. - * - * @var array - */ - public static $modules = array('node', 'field_ui', 'field_group'); - - function setUp() { - - parent::setUp(); - - // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access')); - $this->drupalLogin($admin_user); - - // Create content type, with underscores. - $type_name = Unicode::strtolower($this->randomMachineName(8)) . '_test'; - $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); - $this->type = $type->id(); - - } - - /** - * Test the creation a group on the article content type. - */ - function testCreateGroup() { - - // Create random group name. - $this->group_label = $this->randomString(8); - $this->group_name_input = Unicode::strtolower($this->randomMachineName()); - $this->group_name = 'group_' . $this->group_name_input; - $this->group_formatter = 'details'; - - // Setup new group. - $group = array( - 'group_formatter' => $this->group_formatter, - 'label' => $this->group_label, - ); - - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/add-group', $group, t('Save and continue')); - $this->assertText('Machine-readable name field is required.'); - - // Setup new group. - $group = array( - 'group_formatter' => $this->group_formatter, - 'label' => $this->group_label, - 'group_name' => $this->group_name_input, - ); - - // Add new group on the 'Manage form display' page. - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/add-group', $group, t('Save and continue')); - $this->drupalPostForm(NULL, [], t('Create group')); - - $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.')); - - // Test if group is in the $groups array. - $this->group = field_group_load_field_group($this->group_name, 'node', $this->type, 'form', 'default'); - $this->assertNotNull($this->group, t('Group was loaded')); - - // Add new group on the 'Manage display' page. - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/add-group', $group, t('Save and continue')); - $this->drupalPostForm(NULL, [], t('Create group')); - - $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.')); - - // Test if group is in the $groups array. - $loaded_group = field_group_load_field_group($this->group_name, 'node', $this->type, 'view', 'default'); - $this->assertNotNull($loaded_group, t('Group was loaded')); - } - - /** - * Delete a group. - */ - function testDeleteGroup() { - - $data = array( - 'format_type' => 'fieldset', - 'label' => 'testing', - ); - - $group = $this->createGroup('node', $this->type, 'form', 'default', $data); - - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/' . $group->group_name . '/delete', array(), t('Delete')); - $this->assertRaw(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type)), t('Group removal message displayed on screen.')); - - $display = EntityFormDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode); - $data = $display->getThirdPartySettings('field_group'); - - // Test that group is not in the $groups array. - \Drupal::entityTypeManager()->getStorage('entity_form_display')->resetCache(); - $loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default'); - $this->assertNull($loaded_group, t('Group not found after deleting')); - - $data = array( - 'format_type' => 'fieldset', - 'label' => 'testing', - ); - - $group = $this->createGroup('node', $this->type, 'view', 'default', $data); - - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/' . $group->group_name . '/delete', array(), t('Delete')); - $this->assertRaw(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type)), t('Group removal message displayed on screen.')); - - // Test that group is not in the $groups array. - \Drupal::entityTypeManager()->getStorage('entity_view_display')->resetCache(); - $loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'view', 'default'); - $this->assertNull($loaded_group, t('Group not found after deleting')); - } - - /** - * Nest a field underneath a group. - */ - function testNestField() { - - $data = array( - 'format_type' => 'fieldset', - ); - - $group = $this->createGroup('node', $this->type, 'form', 'default', $data); - - $edit = array( - 'fields[body][parent]' => $group->group_name, - ); - $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, t('Save')); - $this->assertRaw(t('Your settings have been saved.'), t('Settings saved')); - - $group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default'); - $this->assertTrue(in_array('body', $group->children), t('Body is a child of %group', array('%group' => $group->group_name))); - } - -} diff --git a/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml b/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml index 598fab34ed86f8148b4e349aa18f473b0d88f43c..4113863e66716255e9603dcae02378f929d05a4b 100644 --- a/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml +++ b/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml @@ -4,8 +4,8 @@ description: 'Test module for Field Group' package: 'Fields' type: module hidden: TRUE -# Information added by Drupal.org packaging script on 2016-11-28 -version: '8.x-1.0-rc6' +# Information added by Drupal.org packaging script on 2017-11-10 +version: '8.x-1.0' core: '8.x' project: 'field_group' -datestamp: 1480365490 +datestamp: 1510352889 diff --git a/web/modules/field_group/src/Tests/EntityDisplayTest.php b/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php similarity index 79% rename from web/modules/field_group/src/Tests/EntityDisplayTest.php rename to web/modules/field_group/tests/src/Functional/EntityDisplayTest.php index 0bd204ff0efe7ccd0d672ef5aad0beaed8de0efb..5fc5da9ef12a6b8d672560507cbf305f0ed3df99 100644 --- a/web/modules/field_group/src/Tests/EntityDisplayTest.php +++ b/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php @@ -1,48 +1,76 @@ <?php -namespace Drupal\field_group\Tests; +namespace Drupal\Tests\field_group\Functional; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Tests for displaying entities. * * @group field_group */ -class EntityDisplayTest extends WebTestBase { +class EntityDisplayTest extends BrowserTestBase { use FieldGroupTestTrait; /** - * Modules to enable. + * {@inheritdoc} + */ + public static $modules = [ + 'node', + 'field_test', + 'field_ui', + 'field_group', + 'field_group_test', + ]; + + /** + * The node type id. * - * @var array + * @var string */ - public static $modules = array('node', 'field_test', 'field_ui', 'field_group', 'field_group_test'); + protected $type; - function setUp() { + /** + * A node to use for testing. + * + * @var \Drupal\node\NodeInterface + */ + protected $node; + /** + * {@inheritdoc} + */ + public function setUp() { parent::setUp(); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access')); + $admin_user = $this->drupalCreateUser([ + 'access content', + 'administer content types', + 'administer node fields', + 'administer node form display', + 'administer node display', + 'bypass node access' + ]); $this->drupalLogin($admin_user); // Create content type, with underscores. $type_name = strtolower($this->randomMachineName(8)) . '_test'; $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); $this->type = $type->id(); - $display = entity_get_display('node', $type_name, 'default'); + /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ + $display = \Drupal::entityTypeManager() + ->getStorage('entity_view_display') + ->load('node' . '.' . $type_name . '.' . 'default'); // Create a node. $node_values = array('type' => $type_name); // Create test fields. - $test_fields = array('field_test', 'field_test_2', 'field_no_access'); - foreach ($test_fields as $field_name) { - + foreach (['field_test', 'field_test_2', 'field_no_access'] as $field_name) { $field_storage = FieldStorageConfig::create([ 'field_name' => $field_name, 'entity_type' => 'node', @@ -74,15 +102,12 @@ function setUp() { // Save display + create node. $display->save(); $this->node = $this->drupalCreateNode($node_values); - } /** - * Test if an fieldgroup that only contains fields - * that has no access is not shown. + * Test field access for field groups. */ - function testFieldAccess() { - + public function testFieldAccess() { $data = array( 'label' => 'Wrapper', 'children' => array( @@ -105,8 +130,7 @@ function testFieldAccess() { /** * Test the html element formatter. */ - function testHtmlElement() { - + public function testHtmlElement() { $data = array( 'weight' => '1', 'children' => array( @@ -128,11 +152,11 @@ function testHtmlElement() { $this->drupalGet('node/' . $this->node->id()); // Test group ids and classes. - $this->assertFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, t('Wrapper id set on wrapper div')); - $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Test class set on wrapper div') . 'class="' . $group->group_name . ' test-class'); + $this->assertFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, 'Wrapper id set on wrapper div'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, 'Test class set on wrapper div, class="' . $group->group_name . ' test-class'); // Test group label. - $this->assertNoRaw('<h3><span>' . $data['label'] . '</span></h3>', t('Label is not shown')); + $this->assertNoRaw('<h3><span>' . $data['label'] . '</span></h3>'); // Set show label to true. $group->format_settings['show_label'] = TRUE; @@ -140,7 +164,7 @@ function testHtmlElement() { field_group_group_save($group); $this->drupalGet('node/' . $this->node->id()); - $this->assertRaw('<h3>' . $data['label'] . '</h3>', t('Label is shown')); + $this->assertRaw('<h3>' . $data['label'] . '</h3>'); // Change to collapsible with blink effect. $group->format_settings['effect'] = 'blink'; @@ -148,15 +172,14 @@ function testHtmlElement() { field_group_group_save($group); $this->drupalGet('node/' . $this->node->id()); - $this->assertFieldByXPath("//div[contains(@class, 'speed-fast')]", NULL, t('Speed class is set')); - $this->assertFieldByXPath("//div[contains(@class, 'effect-blink')]", NULL, t('Effect class is set')); + $this->assertFieldByXPath("//div[contains(@class, 'speed-fast')]", NULL, 'Speed class is set'); + $this->assertFieldByXPath("//div[contains(@class, 'effect-blink')]", NULL, 'Effect class is set'); } /** * Test the fieldset formatter. */ - function testFieldset() { - + public function testFieldset() { $data = array( 'weight' => '1', 'children' => array( @@ -171,21 +194,19 @@ function testFieldset() { 'description' => 'test description', ), ); - $group = $this->createGroup('node', $this->type, 'view', 'default', $data); + $this->createGroup('node', $this->type, 'view', 'default', $data); $this->drupalGet('node/' . $this->node->id()); // Test group ids and classes. - $this->assertFieldByXPath("//fieldset[contains(@id, 'fieldset-id')]", NULL, t('Correct id set on the fieldset')); - $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class')]", NULL, t('Test class set on the fieldset')); - + $this->assertFieldByXPath("//fieldset[contains(@id, 'fieldset-id')]", NULL, 'Correct id set on the fieldset'); + $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class')]", NULL, 'Test class set on the fieldset'); } /** * Test the tabs formatter. */ - function testTabs() { - + public function testTabs() { $data = array( 'label' => 'Tab 1', 'weight' => '1', @@ -237,9 +258,9 @@ function testTabs() { $this->drupalGet('node/' . $this->node->id()); // Test properties. - $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper')); - $this->assertFieldByXPath("//details[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab')); - $this->assertRaw('<div class="details-description">description of second tab</div>', t('Description of tab is shown')); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, 'Test class set on tabs wrapper'); + $this->assertFieldByXPath("//details[contains(@class, 'test-class-2')]", NULL, 'Test class set on second tab'); + $this->assertRaw('<div class="details-description">description of second tab</div>'); // Test if correctly nested. $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class')]", NULL, 'First tab is displayed as child of the wrapper.'); @@ -256,14 +277,12 @@ function testTabs() { // Test if it's a horizontal tab. $this->assertFieldByXPath('//div[@data-horizontal-tabs-panes=""]', NULL, 'Tabs are shown horizontal.'); - } /** * Test the accordion formatter. */ - function testAccordion() { - + public function testAccordion() { $data = array( 'label' => 'Accordion item 1', 'weight' => '1', @@ -278,7 +297,6 @@ function testAccordion() { ), ); $first_item = $this->createGroup('node', $this->type, 'view', 'default', $data); - $first_item_id = 'node_article_full_' . $first_item->group_name; $data = array( 'label' => 'Accordion item 2', @@ -294,7 +312,6 @@ function testAccordion() { ), ); $second_item = $this->createGroup('node', $this->type, 'view', 'default', $data); - $second_item_id = 'node_article_full_' . $second_item->group_name; $data = array( 'label' => 'Accordion', @@ -310,16 +327,16 @@ function testAccordion() { 'effect' => 'bounceslide' ), ); - $accordion = $this->createGroup('node', $this->type, 'view', 'default', $data); + $this->createGroup('node', $this->type, 'view', 'default', $data); $this->drupalGet('node/' . $this->node->id()); // Test properties. - $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper')); - $this->assertFieldByXPath("//div[contains(@class, 'effect-bounceslide')]", NULL, t('Correct effect is set on the accordion')); - $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Accordion item with test-class is shown')); - $this->assertFieldByXPath("//div[contains(@class, 'test-class-2')]", NULL, t('Accordion item with test-class-2 is shown')); - $this->assertFieldByXPath("//h3[contains(@class, 'field-group-accordion-active')]", NULL, t('Accordion item 2 was set active')); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, 'Test class set on tabs wrapper'); + $this->assertFieldByXPath("//div[contains(@class, 'effect-bounceslide')]", NULL, 'Correct effect is set on the accordion'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, 'Accordion item with test-class is shown'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-2')]", NULL, 'Accordion item with test-class-2 is shown'); + $this->assertFieldByXPath("//h3[contains(@class, 'field-group-accordion-active')]", NULL, 'Accordion item 2 was set active'); // Test if correctly nested $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]", NULL, 'First item is displayed as child of the wrapper.'); diff --git a/web/modules/field_group/src/Tests/FieldGroupTestTrait.php b/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php similarity index 70% rename from web/modules/field_group/src/Tests/FieldGroupTestTrait.php rename to web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php index 7feb156618ecfb7a9302a78adcdf99463edfdd34..c622a10be2e40f41c87a43ecb011b2d3bc9cbc52 100644 --- a/web/modules/field_group/src/Tests/FieldGroupTestTrait.php +++ b/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\field_group\Tests; +namespace Drupal\Tests\field_group\Functional; use Drupal\Component\Utility\Unicode; @@ -11,10 +11,22 @@ trait FieldGroupTestTrait { /** * Create a new group. + * + * @param string $entity_type + * The entity type as string. + * @param string $bundle + * The bundle of the enity type + * @param string $context + * The context for the group. + * @param string $mode + * The view/form mode. * @param array $data * Data for the field group. + * + * @return \stdClass + * An object that represents the field group. */ - function createGroup($entity_type, $bundle, $context, $mode, array $data) { + protected function createGroup($entity_type, $bundle, $context, $mode, array $data) { if (!isset($data['format_settings'])) { $data['format_settings'] = array(); @@ -36,6 +48,7 @@ function createGroup($entity_type, $bundle, $context, $mode, array $data) { 'label' => isset($data['label']) ? $data['label'] : $this->randomString(8), 'format_type' => $data['format_type'], 'format_settings' => $data['format_settings'], + 'region' => 'content', ); field_group_group_save($field_group); diff --git a/web/modules/field_group/src/Tests/FieldGroupWithoutFieldUiTest.php b/web/modules/field_group/tests/src/Functional/FieldGroupWithoutFieldUiTest.php similarity index 56% rename from web/modules/field_group/src/Tests/FieldGroupWithoutFieldUiTest.php rename to web/modules/field_group/tests/src/Functional/FieldGroupWithoutFieldUiTest.php index f9bd3b1fff3309c38ca5ee8c8179c46b37585be9..2beae972bf2c58bf2f3b1bf87e9befaf7e3b15c0 100644 --- a/web/modules/field_group/src/Tests/FieldGroupWithoutFieldUiTest.php +++ b/web/modules/field_group/tests/src/Functional/FieldGroupWithoutFieldUiTest.php @@ -1,19 +1,25 @@ <?php -namespace Drupal\field_group\Tests; +namespace Drupal\Tests\field_group\Functional; use Drupal\Core\Url; -use Drupal\simpletest\WebTestBase; +use Drupal\Tests\BrowserTestBase; /** * Test field_group without field_ui. * * @group field_group */ -class FieldGroupWithoutFieldUiTest extends WebTestBase { +class FieldGroupWithoutFieldUiTest extends BrowserTestBase { - protected static $modules = ['field_group', 'block']; + /** + * {@inheritdoc} + */ + public static $modules = ['field_group', 'block']; + /** + * Test that local actions show up without field ui enabled. + */ public function testLocalActions() { // Local actions of field_group should not depend on field_ui // @see https://www.drupal.org/node/2719569 diff --git a/web/modules/field_group/tests/src/Functional/ManageDisplayTest.php b/web/modules/field_group/tests/src/Functional/ManageDisplayTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e89ee4600f0889641a59ece38bdc8e6f7e819fb5 --- /dev/null +++ b/web/modules/field_group/tests/src/Functional/ManageDisplayTest.php @@ -0,0 +1,158 @@ +<?php + +namespace Drupal\Tests\field_group\Functional; + +use Drupal\Component\Utility\Unicode; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests for managing display of entities. + * + * @group field_group + */ +class ManageDisplayTest extends BrowserTestBase { + + use FieldGroupTestTrait; + + /** + * {@inheritdoc} + */ + public static $modules = array('node', 'field_ui', 'field_group'); + + /** + * Content type id. + * + * @var string + */ + protected $type; + + protected $strictConfigSchema = FALSE; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + // Create test user. + $admin_user = $this->drupalCreateUser([ + 'access content', + 'administer content types', + 'administer node fields', + 'administer node form display', + 'administer node display', + 'bypass node access', + ]); + $this->drupalLogin($admin_user); + + // Create content type, with underscores. + $type_name = 'll4ma_test'; + $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]); + $this->type = $type->id(); + + } + + /** + * Test the creation a group on the article content type. + */ + public function testCreateGroup() { + // Create random group name. + $group_label = $this->randomString(8); + $group_name_input = Unicode::strtolower($this->randomMachineName()); + $group_name = 'group_' . $group_name_input; + $group_formatter = 'details'; + + // Setup new group. + $group = array( + 'group_formatter' => $group_formatter, + 'label' => $group_label, + ); + + $add_form_display = 'admin/structure/types/manage/' . $this->type . '/form-display/add-group'; + $this->drupalPostForm($add_form_display, $group, 'Save and continue'); + $this->assertSession()->pageTextContains('Machine-readable name field is required.'); + + // Add required field to form. + $group['group_name'] = $group_name_input; + + // Add new group on the 'Manage form display' page. + $this->drupalPostForm($add_form_display, $group, 'Save and continue'); + $this->drupalPostForm(NULL, [], 'Create group'); + + $this->assertSession()->responseContains(t('New group %label successfully created.', array('%label' => $group_label))); + + // Test if group is in the $groups array. + $this->group = field_group_load_field_group($group_name, 'node', $this->type, 'form', 'default'); + $this->assertNotNull($group, 'Group was loaded'); + + // Add new group on the 'Manage display' page. + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/add-group', $group, 'Save and continue'); + $this->drupalPostForm(NULL, [], 'Create group'); + + $this->assertSession()->responseContains(t('New group %label successfully created.', array('%label' => $group_label))); + + // Test if group is in the $groups array. + $loaded_group = field_group_load_field_group($group_name, 'node', $this->type, 'view', 'default'); + $this->assertNotNull($loaded_group, 'Group was loaded'); + } + + /** + * Delete a group. + */ + public function testDeleteGroup() { + $data = array( + 'format_type' => 'fieldset', + 'label' => 'testing', + ); + + $group = $this->createGroup('node', $this->type, 'form', 'default', $data); + + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/' . $group->group_name . '/delete', array(), 'Delete'); + $this->assertSession()->responseContains(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type))); + + // Test that group is not in the $groups array. + \Drupal::entityTypeManager() + ->getStorage('entity_form_display') + ->resetCache(); + $loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default'); + $this->assertNull($loaded_group, 'Group not found after deleting'); + + $data = array( + 'format_type' => 'fieldset', + 'label' => 'testing', + ); + + $group = $this->createGroup('node', $this->type, 'view', 'default', $data); + + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/' . $group->group_name . '/delete', array(), t('Delete')); + $this->assertRaw(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type))); + + // Test that group is not in the $groups array. + \Drupal::entityTypeManager() + ->getStorage('entity_view_display') + ->resetCache(); + $loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'view', 'default'); + $this->assertNull($loaded_group, 'Group not found after deleting'); + } + + /** + * Nest a field underneath a group. + */ + public function testNestField() { + $data = array( + 'format_type' => 'fieldset', + ); + + $group = $this->createGroup('node', $this->type, 'form', 'default', $data); + + $edit = array( + 'fields[body][parent]' => $group->group_name, + ); + $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save'); + $this->assertRaw('Your settings have been saved.'); + + $group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default'); + $this->assertTrue(in_array('body', $group->children), t('Body is a child of %group', array('%group' => $group->group_name))); + } + +} diff --git a/web/modules/field_group/tests/src/FunctionalJavascript/FieldGroupUiTest.php b/web/modules/field_group/tests/src/FunctionalJavascript/FieldGroupUiTest.php index 13d7e620eef5d91295e5d3b041f5637bc783a359..75d74ea834348921f1d0ff43eaf1ed14fec12e73 100644 --- a/web/modules/field_group/tests/src/FunctionalJavascript/FieldGroupUiTest.php +++ b/web/modules/field_group/tests/src/FunctionalJavascript/FieldGroupUiTest.php @@ -3,11 +3,10 @@ namespace Drupal\Tests\field_group\FunctionalJavascript; use Drupal\Component\Utility\Unicode; -use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; -use Drupal\field_group\Tests\FieldGroupTestTrait; use Drupal\FunctionalJavascriptTests\JavascriptTestBase; use Drupal\node\Entity\NodeType; +use Drupal\Tests\field_group\Functional\FieldGroupTestTrait; /** * Test field_group user interface. @@ -34,7 +33,14 @@ public function setUp() { parent::setUp(); // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access')); + $admin_user = $this->drupalCreateUser([ + 'access content', + 'administer content types', + 'administer node fields', + 'administer node form display', + 'administer node display', + 'bypass node access', + ]); $this->drupalLogin($admin_user); // Create content type, with underscores. @@ -47,6 +53,9 @@ public function setUp() { $this->nodeType = $type->id(); } + /** + * Test creation and editing trough the UI. + */ public function testCreateAndEdit() { foreach (['test_1', 'test_2'] as $name) { $group = array( @@ -56,8 +65,8 @@ public function testCreateAndEdit() { ); // Add new group on the 'Manage form display' page. - $this->drupalPostForm('admin/structure/types/manage/' . $this->nodeType . '/form-display/add-group', $group, t('Save and continue')); - $this->drupalPostForm(NULL, [], t('Create group')); + $this->drupalPostForm('admin/structure/types/manage/' . $this->nodeType . '/form-display/add-group', $group, 'Save and continue'); + $this->drupalPostForm(NULL, [], 'Create group'); } // Update title in group 1 @@ -84,7 +93,7 @@ public function testCreateAndEdit() { $page->pressButton('Save'); - /** @var EntityFormDisplayInterface $display */ + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */ $display = EntityFormDisplay::load("node.{$this->nodeType}.default"); $this->assertSame('Test 1 - Update', $display->getThirdPartySetting('field_group', 'group_test_1')['label']); $this->assertSame('Test 1 - Update', $display->getThirdPartySetting('field_group', 'group_test_1')['format_settings']['label']); diff --git a/web/modules/inline_entity_form/config/schema/inline_entity_form.schema.yml b/web/modules/inline_entity_form/config/schema/inline_entity_form.schema.yml index a522ec84cbe642e9b34f9f684657b85666007d14..aef7d466edee39696efe701472bb3ab0e608ff68 100644 --- a/web/modules/inline_entity_form/config/schema/inline_entity_form.schema.yml +++ b/web/modules/inline_entity_form/config/schema/inline_entity_form.schema.yml @@ -25,6 +25,12 @@ field.widget.settings.inline_entity_form_simple: match_operator: type: string label: "Match operator" + collapsible: + type: boolean + label: "Collapsible" + collapsed: + type: boolean + label: "Collapsed by default" field.widget.settings.inline_entity_form_complex: type: mapping @@ -51,3 +57,12 @@ field.widget.settings.inline_entity_form_complex: match_operator: type: string label: "Match operator" + allow_duplicate: + type: boolean + label: "Allow duplicate" + collapsible: + type: boolean + label: "Collapsible" + collapsed: + type: boolean + label: "Collapsed by default" diff --git a/web/modules/inline_entity_form/inline_entity_form.info.yml b/web/modules/inline_entity_form/inline_entity_form.info.yml index a70d040a9ceb8435a7257cd9121401862a58463d..d4b6a49d2cffbf01e8f5533fae69129385572c45 100644 --- a/web/modules/inline_entity_form/inline_entity_form.info.yml +++ b/web/modules/inline_entity_form/inline_entity_form.info.yml @@ -6,8 +6,8 @@ package: Fields test_dependencies: - entity_reference_revisions:entity_reference_revisions -# Information added by Drupal.org packaging script on 2016-10-30 -version: '8.x-1.0-beta1' +# Information added by Drupal.org packaging script on 2018-05-22 +version: '8.x-1.0-rc1' core: '8.x' project: 'inline_entity_form' -datestamp: 1477868362 +datestamp: 1527030788 diff --git a/web/modules/inline_entity_form/inline_entity_form.module b/web/modules/inline_entity_form/inline_entity_form.module index 056ee9d4401b320b69563e61ce30acfbe9752f84..7e1e50803e25e4851b2264c2ad599baab4d906e3 100644 --- a/web/modules/inline_entity_form/inline_entity_form.module +++ b/web/modules/inline_entity_form/inline_entity_form.module @@ -72,14 +72,21 @@ function inline_entity_form_reference_form($reference_form, &$form_state) { $ief_id = $reference_form['#ief_id']; /** @var \Drupal\field\Entity\FieldConfig $instance */ $instance = $form_state->get(['inline_entity_form', $ief_id, 'instance']); + $selection_settings = [ + 'match_operator' => $reference_form['#match_operator'], + ] + $instance->getSetting('handler_settings'); $reference_form['#title'] = t('Add existing @type_singular', ['@type_singular' => $labels['singular']]); + $reference_form['entity_id'] = [ '#type' => 'entity_autocomplete', - '#title' => t('@label', ['@label' => ucwords($labels['singular'])]), + // @todo Use bundle defined singular/plural labels as soon as + // https://www.drupal.org/node/2765065 is committed. + // @see https://www.drupal.org/node/2765065 + '#title' => t('@label', ['@label' => ucfirst($labels['singular'])]), '#target_type' => $instance->getSetting('target_type'), '#selection_handler' => $instance->getSetting('handler'), - '#selection_settings' => $instance->getSetting('handler_settings'), + '#selection_settings' => $selection_settings, '#required' => TRUE, '#maxlength' => 255, ]; @@ -125,7 +132,7 @@ function inline_entity_form_reference_form($reference_form, &$form_state) { * Validates the form for adding existing entities. * * @param array $reference_form - * The reference entity form. + * The reference entity form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state of the parent form. */ @@ -158,7 +165,7 @@ function inline_entity_form_reference_form_validate(&$reference_form, FormStateI * Adds the specified entity to the IEF form state. * * @param array $reference_form - * The reference entity form. + * The reference entity form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state of the parent form. */ @@ -334,10 +341,10 @@ function theme_inline_entity_form_entity_table($variables) { // Add header columns for each field. $first = TRUE; foreach ($fields as $field_name => $field) { - $column = ['data' => $field['label']]; + $column = ['data' => $field['label'], 'class' => ['inline-entity-form-' . $entity_type . '-' . $field_name]]; // The first column gets a special class. if ($first) { - $column['class'] = ['ief-first-column-header']; + $column['class'][] = 'ief-first-column-header'; $first = FALSE; } $header[] = $column; @@ -363,7 +370,6 @@ function theme_inline_entity_form_entity_table($variables) { } foreach ($fields as $field_name => $field) { - $data = ''; if ($field['type'] == 'label') { $data = $variables['form'][$key]['#label']; } @@ -385,6 +391,9 @@ function theme_inline_entity_form_entity_table($variables) { $data = call_user_func_array($field['callback'], $arguments); } + else { + $data = t('N/A'); + } $cells[] = ['data' => $data, 'class' => ['inline-entity-form-' . $entity_type . '-' . $field_name]]; } diff --git a/web/modules/inline_entity_form/src/Element/InlineEntityForm.php b/web/modules/inline_entity_form/src/Element/InlineEntityForm.php index b55f7812c852cb6fcdc4f01cd4355f36a0f3798f..0fbe2335ab249b9a568b0d0cb3ef66d305777213 100644 --- a/web/modules/inline_entity_form/src/Element/InlineEntityForm.php +++ b/web/modules/inline_entity_form/src/Element/InlineEntityForm.php @@ -47,9 +47,14 @@ public function getInfo() { '#form_mode' => 'default', // Will save entity on submit if set to TRUE. '#save_entity' => TRUE, - // 'add' or 'edit'. If NULL, determined by whether the entity is new. + // 'add', 'edit' or 'duplicate'. '#op' => NULL, '#process' => [ + // Core's #process for groups, don't remove it. + [$class, 'processGroup'], + + // InlineEntityForm's #process must run after the above ::processGroup + // in case any new elements (like groups) were added in alter hooks. [$class, 'processEntityForm'], ], '#element_validate' => [ @@ -59,9 +64,10 @@ public function getInfo() { [$class, 'submitEntityForm'], ], '#theme_wrappers' => ['container'], - // Allow inline forms to use the #fieldset key. + '#pre_render' => [ - [$class, 'addFieldsetMarkup'], + // Core's #pre_render for groups, don't remove it. + [$class, 'preRenderGroup'], ], ]; } @@ -114,7 +120,13 @@ public static function processEntityForm($entity_form, FormStateInterface $form_ $entity_form['#entity'] = $storage->create($values); } if (!isset($entity_form['#op'])) { - $entity_form['#op'] = $entity_form['#entity']->isNew() ? 'add' : 'edit'; + // When duplicating entities, the entity is new, but already has a UUID. + if ($entity_form['#entity']->isNew() && $entity_form['#entity']->uuid()) { + $entity_form['#op'] = 'duplicate'; + } + else { + $entity_form['#op'] = $entity_form['#entity']->isNew() ? 'add' : 'edit'; + } } // Prepare the entity form and the entity itself for translating. $entity_form['#entity'] = TranslationHelper::prepareEntity($entity_form['#entity'], $form_state); @@ -179,42 +191,4 @@ public static function getInlineFormHandler($entity_type) { return $inline_form_handler; } - /** - * Pre-render callback for the #fieldset form property. - * - * Inline forms use #tree = TRUE to keep their values in a hierarchy for - * easier storage. Moving the form elements into fieldsets during form - * building would break up that hierarchy, so it's not an option for entity - * fields. Therefore, we wait until the pre_render stage, where any changes - * we make affect presentation only and aren't reflected in $form_state. - * - * @param array $entity_form - * The entity form. - * - * @return array - * The modified entity form. - */ - public static function addFieldsetMarkup($entity_form) { - $sort = []; - foreach (Element::children($entity_form) as $key) { - $element = $entity_form[$key]; - if (isset($element['#fieldset']) && isset($entity_form[$element['#fieldset']])) { - $entity_form[$element['#fieldset']][$key] = $element; - // Remove the original element this duplicates. - unset($entity_form[$key]); - // Mark the fieldset for sorting. - if (!in_array($key, $sort)) { - $sort[] = $element['#fieldset']; - } - } - } - - // Sort all fieldsets, so that element #weight stays respected. - foreach ($sort as $key) { - uasort($entity_form[$key], '\Drupal\Component\Utility\SortArray::sortByWeightProperty'); - } - - return $entity_form; - } - } diff --git a/web/modules/inline_entity_form/src/Form/EntityInlineForm.php b/web/modules/inline_entity_form/src/Form/EntityInlineForm.php index 06cbbdc0b809560d7f52ac55dbeba826eab327c3..1ff5073792b9f1c61fb733f4dd7277e213dcb9ed 100644 --- a/web/modules/inline_entity_form/src/Form/EntityInlineForm.php +++ b/web/modules/inline_entity_form/src/Form/EntityInlineForm.php @@ -182,9 +182,46 @@ public function entityForm(array $entity_form, FormStateInterface $form_state) { } } } + + // Determine the children of the entity form before it has been altered. + $children_before = Element::children($entity_form); + // Allow other modules to alter the form. $this->moduleHandler->alter('inline_entity_form_entity_form', $entity_form, $form_state); + // Determine the children of the entity form after it has been altered. + $children_after = Element::children($entity_form); + + // Ensure that any new children added have #tree, #parents, #array_parents + // and handle setting the proper #group if it's referencing a local element. + // Note: the #tree, #parents and #array_parents code is a direct copy from + // \Drupal\Core\Form\FormBuilder::doBuildForm. + $children_diff = array_diff($children_after, $children_before); + foreach ($children_diff as $child) { + // Don't squash an existing tree value. + if (!isset($entity_form[$child]['#tree'])) { + $entity_form[$child]['#tree'] = $entity_form['#tree']; + } + + // Don't squash existing parents value. + if (!isset($entity_form[$child]['#parents'])) { + // Check to see if a tree of child elements is present. If so, + // continue down the tree if required. + $entity_form[$child]['#parents'] = $entity_form[$child]['#tree'] && $entity_form['#tree'] ? array_merge($entity_form['#parents'], [$child]) : [$child]; + } + + // Ensure #array_parents follows the actual form structure. + $array_parents = $entity_form['#array_parents']; + $array_parents[] = $child; + $entity_form[$child]['#array_parents'] = $array_parents; + + // Detect if there is a #group and it specifies a local element. If so, + // change it to use the proper local element's #parents group name. + if (isset($entity_form[$child]['#group']) && isset($entity_form[$entity_form[$child]['#group']])) { + $entity_form[$child]['#group'] = implode('][', $entity_form[$entity_form[$child]['#group']]['#parents']); + } + } + return $entity_form; } @@ -203,7 +240,7 @@ public function entityFormValidate(array &$entity_form, FormStateInterface $form $form_display->validateFormValues($entity, $entity_form, $form_state); $entity->setValidationRequired(FALSE); - foreach($form_state->getErrors() as $name => $message) { + foreach ($form_state->getErrors() as $name => $message) { // $name may be unknown in $form_state and // $form_state->setErrorByName($name, $message) may suppress the error message. $form_state->setError($triggering_element, $message); diff --git a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormBase.php b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormBase.php index 123b58fc65f3355bad1f15a4b2631873388c968f..1e69c7c9fb42683f9043c83a1d7b0768e3af1893 100644 --- a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormBase.php +++ b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormBase.php @@ -2,7 +2,6 @@ namespace Drupal\inline_entity_form\Plugin\Field\FieldWidget; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; @@ -72,7 +71,7 @@ abstract class InlineEntityFormBase extends WidgetBase implements ContainerFacto * The entity type bundle info. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository * The entity display repository. */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) { @@ -194,6 +193,8 @@ public static function defaultSettings() { 'override_labels' => FALSE, 'label_singular' => '', 'label_plural' => '', + 'collapsible' => FALSE, + 'collapsed' => FALSE, ]; } @@ -236,6 +237,21 @@ public function settingsForm(array $form, FormStateInterface $form_state) { ], ], ]; + $element['collapsible'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Collapsible'), + '#default_value' => $this->getSetting('collapsible'), + ]; + $element['collapsed'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Collapsed by default'), + '#default_value' => $this->getSetting('collapsed'), + '#states' => [ + 'visible' => [ + ':input[name="' . $states_prefix . '[collapsible]"]' => ['checked' => TRUE], + ], + ], + ]; return $element; } @@ -262,6 +278,10 @@ public function settingsSummary() { $summary[] = $this->t('Default labels are used.'); } + if ($this->getSetting('collapsible')) { + $summary[] = $this->t($this->getSetting('collapsed') ? 'Collapsible, collapsed by default' : 'Collapsible'); + } + return $summary; } @@ -291,7 +311,7 @@ protected function getEntityTypeLabels() { * - Is IEF handler loaded? * - Are we on a "real" entity form and not on default value widget? * - * @param FormStateInterface $form_state + * @param \Drupal\Core\Form\FormStateInterface $form_state * Form state. * * @return bool @@ -404,7 +424,7 @@ protected function getInlineEntityForm($operation, $bundle, $langcode, $delta, a * @return bool * TRUE if translating is in progress, FALSE otherwise. * - * @see \Drupal\inline_entity_form\TranslationHelper::initFormLangcodes(). + * @see \Drupal\inline_entity_form\TranslationHelper::initFormLangcodes() */ protected function isTranslating(FormStateInterface $form_state) { if (TranslationHelper::isTranslating($form_state)) { @@ -453,8 +473,8 @@ public static function addIefSubmitCallbacks($element) { * still decide to cancel the parent form. * * @param $entity_form - * The form of the entity being managed inline. - * @param $form_state + * The form of the entity being managed inline. + * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state of the parent form. */ public static function submitSaveEntity($entity_form, FormStateInterface $form_state) { @@ -462,7 +482,7 @@ public static function submitSaveEntity($entity_form, FormStateInterface $form_s /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $entity_form['#entity']; - if ($entity_form['#op'] == 'add') { + if (in_array($entity_form['#op'], ['add', 'duplicate'])) { // Determine the correct weight of the new element. $weight = 0; $entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']); diff --git a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormComplex.php b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormComplex.php index 92a52feec55c5eda0f6c4876c568b2c36355bafe..b8424be3b657fda43f08c066c2bd2fdd55a87962 100644 --- a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormComplex.php +++ b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormComplex.php @@ -37,7 +37,7 @@ class InlineEntityFormComplex extends InlineEntityFormBase implements ContainerF protected $moduleHandler; /** - * Constructs a InlineEntityFormBase object. + * Constructs a InlineEntityFormComplex object. * * @param array $plugin_id * The plugin_id for the widget. @@ -53,7 +53,7 @@ class InlineEntityFormComplex extends InlineEntityFormBase implements ContainerF * The entity type bundle info. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository * The entity display repository. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * Module handler service. @@ -89,6 +89,7 @@ public static function defaultSettings() { 'allow_new' => TRUE, 'allow_existing' => FALSE, 'match_operator' => 'CONTAINS', + 'allow_duplicate' => FALSE, ]; return $defaults; @@ -124,6 +125,11 @@ public function settingsForm(array $form, FormStateInterface $form_state) { ], ], ]; + $element['allow_duplicate'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow users to duplicate @label.', ['@label' => $labels['plural']]), + '#default_value' => $this->getSetting('allow_duplicate'), + ]; return $element; } @@ -153,6 +159,13 @@ public function settingsSummary() { $summary[] = $this->t('Existing @label can not be referenced.', ['@label' => $labels['plural']]); } + if ($this->getSetting('allow_duplicate')) { + $summary[] = $this->t('@label can be duplicated.', ['@label' => $labels['plural']]); + } + else { + $summary[] = $this->t('@label can not be duplicated.', ['@label' => $labels['plural']]); + } + return $summary; } @@ -196,7 +209,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $wrapper = 'inline-entity-form-' . $this->getIefId(); $element = [ - '#type' => 'fieldset', + '#type' => $this->getSetting('collapsible') ? 'details' : 'fieldset', '#tree' => TRUE, '#description' => $this->fieldDefinition->getDescription(), '#prefix' => '<div id="' . $wrapper . '">', @@ -209,12 +222,20 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen [get_class($this), 'removeTranslatabilityClue'], ], ] + $element; + if ($element['#type'] == 'details') { + $element['#open'] = !$this->getSetting('collapsed'); + } $element['#attached']['library'][] = 'inline_entity_form/widget'; $this->prepareFormState($form_state, $items, $element['#translating']); $entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']); + // Prepare cardinality information. + $entities_count = count($entities); + $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(); + $cardinality_reached = ($cardinality > 0 && $entities_count == $cardinality); + // Build the "Multiple value" widget. // TODO - does this belong in #element_validate? $element['#element_validate'][] = [get_class($this), 'updateRowWeights']; @@ -242,7 +263,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $this->moduleHandler->alter('inline_entity_form_table_fields', $fields, $context); $element['entities']['#table_fields'] = $fields; - $weight_delta = max(ceil(count($entities) * 1.2), 50); + $weight_delta = max(ceil($entities_count * 1.2), 50); foreach ($entities as $key => $value) { // Data used by theme_inline_entity_form_entity_table(). /** @var \Drupal\Core\Entity\EntityInterface $entity */ @@ -263,7 +284,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ]; // Add the appropriate form. - if ($value['form'] == 'edit') { + if (in_array($value['form'], ['edit', 'duplicate'])) { $element['entities'][$key]['form'] = [ '#type' => 'container', '#attributes' => ['class' => ['ief-form', 'ief-form-row']], @@ -272,8 +293,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $entity->bundle(), $parent_langcode, $key, - array_merge($parents, ['inline_entity_form', 'entities', $key, 'form']), - $entity + array_merge($parents, ['inline_entity_form', 'entities', $key, 'form']), + $value['form'] == 'edit' ? $entity : $entity->createDuplicate() ), ]; @@ -333,6 +354,23 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ]; } + // Add the duplicate button, if allowed. + if ($settings['allow_duplicate'] && !$cardinality_reached && $entity->access('create')) { + $row['actions']['ief_entity_duplicate'] = [ + '#type' => 'submit', + '#value' => $this->t('Duplicate'), + '#name' => 'ief-' . $this->getIefId() . '-entity-duplicate-' . $key, + '#limit_validation_errors' => [array_merge($parents, ['actions'])], + '#ajax' => [ + 'callback' => 'inline_entity_form_get_element', + 'wrapper' => $wrapper, + ], + '#submit' => ['inline_entity_form_open_row_form'], + '#ief_row_delta' => $key, + '#ief_row_form' => 'duplicate', + ]; + } + // If 'allow_existing' is on, the default removal operation is unlink // and the access check for deleting happens inside the controller // removeForm() method. @@ -365,8 +403,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen return $element; } - $entities_count = count($entities); - $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(); if ($cardinality > 1) { // Add a visual cue of cardinality count. $message = $this->t('You have added @entities_count out of @cardinality_count allowed @label.', [ @@ -379,7 +415,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ]; } // Do not return the rest of the form if cardinality count has been reached. - if ($cardinality > 0 && $entities_count == $cardinality) { + if ($cardinality_reached) { return $element; } @@ -399,7 +435,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // The parent entity type and bundle must not be the same as the inline // entity type and bundle, to prevent recursion. $parent_entity_type = $this->fieldDefinition->getTargetEntityTypeId(); - $parent_bundle = $this->fieldDefinition->getTargetBundle(); + $parent_bundle = $this->fieldDefinition->getTargetBundle(); if ($parent_entity_type != $target_type || $parent_bundle != $bundle) { $form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'add'); $form_state->set(['inline_entity_form', $this->getIefId(), 'form settings'], [ @@ -502,10 +538,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // Used by Field API and controller methods to find the relevant // values in $form_state. '#parents' => array_merge($parents), - // Pass the current entity type. '#entity_type' => $target_type, - // Pass the widget specific labels. '#ief_labels' => $this->getEntityTypeLabels(), + '#match_operator' => $this->getSetting('match_operator'), ]; $element['form'] += inline_entity_form_reference_form($element['form'], $form_state); @@ -591,6 +626,9 @@ public static function buildEntityFormActions($element) { if ($element['#op'] == 'add') { $save_label = t('Create @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]); } + elseif ($element['#op'] == 'duplicate') { + $save_label = t('Duplicate @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]); + } else { $delta .= '-' . $element['#ief_row_delta']; $save_label = t('Update @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]); @@ -633,12 +671,12 @@ public static function buildEntityFormActions($element) { ]; } else { - $element['actions']['ief_edit_save']['#ief_row_delta'] = $element['#ief_row_delta']; - $element['actions']['ief_edit_cancel']['#ief_row_delta'] = $element['#ief_row_delta']; + $element['actions']['ief_' . $element['#op'] . '_save']['#ief_row_delta'] = $element['#ief_row_delta']; + $element['actions']['ief_' . $element['#op'] . '_cancel']['#ief_row_delta'] = $element['#ief_row_delta']; - static::addSubmitCallbacks($element['actions']['ief_edit_save']); - $element['actions']['ief_edit_save']['#submit'][] = [get_called_class(), 'submitCloseRow']; - $element['actions']['ief_edit_cancel']['#submit'] = [ + static::addSubmitCallbacks($element['actions']['ief_' . $element['#op'] . '_save']); + $element['actions']['ief_' . $element['#op'] . '_save']['#submit'][] = [get_called_class(), 'submitCloseRow']; + $element['actions']['ief_' . $element['#op'] . '_cancel']['#submit'] = [ [get_called_class(), 'closeChildForms'], [get_called_class(), 'submitCloseRow'], 'inline_entity_form_cleanup_row_form_state', @@ -741,7 +779,7 @@ protected function buildRemoveForm(&$form) { * @param $form_state * The form state of the parent form. * - * @see inline_entity_form_open_row_form(). + * @see inline_entity_form_open_row_form() */ public static function submitCloseRow($form, FormStateInterface $form_state) { $element = inline_entity_form_get_element($form, $form_state); @@ -796,7 +834,7 @@ public static function submitConfirmRemove($form, FormStateInterface $form_state /** * Determines bundle to be used when creating entity. * - * @param FormStateInterface $form_state + * @param \Drupal\Core\Form\FormStateInterface $form_state * Current form state. * * @return string @@ -858,7 +896,7 @@ public static function requiredField($element, FormStateInterface $form_state, $ * @param $form_state * The form state of the parent form. * - * @see inline_entity_form_open_form(). + * @see inline_entity_form_open_form() */ public static function closeForm($form, FormStateInterface $form_state) { $element = inline_entity_form_get_element($form, $form_state); @@ -889,7 +927,7 @@ public static function addSubmitCallbacks(&$element) { * * @param $form * The IEF Form element. - * @param FormStateInterface $form_state + * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state of the parent form. */ public static function closeChildForms($form, FormStateInterface &$form_state) { diff --git a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormSimple.php b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormSimple.php index 87e779229120b128994af77845c63b8ba53810a8..985e06bc6f34091fc0c46d5a6dc4e302481b635b 100644 --- a/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormSimple.php +++ b/web/modules/inline_entity_form/src/Plugin/Field/FieldWidget/InlineEntityFormSimple.php @@ -36,12 +36,16 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $form_state->set(['inline_entity_form', $ief_id], []); $element = [ - '#type' => 'fieldset', + '#type' => $this->getSetting('collapsible') ? 'details' : 'fieldset', '#field_title' => $this->fieldDefinition->getLabel(), '#after_build' => [ [get_class($this), 'removeTranslatabilityClue'], ], ] + $element; + if ($element['#type'] == 'details') { + $element['#open'] = !$this->getSetting('collapsed'); + } + $item = $items->get($delta); if ($item->target_id && !$item->entity) { $element['warning']['#markup'] = $this->t('Unable to load the referenced entity.'); @@ -55,7 +59,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $delta, 'inline_entity_form' ]); - $bundle = !empty($this->getFieldSetting('handler_settings')['target_bundles']) ? reset($this->getFieldSetting('handler_settings')['target_bundles']) : NULL; + $bundle = $this->getBundle(); $element['inline_entity_form'] = $this->getInlineEntityForm($op, $bundle, $langcode, $delta, $parents, $entity); if ($op == 'edit') { @@ -196,4 +200,16 @@ public static function isApplicable(FieldDefinitionInterface $field_definition) return TRUE; } + /** + * Gets the bundle for the inline entity. + * + * @return string|null + * The bundle, or NULL if not known. + */ + protected function getBundle() { + if (!empty($this->getFieldSetting('handler_settings')['target_bundles'])) { + return reset($this->getFieldSetting('handler_settings')['target_bundles']); + } + } + } diff --git a/web/modules/inline_entity_form/src/Tests/ComplexWidgetWebTest.php b/web/modules/inline_entity_form/src/Tests/ComplexWidgetWebTest.php index 974c8a05a040db8dbc99ed08455a856d2a2945f7..d606c841b1487526f2172250fbb4779aaa16af6a 100644 --- a/web/modules/inline_entity_form/src/Tests/ComplexWidgetWebTest.php +++ b/web/modules/inline_entity_form/src/Tests/ComplexWidgetWebTest.php @@ -66,7 +66,7 @@ protected function setUp() { */ public function testEmptyFieldIEF() { // Don't allow addition of existing nodes. - $this->setAllowExisting(FALSE); + $this->updateSetting('allow_existing', FALSE); $this->drupalGet($this->formContentAddUrl); $this->assertFieldByName('multi[form][inline_entity_form][title][0][value]', NULL, 'Title field on inline form exists.'); @@ -75,7 +75,7 @@ public function testEmptyFieldIEF() { $this->assertFieldByXpath('//input[@type="submit" and @value="Create node"]', NULL, 'Found "Create node" submit button'); // Allow addition of existing nodes. - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); $this->drupalGet($this->formContentAddUrl); $this->assertNoFieldByName('multi[form][inline_entity_form][title][0][value]', NULL, 'Title field does not appear.'); @@ -107,7 +107,7 @@ public function testEmptyFieldIEF() { */ public function testEntityCreation() { // Allow addition of existing nodes. - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); $this->drupalGet($this->formContentAddUrl); $this->drupalPostAjaxForm(NULL, [], $this->getButtonName('//input[@type="submit" and @value="Add new node" and @data-drupal-selector="edit-multi-actions-ief-add"]')); @@ -183,7 +183,6 @@ public function testNestedEntityCreationWithDifferentBundlesAjaxSubmit() { foreach ($required_possibilities as $required) { $this->setupNestedComplexForm($required); - $nested3_title = 'nested3 title steps ' . ($required ? 'required' : 'not required'); $nested2_title = 'nested2 title steps ' . ($required ? 'required' : 'not required'); $nested1_title = 'nested1 title steps ' . ($required ? 'required' : 'not required'); @@ -213,10 +212,9 @@ public function testNestedEntityCreationWithDifferentBundlesAjaxSubmit() { * Checks that nested IEF entity references can be edit and saved. * * @param \Drupal\node\Entity\Node $node - * Top level node of type ief_test_nested1 to check. + * Top level node of type ief_test_nested1 to check. * @param bool $ajax_submit - * Whether IEF form widgets should be submitted via AJax or left open. - * + * Whether IEF form widgets should be submitted via AJAX or left open. */ protected function checkNestedEntityEditing(Node $node, $ajax_submit = TRUE) { $this->drupalGet("node/{$node->id()}/edit"); @@ -225,14 +223,14 @@ protected function checkNestedEntityEditing(Node $node, $ajax_submit = TRUE) { /** @var \Drupal\node\Entity\Node $level_2_node */ $level_2_node = $node->test_ref_nested1->entity->test_ref_nested2->entity; $level_2_node_update_title = $level_2_node->getTitle() . ' - updated'; - //edit-test-ref-nested1-entities-0-actions-ief-entity-edit + // edit-test-ref-nested1-entities-0-actions-ief-entity-edit $this->drupalPostAjaxForm(NULL, [], $this->getButtonName('//input[@type="submit" and @data-drupal-selector="edit-test-ref-nested1-entities-0-actions-ief-entity-edit"]')); - //edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-entities-0-actions-ief-entity-edit + // edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-entities-0-actions-ief-entity-edit $this->drupalPostAjaxForm(NULL, [], $this->getButtonName('//input[@type="submit" and @data-drupal-selector="edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-entities-0-actions-ief-entity-edit"]')); $edit['test_ref_nested1[form][inline_entity_form][entities][0][form][test_ref_nested2][form][inline_entity_form][entities][0][form][title][0][value]'] = $level_2_node_update_title; if ($ajax_submit) { // Close IEF Forms with AJAX posts - //edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-form-inline-entity-form-entities-0-form-actions-ief-edit-save + // edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-form-inline-entity-form-entities-0-form-actions-ief-edit-save $this->drupalPostAjaxForm(NULL, $edit, $this->getButtonName('//input[@type="submit" and @data-drupal-selector="edit-test-ref-nested1-form-inline-entity-form-entities-0-form-test-ref-nested2-form-inline-entity-form-entities-0-form-actions-ief-edit-save"]')); $this->drupalPostAjaxForm(NULL, [], $this->getButtonName('//input[@type="submit" and @data-drupal-selector="edit-test-ref-nested1-form-inline-entity-form-entities-0-form-actions-ief-edit-save"]')); $this->drupalPostForm(NULL, [], t('Save')); @@ -278,7 +276,7 @@ public function testNestedEntityCreationWithDifferentBundlesNoAjaxSubmit() { */ public function testEntityEditingAndRemoving() { // Allow addition of existing nodes. - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); // Create three ief_reference_type entities. $referenceNodes = $this->createReferenceContent(3); @@ -291,7 +289,7 @@ public function testEntityEditingAndRemoving() { $parent_node = $this->drupalGetNodeByTitle('Some title'); // Edit the second entity. - $this->drupalGet('node/'. $parent_node->id() .'/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); $cell = $this->xpath('//table[@id="ief-entity-table-edit-multi-entities"]/tbody/tr[@class="ief-row-entity draggable even"]/td[@class="inline-entity-form-node-label"]'); $title = (string) $cell[0]; @@ -315,7 +313,7 @@ public function testEntityEditingAndRemoving() { $this->assertTrue($node->last_name->value == 'Doe', 'Last name in reference node changed to Doe'); // Delete the second entity. - $this->drupalGet('node/'. $parent_node->id() .'/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); $cell = $this->xpath('//table[@id="ief-entity-table-edit-multi-entities"]/tbody/tr[@class="ief-row-entity draggable even"]/td[@class="inline-entity-form-node-label"]'); $title = (string) $cell[0]; @@ -335,13 +333,13 @@ public function testEntityEditingAndRemoving() { $this->assertTrue(empty($deleted_node), 'The inline entity was deleted from the site.'); // Checks that entity does nor appear in IEF. - $this->drupalGet('node/'. $parent_node->id() .'/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); $this->assertNoText($title, 'Deleted inline entity is not present on the page after saving parent.'); // Delete the third entity reference only, don't delete the node. The third // entity now is second referenced entity because the second one was deleted // in previous step. - $this->drupalGet('node/'. $parent_node->id() .'/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); $cell = $this->xpath('//table[@id="ief-entity-table-edit-multi-entities"]/tbody/tr[@class="ief-row-entity draggable even"]/td[@class="inline-entity-form-node-label"]'); $title = (string) $cell[0]; @@ -356,7 +354,7 @@ public function testEntityEditingAndRemoving() { $this->assertResponse(200, 'Saving parent node was successful.'); // Checks that entity does nor appear in IEF. - $this->drupalGet('node/'. $parent_node->id() . '/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); $this->assertNoText($title, 'Deleted inline entity is not present on the page after saving parent.'); // Checks that entity is not deleted. @@ -369,7 +367,7 @@ public function testEntityEditingAndRemoving() { */ public function testReferencingExistingEntities() { // Allow addition of existing nodes. - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); // Create three ief_reference_type entities. $referenceNodes = $this->createReferenceContent(3); @@ -417,10 +415,10 @@ public function testReferencingExistingEntities() { $this->assertResponse(200, 'Saving parent for was successful.'); // Check if entities are referenced. - $this->drupalGet('node/'. $parent_node->id() .'/edit'); + $this->drupalGet('node/' . $parent_node->id() . '/edit'); for ($i = 2; $i <= 3; $i++) { $cell = $this->xpath('//table[@id="ief-entity-table-edit-multi-entities"]/tbody/tr[' . $i . ']/td[@class="inline-entity-form-node-label"]'); - $this->assertTrue($cell[0] == 'Some reference ' . $i, 'Found reference node title "Some reference ' . $i .'" in the IEF table.'); + $this->assertTrue($cell[0] == 'Some reference ' . $i, 'Found reference node title "Some reference ' . $i . '" in the IEF table.'); } // Check if all remaining nodes from all bundles are referenced. $count = 2; @@ -437,7 +435,7 @@ public function testReferencingExistingEntities() { * Also checks if existing entity reference form can be canceled. */ public function testReferenceExistingValidation() { - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); $this->drupalGet('node/add/ief_test_complex'); $this->checkExistingValidationExpectation('', 'Node field is required.'); @@ -465,12 +463,50 @@ public function testReferenceExistingValidation() { } } + /** + * Tests if duplicating entities works. + */ + public function testDuplicatingEntities() { + $this->updateSetting('allow_duplicate', TRUE); + + $referenceNodes = $this->createReferenceContent(2); + $this->drupalCreateNode([ + 'type' => 'ief_test_complex', + 'title' => 'Some title', + 'multi' => array_values($referenceNodes), + ]); + /** @var \Drupal\node\NodeInterface $node */ + $parent_node = $this->drupalGetNodeByTitle('Some title'); + + $this->drupalGet('node/' . $parent_node->id() . '/edit'); + $this->drupalPostAjaxForm(NULL, [], $this->getButtonName('//input[@type="submit" and @id="edit-multi-entities-0-actions-ief-entity-duplicate"]')); + $this->assertResponse(200, 'Opening inline duplicate form was successful.'); + + $edit = [ + 'multi[form][inline_entity_form][entities][0][form][title][0][value]' => 'Duplicate!', + 'multi[form][inline_entity_form][entities][0][form][first_name][0][value]' => 'Bojan', + ]; + $this->drupalPostAjaxForm(NULL, $edit, $this->getButtonName('//input[@type="submit" and @data-drupal-selector="edit-multi-form-inline-entity-form-entities-0-form-actions-ief-duplicate-save"]')); + $this->assertResponse(200, 'Saving inline duplicate form was successful.'); + + $this->assertText('Some reference 1'); + $this->assertText('Some reference 2'); + $this->assertText('Duplicate!'); + $this->drupalPostForm(NULL, [], t('Save')); + $this->assertResponse(200, 'Saving parent entity was successful.'); + + // Confirm a duplicate was made. + $duplicate = Node::load(4); + $this->assertEqual($duplicate->label(), 'Duplicate!'); + $this->assertEqual($duplicate->first_name->value, 'Bojan'); + } + /** * Tests if a referenced content can be edited while the referenced content is * newer than the referencing parent node. */ public function testEditedInlineEntityValidation() { - $this->setAllowExisting(TRUE); + $this->updateSetting('allow_existing', TRUE); // Create referenced content. $referenced_nodes = $this->createReferenceContent(1); @@ -551,16 +587,18 @@ protected function createReferenceContent($numNodes = 3) { } /** - * Sets allow_existing IEF setting. + * Updates an IEF setting and saves the underlying entity display. * - * @param bool $flag - * "allow_existing" flag to be set. + * @param string $name + * The name of the setting. + * @param mixed $value + * The value to set. */ - protected function setAllowExisting($flag) { + protected function updateSetting($name, $value) { /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */ $display = $this->entityFormDisplayStorage->load('node.ief_test_complex.default'); $component = $display->getComponent('multi'); - $component['settings']['allow_existing'] = $flag; + $component['settings'][$name] = $value; $display->setComponent('multi', $component)->save(); } @@ -589,7 +627,7 @@ protected function createNodeForEveryBundle() { * Gets the form. * Opens the inline entity forms if they are not required. * - * @param boolean $required + * @param bool $required * Whether the fields are required. * @param array $permissions * (optional) Permissions to sign testing user in with. You may pass in an @@ -653,9 +691,9 @@ protected function openMultiExistingForm() { * Checks that an invalid value for an existing node will be display the expected error. * * @param $existing_node_text - * The text to enter into the existing node text field. + * The text to enter into the existing node text field. * @param $expected_error - * The error message that is expected to be shown. + * The error message that is expected to be shown. */ protected function checkExistingValidationExpectation($existing_node_text, $expected_error) { $edit = [ diff --git a/web/modules/inline_entity_form/src/Tests/ElementWebTest.php b/web/modules/inline_entity_form/src/Tests/ElementWebTest.php index 59a5a5f9f010985e0ef7f0124f943aa2ad98a212..44686e884c135585a2c8cd4ecaa1877ac97d1384 100644 --- a/web/modules/inline_entity_form/src/Tests/ElementWebTest.php +++ b/web/modules/inline_entity_form/src/Tests/ElementWebTest.php @@ -67,7 +67,7 @@ public function testCustomFormIEF() { $this->assertNodeByTitle($title, 'ief_test_custom'); if ($node = $this->getNodeByTitle($title)) { - $this->drupalGet("ief-edit-test/{$node->id()}/$form_mode_possibility"); + $this->drupalGet("ief-test/$form_mode_possibility/{$node->id()}"); $this->assertFieldByName('inline_entity_form[title][0][value]', $title, 'Node title appears in form.'); $this->checkFormDisplayFields("node.ief_test_custom.$form_mode_possibility", 'inline_entity_form'); $this->assertFieldByName('inline_entity_form[positive_int][0][value]', 11, 'Positive int field appears in form.'); diff --git a/web/modules/inline_entity_form/src/Tests/InlineEntityFormTestBase.php b/web/modules/inline_entity_form/src/Tests/InlineEntityFormTestBase.php index e3bf62d6daedbafcc9b0c1c13aeb5a03dc8e60cc..d3a399f05ef1e5cda78e3dcc219d5297845773e3 100644 --- a/web/modules/inline_entity_form/src/Tests/InlineEntityFormTestBase.php +++ b/web/modules/inline_entity_form/src/Tests/InlineEntityFormTestBase.php @@ -140,7 +140,7 @@ protected function assertEntityByLabel($label, $entity_type_id = 'node', $bundle * in inline_entity_form_test module. * * @param $form_display - * The form display to check. + * The form display to check. */ protected function checkFormDisplayFields($form_display, $prefix) { $form_display_fields = [ @@ -171,7 +171,7 @@ protected function checkFormDisplayFields($form_display, $prefix) { ], ]; if ($fields = $form_display_fields[$form_display]) { - $this->assert('debug', 'Checking form dispaly: '. $form_display); + $this->assert('debug', 'Checking form dispaly: ' . $form_display); foreach ($fields['expected'] as $expected_field) { $this->assertFieldByName($prefix . $expected_field); } diff --git a/web/modules/inline_entity_form/src/Tests/SimpleWidgetWebTest.php b/web/modules/inline_entity_form/src/Tests/SimpleWidgetWebTest.php index 519c2611f8aaa806501e7782cd8c2a81449b164e..531c60212966e824e762ae76e7f5f69693321bd3 100644 --- a/web/modules/inline_entity_form/src/Tests/SimpleWidgetWebTest.php +++ b/web/modules/inline_entity_form/src/Tests/SimpleWidgetWebTest.php @@ -134,8 +134,8 @@ protected function testSimpleValidation() { $child_node = $this->getNodeByTitle($child_title); if ($this->assertNotNull($child_node)) { $this->assertEqual($host_node->single[0]->target_id, $child_node->id(), 'Child node is referenced'); - $this->assertEqual($child_node->positive_int[0]->value,1, 'Child node int field correct.'); - $this->assertEqual($child_node->bundle(),'ief_test_custom', 'Child node is correct bundle.'); + $this->assertEqual($child_node->positive_int[0]->value, 1, 'Child node int field correct.'); + $this->assertEqual($child_node->bundle(), 'ief_test_custom', 'Child node is correct bundle.'); } } } diff --git a/web/modules/inline_entity_form/src/Tests/TranslationTest.php b/web/modules/inline_entity_form/src/Tests/TranslationTest.php index 8d226a3bf35f4ff5864474ff943f7fd3bc09bf83..b58949702b5c22262808adf6314aa7b866e754d3 100644 --- a/web/modules/inline_entity_form/src/Tests/TranslationTest.php +++ b/web/modules/inline_entity_form/src/Tests/TranslationTest.php @@ -56,7 +56,7 @@ protected function setUp() { /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */ $display = $form_display_storage->load('node.ief_test_complex.default'); $component = $display->getComponent('multi'); - $component['settings']['allow_existing'] = TRUe; + $component['settings']['allow_existing'] = TRUE; $display->setComponent('multi', $component)->save(); } diff --git a/web/modules/inline_entity_form/src/TranslationHelper.php b/web/modules/inline_entity_form/src/TranslationHelper.php index 6c71e5f054b261dd377ffb75a964d81549cac7d1..6ff8670eb065dffa8b7856c1a238a311d6a9f844 100644 --- a/web/modules/inline_entity_form/src/TranslationHelper.php +++ b/web/modules/inline_entity_form/src/TranslationHelper.php @@ -21,7 +21,7 @@ class TranslationHelper { * @return \Drupal\Core\Entity\ContentEntityInterface * The prepared entity. * - * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes(). + * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes() */ public static function prepareEntity(ContentEntityInterface $entity, FormStateInterface $form_state) { $form_langcode = $form_state->get('langcode'); @@ -34,6 +34,9 @@ public static function prepareEntity(ContentEntityInterface $entity, FormStateIn // Create a translation from the source language values. $source = $form_state->get(['content_translation', 'source']); $source_langcode = $source ? $source->getId() : $entity_langcode; + if (!$entity->hasTranslation($source_langcode)) { + $entity->addTranslation($source_langcode, $entity->toArray()); + } $source_translation = $entity->getTranslation($source_langcode); $entity->addTranslation($form_langcode, $source_translation->toArray()); $translation = $entity->getTranslation($form_langcode); @@ -97,7 +100,7 @@ public static function updateEntityLangcode(ContentEntityInterface $entity, $for * @return bool * TRUE if translating is in progress, FALSE otherwise. * - * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes(). + * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes() */ public static function isTranslating(FormStateInterface $form_state) { $form_langcode = $form_state->get('langcode'); diff --git a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info.yml b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info.yml index cc800ec7b70f2ab34a07716742bc230dcfb445e5..699134915fcbe93d1f3c4f4fb8ef14351b3b68a0 100644 --- a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info.yml +++ b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.info.yml @@ -10,8 +10,8 @@ dependencies: - file - image -# Information added by Drupal.org packaging script on 2016-10-30 -version: '8.x-1.0-beta1' +# Information added by Drupal.org packaging script on 2018-05-22 +version: '8.x-1.0-rc1' core: '8.x' project: 'inline_entity_form' -datestamp: 1477868362 +datestamp: 1527030788 diff --git a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.routing.yml b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.routing.yml index aff490ae43600f923b502cfe3ec481f3c1f1c881..9cf5a051811e8236b7d1e34d954a786580b497a0 100644 --- a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.routing.yml +++ b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/inline_entity_form_test.routing.yml @@ -1,16 +1,9 @@ inline_entity_form_test.form: - path: '/ief-test/{form_mode}' + path: '/ief-test/{form_mode}/{node}' defaults: _form: '\Drupal\inline_entity_form_test\IefTest' _title: 'IEF test' form_mode: 'default' - requirements: - _access: 'TRUE' -inline_entity_form_test.edit_form: - path: '/ief-edit-test/{node}/{form_mode}' - defaults: - _form: '\Drupal\inline_entity_form_test\IefEditTest' - _title: 'IEF test' - form_mode: 'default' + node: NULL requirements: _access: 'TRUE' diff --git a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefEditTest.php b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefEditTest.php deleted file mode 100644 index 5437e856945cf912fa3453cf710c9303464f0a62..0000000000000000000000000000000000000000 --- a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefEditTest.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -namespace Drupal\inline_entity_form_test; - -use Drupal\Core\Form\FormBase; -use Drupal\Core\Form\FormStateInterface; -use Drupal\node\Entity\Node; - -/** - * Tests Inline entity form element. - */ -class IefEditTest extends FormBase { - - /** - * {@inheritdoc} - */ - public function getFormID() { - return 'ief_edit_test'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, Node $node = NULL, $form_mode = 'default') { - $form['inline_entity_form'] = [ - '#type' => 'inline_entity_form', - '#entity_type' => 'node', - '#bundle' => 'ief_test_custom', - '#default_value' => $node, - '#form_mode' => $form_mode, - ]; - $form['submit'] = [ - '#type' => 'submit', - '#value' => t('Update'), - ]; - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $entity = $form['inline_entity_form']['#entity']; - drupal_set_message(t('Created @entity_type @label.', ['@entity_type' => $entity->getEntityType()->getLabel(), '@label' => $entity->label()])); - } - -} diff --git a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefTest.php b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefTest.php index 2df552702352c5050ce77bfaca463234d2cee40c..344ee7c587b01602c6c4774b9fabbc2a21ce5da5 100644 --- a/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefTest.php +++ b/web/modules/inline_entity_form/tests/modules/inline_entity_form_test/src/IefTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\node\Entity\Node; /** * Tests Inline entity form element. @@ -20,7 +21,7 @@ public function getFormID() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $form_mode = 'default') { + public function buildForm(array $form, FormStateInterface $form_state, $form_mode = 'default', Node $node = NULL) { $form['inline_entity_form'] = [ '#type' => 'inline_entity_form', '#entity_type' => 'node', @@ -31,6 +32,11 @@ public function buildForm(array $form, FormStateInterface $form_state, $form_mod '#type' => 'submit', '#value' => t('Save'), ]; + if (!empty($node)) { + $form['inline_entity_form']['#default_value'] = $node; + $form['submit']['#value'] = t('Update'); + } + return $form; } diff --git a/web/modules/redis/.travis.yml b/web/modules/redis/.travis.yml index 30bba24e71763bf65942c2edb50d8e3ac3763826..400d14326b983ce0bbaffa1f92f2fb6224940136 100644 --- a/web/modules/redis/.travis.yml +++ b/web/modules/redis/.travis.yml @@ -12,12 +12,9 @@ php: - 5.5 - 5.6 - 7 - - hhvm matrix: fast_finish: true - allow_failures: - - php: hhvm env: global: @@ -63,7 +60,7 @@ env: - DRUPAL_TI_BEHAT_BROWSER="firefox" # Use Drupal 8.3.x to run tests. - - DRUPAL_TI_CORE_BRANCH="8.3.x" + - DRUPAL_TI_CORE_BRANCH="8.5.x" # PHPUnit specific commandline arguments. - DRUPAL_TI_PHPUNIT_ARGS="--verbose --debug" diff --git a/web/modules/redis/redis.info.yml b/web/modules/redis/redis.info.yml index cc9f2354863aea10e2854738093d48f6b8f4c570..06a5bbcb67e04e43c7e325b53621ef3d26a816a4 100644 --- a/web/modules/redis/redis.info.yml +++ b/web/modules/redis/redis.info.yml @@ -5,8 +5,8 @@ type: module # core: 8.x configure: redis.admin_display -# Information added by Drupal.org packaging script on 2017-09-14 -version: '8.x-1.0-rc2' +# Information added by Drupal.org packaging script on 2018-05-30 +version: '8.x-1.0' core: '8.x' project: 'redis' -datestamp: 1505390051 +datestamp: 1527699489 diff --git a/web/modules/redis/src/Cache/CacheBase.php b/web/modules/redis/src/Cache/CacheBase.php index 4fe8e5dce7725dc7ab0f7675fcf26c2453ce2ae8..6daf590ace10869e55de1f99f9dfd2b917af0e4c 100644 --- a/web/modules/redis/src/Cache/CacheBase.php +++ b/web/modules/redis/src/Cache/CacheBase.php @@ -3,6 +3,7 @@ namespace Drupal\redis\Cache; use \DateInterval; +use Drupal\Component\Assertion\Inspector; use Drupal\Component\Serialization\SerializationInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; @@ -297,7 +298,7 @@ protected function createEntryHash($cid, $data, $expire = Cache::PERMANENT, arra // Always add a cache tag for the current bin, so that we can use that for // invalidateAll(). $tags[] = $this->getTagForBin(); - assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.'); + assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.'); $hash = [ 'cid' => $cid, 'created' => round(microtime(TRUE), 3), diff --git a/web/modules/redis/src/Lock/Predis.php b/web/modules/redis/src/Lock/Predis.php index bede5d97b89555bf633d19be73c6e851b6951815..aa67ca14350a71e3e4194137a845f5f23153f47a 100644 --- a/web/modules/redis/src/Lock/Predis.php +++ b/web/modules/redis/src/Lock/Predis.php @@ -84,8 +84,8 @@ public function acquire($name, $timeout = 30.0) { // succeed if the key does not exist yet. $result = $this->client->set($key, $id, 'nx', 'px', (int) ($timeout * 1000)); - // If the result is FALSE, we failed to acquire the lock. - if (FALSE === $result) { + // If the result is FALSE or NULL, we failed to acquire the lock. + if (FALSE === $result || NULL === $result) { return FALSE; } diff --git a/web/modules/redis/src/PersistentLock/Predis.php b/web/modules/redis/src/PersistentLock/Predis.php new file mode 100644 index 0000000000000000000000000000000000000000..79252e77e5398f7561c34a01ce8c2f9f63a1b255 --- /dev/null +++ b/web/modules/redis/src/PersistentLock/Predis.php @@ -0,0 +1,26 @@ +<?php + +namespace Drupal\redis\PersistentLock; + +use Drupal\redis\ClientFactory; + +/** + * Predis persistent lock backend + */ +class Predis extends \Drupal\redis\Lock\Predis { + + /** + * Creates a Predis persistent lock backend. + */ + public function __construct(ClientFactory $factory) { + // Do not call the parent constructor to avoid registering a shutdown + // function that releases all the locks at the end of a request. + $this->client = $factory->getClient(); + // Set the lockId to a fixed string to make the lock ID the same across + // multiple requests. The lock ID is used as a page token to relate all the + // locks set during a request to each other. + // @see \Drupal\Core\Lock\LockBackendInterface::getLockId() + $this->lockId = 'persistent'; + } + +} diff --git a/web/modules/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php b/web/modules/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php index fbf4b0461b02c054b0c302ae00ef49b0a7a5de36..d74a05ee2a87a63c220092a27622e0b8e4cc1b07 100644 --- a/web/modules/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php +++ b/web/modules/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php @@ -41,7 +41,7 @@ protected function setUp() { file_put_contents($filename, $contents); $settings = Settings::getAll(); $settings['container_yamls'][] = 'modules/redis/example.services.yml'; - $settings['redis.connection']['interface'] = '\'' . $redis_interface . '\''; + $settings['redis.connection']['interface'] = $redis_interface; new Settings($settings); OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $filename); diff --git a/web/modules/redis/tests/src/Functional/WebTest.php b/web/modules/redis/tests/src/Functional/WebTest.php index d5cfcb22d66d8aa4b74005ff49ad13fc38db6b2b..e49dfa3472ea47ddcf912668cb006493722499cf 100644 --- a/web/modules/redis/tests/src/Functional/WebTest.php +++ b/web/modules/redis/tests/src/Functional/WebTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\redis\Functional; use Drupal\Component\Utility\OpCodeCache; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Database\Database; use Drupal\Core\Site\Settings; use Drupal\field_ui\Tests\FieldUiTestTrait; @@ -142,10 +141,10 @@ public function testModuleInstallation() { // Create a node type with a field. $edit = [ 'name' => $this->randomString(), - 'type' => $node_type = Unicode::strtolower($this->randomMachineName()), + 'type' => $node_type = mb_strtolower($this->randomMachineName()), ]; $this->drupalPostForm('admin/structure/types/add', $edit, t('Save and manage fields')); - $field_name = Unicode::strtolower($this->randomMachineName()); + $field_name = mb_strtolower($this->randomMachineName()); $this->fieldUIAddNewField('admin/structure/types/manage/' . $node_type, $field_name, NULL, 'text'); // Create a node, check display, edit, verify that it has been updated. @@ -154,7 +153,7 @@ public function testModuleInstallation() { 'body[0][value]' => $this->randomMachineName(), 'field_' . $field_name . '[0][value]' => $this->randomMachineName(), ]; - $this->drupalPostForm('node/add/' . $node_type, $edit, t('Save and publish')); + $this->drupalPostForm('node/add/' . $node_type, $edit, t('Save')); // Test the output as anonymous user. $this->drupalLogout(); @@ -169,7 +168,7 @@ public function testModuleInstallation() { $update = [ 'title[0][value]' => $this->randomMachineName(), ]; - $this->drupalPostForm(NULL, $update, t('Save and keep published')); + $this->drupalPostForm(NULL, $update, t('Save')); $this->assertSession()->responseContains($update['title[0][value]']); $this->drupalGet('node'); $this->assertSession()->responseContains($update['title[0][value]']); diff --git a/web/modules/redis/tests/src/Kernel/RedisQueueTest.php b/web/modules/redis/tests/src/Kernel/RedisQueueTest.php index 137cb98213ea2fb555ba59be8c34d26c1bd67d27..78110049ae689ec4a220eac8cf213cd9e2775967 100644 --- a/web/modules/redis/tests/src/Kernel/RedisQueueTest.php +++ b/web/modules/redis/tests/src/Kernel/RedisQueueTest.php @@ -39,7 +39,7 @@ public function testRedisNonBlockingQueue() { $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); $queue2->createQueue(); - $this->queueTest($queue1, $queue2); + $this->runQueueTest($queue1, $queue2); $queue1->deleteQueue(); $queue2->deleteQueue(); @@ -53,7 +53,7 @@ public function testRedisNonBlockingQueue() { $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); $queue2->createQueue(); - $this->queueTest($queue1, $queue2); + $this->runQueueTest($queue1, $queue2); } /** @@ -74,7 +74,7 @@ public function testRedisBlockingQueue() { $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); $queue2->createQueue(); - $this->queueTest($queue1, $queue2); + $this->runQueueTest($queue1, $queue2); } /** diff --git a/web/modules/smtp/config/schema/smtp.schema.yml b/web/modules/smtp/config/schema/smtp.schema.yml index a788b82d571dd74cf424198d2b116abd0e1f8169..cda9cd068eb74439986ebb9c2cf1113ece2e5ab0 100644 --- a/web/modules/smtp/config/schema/smtp.schema.yml +++ b/web/modules/smtp/config/schema/smtp.schema.yml @@ -6,22 +6,22 @@ smtp.settings: type: boolean label: 'Turn this module on or off' smtp_host: - type: text + type: string label: 'SMTP server' smtp_hostbackup: - type: text + type: string label: 'SMTP backup server' smtp_port: - type: text + type: string label: 'SMTP port' smtp_protocol: - type: text + type: string label: 'Use encrypted protocol' smtp_username: - type: text + type: string label: 'Username' smtp_password: - type: text + type: string label: 'Password' smtp_from: type: email @@ -30,13 +30,13 @@ smtp.settings: type: text label: 'E-mail from name' smtp_client_hostname: - type: text + type: string label: 'Hostname' smtp_client_helo: - type: text + type: string label: 'HELO' smtp_allowhtml: - type: text + type: string label: 'Allow to send e-mails formated as HTML' smtp_test_address: type: email @@ -45,5 +45,5 @@ smtp.settings: type: boolean label: 'Enable debugging' prev_mail_system: - type: text + type: string label: 'Previous mail system' diff --git a/web/modules/smtp/smtp.info.yml b/web/modules/smtp/smtp.info.yml index ae327de8800371480da55bda53faf2ff7819d856..ecf2b3d30bbf83551d4c94d96452e73c57b358b1 100644 --- a/web/modules/smtp/smtp.info.yml +++ b/web/modules/smtp/smtp.info.yml @@ -5,8 +5,8 @@ type: module # core: 8.x configure: smtp.config -# Information added by Drupal.org packaging script on 2017-06-27 -version: '8.x-1.0-beta3' +# Information added by Drupal.org packaging script on 2018-05-29 +version: '8.x-1.0-beta4' core: '8.x' project: 'smtp' -datestamp: 1498598947 +datestamp: 1527598397 diff --git a/web/modules/smtp/smtp.module b/web/modules/smtp/smtp.module index 2e69241ea2906c8d1084441d92c6530f02f6efc9..258a878f0b2f0bea662a2775881598d8202294e5 100644 --- a/web/modules/smtp/smtp.module +++ b/web/modules/smtp/smtp.module @@ -31,11 +31,11 @@ function smtp_help($route_name, RouteMatchInterface $route_match) { * Implements hook_menu(). */ function smtp_menu() { - $items['admin/config/system/smtp'] = array( + $items['admin/config/system/smtp'] = [ 'title' => 'SMTP Authentication Support', 'description' => 'Allow for site emails to be sent through an SMTP server of your choice.', 'route_name' => 'smtp.config', - ); + ]; return $items; } @@ -53,12 +53,12 @@ function smtp_mail($key, &$message, $params) { * Implementation of hook_queue_info(). */ function smtp_queue_info() { - $queues['smtp_send_queue'] = array( + $queues['smtp_send_queue'] = [ 'worker callback' => 'smtp_send_queue_runner', - 'cron' => array( + 'cron' => [ 'time' => 60, // This is the max run time per cron run in seconds. - ), - ); + ], + ]; return $queues; } @@ -82,11 +82,11 @@ function _smtp_mailer_send($variables) { $logger = \Drupal::logger('smtp'); // Let the people know what is going on. - $logger->info('Sending mail to: @to', array('@to' => $to)); + $logger->info('Sending mail to: @to', ['@to' => $to]); // Try to send e-mail. If it fails, set watchdog entry. if (!$mailer->Send()) { - $logger->error('Error sending e-mail from @from to @to: @error_message', array('@from' => $from, '@to' => $to, '@error_message' => $mailer->ErrorInfo)); + $logger->error('Error sending e-mail from @from to @to: @error_message', ['@from' => $from, '@to' => $to, '@error_message' => $mailer->ErrorInfo]); return FALSE; } diff --git a/web/modules/smtp/src/Form/SMTPConfigForm.php b/web/modules/smtp/src/Form/SMTPConfigForm.php index c8c2f4c23523e8f1be3e2dfc74c8e8e4ca01676e..97d46bdd0a78821beb73d3ed1a2b963e9a6f07da 100644 --- a/web/modules/smtp/src/Form/SMTPConfigForm.php +++ b/web/modules/smtp/src/Form/SMTPConfigForm.php @@ -2,9 +2,12 @@ namespace Drupal\smtp\Form; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\Messenger; use Drupal\smtp\Plugin\Mail\SMTPMailSystem; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Implements the SMTP admin settings form. @@ -12,179 +15,215 @@ class SMTPConfigForm extends ConfigFormBase { /** - * {@inheritdoc}. + * The D8 messenger. + * + * @var \Drupal\Core\Messenger\Messenger + */ + protected $messenger; + + /** + * Constructs $messenger and $config_factory objects. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + * @param \Drupal\Core\Messenger\Messenger $messenger + * The D8 messenger object. + */ + public function __construct(ConfigFactoryInterface $config_factory, Messenger $messenger) { + $this->messenger = $messenger; + parent::__construct($config_factory); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} */ public function getFormID() { return 'smtp_admin_settings'; } /** - * {@inheritdoc}. + * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->configFactory->get('smtp.settings'); if ($config->get('smtp_on')) { - drupal_set_message(t('SMTP module is active.')); + $this->messenger->addMessage($this->t('SMTP module is active.')); } else { - drupal_set_message(t('SMTP module is INACTIVE.')); + $this->messenger->addMessage($this->t('SMTP module is INACTIVE.')); } - drupal_set_message(t('Disabled fields are overridden in site-specific configuration file.'), 'warning'); - $form['onoff'] = array( + $this->messenger->addMessage($this->t('Disabled fields are overridden in site-specific configuration file.'), 'warning'); + + $form['onoff'] = [ '#type' => 'details', - '#title' => t('Install options'), + '#title' => $this->t('Install options'), '#open' => TRUE, - ); - $form['onoff']['smtp_on'] = array( + ]; + $form['onoff']['smtp_on'] = [ '#type' => 'radios', - '#title' => t('Turn this module on or off'), + '#title' => $this->t('Turn this module on or off'), '#default_value' => $config->get('smtp_on') ? 'on' : 'off', - '#options' => array('on' => t('On'), 'off' => t('Off')), - '#description' => t('To uninstall this module you must turn it off here first.'), + '#options' => ['on' => $this->t('On'), 'off' => $this->t('Off')], + '#description' => $this->t('To uninstall this module you must turn it off here first.'), '#disabled' => $this->isOverridden('smtp_on'), - ); - $form['server'] = array( + ]; + $form['server'] = [ '#type' => 'details', - '#title' => t('SMTP server settings'), + '#title' => $this->t('SMTP server settings'), '#open' => TRUE, - ); - $form['server']['smtp_host'] = array( + ]; + $form['server']['smtp_host'] = [ '#type' => 'textfield', - '#title' => t('SMTP server'), + '#title' => $this->t('SMTP server'), '#default_value' => $config->get('smtp_host'), - '#description' => t('The address of your outgoing SMTP server.'), + '#description' => $this->t('The address of your outgoing SMTP server.'), '#disabled' => $this->isOverridden('smtp_host'), - ); - $form['server']['smtp_hostbackup'] = array( + ]; + $form['server']['smtp_hostbackup'] = [ '#type' => 'textfield', - '#title' => t('SMTP backup server'), + '#title' => $this->t('SMTP backup server'), '#default_value' => $config->get('smtp_hostbackup'), - '#description' => t('The address of your outgoing SMTP backup server. If the primary server can\'t be found this one will be tried. This is optional.'), + '#description' => $this->t("The address of your outgoing SMTP backup server. If the primary server can\'t be found this one will be tried. This is optional."), '#disabled' => $this->isOverridden('smtp_hostbackup'), - ); - $form['server']['smtp_port'] = array( + ]; + $form['server']['smtp_port'] = [ '#type' => 'number', - '#title' => t('SMTP port'), + '#title' => $this->t('SMTP port'), '#size' => 6, '#maxlength' => 6, '#default_value' => $config->get('smtp_port'), - '#description' => t('The default SMTP port is 25, if that is being blocked try 80. Gmail uses 465. See :url for more information on configuring for use with Gmail.', array(':url' => 'http://gmail.google.com/support/bin/answer.py?answer=13287')), + '#description' => $this->t('The default SMTP port is 25, if that is being blocked try 80. Gmail uses 465. See :url for more information on configuring for use with Gmail.', + [':url' => 'http://gmail.google.com/support/bin/answer.py?answer=13287']), '#disabled' => $this->isOverridden('smtp_port'), - ); + ]; + // Only display the option if openssl is installed. if (function_exists('openssl_open')) { - $encryption_options = array( - 'standard' => t('No'), - 'ssl' => t('Use SSL'), - 'tls' => t('Use TLS'), - ); - $encryption_description = t('This allows connection to an SMTP server that requires SSL encryption such as Gmail.'); + $encryption_options = [ + 'standard' => $this->t('No'), + 'ssl' => $this->t('Use SSL'), + 'tls' => $this->t('Use TLS'), + ]; + $encryption_description = $this->t('This allows connection to an SMTP server that requires SSL encryption such as Gmail.'); } // If openssl is not installed, use normal protocol. else { $config->set('smtp_protocol', 'standard'); - $encryption_options = array('standard' => t('No')); - $encryption_description = t('Your PHP installation does not have SSL enabled. See the :url page on php.net for more information. Gmail requires SSL.', array(':url' => 'http://php.net/openssl')); + $encryption_options = ['standard' => $this->t('No')]; + $encryption_description = $this->t('Your PHP installation does not have SSL enabled. See the :url page on php.net for more information. Gmail requires SSL.', + [':url' => 'http://php.net/openssl']); } - $form['server']['smtp_protocol'] = array( + + $form['server']['smtp_protocol'] = [ '#type' => 'select', - '#title' => t('Use encrypted protocol'), + '#title' => $this->t('Use encrypted protocol'), '#default_value' => $config->get('smtp_protocol'), '#options' => $encryption_options, '#description' => $encryption_description, '#disabled' => $this->isOverridden('smtp_protocol'), - ); + ]; - $form['auth'] = array( + $form['auth'] = [ '#type' => 'details', - '#title' => t('SMTP Authentication'), - '#description' => t('Leave blank if your SMTP server does not require authentication.'), + '#title' => $this->t('SMTP Authentication'), + '#description' => $this->t('Leave blank if your SMTP server does not require authentication.'), '#open' => TRUE, - ); - $form['auth']['smtp_username'] = array( + ]; + $form['auth']['smtp_username'] = [ '#type' => 'textfield', - '#title' => t('Username'), + '#title' => $this->t('Username'), '#default_value' => $config->get('smtp_username'), - '#description' => t('SMTP Username.'), + '#description' => $this->t('SMTP Username.'), '#disabled' => $this->isOverridden('smtp_username'), - ); - $form['auth']['smtp_password'] = array( + ]; + $form['auth']['smtp_password'] = [ '#type' => 'password', - '#title' => t('Password'), + '#title' => $this->t('Password'), '#default_value' => $config->get('smtp_password'), - '#description' => t('SMTP password. If you have already entered your password before, you should leave this field blank, unless you want to change the stored password. Please note that this password will be stored as plain-text inside Drupal\'s core configuration variables.'), + '#description' => $this->t("SMTP password. If you have already entered your password before, you should leave this field blank, unless you want to change the stored password. Please note that this password will be stored as plain-text inside Drupal\'s core configuration variables."), '#disabled' => $this->isOverridden('smtp_password'), - ); + ]; - $form['email_options'] = array( + $form['email_options'] = [ '#type' => 'details', - '#title' => t('E-mail options'), + '#title' =>$this->t('E-mail options'), '#open' => TRUE, - ); - $form['email_options']['smtp_from'] = array( + ]; + $form['email_options']['smtp_from'] = [ '#type' => 'textfield', - '#title' => t('E-mail from address'), + '#title' => $this->t('E-mail from address'), '#default_value' => $config->get('smtp_from'), - '#description' => t('The e-mail address that all e-mails will be from.'), + '#description' => $this->t('The e-mail address that all e-mails will be from.'), '#disabled' => $this->isOverridden('smtp_from'), - ); - $form['email_options']['smtp_fromname'] = array( + ]; + $form['email_options']['smtp_fromname'] = [ '#type' => 'textfield', - '#title' => t('E-mail from name'), + '#title' => $this->t('E-mail from name'), '#default_value' => $config->get('smtp_fromname'), - '#description' => t('The name that all e-mails will be from. If left blank will use a default of: @name', + '#description' => $this->t('The name that all e-mails will be from. If left blank will use a default of: @name', ['@name' => $this->configFactory->get('system.site')->get('name')]), '#disabled' => $this->isOverridden('smtp_fromname'), - ); - $form['email_options']['smtp_allowhtml'] = array( + ]; + $form['email_options']['smtp_allowhtml'] = [ '#type' => 'checkbox', - '#title' => t('Allow to send e-mails formatted as HTML'), + '#title' => $this->t('Allow to send e-mails formatted as HTML'), '#default_value' => $config->get('smtp_allowhtml'), - '#description' => t('Checking this box will allow HTML formatted e-mails to be sent with the SMTP protocol.'), + '#description' => $this->t('Checking this box will allow HTML formatted e-mails to be sent with the SMTP protocol.'), '#disabled' => $this->isOverridden('smtp_allowhtml'), - ); + ]; - $form['client'] = array( + $form['client'] = [ '#type' => 'details', - '#title' => t('SMTP client settings'), + '#title' => $this->t('SMTP client settings'), '#open' => TRUE, - ); - $form['client']['smtp_client_hostname'] = array( + ]; + $form['client']['smtp_client_hostname'] = [ '#type' => 'textfield', - '#title' => t('Hostname'), + '#title' => $this->t('Hostname'), '#default_value' => $config->get('smtp_client_hostname'), - '#description' => t('The hostname to use in the Message-Id and Received headers, and as the default HELO string. Leave blank for using %server_name.', array('%server_name' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost.localdomain')), + '#description' => $this->t('The hostname to use in the Message-Id and Received headers, and as the default HELO string. Leave blank for using %server_name.', + ['%server_name' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost.localdomain']), '#disabled' => $this->isOverridden('smtp_client_hostname'), - ); - $form['client']['smtp_client_helo'] = array( + ]; + $form['client']['smtp_client_helo'] = [ '#type' => 'textfield', - '#title' => t('HELO'), + '#title' => $this->t('HELO'), '#default_value' => $config->get('smtp_client_helo'), - '#description' => t('The SMTP HELO/EHLO of the message. Defaults to hostname (see above).'), + '#description' => $this->t('The SMTP HELO/EHLO of the message. Defaults to hostname (see above).'), '#disabled' => $this->isOverridden('smtp_client_helo'), - ); + ]; - $form['email_test'] = array( + $form['email_test'] = [ '#type' => 'details', - '#title' => t('Send test e-mail'), + '#title' => $this->t('Send test e-mail'), '#open' => TRUE, - ); - $form['email_test']['smtp_test_address'] = array( + ]; + $form['email_test']['smtp_test_address'] = [ '#type' => 'textfield', - '#title' => t('E-mail address to send a test e-mail to'), + '#title' => $this->t('E-mail address to send a test e-mail to'), '#default_value' => '', - '#description' => t('Type in an address to have a test e-mail sent there.'), - ); + '#description' => $this->t('Type in an address to have a test e-mail sent there.'), + ]; - $form['smtp_debugging'] = array( + $form['smtp_debugging'] = [ '#type' => 'checkbox', - '#title' => t('Enable debugging'), + '#title' => $this->t('Enable debugging'), '#default_value' => $config->get('smtp_debugging'), - '#description' => t('Checking this box will print SMTP messages from the server for every e-mail that is sent.'), + '#description' => $this->t('Checking this box will print SMTP messages from the server for every e-mail that is sent.'), '#disabled' => $this->isOverridden('smtp_debugging'), - ); + ]; return parent::buildForm($form, $form_state); } @@ -193,9 +232,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { * Check if config variable is overridden by the settings.php. * * @param string $name - * STMP settings key. + * STMP settings key. * * @return bool + * Boolean. */ protected function isOverridden($name) { $original = $this->configFactory->getEditable('smtp.settings')->get($name); @@ -225,12 +265,15 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $form_state->setErrorByName('smtp_test_address', $this->t('The provided test e-mail address is not valid.')); } - // If username is set empty, we must set both username/password empty as well. + // If username is set empty, we must set both + // username/password empty as well. if (empty($values['smtp_username'])) { $values['smtp_password'] = ''; } - // A little hack. When form is presented, the password is not shown (Drupal way of doing). - // So, if user submits the form without changing the password, we must prevent it from being reset. + // A little hack. When form is presented, + // the password is not shown (Drupal way of doing). + // So, if user submits the form without changing the password, + // we must prevent it from being reset. elseif (empty($values['smtp_password'])) { $form_state->unsetValue('smtp_password'); } @@ -289,10 +332,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // If an address was given, send a test e-mail message. if ($test_address = $values['smtp_test_address']) { - $params['subject'] = t('Drupal SMTP test e-mail'); - $params['body'] = array(t('If you receive this message it means your site is capable of using SMTP to send e-mail.')); + $params['subject'] = $this->t('Drupal SMTP test e-mail'); + $params['body'] = [$this->t('If you receive this message it means your site is capable of using SMTP to send e-mail.')]; $account = \Drupal::currentUser(); - // If module is off, send the test message with SMTP by temporarily overriding. + // If module is off, send the test message + // with SMTP by temporarily overriding. if (!$config->get('smtp_on')) { $original = $mail_config->get('interface'); $mail_system = 'SMTPMailSystem'; @@ -302,7 +346,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { if (!$config->get('smtp_on')) { $mail_config->set('interface', $original)->save(); } - drupal_set_message(t('A test e-mail has been sent to @email via SMTP. You may want to check the log for any error messages.', ['@email' => $test_address])); + $this->messenger->addMessage($this->t('A test e-mail has been sent to @email via SMTP. You may want to check the log for any error messages.', ['@email' => $test_address])); } parent::submitForm($form, $form_state); diff --git a/web/modules/smtp/src/PHPMailer/PHPMailer.php b/web/modules/smtp/src/PHPMailer/PHPMailer.php index ed0761df5245d76cf34b2bf8af413ee95e608bfc..ece2aae5a2e6fb2d755c4b1dcf414c2c33dc5ce9 100644 --- a/web/modules/smtp/src/PHPMailer/PHPMailer.php +++ b/web/modules/smtp/src/PHPMailer/PHPMailer.php @@ -1054,7 +1054,8 @@ public function CreateHeader() { $result = ''; // Set the boundaries - $uniq_id = md5(uniqid(REQUEST_TIME)); + $request_time = \Drupal::time()->getRequestTime(); + $uniq_id = md5(uniqid($request_time)); $this->boundary[1] = 'b1_' . $uniq_id; $this->boundary[2] = 'b2_' . $uniq_id; @@ -2288,7 +2289,7 @@ public function DKIM_Add($headers_line, $subject, $body) { $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body $DKIMquery = 'dns/txt'; // Query method - $DKIMtime = REQUEST_TIME; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $DKIMtime = \Drupal::time()->getRequestTime(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) $subject_header = "Subject: $subject"; $headers = explode("\r\n", $headers_line); foreach ($headers as $header) { diff --git a/web/modules/smtp/src/Plugin/Mail/SMTPMailSystem.php b/web/modules/smtp/src/Plugin/Mail/SMTPMailSystem.php index 6d5302a2f55f44c8416455d274e733588ae3c761..1eb4462e047464f416db3e6210edd619211bb4da 100644 --- a/web/modules/smtp/src/Plugin/Mail/SMTPMailSystem.php +++ b/web/modules/smtp/src/Plugin/Mail/SMTPMailSystem.php @@ -127,7 +127,7 @@ public function mail(array $message) { // Defines the From value to what we expect. $mailer->From = $from; - $mailer->FromName = $from_name; + $mailer->FromName = Unicode::mimeHeaderEncode($from_name); $mailer->Sender = $from; $hostname = $this->smtpConfig->get('smtp_client_hostname'); @@ -215,8 +215,8 @@ public function mail(array $message) { break; default: // Everything else is unsuppored by PHPMailer. - drupal_set_message(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value")), 'error'); - $this->logger->error(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value"))); + drupal_set_message(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', ['%header' => "Content-Type: $value"]), 'error'); + $this->logger->error(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', ['%header' => "Content-Type: $value"])); // Force the Content-Type to be text/plain. $mailer->IsHTML(FALSE); @@ -228,7 +228,7 @@ public function mail(array $message) { // Only add a "reply-to" if it's not the same as "return-path". if ($value != $headers['Return-Path']) { $reply_to_comp = $this->_get_components($value); - $mailer->AddReplyTo($reply_to_comp['email'], $reply_to_comp['name']); + $mailer->AddReplyTo($reply_to_comp['email'], Unicode::mimeHeaderEncode($reply_to_comp['name'])); } break; @@ -253,7 +253,7 @@ public function mail(array $message) { $ccrecipients = explode(',', $value); foreach ($ccrecipients as $ccrecipient) { $cc_comp = $this->_get_components($ccrecipient); - $mailer->AddCC($cc_comp['email'], $cc_comp['name']); + $mailer->AddCC($cc_comp['email'], Unicode::mimeHeaderEncode($cc_comp['name'])); } break; @@ -261,7 +261,7 @@ public function mail(array $message) { $bccrecipients = explode(',', $value); foreach ($bccrecipients as $bccrecipient) { $bcc_comp = $this->_get_components($bccrecipient); - $mailer->AddBCC($bcc_comp['email'], $bcc_comp['name']); + $mailer->AddBCC($bcc_comp['email'], Unicode::mimeHeaderEncode($bcc_comp['name'])); } break; @@ -287,7 +287,7 @@ public function mail(array $message) { * } */ // Add the message's subject. - $mailer->Subject = $subject; + $mailer->Subject = Unicode::mimeHeaderEncode($subject); // Processes the message's body. switch ($content_type) { @@ -489,13 +489,13 @@ public function mail(array $message) { $mailer->Port = $this->smtpConfig->get('smtp_port'); $mailer->Mailer = 'smtp'; - $mailerArr = array( + $mailerArr = [ 'mailer' => $mailer, 'to' => $to, 'from' => $from, - ); + ]; if ($this->smtpConfig->get('smtp_queue')) { - $this->logger->info(t('Queue sending mail to: @to', array('@to' => $to))); + $this->logger->info(t('Queue sending mail to: @to', ['@to' => $to])); smtp_send_queue($mailerArr); } else { @@ -519,7 +519,7 @@ public function mail(array $message) { * An array containing the resulting mime parts */ protected function _boundary_split($input, $boundary) { - $parts = array(); + $parts = []; $bs_possible = substr($boundary, 2, -2); $bs_check = '\"' . $bs_possible . '\"'; @@ -549,35 +549,35 @@ protected function _boundary_split($input, $boundary) { protected function _remove_headers($input) { $part_array = explode("\n", $input); - // will strip these headers according to RFC2045 - $headers_to_strip = array( 'Content-Type', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Disposition'); + // Will strip these headers according to RFC2045. + $headers_to_strip = ['Content-Type', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Disposition']; $pattern = '/^(' . implode('|', $headers_to_strip) . '):/'; while (count($part_array) > 0) { - // ignore trailing spaces/newlines + // Ignore trailing spaces/newlines. $line = rtrim($part_array[0]); - // if the line starts with a known header string + // If the line starts with a known header string. if (preg_match($pattern, $line)) { $line = rtrim(array_shift($part_array)); - // remove line containing matched header. + // Remove line containing matched header. - // if line ends in a ';' and the next line starts with four spaces, it's a continuation + // If line ends in a ';' and the next line starts with four spaces, it's a continuation // of the header split onto the next line. Continue removing lines while we have this condition. while (substr($line, -1) == ';' && count($part_array) > 0 && substr($part_array[0], 0, 4) == ' ') { $line = rtrim(array_shift($part_array)); } } else { - // no match header, must be past headers; stop searching. + // No match header, must be past headers; stop searching. break; } } $output = implode("\n", $part_array); return $output; - } // End of _smtp_remove_headers(). + } /** * Returns a string that is contained within another string. @@ -609,7 +609,7 @@ protected function _get_substring($source, $target, $beginning_character, $endin } return $substring; - } // End of _smtp_get_substring(). + } /** * Returns an array of name and email address from a string. @@ -621,11 +621,11 @@ protected function _get_substring($source, $target, $beginning_character, $endin * An array containing a name and an email address. */ protected function _get_components($input) { - $components = array( + $components = [ 'input' => $input, 'name' => '', 'email' => '', - ); + ]; // If the input is a valid email address in its entirety, then there is // nothing to do, just return that. diff --git a/web/modules/smtp/src/Tests/SmtpTest.php b/web/modules/smtp/src/Tests/SmtpTest.php index 0a5edb2231ece8cbfb6a1c14dda70f33ac2be931..70139601f04bb3fb284c5c785ecbb055fa280f5f 100644 --- a/web/modules/smtp/src/Tests/SmtpTest.php +++ b/web/modules/smtp/src/Tests/SmtpTest.php @@ -16,7 +16,7 @@ class SmtpTest extends WebTestBase { * * @var array */ - public static $modules = array('smtp'); + public static $modules = ['smtp']; /** * Perform any initial set up tasks that run before every test method diff --git a/web/modules/smtp/tests/src/Unit/SMTPConfigFormTest.php b/web/modules/smtp/tests/src/Unit/SMTPConfigFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4d806131fec77b5a4b34b42b55bbe62ef202835e --- /dev/null +++ b/web/modules/smtp/tests/src/Unit/SMTPConfigFormTest.php @@ -0,0 +1,97 @@ +<?php + +namespace Drupal\Tests\smtp\Unit; + +use Drupal\Core\Config\Config; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Form\FormState; +use Drupal\Core\Messenger\Messenger; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\smtp\Form\SMTPConfigForm; +use Drupal\Tests\UnitTestCase; +use Prophecy\Argument; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Validate requirements for ProceedingsRealmNegotiator. + * + * @group SMTP + */ +class SMTPConfigFormTest extends UnitTestCase { + + /** + * Test setup. + */ + public function setup() { + $this->mockConfigFactory = $this->prophesize(ConfigFactoryInterface::class); + $this->mockConfig = $this->prophesize(Config::class); + $this->mockConfigFactory->get('smtp.settings')->willReturn($this->mockConfig->reveal()); + $this->mockConfigFactory->getEditable('smtp.settings')->willReturn($this->mockConfig->reveal()); + + $this->mockConfigSystemSite = $this->prophesize(Config::class); + $this->mockConfigSystemSite->get('name')->willReturn('Site name'); + $this->mockConfigFactory->get('system.site')->willReturn($this->mockConfigSystemSite->reveal()); + + $this->mockMessenger = $this->prophesize(Messenger::class); + + $mockContainer = $this->mockContainer = $this->prophesize(ContainerInterface::class); + $mockContainer->get('config.factory')->willReturn($this->mockConfigFactory->reveal()); + $mockContainer->get('messenger')->willReturn($this->mockMessenger->reveal()); + + $mockStringTranslation = $this->prophesize(TranslationInterface::class); + $mockStringTranslation->translate(Argument::any())->willReturnArgument(0); + $mockStringTranslation->translate(Argument::any(), Argument::any())->willReturnArgument(0); + $mockStringTranslation->translateString(Argument::any())->willReturn('.'); + $mockContainer->get('string_translation')->willReturn($mockStringTranslation->reveal()); + + \Drupal::setContainer($this->mockContainer->reveal()); + } + + /** + * Sets the default smtp config. + */ + public function setDefaultConfig() { + $this->mockConfig->get('smtp_on')->willReturn(TRUE); + $this->mockConfig->get('smtp_host')->willReturn(''); + $this->mockConfig->get('smtp_hostbackup')->willReturn(''); + $this->mockConfig->get('smtp_port')->willReturn(''); + $this->mockConfig->get('smtp_protocol')->willReturn(''); + $this->mockConfig->get('smtp_username')->willReturn(''); + $this->mockConfig->get('smtp_password')->willReturn(''); + $this->mockConfig->get('smtp_from')->willReturn(''); + $this->mockConfig->get('smtp_fromname')->willReturn(''); + $this->mockConfig->get('smtp_allowhtml')->willReturn(''); + $this->mockConfig->get('smtp_client_hostname')->willReturn(''); + $this->mockConfig->get('smtp_client_helo')->willReturn(''); + $this->mockConfig->get('smtp_debugging')->willReturn(''); + } + + /** + * Test if enabled message is properly shown. + */ + public function testBuildFormEnabledMessage() { + $this->setDefaultConfig(); + $this->mockConfig->get('smtp_on')->willReturn(TRUE); + + $formBuilder = SMTPConfigForm::create($this->mockContainer->reveal()); + + $form = []; + $formBuilder->buildForm($form, new FormState()); + $this->mockMessenger->addMessage(Argument::which('getUntranslatedString', 'SMTP module is active.'))->shouldHaveBeenCalled(); + } + + /** + * Test if enabled message is properly shown. + */ + public function testBuildFormDisabledMessage() { + $this->setDefaultConfig(); + $this->mockConfig->get('smtp_on')->willReturn(FALSE); + + $formBuilder = SMTPConfigForm::create($this->mockContainer->reveal()); + + $form = []; + $formBuilder->buildForm($form, new FormState()); + $this->mockMessenger->addMessage(Argument::which('getUntranslatedString', 'SMTP module is INACTIVE.'))->shouldHaveBeenCalled(); + } + +} diff --git a/web/modules/views_accordion/README.txt b/web/modules/views_accordion/README.txt index 4d4d6ab85c2cb2701800dc232467ac0ad5f9ab92..d14b91d48deadf24bb1f751fa1419789b833e68f 100644 --- a/web/modules/views_accordion/README.txt +++ b/web/modules/views_accordion/README.txt @@ -2,26 +2,30 @@ /* DESCRIPTION */ Views Accordion provides a display style plugin for the Views module. -It will take the results and display them as a JQuery accordion. It supports grouping of fields and ajax pagination. +It will take the results and display them as a JQuery accordion. It supports +grouping of fields and ajax pagination. /* INSTALATION */ -1. Place the views_accordion module in your modules directory (usually under /modules/). +1. Place the views_accordion module in your modules directory (usually under + /modules/). 2. Go to /admin/modules, and activate the module. /* USING VIEWS ACCORDION MODULE */ Your view must meet the following requirements: - * Row style must be set to Fields + * Row style must be set to Fields. * Provide at least two fields to show. -Choose Views Accordion in the Style dialog within your view, which will prompt you to configure the jquery.ui.accorion settings. +Choose jQuery UI accordion in the Style dialog within your view, which will prompt +you to configure the jquery.ui.accorion settings. -* IMPORTANT * -The first field WILL be used as the header for each accordion section, all others will be displayed -when the header is clicked. The module creates an accordion section per row of results from the view. +* IMPORTANT * +The first field WILL be used as the header for each accordion section, all +others will be displayed when the header is clicked. The module creates an +accordion section per row of results from the view. ************************** diff --git a/web/modules/views_accordion/config/schema/views.style.views_accordion.schema.yml b/web/modules/views_accordion/config/schema/views.style.views_accordion.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..f3388a6422c9319f57ff9fbc098f4832abffd371 --- /dev/null +++ b/web/modules/views_accordion/config/schema/views.style.views_accordion.schema.yml @@ -0,0 +1,36 @@ +views.style.views_accordion: + type: views_style + label: 'Views Accordion' + mapping: + disableifone: + type: integer + label: 'Disable if only one result' + row-start-open: + type: string + label: 'Row to display opened on start' + collapsible: + type: integer + label: 'Collapsible' + animated: + type: string + label: 'Animation effect' + animation_time: + type: string + label: 'Animation time' + heightStyle: + type: string + label: 'heightStyle' + event: + type: string + label: 'Event' + use_header_icons: + type: integer + label: 'Use header icons' + icon_header: + type: 'string' + label: 'Closed row header icon' + icon_active_header: + type: 'string' + label: 'Opened row header icon' + + diff --git a/web/modules/views_accordion/src/Plugin/views/style/ViewsAccordion.php b/web/modules/views_accordion/src/Plugin/views/style/ViewsAccordion.php index 2d13016e20881a0f3752b0ff4f5378cd27a8839c..2b271ed8b57c837f68d353827bcc0dfb417e5d78 100644 --- a/web/modules/views_accordion/src/Plugin/views/style/ViewsAccordion.php +++ b/web/modules/views_accordion/src/Plugin/views/style/ViewsAccordion.php @@ -20,22 +20,17 @@ */ class ViewsAccordion extends StylePluginBase { /** - * Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesRowPlugin + * {@inheritdoc} */ protected $usesRowPlugin = TRUE; /** - * Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesRowClass. - */ + * {@inheritdoc} + */ protected $usesRowClass = TRUE; /** - * Overrides \Drupal\views\Plugin\views\style\StylePluginBase::groupingTheme. - */ - // protected $groupingTheme = 'views_view_grouping'; - - /** - * Set default options + * Set default options. */ protected function defineOptions() { $options = parent::defineOptions(); @@ -59,17 +54,17 @@ protected function defineOptions() { public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); - //// available valid options for grouping (used for use-grouping-header #dependency) - foreach ($this->displayHandler->getHandlers('field') as $field => $handler) { - $options[] = $field; - } - // Find out how many items the display is currently configured to show (row-start-open) + // Find out how many items the display is currently configured to show + // (row-start-open). $maxitems = $this->displayHandler->getOption('items_per_page'); - $maxitems = ($maxitems == 0) ? 10 : $maxitems; // if items_per_page is set to unlimitted (0), 10 rows will be what the user gets to choose from. + // If items_per_page is set to unlimitted (0), 10 rows will be what the user + // gets to choose from. + $maxitems = ($maxitems == 0) ? 10 : $maxitems; - // Setup our array of options for choosing which row should start opened (row-start-open) + // Setup our array of options for choosing which row should start opened + // (row-start-open). $rsopen_options = []; - for ($i = 1; $i <= $maxitems; $i++){ + for ($i = 1; $i <= $maxitems; $i++) { $rsopen_options[] = $this->t('Row @number', ['@number' => $i]); } $rsopen_options['none'] = $this->t('None'); @@ -124,7 +119,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#type' => 'checkbox', '#title' => $this->t('Use the group header as the Accordion header'), '#default_value' => $grouping['use-grouping-header'], - '#description' => $this->t('If checked, the Group\'s header will be used to open/close the accordion.'), + '#description' => $this->t("If checked, the Group's header will be used to open/close the accordion."), '#states' => [ 'invisible' => [ ':input[name="style_options[grouping][' . $i . '][field]"]' => ['value' => ''], @@ -132,7 +127,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { ], ]; } - $form['grouping']['#prefix'] = '<div class="form-item">'. $this->t('<strong>IMPORTANT:</strong> The <em>first field</em> in order of appearance <em>will</em> be the one used as the "header" or "trigger" of the accordion action.') .'</div>'; + $form['grouping']['#prefix'] = '<div class="form-item">' . $this->t('<strong>IMPORTANT:</strong> The <em>first field</em> in order of appearance <em>will</em> be the one used as the "header" or "trigger" of the accordion action.') . '</div>'; $form['disableifone'] = [ '#type' => 'checkbox', @@ -169,16 +164,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#step' => 1, '#description' => $this->t('The animation duration in milliseconds'), ]; - $form['heightStyle'] = [ + $form['heightStyle'] = [ '#type' => 'select', '#title' => $this->t('heightStyle'), '#default_value' => $this->options['heightStyle'], '#description' => $this->t('Controls the height of the accordion and each panel.'), - '#options' => [ + '#options' => [ 'auto' => 'auto', 'fill' => 'fill', 'content' => 'content', - ] + ], ]; $form['event'] = [ '#type' => 'select', @@ -194,11 +189,11 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#type' => 'checkbox', '#title' => $this->t('Use header icons'), '#default_value' => $this->options['use_header_icons'], - '#description' => $this->t('Icons to use for headers, matching an icon provided by the <a href="http://api.jqueryui.com/theming/icons/" target="_false">jQuery UI CSS Framework</a>. Uncheck to have no icons displayed.') + '#description' => $this->t('Icons to use for headers, matching an icon provided by the <a href="http://api.jqueryui.com/theming/icons/" target="_false">jQuery UI CSS Framework</a>. Uncheck to have no icons displayed.'), ]; $show_if_use_header_icons = [ 'visible' => [ - ':input[name="style_options[use_header_icons]"]' => array('checked' => TRUE), + ':input[name="style_options[use_header_icons]"]' => ['checked' => TRUE], ], ]; $form['icon_header'] = [ @@ -215,40 +210,49 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { ]; } + /** + * {@inheritdoc} + */ public function preRender($result) { - // no need do anything if we we have only one result and disableifone is active + // No need do anything if we we have only one result and disableifone is + // active. if ($this->options['disableifone'] == '1') { - if(count($result) < 2) { + if (count($result) < 2) { return; } } $view_settings = []; $header_class = 'views-accordion-header'; + // This is used for triggering the creation of the accordions. + // We append the dom_id so that multiple nested views with accordions work. + $accordion_header_class = 'js-' . $header_class . '-' . $this->view->dom_id; $view_settings['usegroupheader'] = FALSE; foreach ($this->options['grouping'] as $group) { - $view_settings['usegroupheader'] = $group['use-grouping-header'] == 1; - break; // @TODO handle multiple grouping + $view_settings['usegroupheader'] = $group['use-grouping-header'] == 1; + // @TODO handle multiple grouping. + break; } - // Find out about the header field options + // Find out about the header field options. $fields = array_values($this->displayHandler->getOption('fields')); - // Add header class to first not-excluded field + // Add header class to first not-excluded field. foreach ($fields as $field) { if (!isset($field['exclude']) || ($field['exclude'] == 0)) { - // make sure we are using a div for markup at least + // Make sure we are using a div for markup at least. if (empty($field['element_wrapper_type'])) { - $this->view->field[$field['id']]->options['element_wrapper_type'] = 'div'; + $this->view->field[$field['id']]->options['element_wrapper_type'] = 'div'; } - // Setup our wrapper class if not using group header + // Setup our wrapper class if not using group header. if (!$view_settings['usegroupheader']) { - $header_wrapper_class = $header_class; - // if the user configured its own class, set that up with our own class - if(!empty($field['element_wrapper_class'])) { - $header_wrapper_class = $field['element_wrapper_class'] .' '. $header_class; + $header_wrapper_class = $header_class . ' ' . $accordion_header_class; + // If the user configured its own class, set that up with our own + // class. + if (!empty($field['element_wrapper_class'])) { + $header_wrapper_class = $field['element_wrapper_class'] . ' ' . $header_wrapper_class; } - // setup the view to use our processed wrapper class + // Setup the view to use our processed wrapper class. $this->view->field[$field['id']]->options['element_wrapper_class'] = $header_wrapper_class; } break; @@ -257,23 +261,16 @@ public function preRender($result) { $this->view->element['#attached']['library'][] = 'views_accordion/views_accordion.accordion'; - // Add the appropiate effect library if necessary + // Add the appropiate effect library if necessary. $effect = $this->options['animated']; - if(($effect !== 'none') && ($effect !== 'swing') && ($effect !== 'linear')){ - // For now we only use ui core effects library, which provides the easing effects. - // this switch is left here in case we want to integrate and load any other libraries - switch($effect) { - default: - $library = 'core/jquery.ui.effects.core'; - break; - } - if(isset($library)){ - $this->view->element['#attached']['library'][] = $library; - } + if (($effect !== 'none') && ($effect !== 'swing') && ($effect !== 'linear')) { + // jquery.ui.effects.core provides the easing effects. + // It would be possible to integrate and load any other libraries here. + $this->view->element['#attached']['library'][] = 'core/jquery.ui.effects.core'; } // Prepare the JS settings. - // We do it here so we don't have it run once every group + // We do it here so we don't have it run once every group. $view_settings['collapsible'] = $this->options['collapsible']; if ($this->options['row-start-open'] == 'random') { $view_settings['rowstartopen'] = 'random'; @@ -291,10 +288,11 @@ public function preRender($result) { $view_settings['iconActiveHeader'] = $this->options['icon_active_header']; } // The view display selector. - $view_settings['display'] = '.js-view-dom-id-' . $this->view->dom_id; // set in stable & classy themes + // Set in stable & classy themes. + $view_settings['display'] = '.js-view-dom-id-' . $this->view->dom_id; // The accordion header selector. - $view_settings['header'] = '.' . $header_class; + $view_settings['header'] = '.' . $accordion_header_class; if ($view_settings['usegroupheader']) { // @TODO we cannot set a class for the grouping h3 apparently... $view_settings['header'] = '.js-views-accodion-group-header'; @@ -304,7 +302,7 @@ public function preRender($result) { } /** - * Overrides \Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::render(). + * {@inheritdoc} */ public function render() { $rows = parent::render(); @@ -312,13 +310,13 @@ public function render() { '#theme' => $this->themeFunctions(), '#view' => $this->view, '#options' => $this->options, - '#rows' => $rows + '#rows' => $rows, ]; return $output; } /** - * Overrides \Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::validate(). + * {@inheritdoc} */ public function validate() { $errors = parent::validate(); @@ -326,15 +324,17 @@ public function validate() { $errors[] = $this->t('Views accordion requires Fields as row style'); } - foreach($this->options['grouping'] as $group) { + foreach ($this->options['grouping'] as $group) { if (!$group['rendered'] && $group['use-grouping-header']) { $errors[] = $this->t('Views accordion requires "Use rendered output to group rows" enabled in order to use the group header as the Accordion header.'); } - break; // @TODO handle multiple grouping + // @TODO handle multiple grouping. + break; } - if ($this->options['collapsible'] !== 1 && $this->options['row-start-open'] == 'none') { + if ($this->options['collapsible'] !== 1 && $this->options['row-start-open'] === 'none') { $errors[] = $this->t('Setting "Row to display opened on start" to "None" requires "Collapsible" to be enabled.'); } return $errors; } + } diff --git a/web/modules/views_accordion/tests/modules/views_accordion_test/config/install/views.view.views_accordion_test.yml b/web/modules/views_accordion/tests/modules/views_accordion_test/config/install/views.view.views_accordion_test.yml new file mode 100644 index 0000000000000000000000000000000000000000..6ddf526ce98cdb788fa186e6f5ee23e8e9a303b5 --- /dev/null +++ b/web/modules/views_accordion/tests/modules/views_accordion_test/config/install/views.view.views_accordion_test.yml @@ -0,0 +1,263 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + module: + - node + - text + - user + - views_accordion +id: views_accordion_test +label: 'Views Accordion Test' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: views_accordion + options: + grouping: { } + row_class: '' + default_row_class: true + disableifone: 0 + row-start-open: '0' + collapsible: 0 + animated: none + animation_time: '300' + heightStyle: auto + event: click + use_header_icons: 1 + icon_header: ui-icon-triangle-1-e + icon_active_header: ui-icon-triangle-1-s + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + body: + id: body + table: node__body + field: body + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: text_summary_or_trimmed + settings: + trim_length: 600 + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + filters: { } + sorts: { } + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + filter_groups: + operator: AND + groups: { } + title: 'Views Accordion Test' + use_ajax: false + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: + - 'config:field.storage.node.body' + - extensions + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 2 + display_options: + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: + - 'config:field.storage.node.body' + - extensions + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: views-accordion-test + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: + - 'config:field.storage.node.body' + - extensions diff --git a/web/modules/views_accordion/tests/modules/views_accordion_test/views_accordion_test.info.yml b/web/modules/views_accordion/tests/modules/views_accordion_test/views_accordion_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..2e821210760ad3201c02238c0bd5c58f6eea1a4a --- /dev/null +++ b/web/modules/views_accordion/tests/modules/views_accordion_test/views_accordion_test.info.yml @@ -0,0 +1,15 @@ +name: 'Views Accordion Test' +type: module +description: 'Provides test config for views_accordion module tests.' +# core: 8.x +dependencies: + - drupal:node + - drupal:views + - drupal:text + - drupal:views_accordion + +# Information added by Drupal.org packaging script on 2018-03-25 +version: '8.x-1.1' +core: '8.x' +project: 'views_accordion' +datestamp: 1521988091 diff --git a/web/modules/views_accordion/tests/src/Functional/ViewsAccordionTest.php b/web/modules/views_accordion/tests/src/Functional/ViewsAccordionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..626f0b39b5561b5a6f0018e809408472f06a7885 --- /dev/null +++ b/web/modules/views_accordion/tests/src/Functional/ViewsAccordionTest.php @@ -0,0 +1,79 @@ +<?php + +namespace Drupal\Tests\views_accordion\Functional; + +use Drupal\Tests\BrowserTestBase; + +/** + * Functional tests for the Views Accordion module. + * + * @group views_accordion + */ +class ViewsAccordionTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'views_accordion_test', + 'views_ui', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $user = $this->drupalCreateUser([ + 'access administration pages', + 'administer views', + ]); + $this->drupalLogin($user); + } + + /** + * Tests Views Accordion functionality. + */ + public function testViewsAccordion() { + $assert_session = $this->assertSession(); + // Test views add form. + $edit = [ + 'id' => 'test', + 'label' => 'test', + 'show[wizard_key]' => 'node', + 'show[sort]' => 'none', + 'page[create]' => 1, + 'page[title]' => 'Test', + 'page[path]' => 'test', + 'page[style][style_plugin]' => 'views_accordion', + 'page[style][row_plugin]' => 'teasers', + ]; + $this->drupalPostForm('admin/structure/views/add', $edit, 'Save and edit'); + $assert_session->pageTextContains('Views accordion requires Fields as row style'); + + $edit['page[style][row_plugin]'] = 'fields'; + $this->drupalPostForm('admin/structure/views/add', $edit, 'Save and edit'); + $assert_session->pageTextContains('The view test has been saved.'); + + // Assert the options of our exported view display correctly. + $this->drupalGet('admin/structure/views/view/views_accordion_test/edit'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('jQuery UI accordion'); + + // Verify the style options show with the right values in the form. + $this->drupalGet('admin/structure/views/nojs/display/views_accordion_test/page_1/style_options'); + $assert_session->statusCodeEquals(200); + $assert_session->checkboxNotChecked('style_options[grouping][0][use-grouping-header]'); + $assert_session->checkboxNotChecked('style_options[disableifone]'); + $assert_session->checkboxNotChecked('style_options[collapsible]'); + $assert_session->fieldValueEquals('style_options[animated]', 'none'); + $assert_session->fieldValueEquals('style_options[animation_time]', '300'); + $assert_session->fieldValueEquals('style_options[heightStyle]', 'auto'); + $assert_session->fieldValueEquals('style_options[event]', 'click'); + $assert_session->checkboxChecked('style_options[use_header_icons]'); + $assert_session->fieldValueEquals('style_options[icon_header]', 'ui-icon-triangle-1-e'); + $assert_session->fieldValueEquals('style_options[icon_active_header]', 'ui-icon-triangle-1-s'); + } + +} diff --git a/web/modules/views_accordion/tests/src/FunctionalJavascript/ViewsAccordionTest.php b/web/modules/views_accordion/tests/src/FunctionalJavascript/ViewsAccordionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4684c44915a5e2a1fef6505ad3219f2c9bd17f6b --- /dev/null +++ b/web/modules/views_accordion/tests/src/FunctionalJavascript/ViewsAccordionTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\Tests\views_accordion\FunctionalJavascript; + +use Drupal\FunctionalJavascriptTests\JavascriptTestBase; +use Drupal\Tests\node\Traits\NodeCreationTrait; + +/** + * Tests the JavaScript functionality of the Views Accordion module. + * + * @group views_accordion + */ +class ViewsAccordionTest extends JavascriptTestBase { + use NodeCreationTrait; + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'views_accordion_test', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Create a set of nodes for testing. + $this->createNode(); + $this->createNode(); + } + + /** + * Tests Views Accordion functionality. + */ + public function testViewsAccordion() { + $this->drupalGet('views-accordion-test'); + // Assert our JS settings are available. + $settings = $this->getDrupalSettings(); + $this->assertArrayHasKey('views_accordion', $settings, 'Views accordion JS settings avaialable'); + + // Assert that the first row is visible but not the second. + $this->getSession()->getDriver()->isVisible($this->cssSelectToXpath('#ui-id-2'), 'Row one is shown'); + $this->assertFalse($this->getSession()->getDriver()->isVisible($this->cssSelectToXpath('#ui-id-4')), 'Row two is collapsed'); + + // Assert that clicking the first row does not close it. + $this->click('#ui-id-1'); + $this->getSession()->getDriver()->isVisible($this->cssSelectToXpath('#ui-id-2'), 'Row one is stil shown'); + + // Assert the header icons are displayed in the correct place. + $this->getSession()->getDriver()->isVisible($this->cssSelectToXpath('#ui-id-1 span.ui-icon-triangle-1-s'), 'Active header icon is visible'); + $this->getSession()->getDriver()->isVisible($this->cssSelectToXpath('#ui-id-3 span.ui-icon-triangle-1-e'), 'Inactive header icon is visible'); + } + +} diff --git a/web/modules/views_accordion/views-accordion.js b/web/modules/views_accordion/views-accordion.js index de2ab0144838f6b2fc89ee9f53721123a1b5242d..f90c11bf5c97b798375880f01bb670e937d934fa 100644 --- a/web/modules/views_accordion/views-accordion.js +++ b/web/modules/views_accordion/views-accordion.js @@ -1,18 +1,22 @@ +/** + * @file + */ + (function ($, Drupal, drupalSettings) { 'use strict'; - Drupal.behaviors.views_accordion = { + Drupal.behaviors.views_accordion = { attach: function (context) { if (drupalSettings.views_accordion) { $.each(drupalSettings.views_accordion, function (id) { - var $display = $(this.display +':not(.ui-accordion)'); + var $display = $(this.display + ':not(.ui-accordion)'); /* The row count to be used if Row to display opened on start is set to random */ var row_count = 0; /* Prepare our markup for jquery ui accordion */ $(this.header, $display).each(function (i) { - // Wrap the accordion content within a div if necessary + // Wrap the accordion content within a div if necessary. if (!this.usegroupheader) { $(this).siblings().wrapAll('<div></div>'); row_count++; diff --git a/web/modules/views_accordion/views_accordion.info.yml b/web/modules/views_accordion/views_accordion.info.yml index 7fd4d1e2b151542d443969d2975a837b0c8eebf8..d0e1e35c842e32292d69239e6e4911b468705c2c 100644 --- a/web/modules/views_accordion/views_accordion.info.yml +++ b/web/modules/views_accordion/views_accordion.info.yml @@ -3,10 +3,10 @@ type: module description: 'Provides an accordion views display plugin.' # core: 8.x dependencies: - - views + - drupal:views -# Information added by Drupal.org packaging script on 2016-11-19 -version: '8.x-1.0-beta2' +# Information added by Drupal.org packaging script on 2018-03-25 +version: '8.x-1.1' core: '8.x' project: 'views_accordion' -datestamp: 1479568459 +datestamp: 1521988091 diff --git a/web/modules/views_accordion/views_accordion.module b/web/modules/views_accordion/views_accordion.module index d79c4693c6a1832af6fc9048efa78ea73beeffec..ff8666c16dd85d314fabf43498a65442af9ee278 100644 --- a/web/modules/views_accordion/views_accordion.module +++ b/web/modules/views_accordion/views_accordion.module @@ -1,37 +1,36 @@ <?php + /** * @file - * Provide an accordion display style for Views. - * - * This is a placeholder file so drupal will enable the module. All logic is contained in - * other files located with the module. + * Provides an accordion display style for Views. */ use Drupal\Core\Routing\RouteMatchInterface; /** - * Implementation of hook_help(). + * Implements hook_help(). */ function views_accordion_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'help.page.views_accordion': - $output = '<p>'. t('The Views Accordion module is a Views style plugin that displays the results in a JQuery accordion style. For more updated information visit the <a href="@link">Views Accordion documentation page</a>.', ['@link' => 'http://drupal.org/node/366263']) .'</p>'; + $output = '<p>' . t('The Views Accordion module is a Views style plugin that displays the results in a JQuery accordion style. For more updated information visit the <a href="@link">Views Accordion documentation page</a>.', ['@link' => 'http://drupal.org/node/366263']) . '</p>'; - $output .= '<h3>'. t('How to use the plugin') .'</h3>'; - $output .= t('<strong>IMPORTANT:</strong> The first field in order of appearance will be the one used as the "header" or "trigger" of the accordion action.') .'<br />'; + $output .= '<h3>' . t('How to use the plugin') . '</h3>'; + $output .= t('<strong>IMPORTANT:</strong> The first field in order of appearance will be the one used as the "header" or "trigger" of the accordion action.') . '<br />'; - $output .= '<h4>'. t('Your view must meet these requirements:') .'</h4>'; + $output .= '<h4>' . t('Your view must meet these requirements:') . '</h4>'; $output .= '<ul>'; - $output .= '<li>'. t('<em>Row style</em> must be set to <em>Fields</em>.') .'</li>'; + $output .= '<li>' . t('<em>Row style</em> must be set to <em>Fields</em>.') . '</li>'; $output .= '</ul>'; - $output .= '<h4>'. t('Choose <em>Views Accordion</em> in the <em>Style</em> dialog within your view, which will prompt you to configure the jquery.ui.accordion settings.') .'</h4>'; + $output .= '<h4>' . t('Choose <em>Views Accordion</em> in the <em>Style</em> dialog within your view, which will prompt you to configure the jquery.ui.accordion settings.') . '</h4>'; return $output; } } /** * Preprocess function for views_accordion_view template. + * * Default template: views-accordion-view.html.twig. * * @{inheritdoc} diff --git a/web/modules/views_bootstrap/templates/views-bootstrap-grid.html.twig b/web/modules/views_bootstrap/templates/views-bootstrap-grid.html.twig index 364ed1200ae37d674af005fa71f602620e9cda6f..76f5b28950ea358fd5b30c2a649f722556b30f6c 100644 --- a/web/modules/views_bootstrap/templates/views-bootstrap-grid.html.twig +++ b/web/modules/views_bootstrap/templates/views-bootstrap-grid.html.twig @@ -18,6 +18,9 @@ options.alignment, ] %} +{% if title %} + <h3>{{ title }}</h3> +{% endif %} <div id="{{ id }}" {{ attributes.addClass(classes) }}> {% if options.alignment == 'horizontal' %} {% for row in items %}