Commit cb045371 authored by Sergey Shadrin's avatar Sergey Shadrin

[#124455] Patches

-Added package to apply patches
-Removed obsolete patches
-Patched core to replace icons
-Patched `admin_toolbar` to replace icons
parent 5c90473f
......@@ -5,7 +5,7 @@
"license": "GPL-2.0-or-later",
"require": {
"composer/installers": "^1.7",
"drupal/admin_toolbar": "^1.25",
"drupal/admin_toolbar": "^3.2",
"drupal/blazy": "^1.0",
"drupal/bootstrap": "^3.16",
"drupal/ckeditor_font": "^1.0",
......@@ -40,7 +40,8 @@
"drupal/video_embed_field": "^2.0",
"drupal/webform": "^5.0",
"drush/drush": "^9.5",
"oomphinc/composer-installers-extender": "^2.0"
"oomphinc/composer-installers-extender": "^2.0",
"cweagans/composer-patches": "^1.7"
},
"minimum-stability": "dev",
"prefer-stable": true,
......@@ -51,7 +52,8 @@
"drupal/core-composer-scaffold": true,
"drupal/core-project-message": true,
"composer/installers": true,
"oomphinc/composer-installers-extender": true
"oomphinc/composer-installers-extender": true,
"cweagans/composer-patches": true
}
},
"extra": {
......@@ -81,22 +83,10 @@
},
"patches": {
"drupal/core": {
"2698057 Add support for <nolink>, <none> and empty values to the UI": "https://www.drupal.org/files/issues/2018-04-17/add_nolink_support-2698057-23.patch"
"Replace icons": "./patches/core/core-icons.patch"
},
"drupal/linkit": {
"2903176 Views as a dependency": "https://www.drupal.org/files/issues/linkit_views_dependency-2903176-2.patch"
},
"drupal/paragraphs_browser": {
"2917656 Extend from ParagraphsWidget instead of InlineParagraphsWidget": "https://www.drupal.org/files/issues/2018-03-22/paragraphs_browser-extend-from-ParagraphsWidget-2917656-10.patch"
},
"drupal/colorbutton": {
"2881822 Module can't find library in profile directory": "https://www.drupal.org/files/issues/colorbutton-module_can_t_find-2881822-15.patch"
},
"drupal/panelbutton": {
"2881820 Module can't find library in profile directory": "https://www.drupal.org/files/issues/panelbutton-module_can_t_find-2881820-11.patch"
},
"drupal/entity_browser": {
"2932081 fixing bug with file upload validation": "https://www.drupal.org/files/issues/2018-04-13/2932081-7.patch"
"drupal/admin_toolbar": {
"Replace icons": "./patches/admin_toolbar/replace-icons.patch"
}
}
},
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cc20c098c61dee52473958c40b7f0583",
"content-hash": "12e108028ee19eae86ddc59b250272da",
"packages": [
{
"name": "asm89/stack-cors",
......@@ -1147,6 +1147,54 @@
"abandoned": "psr/container",
"time": "2017-02-14T19:40:03+00:00"
},
{
"name": "cweagans/composer-patches",
"version": "1.7.3",
"source": {
"type": "git",
"url": "https://github.com/cweagans/composer-patches.git",
"reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweagans/composer-patches/zipball/e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
"reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": ">=5.3.0"
},
"require-dev": {
"composer/composer": "~1.0 || ~2.0",
"phpunit/phpunit": "~4.6"
},
"type": "composer-plugin",
"extra": {
"class": "cweagans\\Composer\\Patches"
},
"autoload": {
"psr-4": {
"cweagans\\Composer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Cameron Eagans",
"email": "me@cweagans.net"
}
],
"description": "Provides a way to patch Composer packages.",
"support": {
"issues": "https://github.com/cweagans/composer-patches/issues",
"source": "https://github.com/cweagans/composer-patches/tree/1.7.3"
},
"time": "2022-12-20T22:53:13+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v1.1.0",
......@@ -1674,26 +1722,29 @@
},
{
"name": "drupal/admin_toolbar",
"version": "1.27.0",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/admin_toolbar.git",
"reference": "8.x-1.27"
"reference": "3.2.1"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/admin_toolbar-8.x-1.27.zip",
"reference": "8.x-1.27",
"shasum": "fed1fbbdf8f805b1da63d73e7e2c54750f354d61"
"url": "https://ftp.drupal.org/files/projects/admin_toolbar-3.2.1.zip",
"reference": "3.2.1",
"shasum": "7a4bfb716e269be4ca03b7f04e29e4439ec6cf93"
},
"require": {
"drupal/core": "^8"
"drupal/core": "^8.8.0 || ^9.0"
},
"require-dev": {
"drupal/admin_toolbar_tools": "*"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "8.x-1.27",
"datestamp": "1558553350",
"version": "3.2.1",
"datestamp": "1665044276",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
......@@ -1702,7 +1753,7 @@
},
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0+"
"GPL-2.0-or-later"
],
"authors": [
{
......@@ -1746,7 +1797,7 @@
"Toolbar"
],
"support": {
"source": "http://cgit.drupalcode.org/admin_toolbar",
"source": "https://git.drupalcode.org/project/admin_toolbar",
"issues": "https://www.drupal.org/project/issues/admin_toolbar"
}
},
......
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
Replace icons
Source: ./patches/core/core-icons.patch
core/misc/druplicon.png

3.81 KB | W: | H:

core/misc/druplicon.png

13 KB | W: | H:

core/misc/druplicon.png
core/misc/druplicon.png
core/misc/druplicon.png
core/misc/druplicon.png
  • 2-up
  • Swipe
  • Onion skin
core/misc/favicon.ico

5.3 KB | W: | H:

core/misc/favicon.ico

9.44 KB | W: | H:

core/misc/favicon.ico
core/misc/favicon.ico
core/misc/favicon.ico
core/misc/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
<svg xmlns="http://www.w3.org/2000/svg" width="57" height="66" viewBox="471.5 467 57 66" enable-background="new 471.5 467 57 66"><path opacity=".2" fill="#303030" d="M528.5 504.965c0 16.634-13.123 27.615-28.24 27.615-10.29 0-19.894-5.523-24.978-14.167l.605-.027c1.313 1.192 3.39 2.58 7.404 2.515 4.77-.064 5.645-.875 9.855-2.756 22.716-10.17 26.925-19.457 27.736-21.59s2.013-5.587.756-9.415c-.242-.737-.42-1.333-.54-1.808-3.018-3.372-6.017-5.225-6.92-5.784-.14-.093-.29-.177-.43-.26l.44.26c2.01 1.247 14.314 8.782 14.314 25.417z"/><path fill="#fff" d="M509.09 518.507c1.006 0 2.077.065 2.83.568.756.503 1.193 1.63 1.445 2.263.25.634 0 1.006-.503 1.258-.438.25-.503.12-.94-.69-.44-.81-.82-1.63-3.01-1.63s-2.887.755-3.948 1.63c-1.062.876-1.443 1.193-1.825.69s-.253-1.006.437-1.63 1.825-1.63 2.888-2.077c1.06-.45 1.62-.383 2.625-.383zm-10.413 7.152c1.257 1.005 3.14 1.825 7.153 1.825 4.015 0 6.836-1.137 8.094-2.077.568-.438.82-.065.875.187.056.25.186.624-.252 1.07-.316.317-3.194 2.33-6.594 2.636-3.4.31-7.964.504-10.73-2.01-.438-.44-.316-1.07 0-1.323.317-.25.568-.438.94-.438.374.008.317.008.513.13z"/><path opacity=".2" fill="#aaa" d="M520.89 496.545c-.81 2.133-5.02 11.42-27.735 21.59-4.21 1.88-5.085 2.69-9.854 2.756-4.013.066-6.09-1.32-7.403-2.514l-.605.028h-.01c-2.393-4.042-3.78-8.783-3.78-13.952 0-7.852 2.97-13.654 6.287-17.687.11-.13.213-.26.325-.382 2.683-3.148 5.55-5.17 7.218-6.203.038-.028.075-.047.112-.065.42-.25.754-.447.987-.568 2.757-1.51 4.77-2.263 7.963-4.77.12-.092.242-.186.354-.288l.008-.01c.875-.754 1.64-1.76 2.18-3.4v-.008c.325-.97.567-2.16.716-3.65l.02.018c2.253 2.69 4.954 5.886 6.89 7.144.69.447 1.38.848 2.068 1.202l.3.15c2.243 1.126 4.507 1.945 6.807 3.333l.428.26c.903.56 3.902 2.412 6.92 5.784.12.475.298 1.07.54 1.807 1.274 3.837.073 7.292-.737 9.425z"/><path opacity=".5" fill="#333" d="M514.176 479.538c-3.26-2.077-6.464-2.887-9.603-4.955-1.938-1.267-4.64-4.47-6.893-7.162-.438 4.332-1.686 6.148-3.26 7.35-3.195 2.515-5.207 3.26-7.963 4.77-2.338 1.256-14.958 8.726-14.958 24.913 0 5.17 1.387 9.91 3.77 13.96 5.077 8.635 14.68 14.158 24.97 14.158 15.126 0 28.24-10.98 28.24-27.614 0-9.127-3.707-15.526-7.386-19.633-3.016-3.382-6.015-5.217-6.918-5.785zm7.627 7.34c4.117 5.15 6.213 11.23 6.213 18.077 0 3.968-.755 7.712-2.245 11.148-1.414 3.25-3.444 6.13-6.053 8.56-5.15 4.806-12.062 7.45-19.475 7.45-3.67 0-7.265-.698-10.692-2.086-3.372-1.36-6.398-3.297-9.016-5.774-5.532-5.225-8.57-12.257-8.57-19.8 0-6.716 2.18-12.695 6.483-17.753 3.288-3.865 6.836-6.007 8.196-6.743.67-.363 1.285-.69 1.89-.997 1.892-.97 3.68-1.89 6.14-3.818 1.312-.997 2.71-2.58 3.305-6.585 2.077 2.468 4.48 5.234 6.314 6.426 1.63 1.08 3.307 1.835 4.918 2.562 1.527.69 3.11 1.406 4.676 2.403l.056.037c4.62 2.84 7.06 5.896 7.86 6.892z"/><path opacity=".5" fill="#fff" d="M497.98 468.678c.874 2.58.753 3.893.753 4.452 0 .56-.307 2.077-1.313 2.832-.438.317-.568.568-.568.624 0 .25.568.438.568 1.006 0 .69-.317 2.077-3.642 5.393-3.325 3.316-8.103 6.278-11.8 8.103-3.698 1.826-5.468 1.686-5.97.81s.185-2.83 2.514-5.392l9.667-6.278 9.164-6.398.503-2.44"/><path fill="#fff" d="M497.98 468.613c-.57 4.145-1.826 5.393-3.512 6.715-2.83 2.133-5.588 3.446-6.212 3.763-1.63.82-7.535 4.08-10.608 8.784-.94 1.444 0 2.012.186 2.133.187.12 2.33.372 6.9-2.385 4.574-2.757 6.595-4.387 9.175-7.078 1.377-1.444 1.573-2.263 1.573-2.636 0-.438-.316-.624-.82-.754-.25-.065-.316-.187 0-.373.317-.186 1.622-.82 1.938-1.07.318-.25 1.827-1.257 1.882-2.887.065-1.63-.056-2.766-.503-4.21zm-14.112 45.628c.065-4.898 4.648-9.472 10.422-9.536 7.348-.065 12.424 7.283 16.13 7.208 3.14-.064 9.166-6.212 12.118-6.212 3.14 0 4.014 3.26 4.014 5.206 0 1.938-.623 5.458-2.133 7.656-1.51 2.198-2.44 3.008-4.2 2.888-2.264-.187-6.78-7.21-9.67-7.35-3.64-.12-11.547 7.6-17.75 7.6-3.763 0-4.9-.567-6.147-1.378-1.92-1.312-2.85-3.315-2.785-6.08z"/></svg>
<svg width="214" height="214" viewBox="0 0 214 214" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M172 59C154.963 36 135 19 105 0C110.5 30.5 58.5 50 41 68.5C23.5 87 17 106.188 17 125.902C17 174.204 57.6985 213.5 106 213.5C154.302 213.5 196.5 174.204 196.5 125.902C196.5 106.188 189.037 82 172 59Z" fill="#008CF2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.318 83.5C92.4745 83.5 81.0102 88.6894 73.0589 96.8593C70.2775 99.7171 68.2075 104.354 67.3793 109.481C66.5258 114.766 67.2414 118.928 68.2111 120.763C58 122 51.5 113 52.5 104.5C53.2059 98.5 55.8754 91.5746 61.5929 85.7C72.5277 74.4646 88.115 67.5 105.318 67.5C137.872 67.5 165 92.6234 165 124.5C165 156.377 137.872 181.5 105.318 181.5C100.829 181.5 93.7529 180.358 90 180C85.3088 179.654 71.5 179 64 182L83.5 108.5C85.5 102 91.7553 98.2637 102.602 99.6635L84.5586 164.168C86.3276 164.183 88.0427 164.274 89.7083 164.397C91.628 164.538 93.3802 164.707 95.0667 164.87C98.4724 165.198 101.61 165.5 105.318 165.5C129.85 165.5 149 146.747 149 124.5C149 102.253 129.85 83.5 105.318 83.5Z" fill="white"/>
</svg>
Admin Toolbar 8.x-1.27, 2019-05-22
----------------------------------
Changes since 8.x-1.26:
- #3026216 by adriancid, oknate, sunlix, joachim: Add media link to content
menu in toolbar.
- #3051676 by Amsteri, rollingnet, adriancid: Menu items with no link miss of
CSS.
- #3026302 by idebr, adriancid: Admin toolbar icon width/height changes for
themes based on box-sizing: border-box.
- #2986082 by oknate, Bobík, adriancid: Add search for menu items.
- #3026337 by thalles, mpp, adriancid, Gábor Hojtsy: Config labels are not
properly translated.
- #3003678 by adriancid, romainj: New method to invalidate twig templates.
- #3027542 by i-trokhanenko, adriancid, thalles: menu_cache_clear_all is
deprecated.
- #3031310 by akshay_d, abhisekmazumdar, adriancid: Replace deprecated method
assertRaw.
- #3031107 by adriancid: Update the CHANGELOG.txt.
Admin Toolbar 8.x-1.26, 2019-02-03
----------------------------------
Changes since 8.x-1.25:
- #3009193 by oknate, jonnyeom, DYdave, DuneBL, romainj, Pasqualle, thejacer87,
DuaelFr, thalles, cobenash, webdrips, acrosman, kumkum29, PCate, Grimreaper,
azovsky, arturopanetta, s-jack, tirler, CProfessionals, jjmackow, littlecrab,
jl_cs, superlolo95, geraldito, Kaelfaz, andrey_semenoff, Renrhaf, lyalyuk,
abramm, xlith, adriancid: Error because devel module has removed the execute
php feature.
- #3019298 by hayashi, romainj: Menu items for content entity show in wrong
language.
Admin Toolbar 8.x-1.25, 2018-11-22
----------------------------------
Changes since 8.x-1.24:
- #3012102 by tikaszvince, Jody Lynn, adriancid, grahl, jigarius: Provider
property missing on link definition.
- #3010451 by epowelljr, adriancid: Improve the admin_toolbar module
description.
- #2958415 by adriancid, romainj, Prashant.c, harshita29: Replace usages of the
deprecated drupal_set_message() function.
- #2996485 by idebr, adriancid: Remove unused function
admin_toolbar_tools_get_links().
- #2989281 by JKerschner, adriancid: Consistently use $entityTypeManager.
- #2985106 by Eli-T, adriancid, eme: Add ability to hide Drupal.org links
exposed by admin_toolbar_tools from certain users.
- #2938884 by Spurlos, bdanin, romainj, purdy_nc, adriancid: A non-existent
route breaks the site.
Admin Toolbar 8.x-1.24, 2018-05-28
----------------------------------
Changes since 8.x-1.23:
- #2973131 by adriancid: Don't refer to Drupal in the help page.
- #2975170 by adriancid: Add composer.json file to submodules.
- #2975165 by Gamewalker, adriancid: datetime.time services is not found in
ToolbarController.
- #2971466 by recrit, adriancid, acbramley: Add media links incorrectly set to
"node.add.".
- #2972553 by dww, adriancid: Add static cache to speed up
admin_toolbar_links_access_filter_user_has_admin_role().
- #2971435 by adriancid: Module help page for Admin Toolbar Extra Tools module
doesn't show the links.
- #2971398 by adriancid: Add the docblock to the ToolbarController constructor.
- #2969686 by adriancid, Vidushi Mehta: Add a menu link to clean the twig cache.
- #2969325 by adriancid: Fix the module version in the CHANGELOG.txt.
- #2961450 by adriancid: Convert the AdminToolbarAlterTest test class to
PHPUnit.
- #2961445 by adriancid: Convert the AdminToolbarToolsAlterTest test class to
PHPUnit.
- #2959684 by adriancid: Remove recommended modules section from README.txt.
- #2959647 by adriancid: Use interfaces instead of classes to inject
dependencies.
- #2952643 by romainj, if-jds, adriancid: Add Item for Files under Content Menu.
- #2944463 by adriancid: Update the composer.json file.
Admin Toolbar 8.x-1.23, 2018-02-06
----------------------------------
Changes since 8.x-1.22:
- #2924266 by sunlix, romainj, adriancid, samerali: Add a menu link for the
media module.
- #2941184 by daniel.nitsche, adriancid: Remove unwanted comment from
admin.toolbar.css.
- #2912503 by Amsteri, m.abdulqader: Right to left language direction support.
- #2937982 by romainj, adriancid: Auto-generation of menu links conflicts with
other modules.
- #2932873 by vaplas, saravanaprasanth, adriancid: Horizontal mode only works if
the page is loaded in horizontal mode.
- #2935311 by romainj, adriancid: Use the $entityTypeManager variable instead of
the \Drupal::entityTypeManager service.
- #2935449 by adriancid: Add the release information for the 1.22 version in the
CHANGELOG.txt.
- #2932476 by romainj: toolbar.tree library should have a dependency to the
core/drupal library.
- #2931503 by K3vin_nl: Admin toolbar generates invalid class names.
Admin Toolbar 8.x-1.22, 2018-01-02
----------------------------------
Changes since 8.x-1.21:
- #2929053 by kkuhnen, eme, adriancid: admin_toolbar.js should use Drupal
behaviors.
- #2929061 by romainj, dsnopek, adriancid: admin_toolbar_links_access_filter
doesn't do anything unless admin_toolbar is enabled.
- #2928836 by eme: One extra pixel line under the admin toolbar.
- #2925501 by eme, adriancid, RumyanaRuseva: Menu does not take changes into
account (caching issue).
- #2927914 by adriancid: Parameter comment indentation must be 3 spaces.
- #2927911 by adriancid: Remove unused variables.
- #2927905 by adriancid: All dependencies must be prefixed with the project
name.
- #2925327 by Berdir, adriancid: Better check for is-link-in-admin-menu for
local task links.
- #2922046 by mikejw, adriancid, Berdir, BrianLewisDesign: Notice: Undefined
index: entity.eform_type.collection in
admin_toolbar_tools_menu_links_discovered_alter.
- #2925128 by adriancid: Create the module help page for the Admin Toolbar Links
Access Filter submodule.
Admin Toolbar 8.x-1.21, 2017-11-20
----------------------------------
Changes since 8.x-1.20:
- #2923580 by sylus, adriancid: Unsupported operand types in
ToolbarHandler::lazyBuilder().
- #2731369 by stefan.r, DuneBL, Berdir, kbasarab, joachim, adriancid: Local
tasks don't show in the toolbar.
- #2920793 by adriancid: Change the user TAKTAK by matio89 in the CHANGELOG.txt
file.
- #2873228 by smustgrave, adriancid, flocondetoile: Toolbar menu accessible and
navigable with keyboard.
- #2919775 by adriancid: Create the CHANGELOG.txt file.
- #2919772 by adriancid: Use the README.txt template.
- #2919769 by adriancid: Use the README.txt template for the admin_toolbar_tools
submodule.
- #2897309 by finne, adriancid, eme, rgpublic: admin_toolbar_tools module
makes all pages uncacheable.
- #2913299 by esod, Chi, adriancid, hudri, eme: CSS Z-Index of toolbar is
inappropriate.
- #2919390 by adriancid: Create the README.txt file for the admin toolbar links
access filter submodule.
- #2919367 by adriancid: Fix coding standard format.
- #2919346 by adriancid: Don't show menu links that you don't have access
permission for.
- #2917710: Create the .info.yml file for the admin_toolbar_links_access_filter
submodule.
- #2916064 by adriancid, eme, finne: Use in drupal.org links url parameter and
not route_name.
- #2916040 by adriancid, finne, eme: Remove unused route admin_development.
- #2915778 by finne, adriancid: Remove the _csrf_token from routes that don't
need it.
- #2909359 by adriancid, Musa.thomas: Add a menu link to clean the Views cache.
- #2910931 by adriancid: .info.yml files don't have the drupal version.
- #2759135 by Johnny vd Laar, miiimooo, romainj, chegor, adriancid, ckaotik,
bdominguez, SpadXIII, mrtndlmt: Content type, Vocabularies and Menus names are
not translated.
- #2909710 by adriancid, Musa.thomas: Fix coding standard format.
- #2910934 by adriancid, romainj: Error trying to clean the cache.
- #2910892 by adriancid: Delete the LICENSE.txt.
- #2909637 by bapi_22, Musa.thomas, adriancid: Avoid static call inside class
method.
- #2909003 by eme: fix z-index for CKEditor.
- #2504449 by robin.ingelbrecht, rwam, eme: Un-hover delay.
Admin Toolbar 8.x-1.20, 2017-09-07
----------------------------------
Changes since 8.x-1.19:
- #2887439 by romainj: Fix test.
- #2759135 by Johnny vd Laar, miiimooo, chegor, romainj: Content type,
Vocabularies and Menus names are not translated.
- #2894520 by romainj, bapi_22: Remove deprecated constant REQUEST_TIME from
code base.
- #2883098 by romainj, Alka Kumari: Admin Toolbar Extra Tools module display
theme machine names instead of labels.
- #2504449 by robin.ingelbrecht: Un-hover delay.
- #2838636 by ddrozdik, Fons Vandamme, kerby70, romainj: Issue with z-index of
dropdown in horizontal tray.
- #2838636 by Fons Vandamme, ddrozdik, romainj: Issue with z-index of dropdown
in horizontal tray.
- #2870404 by romainj: Add a link to the Webprofiler settings page.
- #2706643 by esod, romainj, Keenegan, DamienMcKenna: Use short array syntax.
Admin Toolbar 8.x-1.19, 2017-04-06
----------------------------------
Changes since 8.x-1.18:
- #2706643 by esod, romainj, Keenegan, DamienMcKenna: Use short array syntax.
- #2781745 by minakshiPh, eelkeblok, romainj, akhilavnair_zyxware: Drupal coding
standard issues found in most of the files.
- #2855720 by stefan.r, romainj: No logout link in admin toolbar anymore.
- #2841512 by Chi, romainj: User error: Redirects to external URLs are not
allowed by default.
Admin Toolbar 8.x-1.18, 2016-12-01
----------------------------------
Changes since 8.x-1.17:
- #2830677 by vaplas, romainj: z-index for sub menu.
- by romainj: Get rid of the Hello popup.
- #2805431 by marcusx: Accidentaly committed alert.
- #2630724 by squarecandy, romainj, reblutus, Hemangi Gokhale, jacoferg,
Balu Ertl, Jeff Burnz, Don Greco: Consider changes to link title parameter to
avoid tooltip visual conflict.
- #2518202 by james.williams, chegor, eme, romainj, DuneBL: Change default link
to taxonomy.
Admin Toolbar 8.x-1.17, 2016-08-19
----------------------------------
Changes since 8.x-1.16:
- #2779251 by romainj, DuneBL: Wrong integration of field_collection delete
form.
- #2776229 by Chi, romainj, mattlt: Chevron icons missing if Drupal is not in
document root.
- by romainj: CSS coding standard cleaning.
- #2781059 by mstrelan, romainj: Why is CRON in all caps?
- #2778935 by akhilavnair_zyxware, rjarraud: As per Drupal Standards each line
in documentation should not exceeds 80 characters.
- by romainj: Adds ending new line where required.
- #2776229 by Chi, rjarraud: Chevron icons missing if Drupal is not in document
root.
- by eme: Commiting again #2707611.
- #2757687 by neerajsingh: Remove usages of \Drupal::url().
- #2707747 by Balu Ertl, nevergone, eme, esod: D8 logo not rendered with smooth
antialiasing.
Admin Toolbar 8.x-1.16, 2016-07-28
----------------------------------
Changes since 8.x-1.15:
- #2723209 by andrewmacpherson, colan, mattshoaf: Enabling admin_toolbar_tools
causes Devel Settings to disappear from the main configuration page.
- #2759335 bycwells, jalpesh: fixes a mistaken link in hook_help().
- #2664564 by kolier, matio89, romainj and Michèle: auto-detect content entities
with Field UI route.
- #2735257 by colan, romainj, Balu Ertl: respect of coding standards in the
module README.txt file.
- #2735257 by colan, romainj, Balu Ertl: shortens the module README.txt
description text.
- by romainj: Fixes a problem with the link to the Admin menu overview page.
- #2735257 by Balu Ertl, colan, romainj: improves Admin Toolbar Extra Tools
description in the README.txt file.
- #2723381 by joachim, romainj: fieldable content entities are no more
hardcoded.
- #2735257 by Balu Ertl, colan, romainj: added some description details in the
README.txt files.
- by romainj: Menu link definitions updated.
- by romainj: Change routing permissions to follow Drupal default permissions
for administration pages.
- by romainj: Fixes issue #2737027.
- by romainj: fixes issue #2701825.
- by romainj: Adds the menu name for each menu item in
admin_toolbar_tools/admin_toolbar_tools.links.menu.yml file.
- #2735153 by Kionn: InvalidArgumentException: Cannot redirect to an empty URL.
в Symfony\Component\HttpFoundation\RedirectResponse->setTargetUrl().
- by matio89: correction InvalidArgumentException: Cannot redirect to an empty
URL. в Symfony\Component\HttpFoundation\RedirectResponse->setTargetUrl().
- by romainj: Add a new Render Cache shortcut on the admin menu.
- by romainj: Fixes issue #2731663 by canceling the move of the Logout menu
link.
Admin Toolbar 8.x-1.15, 2016-05-23
----------------------------------
Changes since 8.x-1.14:
- by matio89: add RTL in admin.toolbar.css.
- by romainj: Changes Devel menu item titles.
- by romainj: Fixes issue #2713899.
- by romainj: Minor code/typo cleaning.
- #2707611 by Balu Ertl: Add fine shadow under dropdown menus.
- #2707789 by esod: Fix Automated Tests.
- by romainj: Code cleaning.
- by romainj: Replace the deprecated entityManager() by entityTypeManager()
service.
- by romainj: Emptying menu cache with all link types.
- #2614962 by dbt102, seppelM, neha.gangwar, chegor: Correct text in
info.yml(s).
- by eme: Minor typo changes.
- by eme: Fix dependencies to core modules.
- by eme: Fix issue #2666964.
- by eme: Fix css background color level3.
- by eme: fix issue #2658896 and refactor and fix chevron-right.svg.
- by eme: fix Issue #2493037.
Admin Toolbar 8.x-1.14, 2016-02-08
----------------------------------
Changes since 8.x-1.13:
- by matio89: Correction differents bugs(problem white page when installing a
new module).
- #2643648 by Lord_of_Codes: Proposed README.txt file for Admin Toolbar.
- by matio89: resolving the dependecies problem(dependencies of others modules).
- by matio89: Correction route of entities.
Admin Toolbar 8.x-1.13, 2016-02-02
----------------------------------
Changes since 8.x-1.12:
- by matio89: Admin toolbar is compatible with 8.0.2.
- by matio89: Commmit dev version compatible with drupal 8.0.2.
- by matio89: New version of admin toolbar compatible with 8.0.2.
Admin Toolbar 8.x-1.12, 2016-01-15
----------------------------------
Changes since 8.x-1.11:
- by matio89: Correction AdminToolbaToolsAlterTest.
- #2493037 by mimran: Empty elements in #toolbar-bar.
- #2598136 by mimran, felribeiro: Menu items duplicated.
- #2635154 by Lukas von Blarer: Too general CSS selectors.
- by eme: Fix chevron right in local state.
Admin Toolbar 8.x-1.11, 2015-12-11
----------------------------------
Changes since 8.x-1.10:
- #2620430 by Vagelis, NarendraR: Typo.
- #2632888 by JamesK: Add dependency on node module.
- by eme: Fix admin toolbar test.
- #2552081 by jonhattan, bruvers: chevron-right.svg not found.
- by Vagelis: Minor typos.
- by eme: Refactor basic tests.
- #2613378 by chegor: Add basic test.
- #2612694 by chegor: Add new view.
- #2627918 by joe_carvajal: Version in info.yml files does Update Manager try to
update.
- by matio89: correction collapsible behaviour in vertical menu layout.
- #2582825 by Ben Coleman: Installing Admin Toolbar Tools loses anonymous user
login link.
- by matio89: Correction the login link on the site when in a logged-out state.
Admin Toolbar 8.x-1.10, 2015-08-07
----------------------------------
Changes since 8.x-1.9:
- #2546939 by jonhattan: Add dependency on toolbar module.
Admin Toolbar 8.x-1.9, 2015-07-22
---------------------------------
Changes since 8.x-1.8:
- #2537016 by matio89: Removing dependance for user & system.
- #2533420 by Dave Reid, twistor: Chase HEAD changes in toolbar.
- #2537016 by twistor: Rewrite admin_toolbar_tools.module
- by matio89: Correction all bugs and add new security features.
Admin Toolbar 8.x-1.8, 2015-06-22
---------------------------------
Changes since 8.x-1.7:
- correction not found route.
- #2493037 by bobrov1989: Empty elements in #toolbar-bar.
- by fethi: icones.
Admin Toolbar 8.x-1.7, 2015-06-22
---------------------------------
Changes since 8.x-1.6:
- correction not found route.
- #2493037 by bobrov1989: Empty elements in #toolbar-bar.
- by fethi: icones.
Admin Toolbar 8.x-1.6, 2015-06-03
---------------------------------
Changes since 8.x-1.5:
- by matio89: correction admin_toolbar.
Admin Toolbar 8.x-1.5, 2015-06-03
---------------------------------
Changes since 8.x-1.4:
- by matio89: test if module update exist or no to display install and update
module.
- by fethi: Change the colors used to match the Seven styleguide.
- by matio89: compatibility with Drupal 8 béta 11.
- by fethi: Change the colors used to match the Seven styleguide.
Admin Toolbar 8.x-1.4, 2015-05-20
---------------------------------
Changes since 8.x-1.3:
- by matio89: correction add/node.
Admin Toolbar 8.x-1.3, 2015-05-19
---------------------------------
Changes since 8.x-1.2:
- by matio89: add new functionalities.
- by matio89: add administration devel link.
- by matio89: correction reload page.
- by matio89: correction redirect to the same page.
Admin Toolbar 8.x-1.2, 2015-05-19
---------------------------------
Changes since 8.x-1.1:
- by matio89: add new functionalities.
- by matio89: add administration devel link.
- by matio89: correction reload page.
- by matio89: correction redirect to the same page.
Admin Toolbar 8.x-1.1, 2015-05-19
---------------------------------
Changes since 8.x-1.0:
- by matio89: add new functionalities.
Admin Toolbar 8.x-1.0, 2015-05-07
---------------------------------
- Initial release.
* For a full list of fixes in the latest release, visit:
https://www.drupal.org/project/admin_toolbar/releases
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
Replace icons
Source: ./patches/admin_toolbar/replace-icons.patch
......@@ -50,7 +50,7 @@ INSTALLATION
CONFIGURATION
-------------
No configuration is needed.
* Configure the admin toolbar tools at (/admin/config/user-interface/settings).
MAINTAINERS
......@@ -65,5 +65,7 @@ Current maintainers:
This project has been sponsored by:
* emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design and
implementation of web interfaces. Visit: http://http://emerya.fr/ for more
implementation of web interfaces. Visit: http://emerya.fr/ for more
information.
* trained people
Trained People is a training company focused on Drupal.
name: Admin Toolbar
description: Provides an improved drop-down menu interface to the site Toolbar.
package: Administration
type: module
# core: 8.x
core_version_requirement: ^8.8.0 || ^9
dependencies:
- drupal:toolbar
# Information added by Drupal.org packaging script on 2019-05-22
version: '8.x-1.27'
core: '8.x'
# Information added by Drupal.org packaging script on 2022-10-06
version: '3.2.1'
project: 'admin_toolbar'
datestamp: 1558552277
datestamp: 1665044278
......@@ -14,3 +14,17 @@ function admin_toolbar_update_8001() {
// Rebuilding the route cache.
\Drupal::service("router.builder")->rebuild();
}
/**
* Add menu_depth param into the config.
*
* @see https://www.drupal.org/project/admin_toolbar/issues/3200542
*/
function admin_toolbar_update_8002() {
$config_factory = \Drupal::configFactory();
$config = $config_factory->getEditable('admin_toolbar.settings');
if (empty($config->get('menu_depth'))) {
$config->set('menu_depth', 4);
$config->save(TRUE);
}
}
......@@ -3,19 +3,19 @@ toolbar.tree:
theme:
css/admin.toolbar.css: {}
js:
js/jquery.hoverIntent.js: {}
js/admin_toolbar.js: {}
dependencies:
- core/jquery
- core/drupal
search:
css:
theme:
css/admin.toolbar_search.css: {}
toolbar.tree.hoverintent:
js:
js/jquery.hoverIntent.js: {}
js/admin_toolbar.hoverintent.js: {}
dependencies:
- core/jquery
toolbar.tree.hover:
js:
js/admin_toolbar_search.js: {}
js/admin_toolbar.hover.js: {}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
- core/jquery.ui.autocomplete
admin_toolbar.settings:
title: 'Admin Toolbar'
description: 'Configure the Admin Toolbar module.'
route_name: admin_toolbar.settings
parent: system.admin_config_ui
......@@ -5,50 +5,28 @@
* This is the module to create a drop-down menu for the core toolbar.
*/
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\admin_toolbar\Render\Element\AdminToolbar;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Component\Utility\Html;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Implements hook_toolbar_alter().
*/
function admin_toolbar_toolbar_alter(&$items) {
$items['administration']['tray']['toolbar_administration']['#pre_render'] = ['admin_toolbar_prerender_toolbar_administration_tray'];
$items['administration']['#attached']['library'][] = 'admin_toolbar/toolbar.tree';
$items['administration_search'] = [
"#type" => "toolbar_item",
'tab' => [
'#type' => 'link',
'#title' => new TranslatableMarkup('Search'),
'#url' => URL::fromRoute('system.admin'),
'#attributes' => [
'class' => [
'toolbar-icon',
],
],
],
'tray' => [
'search' => [
'#title' => 'Search',
'#type' => 'textfield',
'#size' => 60,
'#attributes' => [
'id' => 'admin-toolbar-search-input',
],
],
],
'#attached' => [
'library' => [
'admin_toolbar/search',
],
],
'#wrapper_attributes' => [
"id" => "admin-toolbar-search-tab",
],
$items['administration']['tray']['toolbar_administration']['#pre_render'] = [
[AdminToolbar::class, 'preRenderTray'],
];
$items['administration']['#attached']['library'][] = 'admin_toolbar/toolbar.tree';
$hoverintent_functionality = \Drupal::config('admin_toolbar_tools.settings')->get('hoverintent_functionality');
if ($hoverintent_functionality === TRUE) {
// Use jQuery hover() effect.
$items['administration']['#attached']['library'][] = 'admin_toolbar/toolbar.tree.hoverintent';
}
else {
// User hoverIntent plugin.
$items['administration']['#attached']['library'][] = 'admin_toolbar/toolbar.tree.hover';
}
}
/**
......@@ -61,47 +39,15 @@ function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
':toolbar' => Url::fromRoute('help.page', ['name' => 'toolbar'])->toString(),
':automated_cron' => (\Drupal::moduleHandler()->moduleExists('automated_cron')) ? Url::fromRoute('help.page', ['name' => 'automated_cron'])->toString() : '#',
];
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar module enhances the <a href=":toolbar">Toolbar</a> module by providing fast access to all the administrative links at the top of your site. Admin Toolbar remains a very "lightweight" module by closely integrating with all Toolbar functionality. It can be used in conjunction with all the sub modules included on Admin Toolbar, for quick access to system commands such as Flush all caches, <a href=":automated_cron">Run cron</a>, Run Updates, etc.', $variables) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar greatly improves the user experience for those who regularly interact with the site Toolbar by providing fast, full access to all links in the site Toolbar without having to click to get there.') . '</p>';
return $output;
}
}
/**
* Renders the toolbar's administration tray.
*
* This is a clone of core's toolbar_prerender_toolbar_administration_tray()
* function, which uses setMaxDepth(4) instead of setTopLevelOnly().
*
* @param array $element
* A renderable array.
*
* @return array
* The updated renderable array.
*
* @see toolbar_prerender_toolbar_administration_tray()
*/
function admin_toolbar_prerender_toolbar_administration_tray(array $element) {
$menu_tree = \Drupal::service('toolbar.menu_tree');
$parameters = new MenuTreeParameters();
$parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth(4)->onlyEnabledLinks();
$tree = $menu_tree->load(NULL, $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
['callable' => 'toolbar_tools_menu_navigation_links'],
];
$tree = $menu_tree->transform($tree, $manipulators);
$element['administration_menu'] = $menu_tree->build($tree);
return $element;
}
/**
* Adds toolbar-specific attributes to the menu link tree.
*
......@@ -117,10 +63,8 @@ function toolbar_tools_menu_navigation_links(array $tree) {
toolbar_tools_menu_navigation_links($element->subtree);
}
$link = $element->link;
// Get the non-localized title to make the icon class.
$definition = $link->getPluginDefinition();
$element->options['attributes']['class'][] = 'toolbar-icon';
$string = strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
$element->options['attributes']['class'][] = Html::cleanCssIdentifier('toolbar-icon-' . $string);
......
admin_toolbar.settings:
path: '/admin/config/user-interface/admin-toolbar'
defaults:
_form: '\Drupal\admin_toolbar\Form\AdminToolbarSettingsForm'
_title: 'Admin Toolbar settings'
requirements:
_permission: 'administer site configuration'
......@@ -3,13 +3,12 @@ description: Provides a workaround for the common problem that users with 'Use t
package: Administration
type: module
# core: 8.x
core_version_requirement: ^8.8.0 || ^9.0
dependencies:
- admin_toolbar:admin_toolbar
# Information added by Drupal.org packaging script on 2019-05-22
version: '8.x-1.27'
core: '8.x'
# Information added by Drupal.org packaging script on 2022-10-06
version: '3.2.1'
project: 'admin_toolbar'
datestamp: 1558552277
datestamp: 1665044278
......@@ -6,7 +6,6 @@
*/
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Drupal\Core\Routing\RouteMatchInterface;
......@@ -59,8 +58,14 @@ function admin_toolbar_links_access_filter_preprocess_menu(&$variables) {
* Hides links from admin menu, if user doesn't have access rights.
*/
function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$items) {
foreach ($items as $route => &$item) {
$route_name = $route;
if (Drupal::currentUser()->id() == 1) {
// Admin can access everything.
return;
}
$access_manager = \Drupal::accessManager();
foreach ($items as $menu_id => &$item) {
$route_name = NULL;
$route_params = [];
if (!empty($item['original_link'])) {
/** @var \Drupal\Core\Menu\MenuLinkBase $original_link */
......@@ -72,17 +77,27 @@ function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$i
$route_name = $original_link->getRouteName();
$route_params = $original_link->getRouteParameters();
}
elseif (!empty($item['url'])) {
/** @var \Drupal\Core\Url $url */
$url = $item['url'];
if ($url->isExternal()) {
// Do not filter external URL at all.
continue;
}
$route_name = $url->getRouteName();
$route_params = $url->getRouteParameters();
}
// Check, if user has access rights to the route.
if (!\Drupal::accessManager()->checkNamedRoute($route_name, $route_params)) {
unset($items[$route]);
if (!$access_manager->checkNamedRoute($route_name, $route_params)) {
unset($items[$menu_id]);
}
else {
if (!empty($items[$route]['below'])) {
if (!empty($items[$menu_id]['below'])) {
// Recursively call this function for the child items.
admin_toolbar_links_access_filter_filter_non_accessible_links($items[$route]['below']);
admin_toolbar_links_access_filter_filter_non_accessible_links($items[$menu_id]['below']);
}
if (empty($items[$route]['below']) && \Drupal::moduleHandler()->moduleExists('admin_toolbar')) {
if (empty($items[$menu_id]['below'])) {
// Every child item has been cleared out.
// Now check, if the given route represents an overview page only,
......@@ -90,51 +105,19 @@ function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$i
// unset this item, as there aren't any children left.
// This assumption is only valid, when the admin_toolbar module is
// installed because otherwise we won't have child items at all.
if (admin_toolbar_links_access_filter_is_overview_page($route)) {
unset($items[$route]);
if (admin_toolbar_links_access_filter_is_overview_page($route_name)) {
unset($items[$menu_id]);
}
// If there are no sub-items and the parent does not have a link, then
// it is safe to remove it.
elseif ($route_name === '<nolink>') {
unset($items[$menu_id]);
}
else {
// Let's remove the expanded flag.
$items[$route]['is_expanded'] = FALSE;
}
}
$items[$menu_id]['is_expanded'] = FALSE;
}
}
}
/**
* Implements template_preprocess_admin_block_content().
*/
function admin_toolbar_links_access_filter_admin_block_content(&$variables) {
if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
foreach ($variables['content'] as $key => &$item) {
if (isset($item['url']) && $item['url'] instanceof Url) {
/* @var \Drupal\Core\Url $url */
$url = $item['url'];
if ($url->access()) {
continue;
}
unset($variables['content'][$key]);
}
// The key is structured in the form: "ID title route",
// concatenated with spaces.
$key_parts = explode(' ', $key);
$route = end($key_parts);
// Special handling for Views pages, as they are not defined
// system routes.
// @TODO check the permission for Views + find a generic way for similar
// cases. Best way would be to get the link entity somehow to properly
// check permissions.
if (strpos($route, 'views_view:') === 0) {
continue;
}
// Check, if user has access rights to the route.
if (!\Drupal::accessManager()->checkNamedRoute($route)) {
unset($variables['content'][$key]);
}
}
}
}
......@@ -155,7 +138,7 @@ function admin_toolbar_links_access_filter_admin_block_content(&$variables) {
* FALSE otherwise.
*/
function admin_toolbar_links_access_filter_is_overview_page($route_name) {
// @var \Drupal\Core\Routing\RouteProviderInterface $route_provider.
/** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider. */
$route_provider = \Drupal::service('router.route_provider');
$overview_page_controllers = [
'\Drupal\system\Controller\AdminController::index',
......
{
"name": "drupal/admin_toolbar_links_access_filter",
"description": "Provides a workaround for the common problem that users with 'Use the administration pages and help' permission see menu links they don't have access permission for. Once the issue https://www.drupal.org/node/296693 be solved, this module will be deprecated.",
"type": "drupal-module",
"keywords": ["Drupal", "Toolbar"],
"homepage": "http://drupal.org/project/admin_toolbar",
"license": "GPL-2.0+",
"authors": [
{
"name": "Wilfrid Roze (eme)",
"homepage": "https://www.drupal.org/u/eme",
"role": "Maintainer"
},
{
"name": "Romain Jarraud (romainj)",
"homepage": "https://www.drupal.org/u/romainj",
"role": "Maintainer"
},
{
"name": "Adrian Cid Almaguer (adriancid)",
"email": "adriancid@gmail.com",
"homepage": "https://www.drupal.org/u/adriancid",
"role": "Maintainer"
},
{
"name": "Mohamed Anis Taktak (matio89)",
"homepage": "https://www.drupal.org/u/matio89",
"role": "Maintainer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/admin_toolbar",
"source": "http://cgit.drupalcode.org/admin_toolbar"
},
"require": {
"drupal/admin_toolbar": "^1"
}
}
name: Admin Toolbar Search
description: Provides search of Admin Toolbar items.
package: Administration
type: module
core_version_requirement: ^8.8.0 || ^9.0
configure: admin_toolbar_search.settings
dependencies:
- admin_toolbar:admin_toolbar_tools
# Information added by Drupal.org packaging script on 2022-10-06
version: '3.2.1'
project: 'admin_toolbar'
datestamp: 1665044278
search:
css:
theme:
css/admin.toolbar_search.css: {}
js:
js/admin_toolbar_search.js: {}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
- core/drupal.autocomplete
admin_toolbar_search.settings:
title: 'Admin Toolbar Search'
description: 'Configure the Admin Toolbar Search module.'
route_name: admin_toolbar_search.settings
parent: system.admin_config_ui
<?php
/**
* @file
* Functionality for search of Admin Toolbar.
*/
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_help().
*/
function admin_toolbar_search_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help.
case 'help.page.admin_toolbar_search':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar Search module add a search option to the toolbar for site administrative tasks.') . '</p>';
return $output;
}
}
/**
* Implements hook_toolbar_alter().
*/
function admin_toolbar_search_toolbar_alter(&$items) {
if (!\Drupal::currentUser()->hasPermission('use admin toolbar search')) {
return;
}
$admin_toolbar_tools_enabled = \Drupal::service('module_handler')
->moduleExists('admin_toolbar_tools');
$config = \Drupal::config('admin_toolbar_search.settings');
$display_menu_item = $config->get('display_menu_item');
if (!$display_menu_item) {
$items['administration_mobile_search'] = [
'#type' => 'toolbar_item',
'#weight' => 100,
'tab' => [
'#type' => 'link',
'#title' => new TranslatableMarkup('Search'),
'#url' => Url::fromRoute('system.admin'),
'#attributes' => [
'class' => [
'toolbar-icon',
],
],
],
'#wrapper_attributes' => [
'id' => 'admin-toolbar-mobile-search-tab',
],
];
$items['administration_search'] = [
"#type" => "toolbar_item",
'#weight' => 101,
'tab' => [
'search' => [
'#title' => t('Search'),
'#title_display' => 'invisible',
'#type' => 'search',
'#size' => 30,
'#attributes' => [
'placeholder' => new TranslatableMarkup('Admin Toolbar quick search'),
],
'#id' => 'admin-toolbar-search-input',
],
],
'#attached' => [
'library' => [
'admin_toolbar_search/search',
],
'drupalSettings' => [
'adminToolbarSearch' => [
'loadExtraLinks' => $admin_toolbar_tools_enabled,
],
],
],
'#wrapper_attributes' => [
'id' => 'admin-toolbar-search-tab',
],
'#cache' => [
'contexts' => [
'user.permissions',
],
'tags' => [
'config:admin_toolbar_search.settings',
],
],
];
}
else {
$items['administration_search'] = [
"#type" => "toolbar_item",
'tab' => [
'#type' => 'link',
'#title' => new TranslatableMarkup('Search'),
'#url' => URL::fromRoute('system.admin'),
'#attributes' => [
'class' => [
'toolbar-icon',
],
],
],
'tray' => [
'search' => [
'#title' => t('Search'),
'#type' => 'search',
'#size' => 60,
'#id' => 'admin-toolbar-search-input',
],
],
'#attached' => [
'library' => [
'admin_toolbar_search/search',
],
'drupalSettings' => [
'adminToolbarSearch' => [
'loadExtraLinks' => $admin_toolbar_tools_enabled,
],
],
],
'#wrapper_attributes' => [
"id" => "admin-toolbar-search-tab",
],
'#cache' => [
'contexts' => [
'user.permissions',
],
'tags' => [
'config:admin_toolbar_search.settings',
],
],
];
}
}
admin_toolbar.search:
path: '/admin/admin-toolbar-search'
defaults:
_controller: '\Drupal\admin_toolbar_search\Controller\AdminToolbarSearchController::search'
requirements:
_permission: 'use admin toolbar search'
admin_toolbar_search.settings:
path: '/admin/config/user-interface/admin-toolbar-search-settings'
defaults:
_title: 'Admin toolbar search settings'
_form: 'Drupal\admin_toolbar_search\Form\AdminToolbarSearchSettingsForm'
requirements:
_permission: 'administer site configuration'
services:
admin_toolbar_search.search_links:
class: Drupal\admin_toolbar_search\SearchLinks
arguments:
- '@entity_type.manager'
- '@module_handler'
- '@router.route_provider'
- '@cache_contexts_manager'
- '@cache.toolbar'
- '@config.factory'
admin_toolbar_search.settings:
type: config_object
label: 'Admin Toolbar Search settings'
mapping:
display_menu_item:
type: integer
label: 'How the search input will be displayed'
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab {
display: none;
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab.visible {
display: block;
width: 100%;
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab .js-form-item.form-item {
margin: 0.75rem 0;
padding-left: 1rem;
padding-right: 1rem;
}
#admin-toolbar-mobile-search-tab .toolbar-item::before {
background-image: url('../../misc/icons/bebebe/loupe.svg');
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab #admin-toolbar-search-input {
width: 100%;
}
@media only screen and (min-width: 769px) {
#admin-toolbar-mobile-search-tab {
display: none;
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab {
display: block;
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab.visible {
width: auto;
}
#admin-toolbar-mobile-search-tab ~ #admin-toolbar-search-tab .js-form-item.form-item {
margin-top: 0.3rem;
margin-bottom: 0;
}
}
#admin-toolbar-search-input {
min-height: 30px;
height: 100%;
padding: 0 0.4rem;
line-height: 1.75rem;
margin: 0;
color: #3b3b3b;
background: #fcfcfa;
border: 1px solid #ccc;
border-radius: unset;
font-size: 1em;
}
.ui-autocomplete .ui-menu-item span.admin-toolbar-search-url {
display: none;
}
.admin-toolbar-search-autocomplete-list {
max-height: 300px;
overflow-y: scroll;
}
.admin-toolbar-search-autocomplete-list .ui-menu-item .ui-state-active {
margin: 0;
}
#toolbar-item-administration-search-tray label {
display: inline-block;
color: #000000;
margin-right: .5em;
font-weight: bold;
}
#toolbar-item-administration-search-tray div.form-item {
margin: 0.75em 0;
}
#toolbar-item-administration-search-tray input {
display: inline-block;
padding: 0.3em 0.4em 0.3em 0.5em;
font-size: 1em;
}
#admin-toolbar-search-tab .toolbar-item:before {
background-image: url('../../misc/icons/bebebe/loupe.svg');
}
#admin-toolbar-search-tab .toolbar-item:active:before,
#admin-toolbar-search-tab .toolbar-item.is-active:before {
background-image: url('../../misc/icons/ffffff/loupe.svg');
}
#toolbar-item-administration-search-tray div.form-item.js-form-type-textfield {
margin: 0.75em 0;
}
/**
* @file
* Behaviors for the search widget in the admin toolbar.
*/
(function ($, Drupal) {
'use strict';
Drupal.behaviors.adminToolbarSearch = {
// If extra links have been fetched.
extraFetched: false,
attach: function (context) {
if (context != document) {
return;
}
var $self = this;
$('#toolbar-bar', context).once('admin-toolbar-search').each(function () {
$self.links = [];
var $searchTab = $(this).find('#admin-toolbar-search-tab')
var $searchInput = $searchTab.find('#admin-toolbar-search-input');
if ($searchInput.length === 0) {
return;
}
$searchInput.autocomplete({
minLength: 2,
position: { collision : 'fit' },
source: function (request, response) {
var data = $self.handleAutocomplete(request.term);
if (!$self.extraFetched && drupalSettings.adminToolbarSearch.loadExtraLinks) {
$.getJSON( Drupal.url('admin/admin-toolbar-search'), function ( data ) {
$(data).each(function () {
var item = this;
item.label = this.labelRaw + ' ' + this.value;
$self.links.push(item);
});
$self.extraFetched = true;
var results = $self.handleAutocomplete(request.term);
response(results);
});
}
else {
response(data);
}
},
open: function () {
var zIndex = $('#toolbar-item-administration-tray')
.css('z-index') + 1;
$(this).autocomplete('widget').css('z-index', zIndex);
return false;
},
select: function (event, ui) {
if (ui.item.value) {
location.href = ui.item.value;
return false;
}
}
}).data('ui-autocomplete')._renderItem = (function (ul, item) {
ul.addClass('admin-toolbar-search-autocomplete-list');
return $('<li>')
.append('<div>' + item.labelRaw + ' <span class="admin-toolbar-search-url">' + item.value + '</span></div>')
.appendTo(ul);
});
// Populate the links for search results when the input is pressed.
$searchInput.focus(function () {
Drupal.behaviors.adminToolbarSearch.populateLinks($self);
});
// Show/hide search input field when mobile tab item is pressed.
$('#admin-toolbar-mobile-search-tab .toolbar-item', context).click(function (e) {
e.preventDefault();
$(this).toggleClass('is-active');
$searchTab.toggleClass('visible');
$searchInput.focus();
});
});
},
getItemLabel: function (item) {
var breadcrumbs = [];
$(item).parents().each(function () {
if ($(this).hasClass('menu-item')) {
var $link = $(this).find('a:first');
if ($link.length && !$link.hasClass('admin-toolbar-search-ignore')) {
breadcrumbs.unshift($link.text());
}
}
});
return breadcrumbs.join(' > ');
},
handleAutocomplete: function (term) {
var $self = this;
var keywords = term.split(" "); // Split search terms into list.
var suggestions = [];
$self.links.forEach(function (element) {
var label = element.label.toLowerCase();
// Add exact matches.
if (label.indexOf(term.toLowerCase()) >= 0) {
suggestions.push(element);
}
else {
// Add suggestions where it matches all search terms.
var matchCount = 0;
keywords.forEach(function (keyword) {
if (label.indexOf(keyword.toLowerCase()) >= 0) {
matchCount++;
}
});
if (matchCount == keywords.length) {
suggestions.push(element);
}
}
});
return suggestions;
},
/**
* Populates the links in admin toolbar search.
*/
populateLinks: function ($self) {
// Populate only when links array is empty (only the first time).
if ($self.links.length === 0) {
var getUrl = window.location;
var baseUrl = getUrl.protocol + "//" + getUrl.host + "/";
$('.toolbar-tray a[data-drupal-link-system-path]').each(function () {
if (this.href !== baseUrl) {
var label = $self.getItemLabel(this);
$self.links.push({
'value': this.href,
'label': label + ' ' + this.href,
'labelRaw': Drupal.checkPlain(label)
});
}
});
}
},
};
})(jQuery, Drupal);
<?php
namespace Drupal\admin_toolbar_search\Controller;
use Drupal\admin_toolbar_search\SearchLinks;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* Class AdminToolbarSearchController to the search functionality.
*
* @package Drupal\admin_toolbar_tools\Controller
*/
class AdminToolbarSearchController extends ControllerBase {
/**
* The search links service.
*
* @var \Drupal\admin_toolbar_search\SearchLinks
*/
protected $links;
/**
* Constructs an AdminToolbarSearchController object.
*
* @param \Drupal\admin_toolbar_search\SearchLinks $links
* The search links service.
*/
public function __construct(SearchLinks $links) {
$this->links = $links;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('admin_toolbar_search.search_links')
);
}
/**
* Return additional search links.
*/
public function search() {
return new JsonResponse($this->links->getLinks());
}
}
<?php
namespace Drupal\admin_toolbar_search\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure Admin Toolbar Search settings for this site.
*/
class AdminToolbarSearchSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'admin_toolbar_search_admin_toolbar_search_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['admin_toolbar_search.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['display_menu_item'] = [
'#type' => 'checkbox',
'#title' => $this->t('Display the search input as a menu item.'),
'#description' => $this->t("If set, instead of displaying a text input field, it displays a menu item in the toolbar so the user has to click on it to toggle the search input."),
'#default_value' => $this->config('admin_toolbar_search.settings')->get('display_menu_item'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('admin_toolbar_search.settings')
->set('display_menu_item', $form_state->getValue('display_menu_item'))
->save();
parent::submitForm($form, $form_state);
}
}
<?php
namespace Drupal\admin_toolbar_search;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\system\Entity\Menu;
/**
* Extra search links.
*/
class SearchLinks {
use StringTranslationTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The cache context manager service.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager
*/
protected $cacheContextManager;
/**
* The toolbar cache bin.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $toolbarCache;
/**
* The admin toolbar tools configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* Constructs a SearchLinks object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_context_manager
* The cache contexts manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $toolbar_cache
* Cache backend instance to use.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Config factory mservice.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, RouteProviderInterface $route_provider, CacheContextsManager $cache_context_manager, CacheBackendInterface $toolbar_cache, ConfigFactoryInterface $config_factory) {
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
$this->routeProvider = $route_provider;
$this->cacheContextManager = $cache_context_manager;
$this->toolbarCache = $toolbar_cache;
$this->config = $config_factory->get('admin_toolbar_tools.settings');
}
/**
* Gets extra links for admin toolbar search feature.
*
* @return array
* An array of link data for the JSON used for search.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function getLinks() {
$max_bundle_number = $this->config->get('max_bundle_number');
$additional_keys = $this->cacheContextManager->convertTokensToKeys([
'languages:' . LanguageInterface::TYPE_INTERFACE,
'user.permissions',
])->getKeys();
$cid_parts = array_merge(['admin_toolbar_search:links'], $additional_keys);
$cid = implode(':', $cid_parts);
if ($cache = $this->toolbarCache->get($cid)) {
return $cache->data;
}
$links = [];
$cache_tags = [];
$content_entities = $this->getBundleableEntitiesList();
// Adds common links to entities.
foreach ($content_entities as $entities) {
$content_entity_bundle = $entities['content_entity_bundle'];
$content_entity = $entities['content_entity'];
// Load the remaining items that were not loaded by the toolbar.
$content_entity_bundle_storage = $this->entityTypeManager->getStorage($content_entity_bundle);
$bundles_ids = $content_entity_bundle_storage->getQuery()->range($max_bundle_number)->execute();
if (!empty($bundles_ids)) {
$bundles = $this->entityTypeManager
->getStorage($content_entity_bundle)
->loadMultiple($bundles_ids);
foreach ($bundles as $machine_name => $bundle) {
$cache_tags = Cache::mergeTags($cache_tags, $bundle->getEntityType()->getListCacheTags());
$tparams = [
'@entity_type' => $bundle->getEntityType()->getLabel(),
'@bundle' => $bundle->label(),
];
$label_base = $this->t('@entity_type > @bundle', $tparams);
$params = [$content_entity_bundle => $machine_name];
if ($this->routeExists('entity.' . $content_entity_bundle . '.overview_form')) {
// Some bundles have an overview/list form that make a better root
// link.
$url = Url::fromRoute('entity.' . $content_entity_bundle . '.overview_form', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base,
'value' => $url_string,
];
}
}
if ($this->routeExists('entity.' . $content_entity_bundle . '.edit_form')) {
$url = Url::fromRoute('entity.' . $content_entity_bundle . '.edit_form', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Edit'),
'value' => $url_string,
];
}
}
if ($this->moduleHandler->moduleExists('field_ui')) {
if ($this->routeExists('entity.' . $content_entity . '.field_ui_fields')) {
$url = Url::fromRoute('entity.' . $content_entity . '.field_ui_fields', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Manage fields'),
'value' => $url_string,
];
}
}
if ($this->routeExists('entity.entity_form_display.' . $content_entity . '.default')) {
$url = Url::fromRoute('entity.entity_form_display.' . $content_entity . '.default', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Manage form display'),
'value' => $url_string,
];
}
}
if ($this->routeExists('entity.entity_view_display.' . $content_entity . '.default')) {
$url = Url::fromRoute('entity.entity_view_display.' . $content_entity . '.default', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Manage display'),
'value' => $url_string,
];
}
}
if ($this->moduleHandler->moduleExists('devel') && $this->routeExists('entity.' . $content_entity_bundle . '.devel_load')) {
$url = Url::fromRoute($route_name = 'entity.' . $content_entity_bundle . '.devel_load', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Devel'),
'value' => $url_string,
];
}
}
if ($this->routeExists('entity.' . $content_entity_bundle . '.delete_form')) {
$url = Url::fromRoute('entity.' . $content_entity_bundle . '.delete_form', $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $label_base . ' > ' . $this->t('Delete'),
'value' => $url_string,
];
}
}
}
}
}
}
// Add menu links.
if ($this->moduleHandler->moduleExists('menu_ui')) {
$menus = $this->entityTypeManager->getStorage('menu')->loadMultiple();
uasort($menus, [Menu::class, 'sort']);
$menus = array_slice($menus, $max_bundle_number);
$cache_tags = Cache::mergeTags($cache_tags, ['config:menu_list']);
foreach ($menus as $menu_id => $menu) {
$route_name = 'entity.menu.edit_form';
$params = ['menu' => $menu_id];
$url = Url::fromRoute($route_name, $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $this->t('Menus > @menu_label', ['@menu_label' => $menu->label()]),
'value' => $url_string,
];
}
$route_name = 'entity.menu.add_link_form';
$params = ['menu' => $menu_id];
$url = Url::fromRoute($route_name, $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $this->t('Menus > @menu_label > Add link', ['@menu_label' => $menu->label()]),
'value' => $url_string,
];
}
$menus = ['admin', 'devel', 'footer', 'main', 'tools', 'account'];
if (!in_array($menu_id, $menus)) {
$route_name = 'entity.menu.delete_form';
$params = ['menu' => $menu_id];
$url = Url::fromRoute($route_name, $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $this->t('Menus > @menu_label > Delete', ['@menu_label' => $menu->label()]),
'value' => $url_string,
];
}
}
if ($this->moduleHandler->moduleExists('devel') && $this->routeExists('entity.menu.devel_load')) {
$route_name = 'entity.menu.devel_load';
$params = ['menu' => $menu_id];
$url = Url::fromRoute($route_name, $params);
if ($url->access()) {
$url_string = $url->toString();
$links[] = [
'labelRaw' => $this->t('Menus > @menu_label > Devel', ['@menu_label' => $menu->label()]),
'value' => $url_string,
];
}
}
}
}
$this->toolbarCache->set($cid, $links, Cache::PERMANENT, $cache_tags);
return $links;
}
/**
* Gets a list of content entities.
*
* @return array
* An array of metadata about content entities.
*/
protected function getBundleableEntitiesList() {
$entity_types = $this->entityTypeManager->getDefinitions();
$content_entities = [];
foreach ($entity_types as $key => $entity_type) {
if ($entity_type->getBundleEntityType() && ($entity_type->get('field_ui_base_route') != '')) {
$content_entities[$key] = [
'content_entity' => $key,
'content_entity_bundle' => $entity_type->getBundleEntityType(),
];
}
}
return $content_entities;
}
/**
* Determine if a route exists by name.
*
* @param string $route_name
* The name of the route to check.
*
* @return bool
* Whether a route with that route name exists.
*/
public function routeExists($route_name) {
return (count($this->routeProvider->getRoutesByNames([$route_name])) === 1);
}
}
<?php
namespace Drupal\Tests\admin_toolbar_search\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Test the functionality of admin toolbar search.
*
* @group admin_toolbar
* @group admin_toolbar_search
*/
class AdminToolbarSearchSettingTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'admin_toolbar_search',
'node',
'media',
'field_ui',
'menu_ui',
'block',
];
/**
* A user with the 'Use Admin Toolbar search' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $userWithAccess;
/**
* A test user without the 'Use Admin Toolbar search' permission..
*
* @var \Drupal\user\UserInterface
*/
protected $noAccessUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$permissions = [
'access toolbar',
'administer menu',
'access administration pages',
'administer site configuration',
'administer content types',
];
$this->noAccessUser = $this->drupalCreateUser($permissions);
$permissions[] = 'use admin toolbar search';
$this->userWithAccess = $this->drupalCreateUser($permissions);
}
/**
* Tests search functionality without admin_toolbar_tools enabled.
*/
public function testToolbarSearch() {
$this->drupalLogin($this->userWithAccess);
$this->drupalGet(Url::fromRoute('system.admin'));
$this->assertSession()->responseNotContains('id="toolbar-item-administration-search');
$this->config('admin_toolbar_search.settings')->set('display_menu_item', 1);
$this->config('admin_toolbar_search.settings')->save();
$this->drupalGet(Url::fromRoute('system.admin'));
$this->assertSession()->responseContains('id="toolbar-item-administration-search');
$this->config('admin_toolbar_search.settings')->set('display_menu_item', 0);
$this->config('admin_toolbar_search.settings')->save();
$this->drupalGet(Url::fromRoute('system.admin'));
$this->assertSession()->responseNotContains('id="toolbar-item-administration-search');
}
}
<?php
namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;
/**
* Test the functionality of admin toolbar search.
*
* @group admin_toolbar
* @group admin_toolbar_search
*/
class AdminToolbarSearchTest extends AdminToolbarSearchTestBase {
/**
* Tests search functionality without admin_toolbar_tools enabled.
*/
public function testToolbarSearch() {
$search_tab = '#admin-toolbar-search-tab';
$search_toolbar_item = '#toolbar-item-administration-search';
$search_tray = '#toolbar-item-administration-search-tray';
$this->drupalLogin($this->userWithAccess);
$assert_session = $this->assertSession();
$assert_session->responseContains('admin.toolbar_search.css');
$assert_session->responseContains('admin_toolbar_search.js');
$assert_session->waitForElementVisible('css', $search_tab);
$assert_session->waitForElementVisible('css', $search_toolbar_item);
$assert_session->waitForElementVisible('css', $search_tray);
$this->assertSuggestionContains('perfor', 'admin/config/development/performance');
$this->assertSuggestionContains('develop', 'admin/config/development/maintenance');
$this->assertSuggestionContains('types', 'admin/structure/types');
}
/**
* Tests a user without the search permission can't use search.
*/
public function testNoAccess() {
$search_tab = '#admin-toolbar-search-tab';
$search_toolbar_item = '#toolbar-item-administration-search';
$search_tray = '#toolbar-item-administration-search-tray';
$this->drupalLogin($this->noAccessUser);
$assert_session = $this->assertSession();
$assert_session->responseNotContains('admin.toolbar_search.css');
$assert_session->responseNotContains('admin_toolbar_search.js');
$assert_session->elementNotExists('css', $search_tab);
$assert_session->elementNotExists('css', $search_toolbar_item);
$assert_session->elementNotExists('css', $search_tray);
}
}
<?php
namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\system\Entity\Menu;
/**
* Base class for testing the functionality of admin toolbar search.
*
* @group admin_toolbar
* @group admin_toolbar_search
*/
abstract class AdminToolbarSearchTestBase extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'admin_toolbar_search',
'node',
'media',
'field_ui',
'menu_ui',
'block',
];
/**
* A user with the 'Use Admin Toolbar search' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $userWithAccess;
/**
* A test user without the 'Use Admin Toolbar search' permission..
*
* @var \Drupal\user\UserInterface
*/
protected $noAccessUser;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$baby_names = [
'ada' => 'Ada',
'amara' => 'Amara',
'amelia' => 'Amelia',
'arabella' => 'Arabella',
'asher' => 'Asher',
'astrid' => 'Astrid',
'atticus' => 'Atticus',
'aurora' => 'Aurora',
'ava' => 'Ava',
'cora' => 'Cora',
'eleanor' => 'Eleanor',
'eloise' => 'Eloise',
'felix' => 'Felix',
'freya' => 'Freya',
'genevieve' => 'Genevieve',
'isla' => 'Isla',
'jasper' => 'Jasper',
'luna' => 'Luna',
'maeve' => 'Maeve',
'milo' => 'Milo',
'nora' => 'Nora',
'olivia' => 'Olivia',
'ophelia' => 'Ophelia',
'posie' => 'Posie',
'rose' => 'Rose',
'silas' => 'Silas',
'soren' => 'Soren',
];
foreach ($baby_names as $id => $label) {
$menu = Menu::create([
'id' => $id,
'label' => $label,
]);
$menu->save();
}
$this->drupalPlaceBlock('local_tasks_block');
$permissions = [
'access toolbar',
'administer menu',
'access administration pages',
'administer site configuration',
'administer content types',
];
$this->noAccessUser = $this->drupalCreateUser($permissions);
$permissions[] = 'use admin toolbar search';
$this->userWithAccess = $this->drupalCreateUser($permissions);
}
/**
* Assert that the search suggestions contain a given string with given input.
*
* @param string $search
* The string to search for.
* @param string $contains
* Some HTML that is expected to be within the suggestions element.
*/
protected function assertSuggestionContains($search, $contains) {
$this->resetSearch();
$page = $this->getSession()->getPage();
$page->fillField('admin-toolbar-search-input', $search);
$this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-input"]', ' ');
$page->waitFor(3, function () use ($page) {
return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === TRUE);
});
$suggestions_markup = $page->find('css', 'ul.ui-autocomplete')->getHtml();
$this->assertStringContainsString($contains, $suggestions_markup);
}
/**
* Assert that the search suggestions does not contain a given string.
*
* Assert that the search suggestions does not contain a given string with a
* given input.
*
* @param string $search
* The string to search for.
* @param string $contains
* Some HTML that is not expected to be within the suggestions element.
*/
protected function assertSuggestionNotContains($search, $contains) {
$this->resetSearch();
$page = $this->getSession()->getPage();
$page->fillField('admin-toolbar-search-input', $search);
$this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-input"]', ' ');
$page->waitFor(3, function () use ($page) {
return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === TRUE);
});
if ($page->find('css', 'ul.ui-autocomplete')->isVisible() === FALSE) {
return;
}
else {
$suggestions_markup = $page->find('css', 'ul.ui-autocomplete')->getHtml();
$this->assertStringNotContainsString($contains, $suggestions_markup);
}
}
/**
* Search for an empty string to clear out the autocomplete suggestions.
*/
protected function resetSearch() {
$page = $this->getSession()->getPage();
// Empty out the suggestions.
$page->fillField('admin-toolbar-search-input', '');
$this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-input"]', ' ');
$page->waitFor(3, function () use ($page) {
return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === FALSE);
});
}
/**
* Checks that there is a link with the specified url in the admin toolbar.
*
* @param string $url
* The url to assert exists in the admin menu.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
*/
protected function assertMenuHasHref($url) {
$this->assertSession()
->elementExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
}
/**
* Checks that there is no link with the specified url in the admin toolbar.
*
* @param string $url
* The url to assert exists in the admin menu.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function assertMenuDoesNotHaveHref($url) {
$this->assertSession()
->elementNotExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
}
}
<?php
namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;
use Drupal\media\Entity\MediaType;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Test the functionality of admin toolbar search.
*
* @group admin_toolbar
* @group admin_toolbar_search
*/
class AdminToolbarToolsSearchTest extends AdminToolbarSearchTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'admin_toolbar_tools',
'admin_toolbar_search',
'node',
'media',
'field_ui',
'menu_ui',
'block',
];
/**
* The admin user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
$dog_names = [
'archie' => 'Archie',
'bailey' => 'Bailey',
'bella' => 'Bella',
'buddy' => 'Buddy',
'charlie' => 'Charlie',
'coco' => 'Coco',
'daisy' => 'Daisy',
'frankie' => 'Frankie',
'jack' => 'Jack',
'lola' => 'Lola',
'lucy' => 'Lucy',
'max' => 'Max',
'milo' => 'Milo',
'molly' => 'Molly',
'ollie' => 'Ollie',
'oscar' => 'Oscar',
'rosie' => 'Rosie',
'ruby' => 'Ruby',
'teddy' => 'Teddy',
'toby' => 'Toby',
'tonga' => 'Tonga',
'tracey' => 'Tracey',
'tuna' => 'Tuna',
'uno' => 'Uno',
'venus' => 'Venus',
'vicky' => 'Vicky',
'wimpy' => 'Wimpy',
'yellow' => 'Yellow',
'zac' => 'zac',
'zora' => 'zora',
];
foreach ($dog_names as $machine_name => $label) {
$this->createMediaType('image', [
'id' => $machine_name,
'label' => $label,
]);
}
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'administer menu',
'access administration pages',
'administer site configuration',
'administer content types',
'administer node fields',
'access media overview',
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
'use admin toolbar search',
]);
}
/**
* Tests search functionality with admin_toolbar_tools enabled.
*/
public function testToolbarSearch() {
$search_tab = '#admin-toolbar-search-tab';
$search_toolbar_item = '#toolbar-item-administration-search';
$search_tray = '#toolbar-item-administration-search-tray';
$this->drupalLogin($this->adminUser);
$assert_session = $this->assertSession();
$assert_session->responseContains('admin.toolbar_search.css');
$assert_session->responseContains('admin_toolbar_search.js');
$assert_session->waitForElementVisible('css', $search_tab);
$assert_session->waitForElementVisible('css', $search_toolbar_item);
$assert_session->waitForElementVisible('css', $search_tray);
$this->assertSuggestionContains('basic', 'admin/config/system/site-information');
// Rebuild menu items.
drupal_flush_all_caches();
// Test that the route admin_toolbar.search returns expected json.
$this->drupalGet('/admin/admin-toolbar-search');
$search_menus = [
'maeve',
'milo',
'nora',
'olivia',
'ophelia',
'posie',
'rose',
'silas',
'soren',
];
$toolbar_menus = [
'ada',
'amara',
'amelia',
'arabella',
'asher',
'astrid',
'atticus',
'aurora',
'ava',
];
foreach ($search_menus as $menu_id) {
$assert_session->responseContains('\/admin\/structure\/menu\/manage\/' . $menu_id);
}
foreach ($toolbar_menus as $menu_id) {
$assert_session->responseNotContains('\/admin\/structure\/menu\/manage\/' . $menu_id);
}
$this->drupalGet('/admin');
foreach ($search_menus as $menu_id) {
$this->assertMenuDoesNotHaveHref('/admin/structure/menu/manage/' . $menu_id);
}
foreach ($toolbar_menus as $menu_id) {
$this->assertMenuHasHref('/admin/structure/menu/manage/' . $menu_id);
}
$this->drupalGet('admin/structure/types/manage/article/fields');
$assert_session->waitForElementVisible('css', $search_tray);
$this->assertSuggestionContains('article manage fields', '/admin/structure/types/manage/article/fields');
$suggestions = $assert_session
->waitForElementVisible('css', 'ul.ui-autocomplete');
// Assert there is only one suggestion with a link to
// /admin/structure/types/manage/article/fields.
$count = count($suggestions->findAll('xpath', '//span[contains(text(), "/admin/structure/types/manage/article/fields")]'));
$this->assertEquals(1, $count);
// Test that bundle within admin toolbar appears in search.
$this->assertSuggestionContains('lola', 'admin/structure/media/manage/lola/fields');
// Assert that a link after the limit doesn't appear in admin toolbar.
$zora_url = '/admin/structure/media/manage/zora/fields';
$assert_session->elementNotContains('css', '#toolbar-administration', $zora_url);
// Assert that a link excluded from admin toolbar appears in search.
$this->assertSuggestionContains('zora', $zora_url);
// Test that adding a new bundle updates the extra links loaded from
// admin_toolbar.search route.
$this->createMediaType('image', [
'id' => 'zuzu',
'label' => 'Zuzu',
]);
$this->drupalGet('admin');
$assert_session->waitForElementVisible('css', $search_tray);
$this->assertSuggestionContains('zuzu', '/admin/structure/media/manage/zuzu/fields');
// Test that deleting a bundle updates the extra links loaded from
// admin_toolbar.search route.
$zora = MediaType::load('zora');
$zora->delete();
$this->getSession()->reload();
$assert_session->waitForElementVisible('css', $search_tray);
$this->assertSuggestionNotContains('zora', $zora);
}
}
......@@ -58,5 +58,7 @@ Current maintainers:
This project has been sponsored by:
* emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design and
implementation of web interfaces. Visit: http://http://emerya.fr/ for more
implementation of web interfaces. Visit: http://emerya.fr/ for more
information.
* trained people
Trained People is a training company focused on Drupal.
name: Admin Toolbar Extra Tools
description: Adds menu links to the Admin Toolbar.
description: Adds menu links like Flush cache, Run cron, Run updates, and Logout under Drupal icon.
package: Administration
configure: admin_toolbar_tools.settings
type: module
# core: 8.x
core_version_requirement: ^8.8.0 || ^9.0
dependencies:
- admin_toolbar:admin_toolbar
- drupal:system (>=8.6)
# Information added by Drupal.org packaging script on 2019-05-22
version: '8.x-1.27'
core: '8.x'
# Information added by Drupal.org packaging script on 2022-10-06
version: '3.2.1'
project: 'admin_toolbar'
datestamp: 1558552277
datestamp: 1665044278
<?php
/**
* @file
* Install, update and uninstall functions for the Admin Toolbar Tools module.
*/
/**
* Install the Admin Toolbar Search module.
*/
function admin_toolbar_tools_update_8001() {
// Installing the Admin Toolbar Search module.
\Drupal::service('module_installer')->install(['admin_toolbar_search']);
}
/**
* Default setting for maximum number of bundles per entity type to display.
*/
function admin_toolbar_tools_update_8201() {
\Drupal::service('config.factory')
->getEditable('admin_toolbar_tools.settings')
->set('max_bundle_number', 20)
->save(TRUE);
}
/**
* Default setting for enable hoverintent.
*/
function admin_toolbar_tools_update_8202() {
\Drupal::service('config.factory')
->getEditable('admin_toolbar_tools.settings')
->set('hoverintent_functionality', TRUE)
->save(TRUE);
}
......@@ -40,7 +40,7 @@ admin_toolbar_tools.flush:
menu_name: admin
admin_toolbar_tools.cssjs:
title: 'Flush CSS and Javascript'
title: 'Flush CSS and JavaScript'
route_name: admin_toolbar_tools.cssjs
parent: admin_toolbar_tools.flush
menu_name: admin
......@@ -74,3 +74,19 @@ admin_toolbar_tools.flush_rendercache:
route_name: admin_toolbar_tools.flush_rendercache
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.theme_rebuild:
title: 'Rebuild theme registry'
route_name: admin_toolbar_tools.theme_rebuild
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.extra_links:
deriver: \Drupal\admin_toolbar_tools\Plugin\Derivative\ExtraLinks
menu_name: admin
admin_toolbar_tools.settings:
title: 'Admin Toolbar Tools'
description: 'Configure the Admin Toolbar Tools module.'
route_name: admin_toolbar_tools.settings
parent: system.admin_config_ui
......@@ -7,6 +7,8 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Implements hook_toolbar().
......@@ -24,9 +26,61 @@ function admin_toolbar_tools_toolbar() {
'#attached' => ['library' => ['admin_toolbar_tools/toolbar.icon']],
];
// Toolbar item for primary local tasks.
$config_object = \Drupal::config('admin_toolbar_tools.settings');
$config = $config_object->get('show_local_tasks');
$items['admin_toolbar_local_tasks'] = [];
CacheableMetadata::createFromObject($config_object)->applyTo($items['admin_toolbar_local_tasks']);
if ($config) {
$items['admin_toolbar_local_tasks'] += [
'#type' => 'toolbar_item',
'#wrapper_attributes' => [
'class' => ['local-tasks-toolbar-tab'],
],
// Put it after contextual toolbar item so when float right is applied
// local tasks item will be first.
'#weight' => 10,
'tab' => [
// We can't use #lazy_builder here because
// ToolbarItem::preRenderToolbarItem will insert #attributes before
// lazy_builder callback and this will produce Exception.
// This means that for now we always render Local Tasks item even when
// the tray is empty.
'#type' => 'link',
'#title' => t('Local Tasks'),
'#url' => Url::fromRoute('<none>'),
'#attributes' => [
'class' => [
'toolbar-icon',
'toolbar-icon-local-tasks',
],
],
],
'tray' => [
'local_links' => [
'#lazy_builder' => [
'admin_toolbar_tools.helper:localTasksTrayLazyBuilder',
[],
],
],
],
'#attached' => ['library' => ['admin_toolbar_tools/toolbar.icon']],
];
}
return $items;
}
/**
* Implements hook_preprocess_html().
*/
function admin_toolbar_tools_preprocess_html(&$variables) {
if (\Drupal::currentUser()->hasPermission('access toolbar')) {
$variables['attributes']['class'][] = 'toolbar-icon-' . intval(\Drupal::VERSION);
}
}
/**
* Implements hook_help().
*/
......@@ -35,671 +89,52 @@ function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match)
case 'help.page.admin_toolbar_tools':
$output = '';
$output .= '<p>';
$output .= t('The Admin Toolbar Extra Tools module comes packaged with the <a href=":admin-toolbar">Admin Toolbar</a> module and adds functionality to it. The additional functionality is accessed thru extra links on the main administration Toolbar. Some links to Admin Toolbar Extra Tools administration pages are located at the bottom of this page.</a>', [':admin-toolbar' => Url::fromRoute('help.page', ['name' => 'admin_toolbar'])->toString()]);
$output .= t('The Admin Toolbar Extra Tools module comes packaged with the <a href=":admin-toolbar">Admin Toolbar</a> module and adds functionality to it. The additional functionality is accessed through extra links on the main administration Toolbar. Some links to Admin Toolbar Extra Tools administration pages are located at the bottom of this page.</a>', [':admin-toolbar' => Url::fromRoute('help.page', ['name' => 'admin_toolbar'])->toString()]);
$output .= '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('To use Admin Toolbar Extra Tools just install it like any other module. There is no other configuration required.') . '</p>';
return $output;
}
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$moduleHandler = \Drupal::moduleHandler();
$entityTypeManager = \Drupal::entityTypeManager();
$routeProvider = \Drupal::service('router.route_provider');
/**
* Determine if a route exists by name.
*
* @param $routeName
* The name of the route to check.
*
* @return bool
* Whether a route with that route name exists.
* Implements hook_entity_insert().
*/
$routeExists = function ($routeName) use ($routeProvider) {
return (count($routeProvider->getRoutesByNames([$routeName])) === 1);
};
$entityTypes = $entityTypeManager->getDefinitions();
$content_entities = [];
foreach ($entityTypes as $key => $entityType) {
if ($entityType->getBundleEntityType() && ($entityType->get('field_ui_base_route') != '')) {
$content_entities[$key] = [
'content_entity' => $key,
'content_entity_bundle' => $entityType->getBundleEntityType(),
];
}
}
// Adding a menu link to clean the Views cache.
if ($moduleHandler->moduleExists('views')) {
$links['admin_toolbar_tools.flush_views'] = [
'title' => t('Flush views cache'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'admin_toolbar_tools.flush_views',
'menu_name' => 'admin',
'parent' => 'admin_toolbar_tools.flush',
];
// Adding a menu link to Files.
if ($moduleHandler->moduleExists('file') && $routeExists('view.files.page_1')) {
$links['admin_toolbar_tools.view.files'] = [
'title' => t('Files'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'view.files.page_1',
'menu_name' => 'admin',
'parent' => 'system.admin_content',
];
}
}
// Adds common links to entities.
foreach ($content_entities as $entities) {
$content_entity_bundle = $entities['content_entity_bundle'];
$content_entity = $entities['content_entity'];
foreach ($entityTypeManager->getStorage($content_entity_bundle)->loadMultiple() as $machine_name => $bundle) {
// Normally, the edit form for the bundle would be its root link.
$content_entity_bundle_root = NULL;
if ($routeExists('entity.' . $content_entity_bundle . '.overview_form')) {
// Some bundles have an overview/list form that make a better root link.
$content_entity_bundle_root = 'entity.' . $content_entity_bundle . '.overview_form.' . $machine_name;
$links[$content_entity_bundle_root] = [
'title' => $bundle->label(),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.overview_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => [$content_entity_bundle => $machine_name],
];
}
if ($routeExists('entity.' . $content_entity_bundle . '.edit_form')) {
$key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name;
$links[$key] = [
'title' => $bundle->label(),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => [$content_entity_bundle => $machine_name],
];
if (empty($content_entity_bundle_root)) {
$content_entity_bundle_root = $key;
}
else {
$links[$key]['parent'] = $content_entity_bundle_root;
$links[$key]['title'] = t('Edit');
function admin_toolbar_tools_entity_insert(EntityInterface $entity) {
// Skip rebuild during config sync because rebuild should
// always be a post-sync step.
if (!\Drupal::isConfigSyncing()) {
$entities = \Drupal::service('admin_toolbar_tools.helper')->getRebuildEntityTypes();
if (in_array($entity->getEntityTypeId(), $entities)) {
\Drupal::service('plugin.manager.menu.link')->rebuild();
}
}
if ($moduleHandler->moduleExists('field_ui')) {
if ($routeExists('entity.' . $content_entity . '.field_ui_fields')) {
$links['entity.' . $content_entity . '.field_ui_fields' . $machine_name] = [
'title' => t('Manage fields'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity . '.field_ui_fields',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 1,
];
}
if ($routeExists('entity.entity_form_display.' . $content_entity . '.default')) {
$links['entity.entity_form_display.' . $content_entity . '.default' . $machine_name] = [
'title' => t('Manage form display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_form_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 2,
];
}
if ($routeExists('entity.entity_view_display.' . $content_entity . '.default')) {
$links['entity.entity_view_display.' . $content_entity . '.default.' . $machine_name] = [
'title' => t('Manage display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_view_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 3,
];
}
}
if ($moduleHandler->moduleExists('devel') && $routeExists('entity.' . $content_entity_bundle . '.devel_load')) {
$links['entity.' . $content_entity_bundle . '.devel_load.' . $machine_name] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.devel_load',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 4,
];
}
if ($routeExists('entity.' . $content_entity_bundle . '.delete_form')) {
$links['entity.' . $content_entity_bundle . '.delete_form.' . $machine_name] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.delete_form',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 5,
];
}
}
}
// Add user links.
$links['user.admin_create'] = [
'title' => t('Add a new user'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.admin_create',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['user.admin_permissions'] = [
'title' => t('Permissions'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.admin_permissions',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['entity.user_role.collection'] = [
'title' => t('Roles'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.collection',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['admin_toolbar_tools.user.logout'] = [
'title' => t('Logout'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.logout',
'parent' => 'admin_toolbar_tools.help',
'weight' => 10,
];
$links['user.role_add'] = [
'title' => t('Add a new role'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.role_add',
'menu_name' => 'admin',
'parent' => 'entity.user_role.collection',
'weight' => -5,
];
if ($moduleHandler->moduleExists('field_ui')) {
$links['entity.user.field_ui_fields_'] = [
'title' => t('Manage fields'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user.field_ui_fields',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
];
$links['entity.entity_form_display.user.default_'] = [
'title' => t('Manage form display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_form_display.user.default',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
];
$links['entity.entity_view_display.user.default_'] = [
'title' => t('Manage display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_view_display.user.default',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
];
}
foreach (user_roles() as $role) {
$links['entity.user_role.edit_form.' . $role->id()] = [
'title' => $role->label(),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.collection',
'route_parameters' => ['user_role' => $role->id()],
];
$links['entity.user_role.edit_permissions_form.' . $role->id()] = [
'title' => t('Edit permissions'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.edit_permissions_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
];
$links['entity.user_role.delete_form.' . $role->id()] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.delete_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
];
if ($moduleHandler->moduleExists('devel')) {
$links['entity.user_role.devel_load.' . $role->id()] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.devel_load',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
];
}
}
if ($moduleHandler->moduleExists('node')) {
$links['admin_toolbar_tools.add_content'] = $links['node.add_page'];
$links['admin_toolbar_tools.add_content']['parent'] = 'system.admin_content';
$links['node.type_add'] = [
'title' => t('Add content type'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'node.type_add',
'menu_name' => 'admin',
'parent' => 'entity.node_type.collection',
'weight' => -5,
];
// Add node links for each content type.
foreach ($entityTypeManager->getStorage('node_type')->loadMultiple() as $type) {
$links['node.add.' . $type->id()] = [
'title' => $type->label(),
'provider' => 'admin_toolbar_tools',
'route_name' => 'node.add',
'parent' => 'admin_toolbar_tools.add_content',
'route_parameters' => ['node_type' => $type->id()],
];
}
}
if ($moduleHandler->moduleExists('field_ui')) {
$links['field_ui.entity_form_mode_add'] = [
'title' => t('Add new form mode'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'field_ui.entity_form_mode_add',
'menu_name' => 'admin',
'parent' => 'entity.entity_form_mode.collection',
];
$links['field_ui.entity_view_mode_add'] = [
'title' => t('Add new view mode'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'field_ui.entity_view_mode_add',
'menu_name' => 'admin',
'parent' => 'entity.entity_view_mode.collection',
];
}
if ($moduleHandler->moduleExists('taxonomy')) {
$links['entity.taxonomy_vocabulary.add_form'] = [
'title' => t('Add vocabulary'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.taxonomy_vocabulary.add_form',
'menu_name' => 'admin',
'parent' => 'entity.taxonomy_vocabulary.collection',
'weight' => -5,
];
}
if ($moduleHandler->moduleExists('menu_ui')) {
$links['entity.menu.add_form'] = [
'title' => t('Add menu'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.add_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
'weight' => -50,
];
// Adds links to /admin/structure/menu.
foreach (menu_ui_get_menus() as $machine_name => $label) {
$links['entity.menu.edit_form.' . $machine_name] = [
'title' => $label,
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
'route_parameters' => ['menu' => $machine_name],
];
$links['entity.menu.delete_form.' . $machine_name] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.delete_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
'route_parameters' => ['menu' => $machine_name],
];
if ($moduleHandler->moduleExists('devel') && $routeExists('entity.menu.devel_load')) {
$links['entity.menu.devel_load.' . $machine_name] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.devel_load',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
'route_parameters' => ['menu' => $machine_name],
];
}
$links['entity.menu.add_link_form.' . $machine_name] = [
'title' => t('Add link'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.add_link_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
'route_parameters' => ['menu' => $machine_name],
];
}
}
// If module block_content is enabled.
if ($moduleHandler->moduleExists('block_content')) {
$links['block_content.add_page'] = [
'title' => t('Add custom block'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'block_content.add_page',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
'weight' => -100,
];
$links['entity.block_content.collection'] = [
'title' => t('Custom block library'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.block_content.collection',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
];
$links['entity.block_content_type.collection'] = [
'title' => t('Types'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.block_content_type.collection',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
];
}
// If module Contact is enabled.
if ($moduleHandler->moduleExists('contact')) {
$links['contact.form_add'] = [
'title' => t('Add contact form'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'contact.form_add',
'menu_name' => 'admin',
'parent' => 'entity.contact_form.collection',
'weight' => -5,
];
}
// If module Update Manager is enabled.
if ($moduleHandler->moduleExists('update')) {
$links['update.module_update'] = [
'title' => t('Update'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.module_update',
'menu_name' => 'admin',
'parent' => 'system.modules_list',
];
$links['update.module_install'] = [
'title' => t('Install new module'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.module_install',
'menu_name' => 'admin',
'parent' => 'system.modules_list',
];
}
// If module Devel is enabled.
if ($moduleHandler->moduleExists('devel')) {
$links['admin_development'] = [
'title' => t('Development'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.admin_config_development',
'menu_name' => 'admin',
'parent' => 'admin_toolbar_tools.help',
'weight' => '-8',
];
$links['admin_toolbar_tools.devel.admin_settings'] = [
'title' => t('Devel settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.admin_settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
'weight' => '-1',
];
if ($moduleHandler->moduleExists('webprofiler')) {
$links['admin_toolbar_tools.devel.webprofiler'] = [
'title' => t('Web Profiler settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'webprofiler.settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
$links['admin_toolbar_tools.devel.configs_list'] = [
'title' => t('Config editor'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.configs_list',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.reinstall'] = [
'title' => t('Reinstall modules'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.reinstall',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.menu_rebuild'] = [
'title' => t('Rebuild menu'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.menu_rebuild',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.state_system_page'] = [
'title' => t('State editor'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.state_system_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.theme_registry'] = [
'title' => t('Theme registry'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.theme_registry',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.entity_info_page'] = [
'title' => t('Entity Info'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.entity_info_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.session'] = [
'title' => t('Session viewer'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.session',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.elements_page'] = [
'title' => t('Form API field types'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.elements_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
// Menu link for the Toolbar module.
$links['admin_toolbar_tools.toolbar.settings'] = [
'title' => t('Toolbar settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.toolbar.settings_form',
'menu_name' => 'admin',
'parent' => 'devel.admin_settings',
];
}
// If module Devel PHP is enabled.
if ($moduleHandler->moduleExists('devel_php') && $routeExists('devel_php.execute_php')) {
$links['admin_toolbar_tools.devel_php.execute_php'] = [
'title' => t('Execute PHP Code'),
'route_name' => 'devel_php.execute_php',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
elseif ($moduleHandler->moduleExists('devel') && $routeExists('devel.execute_php')) {
$links['admin_toolbar_tools.devel.execute_php'] = [
'title' => t('Execute PHP Code'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.execute_php',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
// If module Views Ui enabled.
if ($moduleHandler->moduleExists('views_ui')) {
$links['admin_toolbar_tools.views_ui.add'] = [
'title' => t('Add new view'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'views_ui.add',
'menu_name' => 'admin',
'parent' => 'entity.view.collection',
'weight' => -5,
];
$links['admin_toolbar_tools.views_ui.field_list'] = [
'title' => t('Used in views'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'views_ui.reports_fields',
'menu_name' => 'admin',
'parent' => 'entity.field_storage_config.collection',
];
}
$links['admin_toolbar_tools.system.theme_settings'] = [
'title' => t('Settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.theme_settings',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
];
if ($moduleHandler->moduleExists('webprofiler')) {
$links['admin_toolbar_tools.devel.webprofiler'] = [
'title' => t('Webprofiler settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'webprofiler.settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
if ($moduleHandler->moduleExists('update')) {
$links['update.theme_install_'] = [
'title' => t('Install new theme'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.theme_install',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
];
$links['update.theme_update_'] = [
'title' => t('Update'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.theme_update',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
];
// Lists installed themes.
$installed_themes = admin_toolbar_tools_installed_themes();
foreach ($installed_themes as $key_theme => $label_theme) {
$links['system.theme_settings_theme.' . $key_theme] = [
'title' => $label_theme,
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.theme_settings_theme',
'menu_name' => 'admin',
'parent' => 'system.theme_settings_',
'route_parameters' => [
'theme' => $key_theme,
],
];
}
}
// If module Language enabled.
if ($moduleHandler->moduleExists('language')) {
$links['admin_toolbar_tools.language.negotiation'] = [
'title' => t('Detection and selection'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'language.negotiation',
'menu_name' => 'admin',
'parent' => 'entity.configurable_language.collection',
];
}
// If module Media enabled.
if ($moduleHandler->moduleExists('media')) {
// Displays media link in toolbar.
$links['admin_toolbar_tools.media_page'] = [
'title' => t('Media'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.collection',
'menu_name' => 'admin',
'parent' => 'system.admin_content',
];
}
$links['admin_toolbar_tools.add_media'] = [
'title' => t('Add media'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.add_page',
'menu_name' => 'admin',
'parent' => 'system.admin_content',
];
// Add node links for each media type.
foreach ($entityTypeManager->getStorage('media_type')->loadMultiple() as $type) {
$links['media.add.' . $type->id()] = [
'title' => $type->label(),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.add_form',
'parent' => 'admin_toolbar_tools.add_media',
'route_parameters' => ['media_type' => $type->id()],
];
/**
* Implements hook_entity_update().
*/
function admin_toolbar_tools_entity_update(EntityInterface $entity) {
// Skip rebuild during config sync because rebuild should
// always be a post-sync step.
if (!\Drupal::isConfigSyncing()) {
$entities = \Drupal::service('admin_toolbar_tools.helper')->getRebuildEntityTypes();
if (in_array($entity->getEntityTypeId(), $entities)) {
\Drupal::service('plugin.manager.menu.link')->rebuild();
}
}
// If module Config enabled.
if ($moduleHandler->moduleExists('config')) {
$links['admin_toolbar_tools.config.import'] = [
'title' => t('Import'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'config.import_full',
'menu_name' => 'admin',
'parent' => 'config.sync',
'weight' => 1,
];
$links['admin_toolbar_tools.config.export'] = [
'title' => t('Export'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'config.export_full',
'menu_name' => 'admin',
'parent' => 'config.sync',
'weight' => 2,
];
}
}
/**
* Return installed themes.
*
* @return array
* An array of friendly theme names, keyed by the machine name.
* Implements hook_entity_delete().
*/
function admin_toolbar_tools_installed_themes() {
$themeHandler = \Drupal::service('theme_handler');
$all_themes = $themeHandler->listInfo();
$themes_installed = [];
foreach ($all_themes as $key_theme => $theme) {
if ($themeHandler->hasUi($key_theme)) {
$themes_installed[$key_theme] = $themeHandler->getName($key_theme);
function admin_toolbar_tools_entity_delete(EntityInterface $entity) {
// Skip rebuild during config sync because rebuild should
// always be a post-sync step.
if (!\Drupal::isConfigSyncing()) {
$entities = \Drupal::service('admin_toolbar_tools.helper')->getRebuildEntityTypes();
if (in_array($entity->getEntityTypeId(), $entities)) {
\Drupal::service('plugin.manager.menu.link')->rebuild();
}
}
return $themes_installed;
}
......@@ -11,7 +11,7 @@ admin_toolbar_tools.cssjs:
path: '/admin/flush/cssjs'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushJsCss'
_title: 'Flush Css and Javascript'
_title: 'Flush CSS and JavaScript'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
......@@ -70,6 +70,15 @@ admin_toolbar_tools.flush_twig:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.theme_rebuild:
path: '/admin/flush/theme_rebuild'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::themeRebuild'
_title: 'Theme Rebuild'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar.run.cron:
path: '/run-cron'
defaults:
......@@ -78,3 +87,11 @@ admin_toolbar.run.cron:
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.settings:
path: '/admin/config/user-interface/admin-toolbar-tools'
defaults:
_form: '\Drupal\admin_toolbar_tools\Form\AdminToolbarToolsSettingsForm'
_title: 'Admin Toolbar Tools settings'
requirements:
_permission: 'administer site configuration'
services:
admin_toolbar_tools.helper:
class: Drupal\admin_toolbar_tools\AdminToolbarToolsHelper
arguments:
- '@entity_type.manager'
- '@plugin.manager.menu.local_task'
- '@current_route_match'
{
"name": "drupal/admin_toolbar_tools",
"description": "Adds menu links to the Admin Toolbar.",
"type": "drupal-module",
"keywords": ["Drupal", "Toolbar"],
"homepage": "http://drupal.org/project/admin_toolbar",
"license": "GPL-2.0+",
"authors": [
{
"name": "Wilfrid Roze (eme)",
"homepage": "https://www.drupal.org/u/eme",
"role": "Maintainer"
},
{
"name": "Romain Jarraud (romainj)",
"homepage": "https://www.drupal.org/u/romainj",
"role": "Maintainer"
},
{
"name": "Adrian Cid Almaguer (adriancid)",
"email": "adriancid@gmail.com",
"homepage": "https://www.drupal.org/u/adriancid",
"role": "Maintainer"
},
{
"name": "Mohamed Anis Taktak (matio89)",
"homepage": "https://www.drupal.org/u/matio89",
"role": "Maintainer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/admin_toolbar",
"source": "http://cgit.drupalcode.org/admin_toolbar"
},
"require": {
"drupal/admin_toolbar": "^1",
"drupal/core": "~8.6"
}
}
max_bundle_number: 20
hoverintent_functionality: true
show_local_tasks: false
admin_toolbar_tools.settings:
type: config_object
label: 'Admin Toolbar Tools settings'
mapping:
max_bundle_number:
type: integer
label: 'Number of bundles per entity type to display'
hoverintent_functionality:
type: boolean
label: 'Enable or disable hoverintent functionality'
show_local_tasks:
type: boolean
label: 'Show local tasks in toolbar'
......@@ -2,16 +2,46 @@
text-indent: -9999px;
}
.toolbar-icon-admin-toolbar-tools-help:before {
.toolbar-icon-9 .toolbar-icon-admin-toolbar-tools-help:before,
.toolbar-icon-10 .toolbar-icon-admin-toolbar-tools-help:before {
box-sizing: content-box;
background-image: url(../misc/icons/ffffff/d8-item.svg);
background-image: url(../misc/icons/ffffff/drupal-9-logo.svg);
padding-bottom: 0;
padding-left: 2px;
padding-right: 2px;
padding-top: 2px;
margin-left: 4px;
}
.toolbar-icon-9 .toolbar-icon-admin-toolbar-tools-help:active:before,
.toolbar-icon-9 .toolbar-icon-admin-toolbar-tools-help.active:before,
.toolbar-icon-10 .toolbar-icon-admin-toolbar-tools-help:active:before,
.toolbar-icon-10 .toolbar-icon-admin-toolbar-tools-help.active:before {
background-image: url(../misc/icons/ffffff/drupal-9-logo.svg);
}
.toolbar-icon-8 .toolbar-icon-admin-toolbar-tools-help:before {
box-sizing: content-box;
background-image: url(../misc/icons/ffffff/drupal-8-logo.svg);
padding-bottom: 0;
padding-left: 4px;
padding-right: 8px;
padding-top: 2px;
}
.toolbar-icon-admin-toolbar-tools-help:active:before,
.toolbar-icon-admin-toolbar-tools-help.active:before {
background-image: url(../misc/icons/ffffff/d8-item.svg);
.toolbar-icon-8 .toolbar-icon-admin-toolbar-tools-help:active:before,
.toolbar-icon-8 .toolbar-icon-admin-toolbar-tools-help.active:before {
background-image: url(../misc/icons/ffffff/drupal-8-logo.svg);
}
.toolbar-oriented .toolbar-bar .local-tasks-toolbar-tab {
float: right;
}
.toolbar-horizontal .local-tasks-toolbar-tab .toolbar-menu {
float: right;
}
.toolbar-bar .toolbar-icon-local-tasks:before {
background-image: url(../misc/icons/bebebe/tasks.svg);
}
<svg fill="#bebebe" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/>
<path d="M0 0h24v24H0V0z" fill="none"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMinYMin" viewBox="0 0 611 646"><path fill="#2ba9e0" d="M 161.14248,607.07981 C 135.931,577.51327 120.7662,539.33875 120.7662,497.60876 c 0,-87.01545 66.15644,-158.68624 151.648,-168.60413 -14.59612,-20.95856 -23.31588,-46.40824 -23.31588,-73.72922 0,-71.85792 58.95316,-129.86822 131.7442,-129.86822 6.06592,0 11.94228,0.37426 17.62908,1.12278 C 355.44148,89.85249 312.41136,52.61362 278.48012,12.00641 295.73008,190.15417 114.1316,125.40719 46.8378,289.70733 1.91208,399.73977 42.47792,535.78328 161.14248,607.07981 Z m 143.1178,-351.8044 c 0,41.72999 34.31036,75.41339 76.39268,75.41339 42.08232,0 76.58224,-33.87053 76.58224,-75.41339 0,-41.72999 -34.31036,-75.41339 -76.39268,-75.41339 -42.08232,0 -76.58224,33.6834 -76.58224,75.41339 z m 112.97776,124.81571 c 29.57136,30.50219 47.76912,71.85792 47.76912,117.51764 0,57.82317 -29.19224,108.72253 -73.73884,139.41185 82.4586,-25.07542 150.7002,-86.26693 181.21936,-160.37041 42.27188,-102.54724 2.8434,-179.6448 -63.12348,-249.63142 2.08516,8.98224 3.22252,18.52587 3.22252,28.0695 -0.18956,59.50734 -40.37628,109.47105 -95.34868,125.00284 z m -124.35136,18.90013 c -55.16196,0 -99.89812,44.16268 -99.89812,98.61751 0,54.45483 44.73616,98.61751 99.89812,98.61751 55.16196,0 99.89812,-44.16268 99.89812,-98.61751 0,-54.45483 -44.73616,-98.61751 -99.89812,-98.61751 z" /></svg>
<svg width="214" height="214" viewBox="0 0 214 214" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M172 59C154.963 36 135 19 105 0C110.5 30.5 58.5 50 41 68.5C23.5 87 17 106.188 17 125.902C17 174.204 57.6985 213.5 106 213.5C154.302 213.5 196.5 174.204 196.5 125.902C196.5 106.188 189.037 82 172 59Z" fill="#008CF2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.318 83.5C92.4745 83.5 81.0102 88.6894 73.0589 96.8593C70.2775 99.7171 68.2075 104.354 67.3793 109.481C66.5258 114.766 67.2414 118.928 68.2111 120.763C58 122 51.5 113 52.5 104.5C53.2059 98.5 55.8754 91.5746 61.5929 85.7C72.5277 74.4646 88.115 67.5 105.318 67.5C137.872 67.5 165 92.6234 165 124.5C165 156.377 137.872 181.5 105.318 181.5C100.829 181.5 93.7529 180.358 90 180C85.3088 179.654 71.5 179 64 182L83.5 108.5C85.5 102 91.7553 98.2637 102.602 99.6635L84.5586 164.168C86.3276 164.183 88.0427 164.274 89.7083 164.397C91.628 164.538 93.3802 164.707 95.0667 164.87C98.4724 165.198 101.61 165.5 105.318 165.5C129.85 165.5 149 146.747 149 124.5C149 102.253 129.85 83.5 105.318 83.5Z" fill="white"/>
</svg>
<svg width="214" height="214" viewBox="0 0 214 214" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M172 59C154.963 36 135 19 105 0C110.5 30.5 58.5 50 41 68.5C23.5 87 17 106.188 17 125.902C17 174.204 57.6985 213.5 106 213.5C154.302 213.5 196.5 174.204 196.5 125.902C196.5 106.188 189.037 82 172 59Z" fill="#008CF2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.318 83.5C92.4745 83.5 81.0102 88.6894 73.0589 96.8593C70.2775 99.7171 68.2075 104.354 67.3793 109.481C66.5258 114.766 67.2414 118.928 68.2111 120.763C58 122 51.5 113 52.5 104.5C53.2059 98.5 55.8754 91.5746 61.5929 85.7C72.5277 74.4646 88.115 67.5 105.318 67.5C137.872 67.5 165 92.6234 165 124.5C165 156.377 137.872 181.5 105.318 181.5C100.829 181.5 93.7529 180.358 90 180C85.3088 179.654 71.5 179 64 182L83.5 108.5C85.5 102 91.7553 98.2637 102.602 99.6635L84.5586 164.168C86.3276 164.183 88.0427 164.274 89.7083 164.397C91.628 164.538 93.3802 164.707 95.0667 164.87C98.4724 165.198 101.61 165.5 105.318 165.5C129.85 165.5 149 146.747 149 124.5C149 102.253 129.85 83.5 105.318 83.5Z" fill="white"/>
</svg>
<?php
namespace Drupal\admin_toolbar_tools;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
/**
* Admin Toolbar Tools helper service.
*/
class AdminToolbarToolsHelper implements TrustedCallbackInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The local task manger.
*
* @var \Drupal\Core\Menu\LocalTaskManager
* The local task manager menu.
*/
protected $localTaskManager;
/**
* The route match interface.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
* The route match.
*/
protected $routeMatch;
/**
* Create an AdminToolbarToolsHelper object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Menu\LocalTaskManager $local_task_manager
* The local task manager.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, LocalTaskManager $local_task_manager, RouteMatchInterface $route_match) {
$this->entityTypeManager = $entity_type_manager;
$this->localTaskManager = $local_task_manager;
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['localTasksTrayLazyBuilder'];
}
/**
* Lazy builder callback for the admin_toolbar_local_tasks tray items.
*
* @return array
* A renderable array as expected by the renderer service.
*/
public function localTasksTrayLazyBuilder() {
// Get primary local task links and inject them into new
// admin_toolbar_local_tasks toolbar tray.
$links = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName(), 0);
if (!empty($links['tabs'])) {
$build = [
'#theme' => 'links',
'#links' => [],
'#attributes' => [
'class' => ['toolbar-menu'],
],
];
Element::children($links['tabs'], TRUE);
$routes = Element::getVisibleChildren($links['tabs']);
foreach ($routes as $route) {
$build['#links'][$route] = $links['tabs'][$route]['#link'];
}
$links['cacheability']->applyTo($build);
return $build;
}
return [];
}
/**
* Gets a list of content entities.
*
* @return array
* An array of metadata about content entities.
*/
public function getBundleableEntitiesList() {
$entity_types = $this->entityTypeManager->getDefinitions();
$content_entities = [];
foreach ($entity_types as $key => $entity_type) {
if ($entity_type->getBundleEntityType() && ($entity_type->get('field_ui_base_route') != '')) {
$content_entities[$key] = [
'content_entity' => $key,
'content_entity_bundle' => $entity_type->getBundleEntityType(),
];
}
}
return $content_entities;
}
/**
* Gets an array of entity types that should trigger a menu rebuild.
*
* @return array
* An array of entity machine names.
*/
public function getRebuildEntityTypes() {
$types = ['menu'];
$content_entities = $this->getBundleableEntitiesList();
$types = array_merge($types, array_column($content_entities, 'content_entity_bundle'));
return $types;
}
}
......@@ -6,19 +6,19 @@ use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\CronInterface;
use Drupal\Core\Menu\ContextualLinkManagerInterface;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Menu\LocalTaskManagerInterface;
use Drupal\Core\Menu\ContextualLinkManager;
use Drupal\Core\Menu\LocalActionManager;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\Core\Theme\Registry;
/**
* Class ToolbarController.
* Controller for AdminToolbar Tools.
*
* @package Drupal\admin_toolbar_tools\Controller
*/
......@@ -41,21 +41,21 @@ class ToolbarController extends ControllerBase {
/**
* A context link manager instance.
*
* @var \Drupal\Core\Menu\ContextualLinkManagerInterface
* @var \Drupal\Core\Menu\ContextualLinkManager
*/
protected $contextualLinkManager;
/**
* A local task manager instance.
*
* @var \Drupal\Core\Menu\LocalTaskManagerInterface
* @var \Drupal\Core\Menu\LocalTaskManager
*/
protected $localTaskLinkManager;
/**
* A local action manager instance.
*
* @var \Drupal\Core\Menu\LocalActionManagerInterface
* @var \Drupal\Core\Menu\LocalActionManager
*/
protected $localActionLinkManager;
......@@ -101,6 +101,13 @@ class ToolbarController extends ControllerBase {
*/
protected $twig;
/**
* The search theme.registry service.
*
* @var \Drupal\Core\Theme\Registry
*/
protected $themeRegistry;
/**
* Constructs a ToolbarController object.
*
......@@ -108,11 +115,11 @@ class ToolbarController extends ControllerBase {
* A cron instance.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
* A menu link manager instance.
* @param \Drupal\Core\Menu\ContextualLinkManagerInterface $contextualLinkManager
* @param \Drupal\Core\Menu\ContextualLinkManager $contextualLinkManager
* A context link manager instance.
* @param \Drupal\Core\Menu\LocalTaskManagerInterface $localTaskLinkManager
* @param \Drupal\Core\Menu\LocalTaskManager $localTaskLinkManager
* A local task manager instance.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $localActionLinkManager
* @param \Drupal\Core\Menu\LocalActionManager $localActionLinkManager
* A local action manager instance.
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheRender
* A cache backend interface instance.
......@@ -126,18 +133,23 @@ class ToolbarController extends ControllerBase {
* A cache menu instance.
* @param \Drupal\Core\Template\TwigEnvironment $twig
* A TwigEnvironment instance.
* @param \Drupal\Core\Theme\Registry $theme_registry
* The theme.registry service.
*/
public function __construct(CronInterface $cron,
public function __construct(
CronInterface $cron,
MenuLinkManagerInterface $menuLinkManager,
ContextualLinkManagerInterface $contextualLinkManager,
LocalTaskManagerInterface $localTaskLinkManager,
LocalActionManagerInterface $localActionLinkManager,
ContextualLinkManager $contextualLinkManager,
LocalTaskManager $localTaskLinkManager,
LocalActionManager $localActionLinkManager,
CacheBackendInterface $cacheRender,
TimeInterface $time,
RequestStack $request_stack,
CachedDiscoveryClearerInterface $plugin_cache_clearer,
CacheBackendInterface $cache_menu,
TwigEnvironment $twig) {
TwigEnvironment $twig,
Registry $theme_registry
) {
$this->cron = $cron;
$this->menuLinkManager = $menuLinkManager;
$this->contextualLinkManager = $contextualLinkManager;
......@@ -149,6 +161,7 @@ class ToolbarController extends ControllerBase {
$this->pluginCacheClearer = $plugin_cache_clearer;
$this->cacheMenu = $cache_menu;
$this->twig = $twig;
$this->themeRegistry = $theme_registry;
}
/**
......@@ -166,7 +179,8 @@ class ToolbarController extends ControllerBase {
$container->get('request_stack'),
$container->get('plugin.cache_clearer'),
$container->get('cache.menu'),
$container->get('twig')
$container->get('twig'),
$container->get('theme.registry')
);
}
......@@ -179,7 +193,7 @@ class ToolbarController extends ControllerBase {
return $request->server->get('HTTP_REFERER');
}
else {
return '/';
return base_path();
}
}
......@@ -269,4 +283,13 @@ class ToolbarController extends ControllerBase {
return new RedirectResponse($this->reloadPage());
}
/**
* Rebuild the theme registry.
*/
public function themeRebuild() {
$this->themeRegistry->reset();
$this->messenger()->addMessage($this->t('Theme registry rebuilded.'));
return new RedirectResponse($this->reloadPage());
}
}
<?php
namespace Drupal\admin_toolbar_tools\Form;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Settings form for AdminToobar Tools.
*
* @package Drupal\admin_toolbar_tools\Form
*/
class AdminToolbarToolsSettingsForm extends ConfigFormBase {
/**
* The cache menu instance.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheMenu;
/**
* The menu link manager instance.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $menuLinkManager;
/**
* AdminToolbarToolsSettingsForm constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The factory for configuration objects.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
* A menu link manager instance.
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheMenu
* A cache menu instance.
*/
public function __construct(ConfigFactoryInterface $configFactory, MenuLinkManagerInterface $menuLinkManager, CacheBackendInterface $cacheMenu) {
parent::__construct($configFactory);
$this->cacheMenu = $cacheMenu;
$this->menuLinkManager = $menuLinkManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('plugin.manager.menu.link'),
$container->get('cache.menu')
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'admin_toolbar_tools.settings',
];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'admin_toolbar_tools_settings';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('admin_toolbar_tools.settings');
$form['max_bundle_number'] = [
'#type' => 'number',
'#title' => $this->t('Maximum number of bundle sub-menus to display'),
'#description' => $this->t('Loading a large number of items can cause performance issues.'),
'#default_value' => $config->get('max_bundle_number'),
];
$form['hoverintent_functionality'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable/Disable the hoverintent functionality'),
'#description' => $this->t('Check it if you want to enable the hoverintent feature.'),
'#default_value' => $config->get('hoverintent_functionality'),
];
$form['show_local_tasks'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable/Disable local tasks display'),
'#description' => $this->t('Local tasks such as node edit and delete.'),
'#default_value' => $config->get('show_local_tasks'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('admin_toolbar_tools.settings')
->set('max_bundle_number', $form_state->getValue('max_bundle_number'))
->set('hoverintent_functionality', $form_state->getValue('hoverintent_functionality'))
->set('show_local_tasks', $form_state->getValue('show_local_tasks'))
->save();
parent::submitForm($form, $form_state);
$this->cacheMenu->invalidateAll();
$this->menuLinkManager->rebuild();
}
}
<?php
namespace Drupal\admin_toolbar_tools\Plugin\Derivative;
use Drupal\system\Entity\Menu;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a default implementation for menu link plugins.
*/
class ExtraLinks extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The admin toolbar tools configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, RouteProviderInterface $route_provider, ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, AccountInterface $current_user) {
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
$this->routeProvider = $route_provider;
$this->themeHandler = $theme_handler;
$this->config = $config_factory->get('admin_toolbar_tools.settings');
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('module_handler'),
$container->get('router.route_provider'),
$container->get('theme_handler'),
$container->get('config.factory'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$links = [];
$max_bundle_number = $this->config->get('max_bundle_number');
$entity_types = $this->entityTypeManager->getDefinitions();
$content_entities = [];
foreach ($entity_types as $key => $entity_type) {
if ($entity_type->getBundleEntityType() && ($entity_type->get('field_ui_base_route') != '')) {
$content_entities[$key] = [
'content_entity' => $key,
'content_entity_bundle' => $entity_type->getBundleEntityType(),
];
}
}
// Adds common links to entities.
foreach ($content_entities as $entities) {
$content_entity_bundle = $entities['content_entity_bundle'];
$content_entity = $entities['content_entity'];
$content_entity_bundle_storage = $this->entityTypeManager->getStorage($content_entity_bundle);
$bundles_ids = $content_entity_bundle_storage->getQuery()->pager($max_bundle_number)->execute();
$bundles = $this->entityTypeManager->getStorage($content_entity_bundle)->loadMultiple($bundles_ids);
if (count($bundles) == $max_bundle_number && $this->routeExists('entity.' . $content_entity_bundle . '.collection')) {
$links[$content_entity_bundle . '.collection'] = [
'title' => $this->t('All types'),
'route_name' => 'entity.' . $content_entity_bundle . '.collection',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'weight' => -1,
] + $base_plugin_definition;
}
foreach ($bundles as $machine_name => $bundle) {
// Normally, the edit form for the bundle would be its root link.
$content_entity_bundle_root = NULL;
if ($this->routeExists('entity.' . $content_entity_bundle . '.overview_form')) {
// Some bundles have an overview/list form that make a better root
// link.
$content_entity_bundle_root = 'entity.' . $content_entity_bundle . '.overview_form.' . $machine_name;
$links[$content_entity_bundle_root] = [
'route_name' => 'entity.' . $content_entity_bundle . '.overview_form',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => [$content_entity_bundle => $machine_name],
'class' => 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity',
'metadata' => [
'entity_type' => $bundle->getEntityTypeId(),
'entity_id' => $bundle->id(),
],
] + $base_plugin_definition;
}
if ($this->routeExists('entity.' . $content_entity_bundle . '.edit_form')) {
$key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name;
$links[$key] = [
'route_name' => 'entity.' . $content_entity_bundle . '.edit_form',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => [$content_entity_bundle => $machine_name],
] + $base_plugin_definition;
if (empty($content_entity_bundle_root)) {
$content_entity_bundle_root = $key;
$links[$key]['parent'] = 'entity.' . $content_entity_bundle . '.collection';
// When not grouped by bundle, use bundle name as title.
$links[$key]['class'] = 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity';
$links[$key]['metadata'] = [
'entity_type' => $bundle->getEntityTypeId(),
'entity_id' => $bundle->id(),
];
}
else {
$links[$key]['parent'] = $base_plugin_definition['id'] . ':' . $content_entity_bundle_root;
$links[$key]['title'] = $this->t('Edit');
}
}
if ($this->moduleHandler->moduleExists('field_ui')) {
if ($this->routeExists('entity.' . $content_entity . '.field_ui_fields')) {
$links['entity.' . $content_entity . '.field_ui_fields' . $machine_name] = [
'title' => $this->t('Manage fields'),
'route_name' => 'entity.' . $content_entity . '.field_ui_fields',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 1,
] + $base_plugin_definition;
}
if ($this->routeExists('entity.entity_form_display.' . $content_entity . '.default')) {
$links['entity.entity_form_display.' . $content_entity . '.default' . $machine_name] = [
'title' => $this->t('Manage form display'),
'route_name' => 'entity.entity_form_display.' . $content_entity . '.default',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 2,
] + $base_plugin_definition;
}
if ($this->routeExists('entity.entity_view_display.' . $content_entity . '.default')) {
$links['entity.entity_view_display.' . $content_entity . '.default.' . $machine_name] = [
'title' => $this->t('Manage display'),
'route_name' => 'entity.entity_view_display.' . $content_entity . '.default',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 3,
] + $base_plugin_definition;
}
if ($this->routeExists('entity.' . $bundle->getEntityTypeId() . '.entity_permissions_form')) {
$links['entity.entity_permissions_form.' . $content_entity . '.default.' . $machine_name] = [
'title' => $this->t('Manage permissions'),
'route_name' => 'entity.' . $bundle->getEntityTypeId() . '.entity_permissions_form',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [
$bundle->getEntityTypeId() => $machine_name,
],
'weight' => 3,
] + $base_plugin_definition;
}
}
if ($this->moduleHandler->moduleExists('devel') && $this->routeExists('entity.' . $content_entity_bundle . '.devel_load')) {
$links['entity.' . $content_entity_bundle . '.devel_load.' . $machine_name] = [
'title' => $this->t('Devel'),
'route_name' => 'entity.' . $content_entity_bundle . '.devel_load',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 4,
] + $base_plugin_definition;
}
if ($this->routeExists('entity.' . $content_entity_bundle . '.delete_form')) {
$links['entity.' . $content_entity_bundle . '.delete_form.' . $machine_name] = [
'title' => $this->t('Delete'),
'route_name' => 'entity.' . $content_entity_bundle . '.delete_form',
'parent' => $base_plugin_definition['id'] . ':' . $content_entity_bundle_root,
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 5,
] + $base_plugin_definition;
}
}
}
// Adds user links.
$links['user.admin_create'] = [
'title' => $this->t('Add user'),
'route_name' => 'user.admin_create',
'parent' => 'entity.user.collection',
] + $base_plugin_definition;
$links['user.admin_permissions'] = [
'title' => $this->t('Permissions'),
'route_name' => 'user.admin_permissions',
'parent' => 'entity.user.collection',
] + $base_plugin_definition;
$links['entity.user_role.collection'] = [
'title' => $this->t('Roles'),
'route_name' => 'entity.user_role.collection',
'parent' => 'entity.user.collection',
] + $base_plugin_definition;
$links['user.logout'] = [
'title' => $this->t('Logout'),
'route_name' => 'user.logout',
'parent' => 'admin_toolbar_tools.help',
'weight' => 10,
] + $base_plugin_definition;
$links['user.role_add'] = [
'title' => $this->t('Add role'),
'route_name' => 'user.role_add',
'parent' => $base_plugin_definition['id'] . ':entity.user_role.collection',
'weight' => -50,
] + $base_plugin_definition;
// Adds sub-links to Account settings link.
if ($this->moduleHandler->moduleExists('field_ui')) {
$links['entity.user.field_ui_fields_'] = [
'title' => $this->t('Manage fields'),
'route_name' => 'entity.user.field_ui_fields',
'parent' => 'entity.user.admin_form',
'weight' => 1,
] + $base_plugin_definition;
$links['entity.entity_form_display.user.default_'] = [
'title' => $this->t('Manage form display'),
'route_name' => 'entity.entity_form_display.user.default',
'parent' => 'entity.user.admin_form',
'weight' => 2,
] + $base_plugin_definition;
$links['entity.entity_view_display.user.default_'] = [
'title' => $this->t('Manage display'),
'route_name' => 'entity.entity_view_display.user.default',
'parent' => 'entity.user.admin_form',
'weight' => 3,
] + $base_plugin_definition;
}
foreach ($this->entityTypeManager->getStorage('user_role')->loadMultiple() as $role) {
$links['entity.user_role.edit_form.' . $role->id()] = [
'route_name' => 'entity.user_role.edit_form',
'parent' => $base_plugin_definition['id'] . ':entity.user_role.collection',
'weight' => $role->getWeight(),
'route_parameters' => ['user_role' => $role->id()],
'class' => 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity',
'metadata' => [
'entity_type' => $role->getEntityTypeId(),
'entity_id' => $role->id(),
],
] + $base_plugin_definition;
$links['entity.user_role.edit_permissions_form.' . $role->id()] = [
'title' => $this->t('Edit permissions'),
'route_name' => 'entity.user_role.edit_permissions_form',
'parent' => $base_plugin_definition['id'] . ':entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
] + $base_plugin_definition;
if ($role->id() != 'anonymous' && $role->id() != 'authenticated') {
$links['entity.user_role.delete_form.' . $role->id()] = [
'title' => $this->t('Delete'),
'route_name' => 'entity.user_role.delete_form',
'parent' => $base_plugin_definition['id'] . ':entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
] + $base_plugin_definition;
}
if ($this->moduleHandler->moduleExists('devel')) {
$links['entity.user_role.devel_load.' . $role->id()] = [
'title' => $this->t('Devel'),
'route_name' => 'entity.user_role.devel_load',
'parent' => $base_plugin_definition['id'] . ':entity.user_role.edit_form.' . $role->id(),
'route_parameters' => ['user_role' => $role->id()],
] + $base_plugin_definition;
}
}
if ($this->moduleHandler->moduleExists('node')) {
$links['node.type_add'] = [
'title' => $this->t('Add content type'),
'route_name' => 'node.type_add',
'parent' => 'entity.node_type.collection',
'weight' => -2,
] + $base_plugin_definition;
$links['node.add'] = [
'title' => $this->t('Add content'),
'route_name' => 'node.add_page',
'parent' => 'system.admin_content',
] + $base_plugin_definition;
// Adds node links for each content type.
foreach ($this->entityTypeManager->getStorage('node_type')->loadMultiple() as $type) {
$links['node.add.' . $type->id()] = [
'route_name' => 'node.add',
'parent' => $base_plugin_definition['id'] . ':node.add',
'route_parameters' => ['node_type' => $type->id()],
'class' => 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity',
'metadata' => [
'entity_type' => $type->getEntityTypeId(),
'entity_id' => $type->id(),
],
] + $base_plugin_definition;
}
}
if ($this->moduleHandler->moduleExists('field_ui')) {
$links['field_ui.entity_form_mode_add'] = [
'title' => $this->t('Add form mode'),
'route_name' => 'field_ui.entity_form_mode_add',
'parent' => 'entity.entity_form_mode.collection',
] + $base_plugin_definition;
$links['field_ui.entity_view_mode_add'] = [
'title' => $this->t('Add view mode'),
'route_name' => 'field_ui.entity_view_mode_add',
'parent' => 'entity.entity_view_mode.collection',
] + $base_plugin_definition;
}
if ($this->moduleHandler->moduleExists('taxonomy')) {
$links['entity.taxonomy_vocabulary.add_form'] = [
'title' => $this->t('Add vocabulary'),
'route_name' => 'entity.taxonomy_vocabulary.add_form',
'parent' => 'entity.taxonomy_vocabulary.collection',
'weight' => -5,
] + $base_plugin_definition;
}
if ($this->moduleHandler->moduleExists('menu_ui')) {
$links['entity.menu.add_form'] = [
'title' => $this->t('Add menu'),
'route_name' => 'entity.menu.add_form',
'parent' => 'entity.menu.collection',
'weight' => -2,
] + $base_plugin_definition;
// Adds links to /admin/structure/menu.
$menus = $this->entityTypeManager->getStorage('menu')->loadMultiple();
uasort($menus, [Menu::class, 'sort']);
$menus = array_slice($menus, 0, $max_bundle_number);
if (count($menus) == $max_bundle_number) {
$links['entity.menu.collection'] = [
'title' => $this->t('All menus'),
'route_name' => 'entity.menu.collection',
'parent' => 'entity.menu.collection',
'weight' => -1,
] + $base_plugin_definition;
}
$weight = 0;
foreach ($menus as $menu_id => $menu) {
$links['entity.menu.edit_form.' . $menu_id] = [
'route_name' => 'entity.menu.edit_form',
'parent' => 'entity.menu.collection',
'route_parameters' => ['menu' => $menu_id],
'weight' => $weight,
'class' => 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity',
'metadata' => [
'entity_type' => $menu->getEntityTypeId(),
'entity_id' => $menu->id(),
],
] + $base_plugin_definition;
$links['entity.menu.add_link_form.' . $menu_id] = [
'title' => $this->t('Add link'),
'route_name' => 'entity.menu.add_link_form',
'parent' => $base_plugin_definition['id'] . ':entity.menu.edit_form.' . $menu_id,
'route_parameters' => ['menu' => $menu_id],
] + $base_plugin_definition;
// Un-deletable menus.
$un_deletable_menus = [
'admin',
'devel',
'footer',
'main',
'tools',
'account',
];
if (!in_array($menu_id, $un_deletable_menus)) {
$links['entity.menu.delete_form.' . $menu_id] = [
'title' => $this->t('Delete'),
'route_name' => 'entity.menu.delete_form',
'parent' => $base_plugin_definition['id'] . ':entity.menu.edit_form.' . $menu_id,
'route_parameters' => ['menu' => $menu_id],
] + $base_plugin_definition;
}
if ($this->moduleHandler->moduleExists('devel') && $this->routeExists('entity.menu.devel_load')) {
$links['entity.menu.devel_load.' . $menu_id] = [
'title' => $this->t('Devel'),
'route_name' => 'entity.menu.devel_load',
'parent' => $base_plugin_definition['id'] . ':entity.menu.edit_form.' . $menu_id,
'route_parameters' => ['menu' => $menu_id],
] + $base_plugin_definition;
}
$weight++;
}
}
// If module block_content is enabled.
if ($this->moduleHandler->moduleExists('block_content')) {
$links['block_content.add_page'] = [
'title' => $this->t('Add custom block'),
'route_name' => 'block_content.add_page',
'parent' => 'block.admin_display',
] + $base_plugin_definition;
$links['entity.block_content.collection'] = [
'title' => $this->t('Custom block library'),
'route_name' => 'entity.block_content.collection',
'parent' => 'block.admin_display',
] + $base_plugin_definition;
$links['entity.block_content_type.collection'] = [
'title' => $this->t('Block types'),
'route_name' => 'entity.block_content_type.collection',
'parent' => 'block.admin_display',
] + $base_plugin_definition;
}
// If module Contact is enabled.
if ($this->moduleHandler->moduleExists('contact')) {
$links['contact.form_add'] = [
'title' => $this->t('Add contact form'),
'route_name' => 'contact.form_add',
'parent' => 'entity.contact_form.collection',
'weight' => -5,
] + $base_plugin_definition;
}
// If module Update Manager is enabled.
if ($this->moduleHandler->moduleExists('update')) {
$links['update.module_install'] = [
'title' => $this->t('Install new module'),
'route_name' => 'update.module_install',
'parent' => 'system.modules_list',
] + $base_plugin_definition;
$links['update.module_update'] = [
'title' => $this->t('Update'),
'route_name' => 'update.module_update',
'parent' => 'system.modules_list',
] + $base_plugin_definition;
$links['update.theme_install'] = [
'title' => $this->t('Install new theme'),
'route_name' => 'update.theme_install',
'parent' => 'system.themes_page',
] + $base_plugin_definition;
$links['update.theme_update'] = [
'title' => $this->t('Update'),
'route_name' => 'update.theme_update',
'parent' => 'system.themes_page',
] + $base_plugin_definition;
}
// If module Devel is enabled.
if ($this->moduleHandler->moduleExists('devel')) {
$links['devel'] = [
'title' => $this->t('Development'),
'route_name' => 'system.admin_config_development',
'parent' => 'admin_toolbar_tools.help',
'weight' => '-8',
] + $base_plugin_definition;
$links['devel.admin_settings'] = [
'title' => $this->t('Devel settings'),
'route_name' => 'devel.admin_settings',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.configs_list'] = [
'title' => $this->t('Config editor'),
'route_name' => 'devel.configs_list',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.reinstall'] = [
'title' => $this->t('Reinstall modules'),
'route_name' => 'devel.reinstall',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.menu_rebuild'] = [
'title' => $this->t('Rebuild menu'),
'route_name' => 'devel.menu_rebuild',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.state_system_page'] = [
'title' => $this->t('State editor'),
'route_name' => 'devel.state_system_page',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.theme_registry'] = [
'title' => $this->t('Theme registry'),
'route_name' => 'devel.theme_registry',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.entity_info_page'] = [
'title' => $this->t('Entity info'),
'route_name' => 'devel.entity_info_page',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.session'] = [
'title' => $this->t('Session viewer'),
'route_name' => 'devel.session',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
$links['devel.element_info'] = [
'title' => $this->t('Element Info'),
'route_name' => 'devel.elements_page',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
// Menu link for the Toolbar module.
$links['devel.toolbar.settings'] = [
'title' => $this->t('Devel Toolbar Settings'),
'route_name' => 'devel.toolbar.settings_form',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
if ($this->moduleHandler->moduleExists('webprofiler')) {
$links['devel.webprofiler'] = [
'title' => $this->t('Webprofiler settings'),
'route_name' => 'webprofiler.settings',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
}
// If module Devel PHP is enabled.
if ($this->moduleHandler->moduleExists('devel_php') && $this->routeExists('devel_php.execute_php')) {
$links['devel.devel_php.execute_php'] = [
'title' => $this->t('Execute PHP Code'),
'route_name' => 'devel_php.execute_php',
'parent' => $base_plugin_definition['id'] . ':devel',
] + $base_plugin_definition;
}
}
// If module Views Ui enabled.
if ($this->moduleHandler->moduleExists('views_ui')) {
$links['views_ui.add'] = [
'title' => $this->t('Add view'),
'route_name' => 'views_ui.add',
'parent' => 'entity.view.collection',
'weight' => -5,
] + $base_plugin_definition;
$views = $this->entityTypeManager->getStorage('view')->loadMultiple();
foreach ($views as $view) {
$links['views_ui.' . $view->id()] = [
'title' => $view->label(),
'route_name' => 'entity.view.edit_form',
'route_parameters' => ['view' => $view->id()],
'parent' => 'entity.view.collection',
] + $base_plugin_definition;
}
$links['views_ui.field_list'] = [
'title' => $this->t('Used in views'),
'route_name' => 'views_ui.reports_fields',
'parent' => 'entity.field_storage_config.collection',
] + $base_plugin_definition;
}
// Adds theme management links.
$links['system.theme_settings'] = [
'title' => $this->t('Settings'),
'route_name' => 'system.theme_settings',
'parent' => 'system.themes_page',
] + $base_plugin_definition;
$installed_themes = $this->installedThemes();
foreach ($installed_themes as $key_theme => $label_theme) {
$links['system.theme_settings_theme.' . $key_theme] = [
'title' => $label_theme,
'route_name' => 'system.theme_settings_theme',
'parent' => $base_plugin_definition['id'] . ':system.theme_settings',
'route_parameters' => ['theme' => $key_theme],
] + $base_plugin_definition;
}
// If module Language enabled.
if ($this->moduleHandler->moduleExists('language')) {
$links['language.negotiation'] = [
'title' => $this->t('Detection and selection'),
'route_name' => 'language.negotiation',
'parent' => 'entity.configurable_language.collection',
] + $base_plugin_definition;
$links['language.add'] = [
'title' => $this->t('Add language'),
'route_name' => 'language.add',
'parent' => 'entity.configurable_language.collection',
] + $base_plugin_definition;
}
// If module Media enabled.
if ($this->moduleHandler->moduleExists('media')) {
$links['media.type_add'] = [
'title' => $this->t('Add media type'),
'route_name' => 'entity.media_type.add_form',
'parent' => 'entity.media_type.collection',
'weight' => -2,
] + $base_plugin_definition;
// Displays media link in toolbar.
$links['media_page'] = [
'title' => $this->t('Media'),
'route_name' => 'entity.media.collection',
'parent' => 'system.admin_content',
] + $base_plugin_definition;
if ($this->moduleHandler->moduleExists('media_library') && $this->routeExists('view.media_library.page')) {
$links['media_library'] = [
'title' => $this->t('Media library'),
'route_name' => 'view.media_library.page',
'parent' => $base_plugin_definition['id'] . ':media_page',
] + $base_plugin_definition;
}
$links['add_media'] = [
'title' => $this->t('Add media'),
'route_name' => 'entity.media.add_page',
'parent' => $base_plugin_definition['id'] . ':media_page',
] + $base_plugin_definition;
// Adds links for each media type.
foreach ($this->entityTypeManager->getStorage('media_type')->loadMultiple() as $type) {
$links['media.add.' . $type->id()] = [
'route_name' => 'entity.media.add_form',
'parent' => $base_plugin_definition['id'] . ':add_media',
'route_parameters' => ['media_type' => $type->id()],
'class' => 'Drupal\admin_toolbar_tools\Plugin\Menu\MenuLinkEntity',
'metadata' => [
'entity_type' => $type->getEntityTypeId(),
'entity_id' => $type->id(),
],
] + $base_plugin_definition;
}
}
// If module Config enabled.
if ($this->moduleHandler->moduleExists('config')) {
$links['config.import'] = [
'title' => $this->t('Import'),
'route_name' => 'config.import_full',
'parent' => 'config.sync',
'weight' => 1,
] + $base_plugin_definition;
$links['config.export'] = [
'title' => $this->t('Export'),
'route_name' => 'config.export_full',
'parent' => 'config.sync',
'weight' => 2,
] + $base_plugin_definition;
}
// Adds a menu link to clear Views cache.
if ($this->moduleHandler->moduleExists('views')) {
$links['flush_views'] = [
'title' => $this->t('Flush views cache'),
'route_name' => 'admin_toolbar_tools.flush_views',
'parent' => 'admin_toolbar_tools.flush',
] + $base_plugin_definition;
// Adding a menu link to Files.
if ($this->moduleHandler->moduleExists('file') && $this->routeExists('view.files.page_1')) {
$links['view.files'] = [
'title' => $this->t('Files'),
'route_name' => 'view.files.page_1',
'parent' => 'system.admin_content',
] + $base_plugin_definition;
}
}
return $links;
}
/**
* Determine if a route exists by name.
*
* @param string $route_name
* The name of the route to check.
*
* @return bool
* Whether a route with that route name exists.
*/
public function routeExists($route_name) {
return (count($this->routeProvider->getRoutesByNames([$route_name])) === 1);
}
/**
* Lists all installed themes.
*
* @return array
* The list of installed themes.
*/
public function installedThemes() {
$themeHandler = $this->themeHandler;
$all_themes = $themeHandler->listInfo();
$themes_installed = [];
foreach ($all_themes as $key_theme => $theme) {
if ($themeHandler->hasUi($key_theme)) {
$themes_installed[$key_theme] = $themeHandler->getName($key_theme);
}
}
return $themes_installed;
}
}
<?php
namespace Drupal\admin_toolbar_tools\Plugin\Menu;
use Drupal\Core\Entity\EntityDescriptionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\node\NodeTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a menu link plugins for configuration entities.
*/
class MenuLinkEntity extends MenuLinkDefault {
/**
* The entity represented in the menu link.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* Constructs a new MenuLinkEntity.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
* The static override storage.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $static_override);
$this->entity = $entity_type_manager->getStorage($this->pluginDefinition['metadata']['entity_type'])->load($this->pluginDefinition['metadata']['entity_id']);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu_link.static.overrides'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
if ($this->entity) {
return (string) $this->entity->label();
}
return $this->pluginDefinition['title'] ?: $this->t('Missing');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
// @todo Remove node_type special handling.
if ($this->entity instanceof EntityDescriptionInterface || $this->entity instanceof NodeTypeInterface) {
return $this->entity->getDescription();
}
return parent::getDescription();
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
if ($this->entity) {
return $this->entity->getCacheContexts();
}
return parent::getCacheContexts();
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
if ($this->entity) {
return $this->entity->getCacheTags();
}
return parent::getCacheTags();
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
if ($this->entity) {
return $this->entity->getCacheMaxAge();
}
return parent::getCacheMaxAge();
}
}
......@@ -22,6 +22,11 @@ class AdminToolbarToolsAlterTest extends BrowserTestBase {
'admin_toolbar_tools',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A test user with permission to access the administrative toolbar.
*
......@@ -32,7 +37,7 @@ class AdminToolbarToolsAlterTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
protected function setUp(): void {
parent::setUp();
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
......
......@@ -2,9 +2,12 @@
"name": "drupal/admin_toolbar",
"description": "Provides a drop-down menu interface to the core Drupal Toolbar.",
"type": "drupal-module",
"keywords": ["Drupal", "Toolbar"],
"keywords": [
"Drupal",
"Toolbar"
],
"homepage": "http://drupal.org/project/admin_toolbar",
"license": "GPL-2.0+",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Wilfrid Roze (eme)",
......@@ -30,6 +33,9 @@
],
"support": {
"issues": "https://www.drupal.org/project/issues/admin_toolbar",
"source": "http://cgit.drupalcode.org/admin_toolbar"
"source": "https://git.drupalcode.org/project/admin_toolbar"
},
"require": {
"drupal/core": "^8.8.0 || ^9.0"
}
}
admin_toolbar.settings:
type: config_object
label: 'Admin Toolbar settings'
mapping:
menu_depth:
type: integer
label: 'Depth of displayed menu'
......@@ -8,7 +8,7 @@
.toolbar-tray-horizontal .toolbar-menu:not(:first-child) li.menu-item--expanded > a:focus {
background-position: center right;
background-image: url('../misc/icons/0074bd/chevron-right.svg');
background-image: url(../misc/icons/0074bd/chevron-right.svg);
background-repeat: no-repeat;
}
......@@ -24,18 +24,18 @@
.toolbar-tray-horizontal ul li li.menu-item {
border-top: none transparent;
border-right: 1px solid #dddddd;
border-bottom: 1px solid #dddddd;
border-left: 1px solid #dddddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
border-left: 1px solid #ddd;
}
.toolbar .toolbar-tray-horizontal .menu-item:last-child {
border-left: 1px solid #dddddd;
border-right: 1px solid #dddddd;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
}
.toolbar .toolbar-tray-horizontal ul ul li.menu-item:first-child {
border-top: 1px solid #dddddd;
border-top: 1px solid #ddd;
}
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul,
......@@ -75,7 +75,7 @@
.toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
background-position: center right;
background-image: url('../misc/icons/0074bd/chevron-right.svg');
background-image: url(../misc/icons/0074bd/chevron-right.svg);
background-repeat: no-repeat;
}
......@@ -121,7 +121,7 @@
[dir="rtl"] .toolbar-tray-horizontal .toolbar-menu:not(:first-child) li.menu-item--expanded > a:focus {
background-position: center right;
background-image: url('../misc/icons/0074bd/chevron-right.svg');
background-image: url(../misc/icons/0074bd/chevron-right.svg);
background-repeat: no-repeat;
}
......@@ -137,18 +137,18 @@
[dir="rtl"] .toolbar-tray-horizontal ul li li.menu-item {
border-top: none transparent;
border-right: 1px solid #dddddd;
border-bottom: 1px solid #dddddd;
border-left: 1px solid #dddddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
border-left: 1px solid #ddd;
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal .menu-item:last-child {
border-left: 1px solid #dddddd;
border-right: 1px solid #dddddd;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal ul ul li.menu-item:first-child {
border-top: 1px solid #dddddd;
border-top: 1px solid #ddd;
}
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul,
......@@ -188,7 +188,7 @@
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
background-position: center left;
background-image: url('../misc/icons/0074bd/chevron-left.svg');
background-image: url(../misc/icons/0074bd/chevron-left.svg);
background-repeat: no-repeat;
}
......
......@@ -3,17 +3,17 @@
}
#admin-toolbar-search-tab .toolbar-item:before {
background-image: url('../misc/icons/bebebe/loupe.svg');
background-image: url(../misc/icons/bebebe/loupe.svg);
}
#admin-toolbar-search-tab .toolbar-item:active:before,
#admin-toolbar-search-tab .toolbar-item.is-active:before {
background-image: url('../misc/icons/ffffff/loupe.svg');
background-image: url(../misc/icons/ffffff/loupe.svg);
}
#toolbar-item-administration-search-tray label {
display: inline-block;
color: #000000;
color: #000;
margin-right: .5em;
font-weight: bold;
}
......@@ -29,5 +29,5 @@
}
.ui-autocomplete .ui-menu-item span.admin-toolbar-search-url {
color: rgba(0, 0, 0, 0.50);
display: none;
}
(function ($) {
$(document).ready(function () {
$('.toolbar-tray.toolbar-tray-horizontal .menu-item.menu-item--expanded').hover(function () {
// At the current depth, we should delete all "hover-intent" classes.
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
$(this).parent().find('li').removeClass('hover-intent');
$(this).addClass('hover-intent');
},
function () {
$(this).removeClass('hover-intent');
});
});
})(jQuery);
(function ($) {
$(document).ready(function () {
$('.toolbar-tray-horizontal li.menu-item--expanded, .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item').hoverIntent({
over: function () {
// At the current depth, we should delete all "hover-intent" classes.
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
$(this).parent().find('li').removeClass('hover-intent');
$(this).addClass('hover-intent');
},
out: function () {
$(this).removeClass('hover-intent');
},
timeout: 250
});
});
})(jQuery);
......@@ -4,19 +4,6 @@
$('a.toolbar-icon', context).removeAttr('title');
$('.toolbar-tray li.menu-item--expanded, .toolbar-tray ul li.menu-item--expanded .menu-item', context).hoverIntent({
over: function () {
// At the current depth, we should delete all "hover-intent" classes.
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
$(this).parent().find('li').removeClass('hover-intent');
$(this).addClass('hover-intent');
},
out: function () {
$(this).removeClass('hover-intent');
},
timeout: 250
});
// Make the toolbar menu navigable with keyboard.
$('ul.toolbar-menu li.menu-item--expanded a', context).on('focusin', function () {
$('li.menu-item--expanded', context).removeClass('hover-intent');
......@@ -48,6 +35,13 @@
}
});
// Always hide the dropdown menu on mobile.
if (window.matchMedia("(max-width: 767px)").matches && $('body').hasClass('toolbar-tray-open')) {
$('body').removeClass('toolbar-tray-open');
$('#toolbar-item-administration').removeClass('is-active');
$('#toolbar-item-administration-tray').removeClass('is-active');
};
}
};
})(jQuery, Drupal);
(function ($, Drupal) {
Drupal.behaviors.adminToolbarSearch = {
attach: function (context) {
if (context != document) {
return;
}
var getUrl = window.location;
var baseUrl = getUrl.protocol + "//" + getUrl.host + "/";
var $self = this;
this.links = [];
$('a[data-drupal-link-system-path]').each(function() {
if (this.href != baseUrl) {
var label = $self.getItemLabel(this);
$self.links.push({
'value': $(this).attr('href'),
'label': label + ' ' + $(this).attr('href'),
'labelRaw': label
});
}
});
$( "#admin-toolbar-search-input").autocomplete({
minLength: 2,
source: function(request, response) {
var data = $self.handleAutocomplete(request.term);
response(data);
},
open: function(){
var zIndex = $('#toolbar-item-administration-search-tray').css("z-index")+1;
$(this).autocomplete('widget').css('z-index', zIndex);
return false;
},
select: function( event, ui ) {
if (ui.item.value) {
location.href = ui.item.value;
return false;
}
}
}).data("ui-autocomplete")._renderItem = (function(ul, item) {
return $("<li>")
.append('<div>' + item.labelRaw + ' <span class="admin-toolbar-search-url">' + item.value + '</span></div>')
.appendTo(ul);
});
// Focus on search field when tab is clicked, or enter is pressed.
$(context).find('#toolbar-item-administration-search').once('admin_toolbar_search').each(function () {
if ($('#toolbar-item-administration-search-tray:visible').length) {
$('#admin-toolbar-search-input').focus();
}
$(this).on('click', function() {
$self.focusOnSearchElement();
});
});
},
focusOnSearchElement: function() {
var waitforVisible = function() {
if ($('#toolbar-item-administration-search-tray:visible').length) {
$('#admin-toolbar-search-input').focus();
} else {
setTimeout(function() {
waitforVisible();
}, 1);
}
};
waitforVisible();
},
getItemLabel: function(item) {
var breadcrumbs = [];
$(item).parents().each(function() {
if ($(this).hasClass('menu-item')) {
var $link = $(this).find('a:first');
if ($link.length && !$link.hasClass('admin-toolbar-search-ignore')) {
breadcrumbs.unshift($link.text());
}
}
});
label = breadcrumbs.join(' > ');
return label;
},
handleAutocomplete: function(term) {
var $self = this;
var keywords = term.split(" "); // split search terms into list.
var suggestions = [];
$self.links.forEach(function(element) {
var label = element.label.toLowerCase();
// Add exact matches.
if (label.indexOf(term.toLowerCase()) >= 0) {
suggestions.push(element);
}
else {
// Add suggestions where it matches all search terms.
var matchCount = 0;
keywords.forEach(function(keyword) {
if (label.indexOf(keyword.toLowerCase()) >= 0) {
matchCount++;
}
});
if (matchCount == keywords.length) {
suggestions.push(element);
}
}
});
return suggestions;
}
};
})(jQuery, Drupal);
/*!
;/*!
* hoverIntent v1.8.1 // 2014.08.11 // jQuery v1.9.1+
* http://briancherne.github.io/jquery-hoverIntent/
*
......@@ -28,16 +28,14 @@
* @param handlerOut function OR selector for delegation OR undefined
* @param selector selector OR undefined
* @author Brian Cherne <brian(at)cherne(dot)net>
*/
;(function(factory) {
*/(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (jQuery && !jQuery.fn.hoverIntent) {
factory(jQuery);
}
})(function($) {
})(function ($) {
'use strict';
// default configuration values
......@@ -54,15 +52,15 @@
var cX, cY;
// saves the current pointer position coordinates based on the given mousemove event
var track = function(ev) {
var track = function (ev) {
cX = ev.pageX;
cY = ev.pageY;
};
// compares current and previous mouse positions
var compare = function(ev,$el,s,cfg) {
var compare = function (ev,$el,s,cfg) {
// compare mouse positions to see if pointer has slowed enough to trigger `over` function
if ( Math.sqrt( (s.pX-cX)*(s.pX-cX) + (s.pY-cY)*(s.pY-cY) ) < cfg.sensitivity ) {
if ( Math.sqrt( (s.pX - cX) * (s.pX - cX) + (s.pY - cY) * (s.pY - cY) ) < cfg.sensitivity ) {
$el.off(s.event,track);
delete s.timeoutId;
// set hoverIntent state as active for this element (permits `out` handler to trigger)
......@@ -76,17 +74,17 @@
// set previous coordinates for next comparison
s.pX = cX; s.pY = cY;
// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
s.timeoutId = setTimeout( function(){compare(ev, $el, s, cfg);} , cfg.interval );
s.timeoutId = setTimeout( function () {compare(ev, $el, s, cfg);} , cfg.interval );
}
};
// triggers given `out` function at configured `timeout` after a mouseleave and clears state
var delay = function(ev,$el,s,out) {
var delay = function (ev,$el,s,out) {
delete $el.data('hoverIntent')[s.id];
return out.apply($el[0],[ev]);
};
$.fn.hoverIntent = function(handlerIn,handlerOut,selector) {
$.fn.hoverIntent = function (handlerIn,handlerOut,selector) {
// instance ID, used as a key to store and retrieve state information on an element
var instanceId = INSTANCE_COUNT++;
......@@ -104,7 +102,7 @@
}
// A private function for handling mouse 'hovering'
var handleHover = function(e) {
var handleHover = function (e) {
// cloned event to pass to handlers (copy required for event object to be passed in IE)
var ev = $.extend({},e);
......@@ -130,7 +128,7 @@
if (state.timeoutId) { state.timeoutId = clearTimeout(state.timeoutId); }
// namespaced event used to register and unregister mousemove tracking
var mousemove = state.event = 'mousemove.hoverIntent.hoverIntent'+instanceId;
var mousemove = state.event = 'mousemove.hoverIntent.hoverIntent' + instanceId;
// handle the event, based on its type
if (e.type === 'mouseenter') {
......@@ -141,14 +139,14 @@
// update "current" X and Y position based on mousemove
$el.off(mousemove,track).on(mousemove,track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
state.timeoutId = setTimeout( function(){compare(ev,$el,state,cfg);} , cfg.interval );
state.timeoutId = setTimeout( function () {compare(ev,$el,state,cfg);} , cfg.interval );
} else { // "mouseleave"
// do nothing if not already active
if (!state.isActive) { return; }
// unbind expensive mousemove event
$el.off(mousemove,track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
state.timeoutId = setTimeout( function(){delay(ev,$el,state,cfg.out);} , cfg.timeout );
state.timeoutId = setTimeout( function () {delay(ev,$el,state,cfg.out);} , cfg.timeout );
}
};
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
<?php
namespace Drupal\admin_toolbar\Form;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class AdminToolbarSettingsForm. The config form for the admin_toolbar module.
*
* @package Drupal\admin_toolbar\Form
*/
class AdminToolbarSettingsForm extends ConfigFormBase {
/**
* The cache menu instance.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheMenu;
/**
* The menu link manager instance.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $menuLinkManager;
/**
* AdminToolbarSettingsForm constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory for the form.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
* A menu link manager instance.
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheMenu
* A cache menu instance.
*/
public function __construct(ConfigFactoryInterface $config_factory, MenuLinkManagerInterface $menuLinkManager, CacheBackendInterface $cacheMenu) {
parent::__construct($config_factory);
$this->cacheMenu = $cacheMenu;
$this->menuLinkManager = $menuLinkManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('plugin.manager.menu.link'),
$container->get('cache.menu')
);
}
/**
* {@inheritDoc}
*/
protected function getEditableConfigNames() {
return [
'admin_toolbar.settings',
];
}
/**
* {@inheritDoc}
*/
public function getFormId() {
return 'admin_toolbar_settings';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('admin_toolbar.settings');
$depth_values = range(1, 9);
$form['menu_depth'] = [
'#type' => 'select',
'#title' => $this->t('Menu depth'),
'#description' => $this->t('Maximal depth of displayed menu.'),
'#default_value' => $config->get('menu_depth'),
'#options' => array_combine($depth_values, $depth_values),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('admin_toolbar.settings')
->set('menu_depth', $form_state->getValue('menu_depth'))
->save();
parent::submitForm($form, $form_state);
$this->cacheMenu->invalidateAll();
$this->menuLinkManager->rebuild();
}
}
<?php
namespace Drupal\admin_toolbar\Render\Element;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Security\TrustedCallbackInterface;
/**
* Render element element for AdminToolbar.
*
* @package Drupal\admin_toolbar\Render\Element
*/
class AdminToolbar implements TrustedCallbackInterface {
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRenderTray'];
}
/**
* Renders the toolbar's administration tray.
*
* This is a clone of core's toolbar_prerender_toolbar_administration_tray()
* function, which uses setMaxDepth(4) instead of setTopLevelOnly().
*
* @param array $build
* A renderable array.
*
* @return array
* The updated renderable array.
*
* @see toolbar_prerender_toolbar_administration_tray()
*/
public static function preRenderTray(array $build) {
$menu_tree = \Drupal::service('toolbar.menu_tree');
$parameters = new MenuTreeParameters();
$max_depth = \Drupal::config('admin_toolbar.settings')->get('menu_depth');
$parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth($max_depth)->onlyEnabledLinks();
$tree = $menu_tree->load('admin', $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
['callable' => 'toolbar_tools_menu_navigation_links'],
];
$tree = $menu_tree->transform($tree, $manipulators);
$build['administration_menu'] = $menu_tree->build($tree);
return $build;
}
}
<?php
namespace Drupal\Tests\admin_toolbar\Functional;
use Drupal\Tests\toolbar\Functional\ToolbarAdminMenuTest;
/**
* Tests the caching of the admin menu subtree items.
*
* @group admin_toolbar
*/
class AdminToolbarAdminMenuTest extends ToolbarAdminMenuTest {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'admin_toolbar',
];
}
......@@ -22,6 +22,11 @@ class AdminToolbarAlterTest extends BrowserTestBase {
'admin_toolbar',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A test user with permission to access the administrative toolbar.
*
......@@ -32,7 +37,7 @@ class AdminToolbarAlterTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
protected function setUp(): void {
parent::setUp();
// Create and log in an administrative user.
......
<?php
namespace Drupal\Tests\admin_toolbar\Functional;
use Drupal\media\Entity\MediaType;
use Drupal\system\Entity\Menu;
use Drupal\Tests\BrowserTestBase;
/**
* Tests Admin Toolbar tools functionality.
*
* @group admin_toolbar
*/
class AdminToolbarToolsSortTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'toolbar',
'breakpoint',
'admin_toolbar',
'admin_toolbar_tools',
'menu_ui',
'media',
'field_ui',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A test user with permission to access the administrative toolbar.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Tests that menu updates on entity add/update/delete.
*/
public function testMenuUpdate() {
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'access administration pages',
'administer site configuration',
'administer menu',
'access media overview',
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
]);
$this->drupalLogin($this->adminUser);
$menu = Menu::create([
'id' => 'armadillo',
'label' => 'Armadillo',
]);
$menu->save();
$this->container->get('plugin.manager.menu.link')->rebuild();
$this->drupalGet('/admin');
// Assert that special menu items are present in the HTML.
$this->assertSession()->responseContains('class="toolbar-icon toolbar-icon-admin-toolbar-tools-flush"');
// Assert that adding a media type adds it to the admin toolbar.
$chinchilla_media_type = MediaType::create([
'id' => 'chinchilla',
'label' => 'Chinchilla',
'source' => 'image',
]);
$chinchilla_media_type->save();
$this->drupalGet('/admin');
$this->assertMenuHasHref('/admin/structure/media/manage/chinchilla');
// Assert that adding a menu adds it to the admin toolbar.
$menu = Menu::create([
'id' => 'chupacabra',
'label' => 'Chupacabra',
]);
$menu->save();
$this->drupalGet('/admin');
$this->assertMenuHasHref('/admin/structure/menu/manage/chupacabra');
// Assert that deleting a menu removes it from the admin toolbar.
$this->assertMenuHasHref('/admin/structure/menu/manage/armadillo');
$menu = Menu::load('armadillo');
$menu->delete();
$this->drupalGet('/admin');
$this->assertMenuDoesNotHaveHref('/admin/structure/menu/manage/armadillo');
// Assert that deleting a content entity bundle removes it from admin menu.
$this->assertMenuHasHref('/admin/structure/media/manage/chinchilla');
$chinchilla_media_type = MediaType::load('chinchilla');
$chinchilla_media_type->delete();
$this->drupalGet('/admin');
$this->assertMenuDoesNotHaveHref('/admin/structure/media/manage/chinchilla');
}
/**
* Tests sorting of menus by label rather than machine name.
*/
public function testMenuSorting() {
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'access administration pages',
'administer site configuration',
'administer menu',
]);
$menus = [
'aaa' => 'qqq',
'bbb' => 'ppp',
'ccc' => 'ooo',
'ddd' => 'nnn',
'eee' => 'mmm',
'fff' => 'lll',
'ggg' => 'kkk',
'hhh' => 'jjj',
'iii' => 'iii',
'jjj' => 'hhh',
'kkk' => 'ggg',
'lll' => 'fff',
'mmm' => 'eee',
'nnn' => 'ddd',
'ooo' => 'ccc',
'ppp' => 'bbb',
'qqq' => 'aaa',
];
foreach ($menus as $machine_name => $label) {
$menu = Menu::create([
'id' => $machine_name,
'label' => $label,
]);
$menu->save();
}
$this->drupalLogin($this->adminUser);
$this->container->get('plugin.manager.menu.link')->rebuild();
$this->drupalGet('/admin');
$results = $this->getSession()->getPage()->findAll('xpath', '//a[contains(@href, "/admin/structure/menu/manage")]');
$links = [];
foreach ($results as $result) {
$links[] = $result->getAttribute('href');
}
$expected = [
0 => '/admin/structure/menu/manage/qqq',
1 => '/admin/structure/menu/manage/qqq/add',
2 => '/admin/structure/menu/manage/qqq/delete',
3 => '/admin/structure/menu/manage/admin',
4 => '/admin/structure/menu/manage/admin/add',
5 => '/admin/structure/menu/manage/ppp',
6 => '/admin/structure/menu/manage/ppp/add',
7 => '/admin/structure/menu/manage/ppp/delete',
8 => '/admin/structure/menu/manage/ooo',
9 => '/admin/structure/menu/manage/ooo/add',
10 => '/admin/structure/menu/manage/ooo/delete',
11 => '/admin/structure/menu/manage/nnn',
12 => '/admin/structure/menu/manage/nnn/add',
13 => '/admin/structure/menu/manage/nnn/delete',
14 => '/admin/structure/menu/manage/mmm',
15 => '/admin/structure/menu/manage/mmm/add',
16 => '/admin/structure/menu/manage/mmm/delete',
17 => '/admin/structure/menu/manage/lll',
18 => '/admin/structure/menu/manage/lll/add',
19 => '/admin/structure/menu/manage/lll/delete',
20 => '/admin/structure/menu/manage/footer',
21 => '/admin/structure/menu/manage/footer/add',
22 => '/admin/structure/menu/manage/kkk',
23 => '/admin/structure/menu/manage/kkk/add',
24 => '/admin/structure/menu/manage/kkk/delete',
25 => '/admin/structure/menu/manage/jjj',
26 => '/admin/structure/menu/manage/jjj/add',
27 => '/admin/structure/menu/manage/jjj/delete',
28 => '/admin/structure/menu/manage/iii',
29 => '/admin/structure/menu/manage/iii/add',
30 => '/admin/structure/menu/manage/iii/delete',
31 => '/admin/structure/menu/manage/hhh',
32 => '/admin/structure/menu/manage/hhh/add',
33 => '/admin/structure/menu/manage/hhh/delete',
34 => '/admin/structure/menu/manage/ggg',
35 => '/admin/structure/menu/manage/ggg/add',
36 => '/admin/structure/menu/manage/ggg/delete',
37 => '/admin/structure/menu/manage/fff',
38 => '/admin/structure/menu/manage/fff/add',
39 => '/admin/structure/menu/manage/fff/delete',
40 => '/admin/structure/menu/manage/main',
41 => '/admin/structure/menu/manage/main/add',
42 => '/admin/structure/menu/manage/eee',
43 => '/admin/structure/menu/manage/eee/add',
44 => '/admin/structure/menu/manage/eee/delete',
45 => '/admin/structure/menu/manage/ddd',
46 => '/admin/structure/menu/manage/ddd/add',
47 => '/admin/structure/menu/manage/ddd/delete',
48 => '/admin/structure/menu/manage/ccc',
49 => '/admin/structure/menu/manage/ccc/add',
50 => '/admin/structure/menu/manage/ccc/delete',
51 => '/admin/structure/menu/manage/bbb',
52 => '/admin/structure/menu/manage/bbb/add',
53 => '/admin/structure/menu/manage/bbb/delete',
54 => '/admin/structure/menu/manage/aaa',
55 => '/admin/structure/menu/manage/aaa/add',
56 => '/admin/structure/menu/manage/aaa/delete',
];
foreach ($links as $key => $link) {
// Using assert contains because prefaces the urls with "/subdirectory".
$this->assertStringContainsString($expected[$key], $link);
}
}
/**
* Checks that there is a link with the specified url in the admin toolbar.
*
* @param string $url
* The url to assert exists in the admin menu.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
*/
protected function assertMenuHasHref($url) {
$this->assertSession()
->elementExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
}
/**
* Checks that there is no link with the specified url in the admin toolbar.
*
* @param string $url
* The url to assert exists in the admin menu.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
protected function assertMenuDoesNotHaveHref($url) {
$this->assertSession()
->elementNotExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
}
}
<?php
namespace Drupal\Tests\admin_toolbar\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Test the functionality of admin toolbar search.
*
* @group admin_toolbar
*/
class AdminToolbarSearchTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'admin_toolbar',
];
/**
* The admin user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'administer menu',
'access administration pages',
'administer site configuration',
]);
}
/**
* Tests search functionality.
*/
public function testSearchFunctionality() {
$search_tab = '#toolbar-item-administration-search';
$search_tray = '#toolbar-item-administration-search-tray';
$search_input = '#admin-toolbar-search-input';
$this->drupalLogin($this->adminUser);
$this->assertSession()->responseContains('admin.toolbar_search.css');
$this->assertSession()->responseContains('admin_toolbar_search.js');
$this->assertSession()->elementExists('css', $search_tab)->click();
$this->assertSession()->waitForElementVisible('css', $search_tray);
$this->assertSession()
->elementExists('css', $search_input)
->setValue('basic');
$autocomplete_suggestions = $this->assertSession()
->waitForElementVisible('css', 'ul.ui-autocomplete');
$suggestion = 'Configuration &gt; System &gt; Basic site settings <span class="admin-toolbar-search-url">/subdirectory/admin/config/system/site-information</span>';
$this->assertSession()
->elementContains('css', 'ul.ui-autocomplete', $suggestion);
}
}
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
2881822 Module can't find library in profile directory
Source: https://www.drupal.org/files/issues/colorbutton-module_can_t_find-2881822-15.patch
--- src/Plugin/CKEditorPlugin/ColorButton.php
+++ src/Plugin/CKEditorPlugin/ColorButton.php
@@ -21,7 +21,7 @@ class ColorButton extends CKEditorPluginBase implements CKEditorPluginConfigurab
* Get path to library folder.
*/
public function getLibraryPath() {
- $path = '/libraries/colorbutton';
+ $path = 'libraries/colorbutton';
if (\Drupal::moduleHandler()->moduleExists('libraries')) {
$path = libraries_get_path('colorbutton');
}
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
2932081 fixing bug with file upload validation
Source: https://www.drupal.org/files/issues/2018-04-13/2932081-7.patch
--- src/Plugin/EntityBrowser/Widget/View.php
+++ src/Plugin/EntityBrowser/Widget/View.php
@@ -134,9 +134,7 @@ class View extends WidgetBase implements ContainerFactoryPluginInterface {
}
}
- // When rebuilding makes no sense to keep checkboxes that were previously
- // selected.
- if (!empty($form['view']['entity_browser_select']) && $form_state->isRebuilding()) {
+ if (!empty($form['view']['entity_browser_select'])) {
foreach (Element::children($form['view']['entity_browser_select']) as $child) {
$form['view']['entity_browser_select'][$child]['#process'][] = ['\Drupal\entity_browser\Plugin\EntityBrowser\Widget\View', 'processCheckbox'];
$form['view']['entity_browser_select'][$child]['#process'][] = ['\Drupal\Core\Render\Element\Checkbox', 'processAjaxForm'];
@@ -159,7 +157,10 @@ class View extends WidgetBase implements ContainerFactoryPluginInterface {
* @see \Drupal\Core\Render\Element\Checkbox::processCheckbox()
*/
public static function processCheckbox(&$element, FormStateInterface $form_state, &$complete_form) {
- $element['#checked'] = FALSE;
+ if ($form_state->isRebuilding()) {
+ $element['#checked'] = FALSE;
+ }
+
return $element;
}
--- tests/src/FunctionalJavascript/EntityBrowserViewsWidgetTest.php
+++ tests/src/FunctionalJavascript/EntityBrowserViewsWidgetTest.php
@@ -86,6 +86,21 @@ class EntityBrowserViewsWidgetTest extends EntityBrowserJavascriptTestBase {
$check_new = $this->assertSession()->fieldExists($new_field);
// Compare value attributes of checkboxes and assert they not equal.
$this->assertNotEquals($check_old->getAttribute('value'), $check_new->getAttribute('value'));
+
+ $uuid = \Drupal::service('uuid')->generate();
+ \Drupal::service('entity_browser.selection_storage')->setWithExpire(
+ $uuid,
+ ['validators' => ['cardinality' => ['cardinality' => 1]]],
+ 21600
+ );
+ $this->drupalGet('/entity-browser/iframe/test_entity_browser_file', ['query' => ['uuid' => $uuid]]);
+ $this->getSession()->getPage()->fillField('entity_browser_select[file:1]', TRUE);
+ $this->getSession()->getPage()->fillField('entity_browser_select[file:2]', TRUE);
+ $this->getSession()->getPage()->pressButton('Select entities');
+
+ $this->assertSession()->pageTextContains('You can not select more than 1 entity.');
+ $this->assertSession()->checkboxNotChecked('entity_browser_select[file:1]');
+ $this->assertSession()->checkboxNotChecked('entity_browser_select[file:2]');
}
}
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
2903176 Views as a dependency
Source: https://www.drupal.org/files/issues/linkit_views_dependency-2903176-2.patch
--- linkit.info.yml
+++ linkit.info.yml
@@ -4,5 +4,7 @@ description: 'Provides an easy interface for internal and external linking with
package: Custom
core: 8.x
configure: entity.linkit_profile.collection
+dependencies:
+ - views
test_dependencies:
- imce
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
2881820 Module can't find library in profile directory
Source: https://www.drupal.org/files/issues/panelbutton-module_can_t_find-2881820-11.patch
--- src/Plugin/CKEditorPlugin/PanelButton.php
+++ src/Plugin/CKEditorPlugin/PanelButton.php
@@ -19,7 +19,7 @@ class PanelButton extends CKEditorPluginBase {
* {@inheritdoc}
*/
public function getFile() {
- $path = '/libraries/panelbutton/plugin.js';
+ $path = 'libraries/panelbutton/plugin.js';
if (\Drupal::moduleHandler()->moduleExists('libraries')) {
$path = libraries_get_path('panelbutton') . '/plugin.js';
}
<?php
/**
* @file
* Paragraphs Previewer widget implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;
/**
* Plugin implementation of the 'entity_reference paragraphs' widget.
*
* We hide add / remove buttons when translating to avoid accidental loss of
* data because these actions effect all languages.
*
* @FieldWidget(
* id = "paragraphs_browser",
* label = @Translation("Paragraphs Browser EXPERIMENTAL"),
* description = @Translation("An experimental paragraphs inline form widget with a previewer."),
* field_types = {
* "entity_reference_revisions"
* }
* )
*/
class ParagraphsBrowserWidget extends ParagraphsWidget {
use ParagraphsBrowserWidgetTrait;
/**
* Returns select options for a plugin setting.
*
* This is done to allow
* \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
* to access option labels. Not all plugin setting are available.
*
* @param string $setting_name
* The name of the widget setting. Supported settings:
* - "edit_mode"
* - "closed_mode"
* - "autocollapse"
* - "add_mode"
*
* @return array|null
* An array of setting option usable as a value for a "#options" key.
*
* @see \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
*/
protected function getSettingOptions($setting_name) {
$options = parent::getSettingOptions($setting_name);
switch($setting_name) {
case 'add_mode':
$options['paragraphs_browser'] = $this->t('Paragraphs Browser');
break;
}
return $options;
}
}
<?php
/**
* @file
* Paragraphs Previewer widget trait implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Trait of Plugin implementation of the 'entity_reference paragraphs' widget.
*
* @var Trait
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\ParagraphsBrowserWidget
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\InlineParagraphsBrowserWidget
*/
trait ParagraphsBrowserWidgetTrait {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['paragraphs_browser'] = '_na';
$settings['modal_width'] = '80%';
$settings['modal_height'] = 'auto';
return $settings;
}
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['add_mode'] = array(
'#type' => 'hidden',
'#value' => 'paragraphs_browser'
);
$paragraph_browsers = entity_load_multiple('paragraphs_browser_type');
$options = array('_na' => 'No Groups');
foreach($paragraph_browsers as $type) {
$options[$type->id] = $type->label;
}
$elements['paragraphs_browser'] = array(
'#type' => 'select',
'#title' => $this->t('Paragraphs Browser'),
'#description' => $this->t('Select which browser to use.'),
'#options' => $options,
'#default_value' => $this->getSetting('paragraphs_browser'),
'#required' => TRUE,
);
$elements['modal_width'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Width'),
'#description' => 'The width of the modal in px or percentage.',
'#default_value' => $this->getSetting('modal_width'),
'#required' => FALSE,
);
$elements['modal_height'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Height'),
'#description' => 'The height of the modal in px or "auto".',
'#default_value' => $this->getSetting('modal_height'),
'#required' => FALSE,
);
return $elements;
}
public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
// If the Paragraph browser hasn't been set, return un-modified form.
if ($this->getSetting('paragraphs_browser') == '_na') {
return $elements;
}
// $elements['add_more'] = array(
// '#type' => 'container',
// '#theme_wrappers' => array('paragraphs_dropbutton_wrapper'),
// );
if (empty($this->uuid)) {
$this->uuid = \Drupal::service('uuid')->generate();
}
$elements['add_more']['add_more_select']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_select']['#attributes']['class'][] = 'js-hide';
$elements['add_more']['add_more_select']['#title_display'] = 'hidden';
$elements['add_more']['add_more_button']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_button']['#attributes']['class'][] = 'js-hide';
unset($elements['add_more']['add_more_button']['#suffix']);
unset($elements['add_more']['add_more_button']['#prefix']);
$elements['#attached']['library'][] = 'paragraphs_browser/modal';
$storage = $form_state->getStorage();
if (isset($storage['content_translation'])) {
unset($elements['add_more']);
}
else {
$elements['add_more']['browse'] = array(
'#type' => 'submit',
'#value' => $this->t('Add :title', [':title' => $this->getSetting('title')]),
'#attributes' => ['class' => ['js-show']],
'#ajax' => [
'url' => Url::fromRoute(
'paragraphs_browser.paragraphs_browser_controller', [
'field_config' => implode('.', array($items->getEntity()->getEntityTypeId(), $items->getEntity()->bundle(), $this->fieldDefinition->getName())),
'paragraphs_browser_type' => $this->getSetting('paragraphs_browser'),
'uuid' => $this->uuid,
]
),
],
);
}
if ($elements['#cardinality'] != -1) {
$keyCount = count(
array_filter(
array_keys($elements),
'is_numeric'
)
);
if ($elements['#cardinality'] <= $keyCount) {
unset($elements['add_more']);
}
}
return $elements;
}
}
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
2917656 Extend from ParagraphsWidget instead of InlineParagraphsWidget
Source: https://www.drupal.org/files/issues/2018-03-22/paragraphs_browser-extend-from-ParagraphsWidget-2917656-10.patch
<?php
/**
* @file
* Paragraphs Previewer widget implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;
/**
* Plugin implementation of the 'entity_reference paragraphs' widget.
*
* We hide add / remove buttons when translating to avoid accidental loss of
* data because these actions effect all languages.
*
* @FieldWidget(
* id = "paragraphs_browser",
* label = @Translation("Paragraphs Browser EXPERIMENTAL"),
* description = @Translation("An experimental paragraphs inline form widget with a previewer."),
* field_types = {
* "entity_reference_revisions"
* }
* )
*/
class ParagraphsBrowserWidget extends ParagraphsWidget {
use ParagraphsBrowserWidgetTrait;
/**
* Returns select options for a plugin setting.
*
* This is done to allow
* \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
* to access option labels. Not all plugin setting are available.
*
* @param string $setting_name
* The name of the widget setting. Supported settings:
* - "edit_mode"
* - "closed_mode"
* - "autocollapse"
* - "add_mode"
*
* @return array|null
* An array of setting option usable as a value for a "#options" key.
*
* @see \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
*/
protected function getSettingOptions($setting_name) {
$options = parent::getSettingOptions($setting_name);
switch($setting_name) {
case 'add_mode':
$options['paragraphs_browser'] = $this->t('Paragraphs Browser');
break;
}
return $options;
}
}
<?php
/**
* @file
* Paragraphs Previewer widget trait implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Trait of Plugin implementation of the 'entity_reference paragraphs' widget.
*
* @var Trait
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\ParagraphsBrowserWidget
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\InlineParagraphsBrowserWidget
*/
trait ParagraphsBrowserWidgetTrait {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['paragraphs_browser'] = '_na';
$settings['modal_width'] = '80%';
$settings['modal_height'] = 'auto';
return $settings;
}
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['add_mode'] = array(
'#type' => 'hidden',
'#value' => 'paragraphs_browser'
);
$paragraph_browsers = entity_load_multiple('paragraphs_browser_type');
$options = array('_na' => 'No Groups');
foreach($paragraph_browsers as $type) {
$options[$type->id] = $type->label;
}
$elements['paragraphs_browser'] = array(
'#type' => 'select',
'#title' => $this->t('Paragraphs Browser'),
'#description' => $this->t('Select which browser to use.'),
'#options' => $options,
'#default_value' => $this->getSetting('paragraphs_browser'),
'#required' => TRUE,
);
$elements['modal_width'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Width'),
'#description' => 'The width of the modal in px or percentage.',
'#default_value' => $this->getSetting('modal_width'),
'#required' => FALSE,
);
$elements['modal_height'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Height'),
'#description' => 'The height of the modal in px or "auto".',
'#default_value' => $this->getSetting('modal_height'),
'#required' => FALSE,
);
return $elements;
}
public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
// If the Paragraph browser hasn't been set, return un-modified form.
if ($this->getSetting('paragraphs_browser') == '_na') {
return $elements;
}
// $elements['add_more'] = array(
// '#type' => 'container',
// '#theme_wrappers' => array('paragraphs_dropbutton_wrapper'),
// );
if (empty($this->uuid)) {
$this->uuid = \Drupal::service('uuid')->generate();
}
$elements['add_more']['add_more_select']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_select']['#attributes']['class'][] = 'js-hide';
$elements['add_more']['add_more_select']['#title_display'] = 'hidden';
$elements['add_more']['add_more_button']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_button']['#attributes']['class'][] = 'js-hide';
unset($elements['add_more']['add_more_button']['#suffix']);
unset($elements['add_more']['add_more_button']['#prefix']);
$elements['#attached']['library'][] = 'paragraphs_browser/modal';
$storage = $form_state->getStorage();
if (isset($storage['content_translation'])) {
unset($elements['add_more']);
}
else {
$elements['add_more']['browse'] = array(
'#type' => 'submit',
'#value' => $this->t('Add :title', [':title' => $this->getSetting('title')]),
'#attributes' => ['class' => ['js-show']],
'#ajax' => [
'url' => Url::fromRoute(
'paragraphs_browser.paragraphs_browser_controller', [
'field_config' => implode('.', array($items->getEntity()->getEntityTypeId(), $items->getEntity()->bundle(), $this->fieldDefinition->getName())),
'paragraphs_browser_type' => $this->getSetting('paragraphs_browser'),
'uuid' => $this->uuid,
]
),
],
);
}
if ($elements['#cardinality'] != -1) {
$keyCount = count(
array_filter(
array_keys($elements),
'is_numeric'
)
);
if ($elements['#cardinality'] <= $keyCount) {
unset($elements['add_more']);
}
}
return $elements;
}
}
<?php
/**
* @file
* Paragraphs Previewer widget implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;
/**
* Plugin implementation of the 'entity_reference paragraphs' widget.
*
* We hide add / remove buttons when translating to avoid accidental loss of
* data because these actions effect all languages.
*
* @FieldWidget(
* id = "paragraphs_browser",
* label = @Translation("Paragraphs Browser EXPERIMENTAL"),
* description = @Translation("An experimental paragraphs inline form widget with a previewer."),
* field_types = {
* "entity_reference_revisions"
* }
* )
*/
class ParagraphsBrowserWidget extends ParagraphsWidget {
use ParagraphsBrowserWidgetTrait;
/**
* Returns select options for a plugin setting.
*
* This is done to allow
* \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
* to access option labels. Not all plugin setting are available.
*
* @param string $setting_name
* The name of the widget setting. Supported settings:
* - "edit_mode"
* - "closed_mode"
* - "autocollapse"
* - "add_mode"
*
* @return array|null
* An array of setting option usable as a value for a "#options" key.
*
* @see \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::settingsSummary()
*/
protected function getSettingOptions($setting_name) {
$options = parent::getSettingOptions($setting_name);
switch($setting_name) {
case 'add_mode':
$options['paragraphs_browser'] = $this->t('Paragraphs Browser');
break;
}
return $options;
}
}
<?php
/**
* @file
* Paragraphs Previewer widget trait implementation for paragraphs.
*/
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Trait of Plugin implementation of the 'entity_reference paragraphs' widget.
*
* @var Trait
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\ParagraphsBrowserWidget
* @see \Drupal\paragraphs_browser\Plugin\Field\FieldWidget\InlineParagraphsBrowserWidget
*/
trait ParagraphsBrowserWidgetTrait {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['paragraphs_browser'] = '_na';
$settings['modal_width'] = '80%';
$settings['modal_height'] = 'auto';
return $settings;
}
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['add_mode'] = array(
'#type' => 'hidden',
'#value' => 'paragraphs_browser'
);
$paragraph_browsers = entity_load_multiple('paragraphs_browser_type');
$options = array('_na' => 'No Groups');
foreach($paragraph_browsers as $type) {
$options[$type->id] = $type->label;
}
$elements['paragraphs_browser'] = array(
'#type' => 'select',
'#title' => $this->t('Paragraphs Browser'),
'#description' => $this->t('Select which browser to use.'),
'#options' => $options,
'#default_value' => $this->getSetting('paragraphs_browser'),
'#required' => TRUE,
);
$elements['modal_width'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Width'),
'#description' => 'The width of the modal in px or percentage.',
'#default_value' => $this->getSetting('modal_width'),
'#required' => FALSE,
);
$elements['modal_height'] = array(
'#type' => 'textfield',
'#title' => $this->t('Paragraphs Browser Modal Height'),
'#description' => 'The height of the modal in px or "auto".',
'#default_value' => $this->getSetting('modal_height'),
'#required' => FALSE,
);
return $elements;
}
public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
// If the Paragraph browser hasn't been set, return un-modified form.
if ($this->getSetting('paragraphs_browser') == '_na') {
return $elements;
}
// $elements['add_more'] = array(
// '#type' => 'container',
// '#theme_wrappers' => array('paragraphs_dropbutton_wrapper'),
// );
if (empty($this->uuid)) {
$this->uuid = \Drupal::service('uuid')->generate();
}
$elements['add_more']['add_more_select']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_select']['#attributes']['class'][] = 'js-hide';
$elements['add_more']['add_more_select']['#title_display'] = 'hidden';
$elements['add_more']['add_more_button']['#attributes']['data-uuid'] = $this->uuid;
$elements['add_more']['add_more_button']['#attributes']['class'][] = 'js-hide';
unset($elements['add_more']['add_more_button']['#suffix']);
unset($elements['add_more']['add_more_button']['#prefix']);
$elements['#attached']['library'][] = 'paragraphs_browser/modal';
$storage = $form_state->getStorage();
if (isset($storage['content_translation'])) {
unset($elements['add_more']);
}
else {
$elements['add_more']['browse'] = array(
'#type' => 'submit',
'#value' => $this->t('Add :title', [':title' => $this->getSetting('title')]),
'#attributes' => ['class' => ['js-show']],
'#ajax' => [
'url' => Url::fromRoute(
'paragraphs_browser.paragraphs_browser_controller', [
'field_config' => implode('.', array($items->getEntity()->getEntityTypeId(), $items->getEntity()->bundle(), $this->fieldDefinition->getName())),
'paragraphs_browser_type' => $this->getSetting('paragraphs_browser'),
'uuid' => $this->uuid,
]
),
],
);
}
if ($elements['#cardinality'] != -1) {
$keyCount = count(
array_filter(
array_keys($elements),
'is_numeric'
)
);
if ($elements['#cardinality'] <= $keyCount) {
unset($elements['add_more']);
}
}
return $elements;
}
}
--- src/Plugin/Field/FieldWidget/InlineParagraphsBrowserWidget.php
+++ src/Plugin/Field/FieldWidget/InlineParagraphsBrowserWidget.php
@@ -7,10 +7,7 @@
namespace Drupal\paragraphs_browser\Plugin\Field\FieldWidget;
-use Drupal\Core\Url;
use Drupal\paragraphs\Plugin\Field\FieldWidget\InlineParagraphsWidget;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'entity_reference paragraphs' widget.
@@ -20,7 +17,7 @@ use Drupal\Core\Field\FieldItemListInterface;
*
* @FieldWidget(
* id = "entity_reference_paragraphs_browser",
- * label = @Translation("Paragraphs Browser"),
+ * label = @Translation("Paragraphs Browser Classic"),
* description = @Translation("An paragraphs inline form widget with a previewer."),
* field_types = {
* "entity_reference_revisions"
@@ -29,120 +26,6 @@ use Drupal\Core\Field\FieldItemListInterface;
*/
class InlineParagraphsBrowserWidget extends InlineParagraphsWidget {
-
- /**
- * {@inheritdoc}
- */
- public static function defaultSettings() {
- $settings = parent::defaultSettings();
- $settings['paragraphs_browser'] = '_na';
- $settings['modal_width'] = '80%';
- $settings['modal_height'] = 'auto';
-
-
- return $settings;
- }
- public function settingsForm(array $form, FormStateInterface $form_state) {
- $elements = parent::settingsForm($form, $form_state);
- $elements['add_mode'] = array(
- '#type' => 'hidden',
- '#value' => 'paragraphs_browser'
- );
-
- $paragraph_browsers = entity_load_multiple('paragraphs_browser_type');
- $options = array('_na' => 'No Groups');
- foreach($paragraph_browsers as $type) {
- $options[$type->id] = $type->label;
- }
- $elements['paragraphs_browser'] = array(
- '#type' => 'select',
- '#title' => $this->t('Paragraphs Browser'),
- '#description' => $this->t('Select which browser to use.'),
- '#options' => $options,
- '#default_value' => $this->getSetting('paragraphs_browser'),
- '#required' => TRUE,
- );
-
- $elements['modal_width'] = array(
- '#type' => 'textfield',
- '#title' => $this->t('Paragraphs Browser Modal Width'),
- '#description' => 'The width of the modal in px or percentage.',
- '#default_value' => $this->getSetting('modal_width'),
- '#required' => FALSE,
- );
-
- $elements['modal_height'] = array(
- '#type' => 'textfield',
- '#title' => $this->t('Paragraphs Browser Modal Height'),
- '#description' => 'The height of the modal in px or "auto".',
- '#default_value' => $this->getSetting('modal_height'),
- '#required' => FALSE,
- );
-
- return $elements;
- }
-
-
- public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
- $elements = parent::formMultipleElements($items, $form, $form_state);
-
- // If the Paragraph browser hasn't been set, return un-modified form.
- if ($this->getSetting('paragraphs_browser') == '_na') {
- return $elements;
- }
-
-// $elements['add_more'] = array(
-// '#type' => 'container',
-// '#theme_wrappers' => array('paragraphs_dropbutton_wrapper'),
-// );
- if (empty($this->uuid)) {
- $this->uuid = \Drupal::service('uuid')->generate();
- }
- $elements['add_more']['add_more_select']['#attributes']['data-uuid'] = $this->uuid;
- $elements['add_more']['add_more_select']['#attributes']['class'][] = 'js-hide';
- $elements['add_more']['add_more_select']['#title_display'] = 'hidden';
- $elements['add_more']['add_more_button']['#attributes']['data-uuid'] = $this->uuid;
- $elements['add_more']['add_more_button']['#attributes']['class'][] = 'js-hide';
- unset($elements['add_more']['add_more_button']['#suffix']);
- unset($elements['add_more']['add_more_button']['#prefix']);
-
- $elements['#attached']['library'][] = 'paragraphs_browser/modal';
-
- $storage = $form_state->getStorage();
-
- if (isset($storage['content_translation'])) {
- unset($elements['add_more']);
- }
- else {
- $elements['add_more']['browse'] = array(
- '#type' => 'submit',
- '#value' => $this->t('Add :title', [':title' => $this->getSetting('title')]),
- '#attributes' => ['class' => ['js-show']],
- '#ajax' => [
- 'url' => Url::fromRoute(
- 'paragraphs_browser.paragraphs_browser_controller', [
- 'field_config' => implode('.', array($items->getEntity()->getEntityTypeId(), $items->getEntity()->bundle(), $this->fieldDefinition->getName())),
- 'paragraphs_browser_type' => $this->getSetting('paragraphs_browser'),
- 'uuid' => $this->uuid,
- ]
- ),
- ],
- );
- }
-
- if ($elements['#cardinality'] != -1) {
- $keyCount = count(
- array_filter(
- array_keys($elements),
- 'is_numeric'
- )
- );
- if ($elements['#cardinality'] <= $keyCount) {
- unset($elements['add_more']);
- }
- }
-
- return $elements;
- }
+ use ParagraphsBrowserWidgetTrait;
}
diff --git a/admin_toolbar_tools/misc/icons/ffffff/drupal-8-logo.svg b/admin_toolbar_tools/misc/icons/ffffff/drupal-8-logo.svg
index 40ffae3..f9961d4 100644
--- a/admin_toolbar_tools/misc/icons/ffffff/drupal-8-logo.svg
+++ b/admin_toolbar_tools/misc/icons/ffffff/drupal-8-logo.svg
@@ -1 +1,4 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMinYMin" viewBox="0 0 611 646"><path fill="#2ba9e0" d="M 161.14248,607.07981 C 135.931,577.51327 120.7662,539.33875 120.7662,497.60876 c 0,-87.01545 66.15644,-158.68624 151.648,-168.60413 -14.59612,-20.95856 -23.31588,-46.40824 -23.31588,-73.72922 0,-71.85792 58.95316,-129.86822 131.7442,-129.86822 6.06592,0 11.94228,0.37426 17.62908,1.12278 C 355.44148,89.85249 312.41136,52.61362 278.48012,12.00641 295.73008,190.15417 114.1316,125.40719 46.8378,289.70733 1.91208,399.73977 42.47792,535.78328 161.14248,607.07981 Z m 143.1178,-351.8044 c 0,41.72999 34.31036,75.41339 76.39268,75.41339 42.08232,0 76.58224,-33.87053 76.58224,-75.41339 0,-41.72999 -34.31036,-75.41339 -76.39268,-75.41339 -42.08232,0 -76.58224,33.6834 -76.58224,75.41339 z m 112.97776,124.81571 c 29.57136,30.50219 47.76912,71.85792 47.76912,117.51764 0,57.82317 -29.19224,108.72253 -73.73884,139.41185 82.4586,-25.07542 150.7002,-86.26693 181.21936,-160.37041 42.27188,-102.54724 2.8434,-179.6448 -63.12348,-249.63142 2.08516,8.98224 3.22252,18.52587 3.22252,28.0695 -0.18956,59.50734 -40.37628,109.47105 -95.34868,125.00284 z m -124.35136,18.90013 c -55.16196,0 -99.89812,44.16268 -99.89812,98.61751 0,54.45483 44.73616,98.61751 99.89812,98.61751 55.16196,0 99.89812,-44.16268 99.89812,-98.61751 0,-54.45483 -44.73616,-98.61751 -99.89812,-98.61751 z" /></svg>
+<svg width="214" height="214" viewBox="0 0 214 214" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M172 59C154.963 36 135 19 105 0C110.5 30.5 58.5 50 41 68.5C23.5 87 17 106.188 17 125.902C17 174.204 57.6985 213.5 106 213.5C154.302 213.5 196.5 174.204 196.5 125.902C196.5 106.188 189.037 82 172 59Z" fill="#008CF2"/>
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M105.318 83.5C92.4745 83.5 81.0102 88.6894 73.0589 96.8593C70.2775 99.7171 68.2075 104.354 67.3793 109.481C66.5258 114.766 67.2414 118.928 68.2111 120.763C58 122 51.5 113 52.5 104.5C53.2059 98.5 55.8754 91.5746 61.5929 85.7C72.5277 74.4646 88.115 67.5 105.318 67.5C137.872 67.5 165 92.6234 165 124.5C165 156.377 137.872 181.5 105.318 181.5C100.829 181.5 93.7529 180.358 90 180C85.3088 179.654 71.5 179 64 182L83.5 108.5C85.5 102 91.7553 98.2637 102.602 99.6635L84.5586 164.168C86.3276 164.183 88.0427 164.274 89.7083 164.397C91.628 164.538 93.3802 164.707 95.0667 164.87C98.4724 165.198 101.61 165.5 105.318 165.5C129.85 165.5 149 146.747 149 124.5C149 102.253 129.85 83.5 105.318 83.5Z" fill="white"/>
+</svg>
diff --git a/admin_toolbar_tools/misc/icons/ffffff/drupal-9-logo.svg b/admin_toolbar_tools/misc/icons/ffffff/drupal-9-logo.svg
index fbc53e3..f9961d4 100644
--- a/admin_toolbar_tools/misc/icons/ffffff/drupal-9-logo.svg
+++ b/admin_toolbar_tools/misc/icons/ffffff/drupal-9-logo.svg
@@ -1 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 42.15 55.08"><defs><style>.cls-1{fill:#009cde;}</style></defs><title>Risorsa 23</title><g id="Livello_2" data-name="Livello 2"><g id="Livello_1-2" data-name="Livello 1"><path class="cls-1" d="M29.75,11.73C25.87,7.86,22.18,4.16,21.08,0,20,4.16,16.28,7.86,12.4,11.73,6.59,17.54,0,24.12,0,34a21.08,21.08,0,1,0,42.15,0C42.15,24.12,35.56,17.54,29.75,11.73ZM10.84,35.92a14.13,14.13,0,0,0-1.65,2.62.54.54,0,0,1-.36.3H8.65c-.47,0-1-.92-1-.92h0c-.14-.22-.27-.45-.4-.69l-.09-.19C5.94,34.25,7,30.28,7,30.28h0a17.42,17.42,0,0,1,2.52-5.41,31.53,31.53,0,0,1,2.28-3l1,1,4.72,4.82a.54.54,0,0,1,0,.72l-4.93,5.47h0ZM21.32,49.73a7.29,7.29,0,0,1-5.4-12.14c1.54-1.83,3.42-3.63,5.46-6,2.42,2.58,4,4.35,5.55,6.29a3.08,3.08,0,0,1,.32.48,7.15,7.15,0,0,1,1.3,4.12A7.23,7.23,0,0,1,21.32,49.73ZM35,38.14v0a.84.84,0,0,1-.67.58h-.14a1.22,1.22,0,0,1-.68-.55h0a37.77,37.77,0,0,0-4.28-5.31l-1.93-2-6.41-6.65a54,54,0,0,1-3.84-3.94,1.3,1.3,0,0,0-.1-.15,3.84,3.84,0,0,1-.51-1c0-.06,0-.13,0-.19a3.4,3.4,0,0,1,1-3c1.24-1.24,2.49-2.49,3.67-3.79,1.3,1.44,2.69,2.82,4.06,4.19v0a57.6,57.6,0,0,1,7.55,8.58A16,16,0,0,1,35.65,34,14.55,14.55,0,0,1,35,38.14Z"/></g></g></svg>
\ No newline at end of file
+<svg width="214" height="214" viewBox="0 0 214 214" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M172 59C154.963 36 135 19 105 0C110.5 30.5 58.5 50 41 68.5C23.5 87 17 106.188 17 125.902C17 174.204 57.6985 213.5 106 213.5C154.302 213.5 196.5 174.204 196.5 125.902C196.5 106.188 189.037 82 172 59Z" fill="#008CF2"/>
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M105.318 83.5C92.4745 83.5 81.0102 88.6894 73.0589 96.8593C70.2775 99.7171 68.2075 104.354 67.3793 109.481C66.5258 114.766 67.2414 118.928 68.2111 120.763C58 122 51.5 113 52.5 104.5C53.2059 98.5 55.8754 91.5746 61.5929 85.7C72.5277 74.4646 88.115 67.5 105.318 67.5C137.872 67.5 165 92.6234 165 124.5C165 156.377 137.872 181.5 105.318 181.5C100.829 181.5 93.7529 180.358 90 180C85.3088 179.654 71.5 179 64 182L83.5 108.5C85.5 102 91.7553 98.2637 102.602 99.6635L84.5586 164.168C86.3276 164.183 88.0427 164.274 89.7083 164.397C91.628 164.538 93.3802 164.707 95.0667 164.87C98.4724 165.198 101.61 165.5 105.318 165.5C129.85 165.5 149 146.747 149 124.5C149 102.253 129.85 83.5 105.318 83.5Z" fill="white"/>
+</svg>
......@@ -6,6 +6,7 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'cweagans\\Composer\\' => array($vendorDir . '/cweagans/composer-patches/src'),
'Webmozart\\PathUtil\\' => array($vendorDir . '/webmozart/path-util/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Twig\\' => array($vendorDir . '/twig/twig/src'),
......
......@@ -42,6 +42,10 @@ class ComposerStaticInitDrupal8
);
public static $prefixLengthsPsr4 = array (
'c' =>
array (
'cweagans\\Composer\\' => 18,
),
'W' =>
array (
'Webmozart\\PathUtil\\' => 19,
......@@ -164,6 +168,10 @@ class ComposerStaticInitDrupal8
);
public static $prefixDirsPsr4 = array (
'cweagans\\Composer\\' =>
array (
0 => __DIR__ . '/..' . '/cweagans/composer-patches/src',
),
'Webmozart\\PathUtil\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/path-util/src',
......
......@@ -1183,6 +1183,57 @@
"abandoned": "psr/container",
"install-path": "../container-interop/container-interop"
},
{
"name": "cweagans/composer-patches",
"version": "1.7.3",
"version_normalized": "1.7.3.0",
"source": {
"type": "git",
"url": "https://github.com/cweagans/composer-patches.git",
"reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweagans/composer-patches/zipball/e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
"reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": ">=5.3.0"
},
"require-dev": {
"composer/composer": "~1.0 || ~2.0",
"phpunit/phpunit": "~4.6"
},
"time": "2022-12-20T22:53:13+00:00",
"type": "composer-plugin",
"extra": {
"class": "cweagans\\Composer\\Patches"
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"cweagans\\Composer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Cameron Eagans",
"email": "me@cweagans.net"
}
],
"description": "Provides a way to patch Composer packages.",
"support": {
"issues": "https://github.com/cweagans/composer-patches/issues",
"source": "https://github.com/cweagans/composer-patches/tree/1.7.3"
},
"install-path": "../cweagans/composer-patches"
},
{
"name": "dflydev/dot-access-data",
"version": "v1.1.0",
......@@ -1734,37 +1785,43 @@
},
{
"name": "drupal/admin_toolbar",
"version": "1.27.0",
"version_normalized": "1.27.0.0",
"version": "3.2.1",
"version_normalized": "3.2.1.0",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/admin_toolbar.git",
"reference": "8.x-1.27"
"reference": "3.2.1"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/admin_toolbar-8.x-1.27.zip",
"reference": "8.x-1.27",
"shasum": "fed1fbbdf8f805b1da63d73e7e2c54750f354d61"
"url": "https://ftp.drupal.org/files/projects/admin_toolbar-3.2.1.zip",
"reference": "3.2.1",
"shasum": "7a4bfb716e269be4ca03b7f04e29e4439ec6cf93"
},
"require": {
"drupal/core": "^8"
"drupal/core": "^8.8.0 || ^9.0"
},
"require-dev": {
"drupal/admin_toolbar_tools": "*"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "8.x-1.27",
"datestamp": "1558553350",
"version": "3.2.1",
"datestamp": "1665044276",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
},
"patches_applied": {
"Replace icons": "./patches/admin_toolbar/replace-icons.patch"
}
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0+"
"GPL-2.0-or-later"
],
"authors": [
{
......@@ -1808,7 +1865,7 @@
"Toolbar"
],
"support": {
"source": "http://cgit.drupalcode.org/admin_toolbar",
"source": "https://git.drupalcode.org/project/admin_toolbar",
"issues": "https://www.drupal.org/project/issues/admin_toolbar"
},
"install-path": "../../modules/contrib/admin_toolbar"
......@@ -2058,7 +2115,8 @@
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"patches_applied": []
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
......@@ -2634,6 +2692,9 @@
"[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt",
"[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt"
}
},
"patches_applied": {
"Replace icons": "./patches/core/core-icons.patch"
}
},
"installation-source": "dist",
......@@ -3216,7 +3277,8 @@
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"patches_applied": []
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
......@@ -3681,7 +3743,8 @@
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"patches_applied": []
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
......@@ -4096,7 +4159,8 @@
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"patches_applied": []
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
......@@ -4241,7 +4305,8 @@
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"patches_applied": []
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
......
<?php return array(
'root' => array(
'name' => 'drupal/drupal',
'pretty_version' => '8.x-dev',
'version' => '8.9999999.9999999.9999999-dev',
'reference' => '7b75fa1b7c6dac6ab2c8264190f1f8898fe09f54',
'pretty_version' => '8.9.x-dev',
'version' => '8.9.9999999.9999999-dev',
'reference' => 'bbe1b5638e8bab77f285cfbff94f997a53a9bb97',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
......@@ -148,6 +148,15 @@
0 => '^1.2',
),
),
'cweagans/composer-patches' => array(
'pretty_version' => '1.7.3',
'version' => '1.7.3.0',
'reference' => 'e190d4466fe2b103a55467dfa83fc2fecfcaf2db',
'type' => 'composer-plugin',
'install_path' => __DIR__ . '/../cweagans/composer-patches',
'aliases' => array(),
'dev_requirement' => false,
),
'dflydev/dot-access-data' => array(
'pretty_version' => 'v1.1.0',
'version' => '1.1.0.0',
......@@ -227,9 +236,9 @@
),
),
'drupal/admin_toolbar' => array(
'pretty_version' => '1.27.0',
'version' => '1.27.0.0',
'reference' => '8.x-1.27',
'pretty_version' => '3.2.1',
'version' => '3.2.1.0',
'reference' => '3.2.1',
'type' => 'drupal-module',
'install_path' => __DIR__ . '/../../modules/contrib/admin_toolbar',
'aliases' => array(),
......@@ -674,9 +683,9 @@
'dev_requirement' => false,
),
'drupal/drupal' => array(
'pretty_version' => '8.x-dev',
'version' => '8.9999999.9999999.9999999-dev',
'reference' => '7b75fa1b7c6dac6ab2c8264190f1f8898fe09f54',
'pretty_version' => '8.9.x-dev',
'version' => '8.9.9999999.9999999-dev',
'reference' => 'bbe1b5638e8bab77f285cfbff94f997a53a9bb97',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
......
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
Copyright 2013 Cameron Eagans
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# composer-patches
Simple patches plugin for Composer. Applies a patch from a local or remote file to any package required with composer.
Note that the 1.x versions of Composer Patches are supported on a best-effort
basis due to the imminent release of 2.0.0. You may still be interested in
using 1.x if you need Composer to cooperate with earlier PHP versions. No new
features will be added to 1.x releases, but any security or bug fixes will
still be accepted.
## Usage
Example composer.json:
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches": {
"drupal/drupal": {
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
}
}
}
}
```
## Using an external patch file
Instead of a patches key in your root composer.json, use a patches-file key.
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches-file": "local/path/to/your/composer.patches.json"
}
}
```
Then your `composer.patches.json` should look like this:
```
{
"patches": {
"vendor/project": {
"Patch title": "http://example.com/url/to/patch.patch"
}
}
}
```
## Allowing patches to be applied from dependencies
If your project doesn't supply any patches of its own, but you still want to accept patches from dependencies, you must have the following in your composer file:
```json
{
"require": {
"cweagans/composer-patches": "^1.5.0"
},
"extra": {
"enable-patching": true
}
}
```
If you do have a `patches` section in your composer file that defines your own set of patches then the `enable-patching` setting will be ignored and patches from dependencies will always be applied.
## Ignoring patches
There may be situations in which you want to ignore a patch supplied by a dependency. For example:
- You use a different more recent version of a dependency, and now a patch isn't applying.
- You have a more up to date patch than the dependency, and want to use yours instead of theirs.
- A dependency's patch adds a feature to a project that you don't need.
- Your patches conflict with a dependency's patches.
```json
{
"require": {
"cweagans/composer-patches": "~1.0",
"drupal/drupal": "~8.2",
"drupal/lightning": "~8.1"
},
"config": {
"preferred-install": "source"
},
"extra": {
"patches": {
"drupal/drupal": {
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
}
},
"patches-ignore": {
"drupal/lightning": {
"drupal/panelizer": {
"This patch has known conflicts with our Quick Edit integration": "https://www.drupal.org/files/issues/2664682-49.patch"
}
}
}
}
}
```
## Allowing to force the patch level (-pX)
Some situations require to force the patchLevel used to apply patches on a particular package.
Its useful for packages like drupal/core which packages only a subdir of the original upstream project on which patches are based.
```json
{
"extra": {
"patchLevel": {
"drupal/core": "-p2"
}
}
}
```
## Using patches from HTTP URLs
Composer [blocks](https://getcomposer.org/doc/06-config.md#secure-http) you from downloading anything from HTTP URLs, you can disable this for your project by adding a `secure-http` setting in the config section of your `composer.json`. Note that the `config` section should be under the root of your `composer.json`.
```json
{
"config": {
"secure-http": false
}
}
```
However, it's always advised to setup HTTPS to prevent MITM code injection.
## Patches containing modifications to composer.json files
Because patching occurs _after_ Composer calculates dependencies and installs packages, changes to an underlying dependency's `composer.json` file introduced in a patch will have _no effect_ on installed packages.
If you need to modify a dependency's `composer.json` or its underlying dependencies, you cannot use this plugin. Instead, you must do one of the following:
- Work to get the underlying issue resolved in the upstream package.
- Fork the package and [specify your fork as the package repository](https://getcomposer.org/doc/05-repositories.md#vcs) in your root `composer.json`
- Specify compatible package version requirements in your root `composer.json`
## Error handling
If a patch cannot be applied (hunk failed, different line endings, etc.) a message will be shown and the patch will be skipped.
To enforce throwing an error and stopping package installation/update immediately, you have two available options:
1. Add `"composer-exit-on-patch-failure": true` option to the `extra` section of your composer.json file.
1. Export `COMPOSER_EXIT_ON_PATCH_FAILURE=1`
By default, failed patches are skipped.
## Patches reporting
When a patch is applied, the plugin writes a report-file `PATCHES.txt` to a patching directory (e.g. `./patch-me/PATCHES.txt`),
which contains a list of applied patches.
If you want to avoid this behavior, add a specific key to the `extra` section:
```json
"extra": {
"composer-patches-skip-reporting": true
}
```
Or provide an environment variable `COMPOSER_PATCHES_SKIP_REPORTING` with a config.
## Patching composer.json in dependencies
This doesn't work like you'd want. By the time you're running `composer install`,
the metadata from your dependencies' composer.json has already been aggregated by
packagist (or whatever metadata repo you're using). Unfortunately, this means that
you cannot e.g. patch a dependency to be compatible with an earlier version of PHP
or change the framework version that a plugin depends on.
@anotherjames over at @computerminds wrote an article about how to work around
that particular problem for a Drupal 8 -> Drupal 9 upgrade:
[Apply Drupal 9 compatibility patches with Composer](https://www.computerminds.co.uk/articles/apply-drupal-9-compatibility-patches-composer) ([archive](https://web.archive.org/web/20210124171010/https://www.computerminds.co.uk/articles/apply-drupal-9-compatibility-patches-composer))
## Difference between this and netresearch/composer-patches-plugin
- This plugin is much more simple to use and maintain
- This plugin doesn't require you to specify which package version you're patching
- This plugin is easy to use with Drupal modules (which don't use semantic versioning).
- This plugin will gather patches from all dependencies and apply them as if they were in the root composer.json
## Credits
A ton of this code is adapted or taken straight from https://github.com/jpstacey/composer-patcher, which is abandoned in favor of https://github.com/netresearch/composer-patches-plugin, which is (IMHO) overly complex and difficult to use.
{
"name": "cweagans/composer-patches",
"description": "Provides a way to patch Composer packages.",
"minimum-stability": "dev",
"license": "BSD-3-Clause",
"type": "composer-plugin",
"extra": {
"class": "cweagans\\Composer\\Patches"
},
"authors": [
{
"name": "Cameron Eagans",
"email": "me@cweagans.net"
}
],
"require": {
"php": ">=5.3.0",
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "~1.0 || ~2.0",
"phpunit/phpunit": "~4.6"
},
"autoload": {
"psr-4": {"cweagans\\Composer\\": "src"}
},
"autoload-dev": {
"psr-4": {"cweagans\\Composer\\Tests\\": "tests"}
}
}
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4a5c841252204815536a37cad51d347b",
"packages": [],
"packages-dev": [
{
"name": "composer/ca-bundle",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd",
"reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-04-08T08:27:21+00:00"
},
{
"name": "composer/composer",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
"reference": "870fdc59dfcffe0bd2d43ca2de4235761d0dec7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/composer/zipball/870fdc59dfcffe0bd2d43ca2de4235761d0dec7a",
"reference": "870fdc59dfcffe0bd2d43ca2de4235761d0dec7a",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.0",
"composer/semver": "^3.0",
"composer/spdx-licenses": "^1.2",
"composer/xdebug-handler": "^1.1",
"justinrainbow/json-schema": "^5.2.10",
"php": "^5.3.2 || ^7.0 || ^8.0",
"psr/log": "^1.0",
"react/promise": "^1.2 || ^2.7",
"seld/jsonlint": "^1.4",
"seld/phar-utils": "^1.0",
"symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0",
"symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0",
"symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0",
"symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0"
},
"require-dev": {
"phpspec/prophecy": "^1.10",
"symfony/phpunit-bridge": "^4.2 || ^5.0"
},
"suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
"ext-zip": "Enabling the zip extension allows you to unzip archives",
"ext-zlib": "Allow gzip compression of HTTP requests"
},
"bin": [
"bin/composer"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\": "src/Composer"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "https://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
"homepage": "https://getcomposer.org/",
"keywords": [
"autoload",
"dependency",
"package"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-07-15T15:02:16+00:00"
},
{
"name": "composer/semver",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "00915994bb1de62e750ae279669c9c5a57379957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/00915994bb1de62e750ae279669c9c5a57379957",
"reference": "00915994bb1de62e750ae279669c9c5a57379957",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.19",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-05-31T11:44:06+00:00"
},
{
"name": "composer/spdx-licenses",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/composer/spdx-licenses.git",
"reference": "6946f785871e2314c60b4524851f3702ea4f2223"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/spdx-licenses/zipball/6946f785871e2314c60b4524851f3702ea4f2223",
"reference": "6946f785871e2314c60b4524851f3702ea4f2223",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Spdx\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "SPDX licenses list and validation library.",
"keywords": [
"license",
"spdx",
"validator"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-07-15T15:35:07+00:00"
},
{
"name": "composer/xdebug-handler",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51",
"reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0",
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8"
},
"type": "library",
"autoload": {
"psr-4": {
"Composer\\XdebugHandler\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "John Stevenson",
"email": "john-stevenson@blueyonder.co.uk"
}
],
"description": "Restarts a process without Xdebug.",
"keywords": [
"Xdebug",
"performance"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-06-04T11:16:35+00:00"
},
{
"name": "doctrine/instantiator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "justinrainbow/json-schema",
"version": "5.x-dev",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b",
"reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"bin": [
"bin/validate-json"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema",
"keywords": [
"json",
"schema"
],
"time": "2020-05-27T16:41:55+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"phpDocumentor": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"time": "2015-02-03T12:10:50+00:00"
},
{
"name": "phpspec/prophecy",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "4f9b1eaf0a7da77c362f8d91cbc68ab1f4718d62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4f9b1eaf0a7da77c362f8d91cbc68ab1f4718d62",
"reference": "4f9b1eaf0a7da77c362f8d91cbc68ab1f4718d62",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"phpdocumentor/reflection-docblock": "~2.0",
"sebastian/comparator": "~1.1"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
}
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"time": "2015-09-22T14:49:23+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.x-dev",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "~1.3",
"sebastian/environment": "^1.3.2",
"sebastian/version": "~1.0"
},
"require-dev": {
"ext-xdebug": ">=2.1.4",
"phpunit/phpunit": "~4"
},
"suggest": {
"ext-dom": "*",
"ext-xdebug": ">=2.2.1",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2015-10-06T15:47:00+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2015-06-21T13:08:43+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21T13:50:34+00:00"
},
{
"name": "phpunit/php-timer",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
"reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"time": "2015-06-21T08:01:12+00:00"
},
{
"name": "phpunit/php-token-stream",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cab6c6fefee93d7b7c3a01292a0fe0884ea66644",
"reference": "cab6c6fefee93d7b7c3a01292a0fe0884ea66644",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"time": "2015-09-23T14:46:55+00:00"
},
{
"name": "phpunit/phpunit",
"version": "4.8.x-dev",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "be067d6105286b74272facefc2697038f8807b77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264188ddf4d3586c80ea615f8ec8eaea34e652a1",
"reference": "be067d6105286b74272facefc2697038f8807b77",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": ">=1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
"symfony/yaml": "~2.1|~3.0"
},
"suggest": {
"phpunit/php-invoker": "~1.1"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.8.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2015-10-14T13:49:40+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.x-dev",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"abandoned": true,
"time": "2015-10-02T06:51:40+00:00"
},
{
"name": "psr/container",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "fc1bc363ecf887921e3897c7b1dad3587ae154eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/fc1bc363ecf887921e3897c7b1dad3587ae154eb",
"reference": "fc1bc363ecf887921e3897c7b1dad3587ae154eb",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2019-10-04T14:07:35+00:00"
},
{
"name": "psr/log",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2020-03-23T09:12:05+00:00"
},
{
"name": "react/promise",
"version": "2.x-dev",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4",
"reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"time": "2020-05-12T15:16:56+00:00"
},
{
"name": "sebastian/comparator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2015-07-26T15:48:44+00:00"
},
{
"name": "sebastian/diff",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "6899b3e33bfbd386d88b5eea5f65f563e8793051"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6899b3e33bfbd386d88b5eea5f65f563e8793051",
"reference": "6899b3e33bfbd386d88b5eea5f65f563e8793051",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "http://www.github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2015-06-22T14:15:55+00:00"
},
{
"name": "sebastian/environment",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"time": "2015-08-03T06:14:51+00:00"
},
{
"name": "sebastian/exporter",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "f88f8936517d54ae6d589166810877fb2015d0a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f88f8936517d54ae6d589166810877fb2015d0a2",
"reference": "f88f8936517d54ae6d589166810877fb2015d0a2",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"ext-mbstring": "*",
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2015-08-09T04:23:41+00:00"
},
{
"name": "sebastian/global-state",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2015-10-12T03:26:01+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-06-21T08:04:50+00:00"
},
{
"name": "sebastian/version",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2015-06-21T13:59:46+00:00"
},
{
"name": "seld/jsonlint",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/jsonlint.git",
"reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1",
"reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1",
"shasum": ""
},
"require": {
"php": "^5.3 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"bin": [
"bin/jsonlint"
],
"type": "library",
"autoload": {
"psr-4": {
"Seld\\JsonLint\\": "src/Seld/JsonLint/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "JSON Linter",
"keywords": [
"json",
"linter",
"parser",
"validator"
],
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/seld/jsonlint",
"type": "tidelift"
}
],
"time": "2020-04-30T19:05:18+00:00"
},
{
"name": "seld/phar-utils",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/phar-utils.git",
"reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8674b1d84ffb47cc59a101f5d5a3b61e87d23796",
"reference": "8674b1d84ffb47cc59a101f5d5a3b61e87d23796",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Seld\\PharUtils\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be"
}
],
"description": "PHAR file format utilities, for when PHP phars you up",
"keywords": [
"phar"
],
"time": "2020-07-07T18:42:57+00:00"
},
{
"name": "symfony/console",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "e4a70bd8c5a4382630197b7b87910b3fc0e6b526"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/e4a70bd8c5a4382630197b7b87910b3fc0e6b526",
"reference": "e4a70bd8c5a4382630197b7b87910b3fc0e6b526",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1|^2",
"symfony/string": "^5.1"
},
"conflict": {
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
},
"provide": {
"psr/log-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/lock": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command line",
"console",
"terminal"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-06T13:25:45+00:00"
},
{
"name": "symfony/filesystem",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "e7550993849f986f01a9161b302d4aed8d4aab0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e7550993849f986f01a9161b302d4aed8d4aab0a",
"reference": "e7550993849f986f01a9161b302d4aed8d4aab0a",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-30T20:38:10+00:00"
},
{
"name": "symfony/finder",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "2a63a45741144325f84d28ea1e67bc1b669b1748"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/2a63a45741144325f84d28ea1e67bc1b669b1748",
"reference": "2a63a45741144325f84d28ea1e67bc1b669b1748",
"shasum": ""
},
"require": {
"php": ">=7.2.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-20T17:44:07+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b740103edbdcc39602239ee8860f0f45a8eb9aa5",
"reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca",
"reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"shasum": ""
},
"require": {
"php": ">=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/process",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d158a452d952049e0e55b7cfe5f360c973edc57c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d158a452d952049e0e55b7cfe5f360c973edc57c",
"reference": "d158a452d952049e0e55b7cfe5f360c973edc57c",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-php80": "^1.15"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-06T13:25:45+00:00"
},
{
"name": "symfony/service-contracts",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442",
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-06T13:23:11+00:00"
},
{
"name": "symfony/string",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "b7914561c03f8d78f83eec3ec4502adbdc343c48"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/b7914561c03f8d78f83eec3ec4502adbdc343c48",
"reference": "b7914561c03f8d78f83eec3ec4502adbdc343c48",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "~1.15"
},
"require-dev": {
"symfony/error-handler": "^4.4|^5.0",
"symfony/http-client": "^4.4|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony String component",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-08T08:28:10+00:00"
},
{
"name": "symfony/yaml",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "8d32eb597b531eb915b4fee3dc582ade5ae1fe6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/ab0314f7544d600ea7917f02cdad774358b81113",
"reference": "8d32eb597b531eb915b4fee3dc582ade5ae1fe6a",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-10-13T16:01:35+00:00"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0",
"composer-plugin-api": "^1.0 || ^2.0"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"
}
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="composer-patches">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<!-- Filter for coverage reports. -->
<filter>
<whitelist>
<directory>src/</directory>
</whitelist>
<blacklist>
<directory>vendor/</directory>
</blacklist>
</filter>
</phpunit>
<?php
/**
* @file
* Dispatch events when patches are applied.
*/
namespace cweagans\Composer;
use Composer\EventDispatcher\Event;
use Composer\Package\PackageInterface;
class PatchEvent extends Event {
/**
* @var PackageInterface $package
*/
protected $package;
/**
* @var string $url
*/
protected $url;
/**
* @var string $description
*/
protected $description;
/**
* Constructs a PatchEvent object.
*
* @param string $eventName
* @param PackageInterface $package
* @param string $url
* @param string $description
*/
public function __construct($eventName, PackageInterface $package, $url, $description) {
parent::__construct($eventName);
$this->package = $package;
$this->url = $url;
$this->description = $description;
}
/**
* Returns the package that is patched.
*
* @return PackageInterface
*/
public function getPackage() {
return $this->package;
}
/**
* Returns the url of the patch.
*
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* Returns the description of the patch.
*
* @return string
*/
public function getDescription() {
return $this->description;
}
}
<?php
/**
* @file
* Dispatch events when patches are applied.
*/
namespace cweagans\Composer;
class PatchEvents {
/**
* The PRE_PATCH_APPLY event occurs before a patch is applied.
*
* The event listener method receives a cweagans\Composer\PatchEvent instance.
*
* @var string
*/
const PRE_PATCH_APPLY = 'pre-patch-apply';
/**
* The POST_PATCH_APPLY event occurs after a patch is applied.
*
* The event listener method receives a cweagans\Composer\PatchEvent instance.
*
* @var string
*/
const POST_PATCH_APPLY = 'post-patch-apply';
}
<?php
/**
* @file
* Provides a way to patch Composer packages after installation.
*/
namespace cweagans\Composer;
use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Installer\PackageEvents;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use Composer\Installer\PackageEvent;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Symfony\Component\Process\Process;
class Patches implements PluginInterface, EventSubscriberInterface {
/**
* @var Composer $composer
*/
protected $composer;
/**
* @var IOInterface $io
*/
protected $io;
/**
* @var EventDispatcher $eventDispatcher
*/
protected $eventDispatcher;
/**
* @var ProcessExecutor $executor
*/
protected $executor;
/**
* @var array $patches
*/
protected $patches;
/**
* @var array $installedPatches
*/
protected $installedPatches;
/**
* Apply plugin modifications to composer
*
* @param Composer $composer
* @param IOInterface $io
*/
public function activate(Composer $composer, IOInterface $io) {
$this->composer = $composer;
$this->io = $io;
$this->eventDispatcher = $composer->getEventDispatcher();
$this->executor = new ProcessExecutor($this->io);
$this->patches = array();
$this->installedPatches = array();
}
/**
* Returns an array of event names this subscriber wants to listen to.
*/
public static function getSubscribedEvents() {
return array(
ScriptEvents::PRE_INSTALL_CMD => array('checkPatches'),
ScriptEvents::PRE_UPDATE_CMD => array('checkPatches'),
PackageEvents::PRE_PACKAGE_INSTALL => array('gatherPatches'),
PackageEvents::PRE_PACKAGE_UPDATE => array('gatherPatches'),
// The following is a higher weight for compatibility with
// https://github.com/AydinHassan/magento-core-composer-installer and more generally for compatibility with
// every Composer plugin which deploys downloaded packages to other locations.
// In such cases you want that those plugins deploy patched files so they have to run after
// the "composer-patches" plugin.
// @see: https://github.com/cweagans/composer-patches/pull/153
PackageEvents::POST_PACKAGE_INSTALL => array('postInstall', 10),
PackageEvents::POST_PACKAGE_UPDATE => array('postInstall', 10),
);
}
/**
* Before running composer install,
* @param Event $event
*/
public function checkPatches(Event $event) {
if (!$this->isPatchingEnabled()) {
return;
}
try {
$repositoryManager = $this->composer->getRepositoryManager();
$localRepository = $repositoryManager->getLocalRepository();
$installationManager = $this->composer->getInstallationManager();
$packages = $localRepository->getPackages();
$extra = $this->composer->getPackage()->getExtra();
$patches_ignore = isset($extra['patches-ignore']) ? $extra['patches-ignore'] : array();
$tmp_patches = $this->grabPatches();
foreach ($packages as $package) {
$extra = $package->getExtra();
if (isset($extra['patches'])) {
if (isset($patches_ignore[$package->getName()])) {
foreach ($patches_ignore[$package->getName()] as $package_name => $patches) {
if (isset($extra['patches'][$package_name])) {
$extra['patches'][$package_name] = array_diff($extra['patches'][$package_name], $patches);
}
}
}
$this->installedPatches[$package->getName()] = $extra['patches'];
}
$patches = isset($extra['patches']) ? $extra['patches'] : array();
$tmp_patches = $this->arrayMergeRecursiveDistinct($tmp_patches, $patches);
}
if ($tmp_patches == FALSE) {
$this->io->write('<info>No patches supplied.</info>');
return;
}
// Remove packages for which the patch set has changed.
$promises = array();
foreach ($packages as $package) {
if (!($package instanceof AliasPackage)) {
$package_name = $package->getName();
$extra = $package->getExtra();
$has_patches = isset($tmp_patches[$package_name]);
$has_applied_patches = isset($extra['patches_applied']) && count($extra['patches_applied']) > 0;
if (($has_patches && !$has_applied_patches)
|| (!$has_patches && $has_applied_patches)
|| ($has_patches && $has_applied_patches && $tmp_patches[$package_name] !== $extra['patches_applied'])) {
$uninstallOperation = new UninstallOperation($package, 'Removing package so it can be re-installed and re-patched.');
$this->io->write('<info>Removing package ' . $package_name . ' so that it can be re-installed and re-patched.</info>');
$promises[] = $installationManager->uninstall($localRepository, $uninstallOperation);
}
}
}
$promises = array_filter($promises);
if ($promises) {
$this->composer->getLoop()->wait($promises);
}
}
// If the Locker isn't available, then we don't need to do this.
// It's the first time packages have been installed.
catch (\LogicException $e) {
return;
}
}
/**
* Gather patches from dependencies and store them for later use.
*
* @param PackageEvent $event
*/
public function gatherPatches(PackageEvent $event) {
// If we've already done this, then don't do it again.
if (isset($this->patches['_patchesGathered'])) {
$this->io->write('<info>Patches already gathered. Skipping</info>', TRUE, IOInterface::VERBOSE);
return;
}
// If patching has been disabled, bail out here.
elseif (!$this->isPatchingEnabled()) {
$this->io->write('<info>Patching is disabled. Skipping.</info>', TRUE, IOInterface::VERBOSE);
return;
}
$this->patches = $this->grabPatches();
if (empty($this->patches)) {
$this->io->write('<info>No patches supplied.</info>');
}
$extra = $this->composer->getPackage()->getExtra();
$patches_ignore = isset($extra['patches-ignore']) ? $extra['patches-ignore'] : array();
// Now add all the patches from dependencies that will be installed.
$operations = $event->getOperations();
$this->io->write('<info>Gathering patches for dependencies. This might take a minute.</info>');
foreach ($operations as $operation) {
if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) {
$package = $this->getPackageFromOperation($operation);
$extra = $package->getExtra();
if (isset($extra['patches'])) {
if (isset($patches_ignore[$package->getName()])) {
foreach ($patches_ignore[$package->getName()] as $package_name => $patches) {
if (isset($extra['patches'][$package_name])) {
$extra['patches'][$package_name] = array_diff($extra['patches'][$package_name], $patches);
}
}
}
$this->patches = $this->arrayMergeRecursiveDistinct($this->patches, $extra['patches']);
}
// Unset installed patches for this package
if(isset($this->installedPatches[$package->getName()])) {
unset($this->installedPatches[$package->getName()]);
}
}
}
// Merge installed patches from dependencies that did not receive an update.
foreach ($this->installedPatches as $patches) {
$this->patches = $this->arrayMergeRecursiveDistinct($this->patches, $patches);
}
// If we're in verbose mode, list the projects we're going to patch.
if ($this->io->isVerbose()) {
foreach ($this->patches as $package => $patches) {
$number = count($patches);
$this->io->write('<info>Found ' . $number . ' patches for ' . $package . '.</info>');
}
}
// Make sure we don't gather patches again. Extra keys in $this->patches
// won't hurt anything, so we'll just stash it there.
$this->patches['_patchesGathered'] = TRUE;
}
/**
* Get the patches from root composer or external file
* @return Patches
* @throws \Exception
*/
public function grabPatches() {
// First, try to get the patches from the root composer.json.
$extra = $this->composer->getPackage()->getExtra();
if (isset($extra['patches'])) {
$this->io->write('<info>Gathering patches for root package.</info>');
$patches = $extra['patches'];
return $patches;
}
// If it's not specified there, look for a patches-file definition.
elseif (isset($extra['patches-file'])) {
$this->io->write('<info>Gathering patches from patch file.</info>');
$patches = file_get_contents($extra['patches-file']);
$patches = json_decode($patches, TRUE);
$error = json_last_error();
if ($error != 0) {
switch ($error) {
case JSON_ERROR_DEPTH:
$msg = ' - Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = ' - Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = ' - Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$msg = ' - Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$msg = ' - Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = ' - Unknown error';
break;
}
throw new \Exception('There was an error in the supplied patches file:' . $msg);
}
if (isset($patches['patches'])) {
$patches = $patches['patches'];
return $patches;
}
elseif(!$patches) {
throw new \Exception('There was an error in the supplied patch file');
}
}
else {
return array();
}
}
/**
* @param PackageEvent $event
* @throws \Exception
*/
public function postInstall(PackageEvent $event) {
// Check if we should exit in failure.
$extra = $this->composer->getPackage()->getExtra();
$exitOnFailure = getenv('COMPOSER_EXIT_ON_PATCH_FAILURE') || !empty($extra['composer-exit-on-patch-failure']);
$skipReporting = getenv('COMPOSER_PATCHES_SKIP_REPORTING') || !empty($extra['composer-patches-skip-reporting']);
// Get the package object for the current operation.
$operation = $event->getOperation();
/** @var PackageInterface $package */
$package = $this->getPackageFromOperation($operation);
$package_name = $package->getName();
if (!isset($this->patches[$package_name])) {
if ($this->io->isVerbose()) {
$this->io->write('<info>No patches found for ' . $package_name . '.</info>');
}
return;
}
$this->io->write(' - Applying patches for <info>' . $package_name . '</info>');
// Get the install path from the package object.
$manager = $event->getComposer()->getInstallationManager();
$install_path = $manager->getInstaller($package->getType())->getInstallPath($package);
// Set up a downloader.
$downloader = new RemoteFilesystem($this->io, $this->composer->getConfig());
// Track applied patches in the package info in installed.json
$localRepository = $this->composer->getRepositoryManager()->getLocalRepository();
$localPackage = $localRepository->findPackage($package_name, $package->getVersion());
$extra = $localPackage->getExtra();
$extra['patches_applied'] = array();
foreach ($this->patches[$package_name] as $description => $url) {
$this->io->write(' <info>' . $url . '</info> (<comment>' . $description. '</comment>)');
try {
$this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::PRE_PATCH_APPLY, $package, $url, $description));
$this->getAndApplyPatch($downloader, $install_path, $url, $package);
$this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::POST_PATCH_APPLY, $package, $url, $description));
$extra['patches_applied'][$description] = $url;
}
catch (\Exception $e) {
$this->io->write(' <error>Could not apply patch! Skipping. The error was: ' . $e->getMessage() . '</error>');
if ($exitOnFailure) {
throw new \Exception("Cannot apply patch $description ($url)!");
}
}
}
$localPackage->setExtra($extra);
$this->io->write('');
if (true !== $skipReporting) {
$this->writePatchReport($this->patches[$package_name], $install_path);
}
}
/**
* Get a Package object from an OperationInterface object.
*
* @param OperationInterface $operation
* @return PackageInterface
* @throws \Exception
*/
protected function getPackageFromOperation(OperationInterface $operation) {
if ($operation instanceof InstallOperation) {
$package = $operation->getPackage();
}
elseif ($operation instanceof UpdateOperation) {
$package = $operation->getTargetPackage();
}
else {
throw new \Exception('Unknown operation: ' . get_class($operation));
}
return $package;
}
/**
* Apply a patch on code in the specified directory.
*
* @param RemoteFilesystem $downloader
* @param $install_path
* @param $patch_url
* @param PackageInterface $package
* @throws \Exception
*/
protected function getAndApplyPatch(RemoteFilesystem $downloader, $install_path, $patch_url, PackageInterface $package) {
// Local patch file.
if (file_exists($patch_url)) {
$filename = realpath($patch_url);
}
else {
// Generate random (but not cryptographically so) filename.
$filename = uniqid(sys_get_temp_dir().'/') . ".patch";
// Download file from remote filesystem to this location.
$hostname = parse_url($patch_url, PHP_URL_HOST);
try {
$downloader->copy($hostname, $patch_url, $filename, false);
} catch (\Exception $e) {
// In case of an exception, retry once as the download might
// have failed due to intermittent network issues.
$downloader->copy($hostname, $patch_url, $filename, false);
}
}
// The order here is intentional. p1 is most likely to apply with git apply.
// p0 is next likely. p2 is extremely unlikely, but for some special cases,
// it might be useful. p4 is useful for Magento 2 patches
$patch_levels = array('-p1', '-p0', '-p2', '-p4');
// Check for specified patch level for this package.
$extra = $this->composer->getPackage()->getExtra();
if (!empty($extra['patchLevel'][$package->getName()])){
$patch_levels = array($extra['patchLevel'][$package->getName()]);
}
// Attempt to apply with git apply.
$patched = $this->applyPatchWithGit($install_path, $patch_levels, $filename);
// In some rare cases, git will fail to apply a patch, fallback to using
// the 'patch' command.
if (!$patched) {
foreach ($patch_levels as $patch_level) {
// --no-backup-if-mismatch here is a hack that fixes some
// differences between how patch works on windows and unix.
if ($patched = $this->executeCommand("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $install_path, $filename)) {
break;
}
}
}
// Clean up the temporary patch file.
if (isset($hostname)) {
unlink($filename);
}
// If the patch *still* isn't applied, then give up and throw an Exception.
// Otherwise, let the user know it worked.
if (!$patched) {
throw new \Exception("Cannot apply patch $patch_url");
}
}
/**
* Checks if the root package enables patching.
*
* @return bool
* Whether patching is enabled. Defaults to TRUE.
*/
protected function isPatchingEnabled() {
$extra = $this->composer->getPackage()->getExtra();
if (empty($extra['patches']) && empty($extra['patches-ignore']) && !isset($extra['patches-file'])) {
// The root package has no patches of its own, so only allow patching if
// it has specifically opted in.
return isset($extra['enable-patching']) ? $extra['enable-patching'] : FALSE;
}
else {
return TRUE;
}
}
/**
* Writes a patch report to the target directory.
*
* @param array $patches
* @param string $directory
*/
protected function writePatchReport($patches, $directory) {
$output = "This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)\n";
$output .= "Patches applied to this directory:\n\n";
foreach ($patches as $description => $url) {
$output .= $description . "\n";
$output .= 'Source: ' . $url . "\n\n\n";
}
file_put_contents($directory . "/PATCHES.txt", $output);
}
/**
* Executes a shell command with escaping.
*
* @param string $cmd
* @return bool
*/
protected function executeCommand($cmd) {
// Shell-escape all arguments except the command.
$args = func_get_args();
foreach ($args as $index => $arg) {
if ($index !== 0) {
$args[$index] = escapeshellarg($arg);
}
}
// And replace the arguments.
$command = call_user_func_array('sprintf', $args);
$output = '';
if ($this->io->isVerbose()) {
$this->io->write('<comment>' . $command . '</comment>');
$io = $this->io;
$output = function ($type, $data) use ($io) {
if ($type == Process::ERR) {
$io->write('<error>' . $data . '</error>');
}
else {
$io->write('<comment>' . $data . '</comment>');
}
};
}
return ($this->executor->execute($command, $output) == 0);
}
/**
* Recursively merge arrays without changing data types of values.
*
* Does not change the data types of the values in the arrays. Matching keys'
* values in the second array overwrite those in the first array, as is the
* case with array_merge.
*
* @param array $array1
* The first array.
* @param array $array2
* The second array.
* @return array
* The merged array.
*
* @see http://php.net/manual/en/function.array-merge-recursive.php#92195
*/
protected function arrayMergeRecursiveDistinct(array $array1, array $array2) {
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
}
else {
$merged[$key] = $value;
}
}
return $merged;
}
/**
* Attempts to apply a patch with git apply.
*
* @param $install_path
* @param $patch_levels
* @param $filename
*
* @return bool
* TRUE if patch was applied, FALSE otherwise.
*/
protected function applyPatchWithGit($install_path, $patch_levels, $filename) {
// Do not use git apply unless the install path is itself a git repo
// @see https://stackoverflow.com/a/27283285
if (!is_dir($install_path . '/.git')) {
return FALSE;
}
$patched = FALSE;
foreach ($patch_levels as $patch_level) {
if ($this->io->isVerbose()) {
$comment = 'Testing ability to patch with git apply.';
$comment .= ' This command may produce errors that can be safely ignored.';
$this->io->write('<comment>' . $comment . '</comment>');
}
$checked = $this->executeCommand('git -C %s apply --check -v %s %s', $install_path, $patch_level, $filename);
$output = $this->executor->getErrorOutput();
if (substr($output, 0, 7) == 'Skipped') {
// Git will indicate success but silently skip patches in some scenarios.
//
// @see https://github.com/cweagans/composer-patches/pull/165
$checked = FALSE;
}
if ($checked) {
// Apply the first successful style.
$patched = $this->executeCommand('git -C %s apply %s %s', $install_path, $patch_level, $filename);
break;
}
}
return $patched;
}
/**
* Indicates if a package has been patched.
*
* @param \Composer\Package\PackageInterface $package
* The package to check.
*
* @return bool
* TRUE if the package has been patched.
*/
public static function isPackagePatched(PackageInterface $package) {
return array_key_exists('patches_applied', $package->getExtra());
}
/**
* {@inheritDoc}
*/
public function deactivate(Composer $composer, IOInterface $io)
{
}
/**
* {@inheritDoc}
*/
public function uninstall(Composer $composer, IOInterface $io)
{
}
}
<?php
/**
* @file
* Tests event dispatching.
*/
namespace cweagans\Composer\Tests;
use cweagans\Composer\PatchEvent;
use cweagans\Composer\PatchEvents;
use Composer\Package\PackageInterface;
class PatchEventTest extends \PHPUnit_Framework_TestCase {
/**
* Tests all the getters.
*
* @dataProvider patchEventDataProvider
*/
public function testGetters($event_name, PackageInterface $package, $url, $description) {
$patch_event = new PatchEvent($event_name, $package, $url, $description);
$this->assertEquals($event_name, $patch_event->getName());
$this->assertEquals($package, $patch_event->getPackage());
$this->assertEquals($url, $patch_event->getUrl());
$this->assertEquals($description, $patch_event->getDescription());
}
public function patchEventDataProvider() {
$prophecy = $this->prophesize('Composer\Package\PackageInterface');
$package = $prophecy->reveal();
return array(
array(PatchEvents::PRE_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
array(PatchEvents::POST_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment