Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -0,0 +1,382 @@
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.

View file

@ -1,45 +1,69 @@
---SUMMARY---
CONTENTS OF THIS FILE
---------------------
Admin Toolbar intends to improve the default Drupal Toolbar to transformit into
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
Admin Toolbar intends to improve the default Drupal Toolbar to transform it into
a drop-down menu, providing a fast and full access to all administration links.
For a full description visit project page:
https://www.drupal.org/project/admin_toolbar
* For a full description of the module, visit the project page:
https://www.drupal.org/project/admin_toolbar
Bug reports, feature suggestions and latest developments:
http://drupal.org/project/issues/admin_toolbar
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
---REQUIREMENTS---
REQUIREMENTS
------------
No special requirements.
*None. (Other than a clean Drupal 8 installation)
RECOMMENDED MODULES
-------------------
* Admin Toolbar Extra Tools (https://www.drupal.org/project/admin_toolbar):
Provides menu links to administration pages or actions (eg. Flushing caches)
that are not generated by Drupal core.
* Admin Toolbar Links Access Filter (https://www.drupal.org/node/2474539):
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.
INSTALLATION
------------
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
---INSTALLATION---
CONFIGURATION
-------------
No configuration is needed.
Install as usual.
Place the entirety of this directory in the /modules folder of your Drupal
installation. Navigate to Administer > Extend. Check the 'Enabled' box next
to the 'Admin toolbar' and/or 'Admin toolbar Extra Tools' and then click
the 'Save Configuration' button at the bottom.
For help regarding installation, visit:
https://www.drupal.org/documentation/install/modules-themes/modules-8
---CONTACT---
Current Maintainers:
*Wilfrid Roze (eme) - https://www.drupal.org/u/eme
*Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid
* Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
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 information.
* 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
information.

View file

@ -1,13 +1,15 @@
name: Admin Toolbar
# core: 8.x
description: Provides a drop-down menu interface to the core Drupal Toolbar.
description: Provides an improved drop-down menu interface to the site Toolbar.
package: Administration
type: module
dependencies:
- toolbar
# Information added by Drupal.org packaging script on 2016-12-01
version: '8.x-1.18'
type: module
# core: 8.x
dependencies:
- drupal:toolbar
# Information added by Drupal.org packaging script on 2018-11-22
version: '8.x-1.25'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1480614186
datestamp: 1542915184

View file

@ -0,0 +1,16 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Admin Toolbar module.
*/
/**
* Rebuild routes to mitigate issue 2938884.
*
* @see https://www.drupal.org/project/admin_toolbar/issues/2938884
*/
function admin_toolbar_update_8001() {
// Rebuilding the route cache.
\Drupal::service("router.builder")->rebuild();
}

View file

@ -3,6 +3,8 @@ toolbar.tree:
theme:
css/admin.toolbar.css: {}
js:
js/jquery.hoverIntent.js: {}
js/admin_toolbar.js: {}
dependencies:
- core/jquery
- core/drupal

View file

@ -8,26 +8,32 @@
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Component\Utility\Html;
/**
* Implements hook_toolbar_alter().
* Implements hook_toolbar_alter().
*/
function admin_toolbar_toolbar_alter(&$items) {
$items['administration']['tray']['toolbar_administration']['#pre_render'] = array('admin_toolbar_prerender_toolbar_administration_tray');
$items['administration']['tray']['toolbar_administration']['#pre_render'] = ['admin_toolbar_prerender_toolbar_administration_tray'];
$items['administration']['#attached']['library'][] = 'admin_toolbar/toolbar.tree';
}
/**
* Implements hook_help().
* Implements hook_help().
*/
function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.admin_toolbar':
$variables = [
':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 or complimentary modules, listed on <a href="https://www.drupal.org/project/admin_toolbar">Admin Toolbar</a>, for quick access to system commands such as Flush all caches, <a href=":automated_cron">Run cron</a>, Run Updates, etc... For more information, see <a href=":admin_toolbar_documentation">the online documentation for the Admin Toolbar module</a>.', array(':toolbar' => Url::fromRoute('help.page', array('name' => 'toolbar'))->toString(), ':automated_cron' => (\Drupal::moduleHandler()->moduleExists('automated_cron')) ? Url::fromRoute('help.page', array('name' => 'automated_cron'))->toString() : '#', ':admin_toolbar_documentation' => 'https://www.drupal.org/node/2713693')) . '</p>';
$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 Drupal Toolbar by providing fast, full access to all links in the Drupal Toolbar without having to click to get there.') . '</p>';
$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;
}
@ -35,8 +41,10 @@ function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
/**
* 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()
* function, which uses setMaxDepth(4) instead of setTopLevelOnly().
*
* @param array $element
* A renderable array.
*
@ -50,18 +58,17 @@ function admin_toolbar_prerender_toolbar_administration_tray(array $element) {
$parameters = new MenuTreeParameters();
$parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth(4)->onlyEnabledLinks();
$tree = $menu_tree->load(NULL, $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
array('callable' => 'toolbar_tools_menu_navigation_links'),
);
$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.
*
@ -82,7 +89,8 @@ function toolbar_tools_menu_navigation_links(array $tree) {
$definition = $link->getPluginDefinition();
$element->options['attributes']['class'][] = 'toolbar-icon';
$element->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(array('.', ' ', '_'), array('-', '-', '-'), $definition['id']));
$string = strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
$element->options['attributes']['class'][] = Html::cleanCssIdentifier('toolbar-icon-' . $string);
$element->options['attributes']['title'] = $link->getDescription();
}
return $tree;

View file

@ -0,0 +1,53 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Admin Toolbar Links Access Filter module 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.
* To know why we create this module check this issue:
https://www.drupal.org/node/2917704
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
REQUIREMENTS
------------
This module requires the following modules:
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar)
INSTALLATION
------------
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
No configuration is needed.
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid

View file

@ -0,0 +1,15 @@
name: 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 <a href='https://www.drupal.org/node/296693'>https://www.drupal.org/node/296693</a> be solved, this module will be deprecated.
package: Administration
type: module
# core: 8.x
dependencies:
- admin_toolbar:admin_toolbar
# Information added by Drupal.org packaging script on 2018-11-22
version: '8.x-1.25'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1542915184

View file

@ -0,0 +1,200 @@
<?php
/**
* @file
* This module don't show menu links that you don't have access permission for.
*/
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;
/**
* Implements hook_help().
*/
function admin_toolbar_links_access_filter_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help.
case 'help.page.admin_toolbar_links_access_filter':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar Links Access Filter module provides a workaround for the common problem that users with <em>Use the administration pages and help</em> permission see menu links they done not have access permission for.') . '</p>';
return $output;
}
}
/**
* Implements hook_preprocess_menu().
*
* Hides links from admin menu, if user doesn't have access rights.
*/
function admin_toolbar_links_access_filter_preprocess_menu(&$variables) {
if (empty($variables['items'])) {
// Additional empty check to prevent exotic situations, where the preprocess
// function is entered even without items.
// @see https://www.drupal.org/node/2833885
return;
}
// Ensure that menu_name exists.
if (!isset($variables['menu_name'])) {
// In rare cases (for unknown reasons) menu_name may not be set.
// As fallback, we can fetch it from the first menu item.
$first_link = reset($variables['items']);
/** @var Drupal\Core\Menu\MenuLinkDefault $original_link */
// Fetch the menu_name from the original link.
$original_link = $first_link['original_link'];
$variables['menu_name'] = $original_link->getMenuName();
}
if ($variables['menu_name'] == 'admin') {
if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
admin_toolbar_links_access_filter_filter_non_accessible_links($variables['items']);
}
}
}
/**
* 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;
$route_params = [];
if (!empty($item['original_link'])) {
/** @var \Drupal\Core\Menu\MenuLinkBase $original_link */
$original_link = $item['original_link'];
if ($original_link->getUrlObject()->isExternal()) {
// Do not filter external URL at all.
continue;
}
$route_name = $original_link->getRouteName();
$route_params = $original_link->getRouteParameters();
}
// Check, if user has access rights to the route.
if (!\Drupal::accessManager()->checkNamedRoute($route_name, $route_params)) {
unset($items[$route]);
}
else {
if (!empty($items[$route]['below'])) {
// Recursively call this function for the child items.
admin_toolbar_links_access_filter_filter_non_accessible_links($items[$route]['below']);
}
if (empty($items[$route]['below']) && \Drupal::moduleHandler()->moduleExists('admin_toolbar')) {
// Every child item has been cleared out.
// Now check, if the given route represents an overview page only,
// without having functionality on its own. In this case, we can safely
// 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]);
}
else {
// Let's remove the expanded flag.
$items[$route]['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]);
}
}
}
}
/**
* Checks if the given route name is an overview page.
*
* Checks if the given route name matches a pure (admin) overview page that can
* be skipped, if there are no child items set. The typical example are routes
* having the SystemController::systemAdminMenuBlockPage() function as their
* controller callback set.
*
* @param string $route_name
* The route name to check.
*
* @return bool
* TRUE, if the given route name matches a pure admin overview page route,
* FALSE otherwise.
*/
function admin_toolbar_links_access_filter_is_overview_page($route_name) {
// @var \Drupal\Core\Routing\RouteProviderInterface $route_provider.
$route_provider = \Drupal::service('router.route_provider');
$overview_page_controllers = [
'\Drupal\system\Controller\AdminController::index',
'\Drupal\system\Controller\SystemController::overview',
'\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage',
];
try {
$route = $route_provider->getRouteByName($route_name);
$controller = $route->getDefault('_controller');
return !empty($controller) && in_array($controller, $overview_page_controllers);
}
catch (RouteNotFoundException $ex) {
}
return FALSE;
}
/**
* Checks, if the given user has admin rights.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check.
*
* @return bool
* TRUE, if the given user account has at least one role with admin rights
* assigned, FALSE otherwise.
*/
function admin_toolbar_links_access_filter_user_has_admin_role(AccountInterface $account) {
static $user_has_admin_role = [];
$uid = $account->id();
if (!isset($user_has_admin_role[$uid])) {
$roles = Role::loadMultiple($account->getRoles());
foreach ($roles as $role) {
if ($role->isAdmin()) {
$user_has_admin_role[$uid] = TRUE;
break;
}
$user_has_admin_role[$uid] = FALSE;
}
}
return $user_has_admin_role[$uid];
}

View file

@ -0,0 +1,38 @@
{
"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"
}
}

View file

@ -1,45 +1,62 @@
---SUMMARY---
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
Admin Toolbar Extra Tools provides menu links to administration pages or actions
(eg. Flushing caches) that are not generated by Drupal core. It adds menu items
that are not generated by Drupal core. For example there are no menu items for
each content type by default or menu links to Manage fields on each entity types.
That's the purpose of Admin Toolbar Extra Tools to add them.
each content type by default or menu links to Manage fields on each entity
types. That's the purpose of Admin Toolbar Extra Tools to add them.
For a full description visit project page:
https://www.drupal.org/project/admin_toolbar
* For a full description of the module, visit the project page:
https://www.drupal.org/project/admin_toolbar
Bug reports, feature suggestions and latest developments:
http://drupal.org/project/issues/admin_toolbar
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
---REQUIREMENTS---
REQUIREMENTS
------------
*Admin Toolbar.
This module requires the following modules:
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar)
---INSTALLATION---
INSTALLATION
------------
Install as usual.
Place the entirety of this directory in the /modules folder of your Drupal
installation. Navigate to Administer > Extend. Check the 'Enabled' box next
'Admin toolbar Extra Tools' and then click the 'Save Configuration' button at
the bottom.
For help regarding installation, visit:
https://www.drupal.org/documentation/install/modules-themes/modules-8
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
---CONTACT---
CONFIGURATION
-------------
Current Maintainers:
*Wilfrid Roze (eme) - https://www.drupal.org/u/eme
*Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
No configuration is needed.
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid
* Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
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 information.
* 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
information.

View file

@ -1,13 +1,16 @@
name: Admin Toolbar Extra Tools
# core: 8.x
description: Adds menu links to the Admin Toolbar.
package: Administration
type: module
dependencies:
- admin_toolbar
# Information added by Drupal.org packaging script on 2016-12-01
version: '8.x-1.18'
type: module
# core: 8.x
dependencies:
- admin_toolbar:admin_toolbar
- drupal:system (>=8.5)
# Information added by Drupal.org packaging script on 2018-11-22
version: '8.x-1.25'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1480614186
datestamp: 1542915184

View file

@ -63,29 +63,14 @@ admin_toolbar_tools.flush_menu:
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.flush_twig:
title: 'Flush twig cache'
route_name: admin_toolbar_tools.flush_twig
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.flush_rendercache:
title: 'Flush render cache'
route_name: admin_toolbar_tools.flush_rendercache
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.drupalorg:
title: 'Drupal.org'
weight: -5
route_name: admin_toolbar_tools.drupalorg
parent: admin_toolbar_tools.help
menu_name: admin
admin_toolbar_tools.listchanges:
title: 'Change records for Drupal core'
weight: -6
route_name: admin_toolbar_tools.listchanges
parent: admin_toolbar_tools.drupalorg
menu_name: admin
admin_toolbar_tools.doc:
title: 'D8 API documentation'
weight: -5
route_name: admin_toolbar_tools.doc
parent: admin_toolbar_tools.drupalorg
menu_name: admin

View file

@ -6,67 +6,90 @@
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_toolbar().
*/
function admin_toolbar_tools_toolbar() {
$items = array();
$items['admin_toolbar_tools'] = array(
$items = [];
$items['admin_toolbar_tools'] = [
'#type' => 'toolbar_item',
'tab' => array(
'tab' => [
'#type' => 'link',
'#attributes' => array(
'class' => array('toolbar-icon', 'toolbar-icon-admin-toolbar-tools-help'),
),
),
'#attached' => array('library' => array('admin_toolbar_tools/toolbar.icon'),
),
);
'#attributes' => [
'class' => ['toolbar-icon', 'toolbar-icon-admin-toolbar-tools-help'],
],
],
'#attached' => ['library' => ['admin_toolbar_tools/toolbar.icon']],
];
return $items;
}
/**
* Implements hook_help().
* Implements hook_help().
*/
function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.admin_toolbar_tools':
$output = '';
$output .= '<p>' . t('The Admin Toolbar Extra Tools module comes packaged with the <a href="https://www.drupal.org/project/admin_toolbar"> Admin Toolbar</a> module and adds functionality to it. The additional functionality is accessed thru extra links on the main administration <a href="https://www.drupal.org/project/toolbar">Toolbar</a>. Some links to Admin Toolbar Extra Tools administration pages are located at the bottom of this page. For more information, see the <a href="https://www.drupal.org/node/2713693">online documentation for Admin Toolbar</a>') . '</p>';
$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 .= '</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. The Admin Toolbar functionality can be further extended by installing complimentary modules. See <a href="https://www.drupal.org/project/admin_toolbar">Admin Toolbar</a> for a complete listing of these complimentary modules.') . '</p>';
$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().
* 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');
$routes = array();
$routes = [];
foreach ($routeProvider->getAllRoutes() as $route_name => $route) {
$routes[] = $route_name;
}
$entityTypes = $entityTypeManager->getDefinitions();
$content_entities = array();
$content_entities = [];
foreach ($entityTypes as $key => $entityType) {
if ($entityType->getBundleEntityType() && ($entityType->get('field_ui_base_route') != '')) {
$content_entities[$key] = array(
$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') && in_array('view.files.page_1', $routes)) {
$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 $module_name => $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) {
@ -75,23 +98,25 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if (in_array('entity.' . $content_entity_bundle . '.overview_form', $routes)) {
// 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] = array(
'title' => $bundle->label(),
$links[$content_entity_bundle_root] = [
'title' => t($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' => array($content_entity_bundle => $machine_name),
);
'route_parameters' => [$content_entity_bundle => $machine_name],
];
}
if (in_array('entity.' . $content_entity_bundle . '.edit_form', $routes)) {
$key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name;
$links[$key] = array(
'title' => $bundle->label(),
$links[$key] = [
'title' => t($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' => array($content_entity_bundle => $machine_name),
);
'route_parameters' => [$content_entity_bundle => $machine_name],
];
if (empty($content_entity_bundle_root)) {
$content_entity_bundle_root = $key;
}
@ -102,420 +127,551 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
}
if ($moduleHandler->moduleExists('field_ui')) {
if (in_array('entity.' . $content_entity . '.field_ui_fields', $routes)) {
$links['entity.' . $content_entity . '.field_ui_fields' . $machine_name] = array(
$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' => array($content_entity_bundle => $machine_name),
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 1,
);
];
}
if (in_array('entity.entity_form_display.' . $content_entity . '.default', $routes)) {
$links['entity.entity_form_display.' . $content_entity . '.default' . $machine_name] = array(
$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' => array($content_entity_bundle => $machine_name),
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 2,
);
];
}
if (in_array('entity.entity_view_display.' . $content_entity . '.default', $routes)) {
$links['entity.entity_view_display.' . $content_entity . '.default.' . $machine_name] = array(
$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' => array($content_entity_bundle => $machine_name),
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 3,
);
];
}
}
if ($moduleHandler->moduleExists('devel') && in_array('entity.' . $content_entity_bundle . '.devel_load', $routes)) {
$links['entity.' . $content_entity_bundle . '.devel_load.' . $machine_name] = array(
$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' => array($content_entity_bundle => $machine_name),
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 4,
);
];
}
if (in_array('entity.' . $content_entity_bundle . '.delete_form', $routes)) {
$links['entity.' . $content_entity_bundle . '.delete_form.' . $machine_name] = array(
$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' => array($content_entity_bundle => $machine_name),
'route_parameters' => [$content_entity_bundle => $machine_name],
'weight' => 5,
);
];
}
}
}
// Add user links.
$links['user.admin_create'] = array(
$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'] = array(
];
$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'] = array(
];
$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['user.role_add'] = array(
];
$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_'] = array(
$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_'] = array(
];
$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_'] = array(
];
$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()] = array(
'title' => $role->label(),
$links['entity.user_role.edit_form.' . $role->id()] = [
'title' => t($role->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.collection',
'route_parameters' => array('user_role' => $role->id()),
);
$links['entity.user_role.edit_permissions_form.' . $role->id()] = array(
'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' => array('user_role' => $role->id()),
);
$links['entity.user_role.delete_form.' . $role->id()] = array(
'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' => array('user_role' => $role->id()),
);
'route_parameters' => ['user_role' => $role->id()],
];
if ($moduleHandler->moduleExists('devel')) {
$links['entity.user_role.devel_load.' . $role->id()] = array(
$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' => array('user_role' => $role->id()),
);
'route_parameters' => ['user_role' => $role->id()],
];
}
}
if ($moduleHandler->moduleExists('node')) {
$links['node.add_page']['parent'] = 'system.admin_content';
$links['node.type_add'] = array(
$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
);
'weight' => -5,
];
// Add node links for each content type.
foreach (node_type_get_names() as $machine_name => $label) {
$links['node.add.' . $machine_name] = array(
'title' => $label,
foreach ($entityTypeManager->getStorage('node_type')->loadMultiple() as $type) {
$links['node.add.' . $type->id()] = [
'title' => t($type->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'node.add',
'menu_name' => 'admin',
'parent' => 'node.add_page',
'route_parameters' => array('node_type' => $machine_name),
);
'parent' => 'admin_toolbar_tools.add_content',
'route_parameters' => ['node_type' => $type->id()],
];
}
}
if ($moduleHandler->moduleExists('field_ui')) {
$links['field_ui.entity_form_mode_add'] = array(
$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'] = array(
];
$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'] = array(
$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'] = array(
$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
);
'weight' => -50,
];
// Adds links to /admin/structure/menu.
foreach (menu_ui_get_menus() as $machine_name => $label) {
$links['entity.menu.edit_form.' . $machine_name] = array(
'title' => $label,
$links['entity.menu.edit_form.' . $machine_name] = [
'title' => t($label),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
'route_parameters' => array('menu' => $machine_name),
);
$links['entity.menu.delete_form.' . $machine_name] = array(
'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' => array('menu' => $machine_name),
);
'route_parameters' => ['menu' => $machine_name],
];
if ($moduleHandler->moduleExists('devel')) {
$links['entity.menu.devel_load.' . $machine_name] = array(
$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' => array('menu' => $machine_name),
);
'route_parameters' => ['menu' => $machine_name],
];
}
$links['entity.menu.add_link_form.' . $machine_name] = array(
$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' => array('menu' => $machine_name),
);
'route_parameters' => ['menu' => $machine_name],
];
}
}
// If module block_content is enabled.
if ($moduleHandler->moduleExists('block_content')) {
$links['block_content.add_page'] = array(
$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'] = array(
'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'] = array(
];
$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'] = array(
$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
);
'weight' => -5,
];
}
// If module Update Manager is enabled.
if ($moduleHandler->moduleExists('update')) {
$links['update.module_update'] = array(
$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'] = array(
];
$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'] = array(
$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'] = array(
];
$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',
);
$links['admin_toolbar_tools.devel.configs_list'] = array(
];
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'] = array(
];
$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'] = array(
];
$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'] = array(
];
$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'] = array(
];
$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'] = array(
];
$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.execute_php'] = array(
];
$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',
);
$links['admin_toolbar_tools.devel.session'] = array(
];
$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'] = array(
];
$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 Views Ui enabled.
if ($moduleHandler->moduleExists('views_ui')) {
$links['views_ui.add'] = array(
$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['system.theme_settings_'] = array(
$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_'] = array(
$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_'] = array(
];
$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 = installedThemes();
$installed_themes = admin_toolbar_tools_installed_themes();
foreach ($installed_themes as $key_theme => $label_theme) {
$links['system.theme_settings_theme' . '.' . $key_theme] = array(
$links['system.theme_settings_theme.' . $key_theme] = [
'title' => t($label_theme),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.theme_settings_theme',
'menu_name' => 'admin',
'parent' => 'system.theme_settings_',
'route_parameters' => array(
'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')) {
$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' => t($type->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.add_form',
'parent' => 'admin_toolbar_tools.add_media',
'route_parameters' => ['media_type' => $type->id()],
];
}
}
// 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.
*/
function installedThemes() {
$all_themes = \Drupal::service('theme_handler')->listInfo();
$themes_installed = array();
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 (\Drupal::service('theme_handler')->hasUi($key_theme)) {
$themes_installed[$key_theme] = $theme->getName();
if ($themeHandler->hasUi($key_theme)) {
$themes_installed[$key_theme] = $themeHandler->getName($key_theme);
}
}
return $themes_installed;
}
/**
* Get all links related to entity
* @param $entity_type_id
* @return array
*/
function getLinks($entity_type_id) {
$entity = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
// Get all links related to entity.
$links = $entity->getLinkTemplates();
return $links;
}

View file

@ -6,38 +6,43 @@ admin_toolbar_tools.flush:
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.cssjs:
path: '/admin/flush/cssjs'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_js_css'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushJsCss'
_title: 'Flush Css and Javascript'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.plugin:
path: '/admin/flush/plugin'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_plugins'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushPlugins'
_title: 'Plugin'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_static:
path: '/admin/flush/static-caches'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_static'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushStatic'
_title: 'Static caches'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_menu:
path: '/admin/flush/menu'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_menu'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushMenu'
_title: 'Menu'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_rendercache:
path: '/admin/flush/rendercache'
defaults:
@ -46,38 +51,25 @@ admin_toolbar_tools.flush_rendercache:
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.drupalorg:
path: '/admin/drupal8'
admin_toolbar_tools.flush_views:
path: '/admin/flush/views'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::drupal_org'
_title: 'Drupal'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_toolbar_tools.listchanges:
path: '/admin/drupal/list-changes'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::list_changes'
_title: 'List changes'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_toolbar_tools.doc:
path: '/admin/drupal/documentation'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::documentation'
_title: 'Documentation D8'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_development:
path: '/admin/development'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::development'
_title: 'Development'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushViews'
_title: 'Views'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_twig:
path: '/admin/flush/twig'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushTwig'
_title: 'Twig'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar.run.cron:
path: '/run-cron'
defaults:

View file

@ -0,0 +1,39 @@
{
"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.5"
}
}

View file

@ -2,52 +2,130 @@
namespace Drupal\admin_toolbar_tools\Controller;
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\MenuLinkManagerInterface;
use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Menu\ContextualLinkManager;
use Drupal\Core\Menu\LocalActionManager;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Core\Menu\MenuLinkManager;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\PhpStorage\PhpStorageFactory;
/**
* Class ToolbarController
* Class ToolbarController.
*
* @package Drupal\admin_toolbar_tools\Controller
*/
class ToolbarController extends ControllerBase {
/**
* The cron service.
* A cron instance.
*
* @var $cron \Drupal\Core\CronInterface
* @var \Drupal\Core\CronInterface
*/
protected $cron;
/**
* A menu link manager instance.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $menuLinkManager;
/**
* A context link manager instance.
*
* @var \Drupal\Core\Menu\ContextualLinkManagerInterface
*/
protected $contextualLinkManager;
/**
* A local task manager instance.
*
* @var \Drupal\Core\Menu\LocalTaskManagerInterface
*/
protected $localTaskLinkManager;
/**
* A local action manager instance.
*
* @var \Drupal\Core\Menu\LocalActionManagerInterface
*/
protected $localActionLinkManager;
/**
* A cache backend interface instance.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheRender;
/**
* Constructs a CronController object.
* A date time instance.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* A request stack symfony instance.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* A plugin cache clear instance.
*
* @var \Drupal\Core\Plugin\CachedDiscoveryClearerInterface
*/
protected $pluginCacheClearer;
/**
* Constructs a ToolbarController object.
*
* @param \Drupal\Core\CronInterface $cron
* The cron service.
* A cron instance.
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
* A menu link manager instance.
* @param \Drupal\Core\Menu\ContextualLinkManagerInterface $contextualLinkManager
* A context link manager instance.
* @param \Drupal\Core\Menu\LocalTaskManagerInterface $localTaskLinkManager
* A local task manager instance.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $localActionLinkManager
* A local action manager instance.
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheRender
* A cache backend interface instance.
* @param \Drupal\Component\Datetime\TimeInterface $time
* A date time instance.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* A request stack symfony instance.
* @param \Drupal\Core\Plugin\CachedDiscoveryClearerInterface $plugin_cache_clearer
* A plugin cache clear instance.
*/
public function __construct(CronInterface $cron,
MenuLinkManager $menuLinkManager,
ContextualLinkManager $contextualLinkManager,
LocalTaskManager $localTaskLinkManager,
LocalActionManager $localActionLinkManager,
CacheBackendInterface $cacheRender) {
MenuLinkManagerInterface $menuLinkManager,
ContextualLinkManagerInterface $contextualLinkManager,
LocalTaskManagerInterface $localTaskLinkManager,
LocalActionManagerInterface $localActionLinkManager,
CacheBackendInterface $cacheRender,
TimeInterface $time,
RequestStack $request_stack,
CachedDiscoveryClearerInterface $plugin_cache_clearer) {
$this->cron = $cron;
$this->menuLinkManager = $menuLinkManager;
$this->contextualLinkManager = $contextualLinkManager;
$this->localTaskLinkManager = $localTaskLinkManager;
$this->localActionLinkManager = $localActionLinkManager;
$this->cacheRender = $cacheRender;
$this->time = $time;
$this->requestStack = $request_stack;
$this->pluginCacheClearer = $plugin_cache_clearer;
}
/**
@ -60,97 +138,112 @@ class ToolbarController extends ControllerBase {
$container->get('plugin.manager.menu.contextual_link'),
$container->get('plugin.manager.menu.local_task'),
$container->get('plugin.manager.menu.local_action'),
$container->get('cache.render')
$container->get('cache.render'),
$container->get('datetime.time'),
$container->get('request_stack'),
$container->get('plugin.cache_clearer')
);
}
// Reload the previous page.
public function reload_page() {
$request = \Drupal::request();
if($request->server->get('HTTP_REFERER')) {
/**
* Reload the previous page.
*/
public function reloadPage() {
$request = $this->requestStack->getCurrentRequest();
if ($request->server->get('HTTP_REFERER')) {
return $request->server->get('HTTP_REFERER');
}
else{
else {
return '/';
}
}
// Flushes all caches.
/**
* Flushes all caches.
*/
public function flushAll() {
$this->messenger()->addMessage($this->t('All caches cleared.'));
drupal_flush_all_caches();
drupal_set_message($this->t('All caches cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
// Flushes css and javascript caches.
public function flush_js_css() {
\Drupal::state()
->set('system.css_js_query_string', base_convert(REQUEST_TIME, 10, 36));
drupal_set_message($this->t('CSS and JavaScript cache cleared.'));
return new RedirectResponse($this->reload_page());
/**
* Flushes css and javascript caches.
*/
public function flushJsCss() {
$this->state()
->set('system.css_js_query_string', base_convert($this->time->getCurrentTime(), 10, 36));
$this->messenger()->addMessage($this->t('CSS and JavaScript cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
// Flushes plugins caches.
public function flush_plugins() {
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
drupal_set_message($this->t('Plugins cache cleared.'));
return new RedirectResponse($this->reload_page());
/**
* Flushes plugins caches.
*/
public function flushPlugins() {
$this->pluginCacheClearer->clearCachedDefinitions();
$this->messenger()->addMessage($this->t('Plugins cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
// Resets all static caches.
public function flush_static() {
/**
* Resets all static caches.
*/
public function flushStatic() {
drupal_static_reset();
drupal_set_message($this->t('Static cache cleared.'));
return new RedirectResponse($this->reload_page());
$this->messenger()->addMessage($this->t('Static cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
// Clears all cached menu data.
public function flush_menu() {
/**
* Clears all cached menu data.
*/
public function flushMenu() {
menu_cache_clear_all();
$this->menuLinkManager->rebuild();
$this->contextualLinkManager->clearCachedDefinitions();
$this->localTaskLinkManager->clearCachedDefinitions();
$this->localActionLinkManager->clearCachedDefinitions();
drupal_set_message($this->t('Routing and links cache cleared.'));
return new RedirectResponse($this->reload_page());
$this->messenger()->addMessage($this->t('Routing and links cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
// Links to drupal.org home page.
public function drupal_org() {
$response = new RedirectResponse("https://www.drupal.org");
$response->send();
return $response;
/**
* Clears all cached views data.
*/
public function flushViews() {
views_invalidate_cache();
$this->messenger()->addMessage($this->t('Views cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
// Displays the administration link Development.
public function development() {
return new RedirectResponse('/admin/structure/menu/');
}
// Access to Drupal 8 changes (list changes of the different versions of drupal core).
public function list_changes() {
$response = new RedirectResponse("https://www.drupal.org/list-changes");
$response->send();
return $response;
}
// Adds a link to the Drupal 8 documentation.
public function documentation() {
$response = new RedirectResponse("https://api.drupal.org/api/drupal/8");
$response->send();
return $response;
/**
* Clears the twig cache.
*/
public function flushTwig() {
// @todo Update once Drupal 8.6 will be released.
// @see https://www.drupal.org/node/2908461
PhpStorageFactory::get('twig')->deleteAll();
$this->messenger()->addMessage($this->t('Twig cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
/**
* Run the cron.
*/
public function runCron() {
$this->cron->run();
drupal_set_message($this->t('Cron ran successfully.'));
return new RedirectResponse($this->reload_page());
$this->messenger()->addMessage($this->t('Cron ran successfully.'));
return new RedirectResponse($this->reloadPage());
}
/**
* Clear the rendered cache.
*/
public function cacheRender() {
$this->cacheRender->invalidateAll();
drupal_set_message($this->t('Render cache cleared.'));
return new RedirectResponse($this->reload_page());
$this->messenger()->addMessage($this->t('Render cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
}

View file

@ -1,23 +1,26 @@
<?php
namespace Drupal\admin_toolbar_tools\Tests;
use Drupal\simpletest\WebTestBase;
namespace Drupal\Tests\admin_toolbar_tools\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests for the existence of Admin Toolbar tools new links.
*
* @group admin_toolbar
*/
class AdminToolbarToolsAlterTest extends WebTestBase {
class AdminToolbarToolsAlterTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['toolbar', 'admin_toolbar', 'admin_toolbar_tools'];
protected static $modules = [
'toolbar',
'admin_toolbar',
'admin_toolbar_tools',
];
/**
* A test user with permission to access the administrative toolbar.
@ -31,20 +34,21 @@ class AdminToolbarToolsAlterTest extends WebTestBase {
*/
protected function setUp() {
parent::setUp();
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'access administration pages',
'administer site configuration',
]);
$this->drupalLogin($this->adminUser);
}
/**
* Tests for a the hover of sub menus.
* Tests for the hover of sub menus.
*/
function testAdminToolbarTools() {
public function testAdminToolbarTools() {
// Assert that special menu items are present in the HTML.
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-flush"');
}
}

View file

@ -1,17 +1,35 @@
{
"name": "drupal/admin_toolbar",
"description": "Admin Toolbar improve the default Drupal Toolbar, it lets the hover of sub menus.",
"description": "Provides a drop-down menu interface to the core Drupal Toolbar.",
"type": "drupal-module",
"keywords": ["Drupal", "Toolbar"],
"homepage": "http://drupal.org/project/admin_toolbar",
"license": "GPL-2.0+",
"authors": [
{
"name": "Mohamed Anis Taktak",
"homepage": "https://www.drupal.org/u/matio89"
}
"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"
},
"license": "GPL-2.0+",
"require": { }
"issues": "https://www.drupal.org/project/issues/admin_toolbar",
"source": "http://cgit.drupalcode.org/admin_toolbar"
}
}

View file

@ -1,12 +1,17 @@
/*---------------------- menu horizontal hover---- Krout Fethi FrontEnd Developer-----*/
.toolbar-tray-horizontal .menu-item:hover {
background: #fff;
}
.toolbar-tray-horizontal .menu-item:focus {
.toolbar-tray-horizontal .menu-item a:focus {
background: #abeae4;
}
.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-repeat: no-repeat;
}
.toolbar-tray-horizontal .menu-item--expanded .menu {
background: #fff;
width: auto;
@ -17,7 +22,7 @@
background-color: #f5f5f2;
}
.toolbar-tray-horizontal ul li.menu-item {
.toolbar-tray-horizontal ul li li.menu-item {
border-top: none transparent;
border-right: 1px solid #dddddd;
border-bottom: 1px solid #dddddd;
@ -33,20 +38,20 @@
border-top: 1px solid #dddddd;
}
.toolbar-tray-horizontal li.menu-item--expanded:hover ul ul,
.toolbar-tray-horizontal li.menu-item--expanded:hover ul ul ul,
.toolbar-tray-horizontal li.menu-item--expanded:hover ul ul ul ul,
.toolbar-tray-horizontal li.menu-item--expanded:hover ul ul ul ul ul {
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul,
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul,
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul,
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul ul {
display: none;
left: -999em; /* LTR */
}
/* Lists nested under hovered list items */
.toolbar-tray-horizontal li.menu-item--expanded:hover ul,
.toolbar-tray-horizontal li li.menu-item--expanded:hover ul,
.toolbar-tray-horizontal li li li.menu-item--expanded:hover ul,
.toolbar-tray-horizontal li li li li.menu-item--expanded:hover ul,
.toolbar-tray-horizontal li li li li li.menu-item--expanded:hover ul {
.toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul,
.toolbar-tray-horizontal li li.menu-item--expanded.hover-intent ul,
.toolbar-tray-horizontal li li li.menu-item--expanded.hover-intent ul,
.toolbar-tray-horizontal li li li li.menu-item--expanded.hover-intent ul,
.toolbar-tray-horizontal li li li li li.menu-item--expanded.hover-intent ul {
display: block;
left: auto; /* LTR */
}
@ -56,7 +61,7 @@
padding: 12px 15px 12px 12px;
}
.toolbar-tray-horizontal ul li.menu-item--expanded:hover ul {
.toolbar-tray-horizontal ul li.menu-item--expanded.hover-intent ul {
display: block;
position: absolute;
width: 200px;
@ -74,7 +79,7 @@
background-repeat: no-repeat;
}
.toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
.toolbar-tray-horizontal ul li.menu-item--expanded .menu-item.hover-intent ul {
display: block;
margin: -40px 0 0 197px;
}
@ -83,6 +88,10 @@
float: none;
}
.toolbar-tray-horizontal li.hover-intent ul li {
float: none;
}
.toolbar-tray-horizontal .toolbar .level-2 > ul {
position: absolute;
padding-top: 0;
@ -95,24 +104,79 @@
display: block;
}
[dir="rtl"] .toolbar-tray-horizontal .toolbar .level-2 > ul {
[dir="rtl"] .toolbar-tray-horizontal .menu-item:hover {
background: #fff;
}
[dir="rtl"] .toolbar-tray-horizontal .menu-item a:focus {
background: #abeae4;
}
[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-repeat: no-repeat;
}
[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded .menu {
background: #fff;
width: auto;
height: auto;
}
[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded {
background-color: #f5f5f2;
}
[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;
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal .menu-item:last-child {
border-left: 1px solid #dddddd;
border-right: 1px solid #dddddd;
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal ul ul li.menu-item:first-child {
border-top: 1px solid #dddddd;
}
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul,
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul,
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul,
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul ul {
display: none;
left: -999em; /* LTR */
}
/* Lists nested under hovered list items */
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul,
[dir="rtl"] .toolbar-tray-horizontal li li.menu-item--expanded.hover-intent ul,
[dir="rtl"] .toolbar-tray-horizontal li li li.menu-item--expanded.hover-intent ul,
[dir="rtl"] .toolbar-tray-horizontal li li li li.menu-item--expanded.hover-intent ul,
[dir="rtl"] .toolbar-tray-horizontal li li li li li.menu-item--expanded.hover-intent ul {
display: block;
left: auto; /* LTR */
}
[dir="rtl"] .toolbar-tray-horizontal .menu ul li a,
[dir="rtl"] .toolbar-tray-horizontal .menu ul .toolbar-icon {
padding: 12px 15px 12px 12px;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded.hover-intent ul {
display: block;
position: absolute;
padding-top: 0;
top: 0;
right: 200px;
width: 200px;
box-shadow: 2px 2px 3px hsla(0, 0%, 0%, 0.4);
z-index: 1;
}
[dir="rtl"] .toolbar-tray-horizontal li:hover ul li {
float: none !important;
}
[dir="rtl"] .toolbar .toolbar-menu ul .toolbar-icon {
padding-left: 1.3333em;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
margin: -40px 197px 0 0;
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item > ul {
display: none;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
@ -120,3 +184,28 @@
background-image: url('../misc/icons/0074bd/chevron-left.svg');
background-repeat: no-repeat;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item.hover-intent ul {
display: block;
margin: -40px 197px 0 0;
}
[dir="rtl"] .toolbar-tray-horizontal li:hover ul li {
float: none;
}
[dir="rtl"] .toolbar-tray-horizontal li.hover-intent ul li {
float: none;
}
[dir="rtl"] .toolbar-tray-horizontal .toolbar .level-2 > ul {
position: absolute;
padding-top: 0;
top: 0;
left: 200px;
width: 200px;
}
[dir="rtl"] .toolbar .toolbar-tray-vertical li.open > ul.toolbar-menu.clearfix {
display: block;
}

View file

@ -1,5 +1,53 @@
(function($) {
$(document).ready(function() {
$('a.toolbar-icon').removeAttr('title');
});
})(jQuery);
(function ($, Drupal) {
Drupal.behaviors.adminToolbar = {
attach: function (context, settings) {
$('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');
$(this).parents('li.menu-item--expanded').addClass('hover-intent');
});
$('ul.toolbar-menu li.menu-item a', context).keydown(function (e) {
if ((e.shiftKey && (e.keyCode || e.which) == 9)) {
if ($(this).parent('.menu-item').prev().hasClass('menu-item--expanded')) {
$(this).parent('.menu-item').prev().addClass('hover-intent');
}
}
});
$('.toolbar-menu:first-child > .menu-item:not(.menu-item--expanded) a, .toolbar-tab > a', context).on('focusin', function () {
$('.menu-item--expanded').removeClass('hover-intent');
});
$('.toolbar-menu:first-child > .menu-item', context).on('hover', function () {
$(this, 'a').css("background: #fff;");
});
$('ul:not(.toolbar-menu)', context).on({
mousemove: function () {
$('li.menu-item--expanded').removeClass('hover-intent');
},
hover: function () {
$('li.menu-item--expanded').removeClass('hover-intent');
}
});
}
};
})(jQuery, Drupal);

View file

@ -0,0 +1,158 @@
/*!
* hoverIntent v1.8.1 // 2014.08.11 // jQuery v1.9.1+
* http://briancherne.github.io/jquery-hoverIntent/
*
* You may use hoverIntent under the terms of the MIT license. Basically that
* means you are free to use hoverIntent as long as this header is left intact.
* Copyright 2007, 2014 Brian Cherne
*/
/* hoverIntent is similar to jQuery's built-in "hover" method except that
* instead of firing the handlerIn function immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the event. The handlerOut function is only
* called after a matching handlerIn.
*
* // basic usage ... just like .hover()
* .hoverIntent( handlerIn, handlerOut )
* .hoverIntent( handlerInOut )
*
* // basic usage ... with event delegation!
* .hoverIntent( handlerIn, handlerOut, selector )
* .hoverIntent( handlerInOut, selector )
*
* // using a basic configuration object
* .hoverIntent( config )
*
* @param handlerIn function OR configuration object
* @param handlerOut function OR selector for delegation OR undefined
* @param selector selector OR undefined
* @author Brian Cherne <brian(at)cherne(dot)net>
*/
;(function(factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (jQuery && !jQuery.fn.hoverIntent) {
factory(jQuery);
}
})(function($) {
'use strict';
// default configuration values
var _cfg = {
interval: 100,
sensitivity: 6,
timeout: 0
};
// counter used to generate an ID for each instance
var INSTANCE_COUNT = 0;
// current X and Y position of mouse, updated during mousemove tracking (shared across instances)
var cX, cY;
// saves the current pointer position coordinates based on the given mousemove event
var track = function(ev) {
cX = ev.pageX;
cY = ev.pageY;
};
// compares current and previous mouse positions
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 ) {
$el.off(s.event,track);
delete s.timeoutId;
// set hoverIntent state as active for this element (permits `out` handler to trigger)
s.isActive = true;
// overwrite old mouseenter event coordinates with most recent pointer position
ev.pageX = cX; ev.pageY = cY;
// clear coordinate data from state object
delete s.pX; delete s.pY;
return cfg.over.apply($el[0],[ev]);
} else {
// 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 );
}
};
// triggers given `out` function at configured `timeout` after a mouseleave and clears state
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) {
// instance ID, used as a key to store and retrieve state information on an element
var instanceId = INSTANCE_COUNT++;
// extend the default configuration and parse parameters
var cfg = $.extend({}, _cfg);
if ( $.isPlainObject(handlerIn) ) {
cfg = $.extend(cfg, handlerIn);
if ( !$.isFunction(cfg.out) ) {
cfg.out = cfg.over;
}
} else if ( $.isFunction(handlerOut) ) {
cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } );
} else {
cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } );
}
// A private function for handling mouse 'hovering'
var handleHover = function(e) {
// cloned event to pass to handlers (copy required for event object to be passed in IE)
var ev = $.extend({},e);
// the current target of the mouse event, wrapped in a jQuery object
var $el = $(this);
// read hoverIntent data from element (or initialize if not present)
var hoverIntentData = $el.data('hoverIntent');
if (!hoverIntentData) { $el.data('hoverIntent', (hoverIntentData = {})); }
// read per-instance state from element (or initialize if not present)
var state = hoverIntentData[instanceId];
if (!state) { hoverIntentData[instanceId] = state = { id: instanceId }; }
// state properties:
// id = instance ID, used to clean up data
// timeoutId = timeout ID, reused for tracking mouse position and delaying "out" handler
// isActive = plugin state, true after `over` is called just until `out` is called
// pX, pY = previously-measured pointer coordinates, updated at each polling interval
// event = string representing the namespaced event used for mouse tracking
// clear any existing timeout
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;
// handle the event, based on its type
if (e.type === 'mouseenter') {
// do nothing if already active
if (state.isActive) { return; }
// set "previous" X and Y position based on initial entry point
state.pX = ev.pageX; state.pY = ev.pageY;
// 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 );
} 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 );
}
};
// listen for mouseenter and mouseleave
return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector);
};
});

View file

@ -1,23 +1,26 @@
<?php
namespace Drupal\admin_toolbar\Tests;
use Drupal\simpletest\WebTestBase;
namespace Drupal\Tests\admin_toolbar\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Test the existence of Admin Toolbar module.
*
* @group admin_toolbar
*/
class AdminToolbarAlterTest extends WebTestBase {
class AdminToolbarAlterTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['toolbar', 'breakpoint', 'admin_toolbar'];
protected static $modules = [
'toolbar',
'breakpoint',
'admin_toolbar',
];
/**
* A test user with permission to access the administrative toolbar.
@ -43,8 +46,9 @@ class AdminToolbarAlterTest extends WebTestBase {
/**
* Tests for a the hover of sub menus.
*/
function testAdminToolbar() {
public function testAdminToolbar() {
// Assert that expanded links are present in the HTML.
$this->assertRaw('class="toolbar-icon toolbar-icon-user-admin-index"');
}
}

View file

@ -11,3 +11,10 @@ reduce_ascii : FALSE
case : TRUE
ignore_words : 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with'
update_action : 2
safe_tokens:
- alias
- path
- join-path
- login-url
- url
- url-brief

View file

@ -22,14 +22,17 @@ pathauto.settings:
type: boolean
reduce_ascii:
type: boolean
ignore_words:
type: string
case:
type: boolean
ignore_words:
type: string
update_action:
type: integer
safe_tokens:
label: Tokens that are safe to use and do not need to be cleaned.
type: sequence
sequence:
type: string
action.configuration.pathauto_update_alias:
type: action_configuration_default

View file

@ -93,9 +93,8 @@ function hook_pathauto_is_alias_reserved($alias, $source, $langcode) {
* This hook will only be called if a default pattern is configured (on
* admin/config/search/path/patterns).
*
* @param string $pattern
* The alias pattern for Pathauto to pass to token_replace() to generate the
* URL alias.
* @param \Drupal\pathauto\PathautoPatternInterface $pattern
* The Pathauto pattern to be used.
* @param array $context
* An associative array of additional options, with the following elements:
* - 'module': The module or entity type being aliased.
@ -103,14 +102,14 @@ function hook_pathauto_is_alias_reserved($alias, $source, $langcode) {
* aliased. Can be either 'insert', 'update', 'return', or 'bulkupdate'.
* - 'source': A string of the source path for the alias (e.g. 'node/1').
* - 'data': An array of keyed objects to pass to token_replace().
* - 'type': The sub-type or bundle of the object being aliased.
* - 'bundle': The sub-type or bundle of the object being aliased.
* - 'language': A string of the language code for the alias (e.g. 'en').
* This can be altered by reference.
*/
function hook_pathauto_pattern_alter(&$pattern, array $context) {
function hook_pathauto_pattern_alter(\Drupal\pathauto\PathautoPatternInterface $pattern, array $context) {
// Switch out any [node:created:*] tokens with [node:updated:*] on update.
if ($context['module'] == 'node' && ($context['op'] == 'update')) {
$pattern = preg_replace('/\[node:created(\:[^]]*)?\]/', '[node:updated$1]', $pattern);
$pattern->setPattern(preg_replace('/\[node:created(\:[^]]*)?\]/', '[node:updated$1]', $pattern->getPattern()));
}
}
@ -133,7 +132,7 @@ function hook_pathauto_pattern_alter(&$pattern, array $context) {
* - 'pattern': A string of the pattern used for aliasing the object.
*/
function hook_pathauto_alias_alter(&$alias, array &$context) {
// Add a suffix so that all aliases get saved as 'content/my-title.html'
// Add a suffix so that all aliases get saved as 'content/my-title.html'.
$alias .= '.html';
// Force all aliases to be saved as language neutral.
@ -143,7 +142,7 @@ function hook_pathauto_alias_alter(&$alias, array &$context) {
/**
* Alter the list of punctuation characters for Pathauto control.
*
* @param $punctuation
* @param array $punctuation
* An array of punctuation to be controlled by Pathauto during replacement
* keyed by punctuation name. Each punctuation record should be an array
* with the following key/value pairs:

View file

@ -6,6 +6,7 @@ type: module
dependencies:
- ctools:ctools
- drupal:path
- drupal:system (>=8.5)
- token:token
configure: entity.pathauto_pattern.collection
@ -13,8 +14,8 @@ configure: entity.pathauto_pattern.collection
recommends:
- redirect:redirect
# Information added by Drupal.org packaging script on 2017-04-29
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-09-08
version: '8.x-1.3'
core: '8.x'
project: 'pathauto'
datestamp: 1493468049
datestamp: 1536407890

View file

@ -8,15 +8,13 @@
*/
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\pathauto\Entity\PathautoPattern;
/**
* Implements hook_install().
*/
function pathauto_install() {
// Set the weight to 1
// Set the weight to 1.
module_set_weight('pathauto', 1);
// Ensure the url_alias table exists.
@ -170,7 +168,8 @@ function pathauto_update_8100() {
continue;
}
// This is a pattern for a bundle and a language, such as "node_article_es".
// This is a pattern for a bundle and a language, such as
// "node_article_es".
$pattern = PathautoPattern::create([
'id' => $entity_type . '_' . $extracted_bundle . '_' . str_replace('-', '_', $langcode),
'label' => $entity_label . ' ' . $bundle_info[$extracted_bundle]['label'] . ' ' . $language->getName(),
@ -299,3 +298,23 @@ function pathauto_update_8106() {
$config->set('enabled_entity_types', ['user']);
$config->save();
}
/**
* Initialize the new safe tokens setting.
*/
function pathauto_update_8107() {
$safe_tokens = [
'alias',
'alias',
'path',
'join-path',
'login-url',
'url',
'url-brief',
];
\Drupal::configFactory()->getEditable('pathauto.settings')
->set('safe_tokens', $safe_tokens)
->save();
}

View file

@ -2,9 +2,9 @@
'use strict';
Drupal.behaviors.pathFieldsetSummaries = {
attach: function (context) {
$('fieldset.path-form', context).drupalSetSummary(function (context) {
var path = $('.form-item-path-alias input', context).val();
var automatic = $('.form-item-path-pathauto input', context).attr('checked');
$(context).find('.path-form').drupalSetSummary(function (context) {
var path = $('.js-form-item-path-0-alias input', context).val();
var automatic = $('.js-form-item-path-0-pathauto input', context).prop('checked');
if (automatic) {
return Drupal.t('Automatic alias');

View file

@ -3,4 +3,3 @@ entity.pathauto_pattern.add_form:
title: 'Add Pathauto pattern'
appears_on:
- entity.pathauto_pattern.collection

View file

@ -25,7 +25,8 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\pathauto\PathautoState;
use Drupal\pathauto\PathautoFieldItemList;
use Drupal\pathauto\PathautoItem;
/**
* The default ignore word list.
@ -98,12 +99,12 @@ function pathauto_entity_update(EntityInterface $entity) {
}
/**
* Implements hook_entity_update().
* Implements hook_entity_delete().
*/
function pathauto_entity_delete(EntityInterface $entity) {
if ($entity->hasLinkTemplate('canonical') && $entity instanceof ContentEntityInterface && $entity->hasField('path')) {
\Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($entity);
$entity->path->first()->get('pathauto')->purge();
$entity->get('path')->first()->get('pathauto')->purge();
}
}
@ -111,7 +112,8 @@ function pathauto_entity_delete(EntityInterface $entity) {
* Implements hook_field_info_alter().
*/
function pathauto_field_info_alter(&$info) {
$info['path']['class'] = '\Drupal\pathauto\PathautoItem';
$info['path']['class'] = PathautoItem::class;
$info['path']['list_class'] = PathautoFieldItemList::class;
}
/**
@ -146,17 +148,6 @@ function pathauto_entity_base_field_info(EntityTypeInterface $entity_type) {
}
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function pathauto_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
if (isset($fields['path'])) {
// Path fields need to be computed so that the pathauto state can be
// accessed even if there is no alias being set.
$fields['path']->setComputed(TRUE);
}
}
/**
* Validate the pattern field, to ensure it doesn't contain any characters that
* are invalid in URLs.

View file

@ -15,7 +15,7 @@ services:
arguments: ['@config.factory', '@pathauto.alias_storage_helper','@module_handler', '@router.route_provider', '@path.alias_manager']
pathauto.verbose_messenger:
class: Drupal\pathauto\VerboseMessenger
arguments: ['@config.factory', '@current_user']
arguments: ['@config.factory', '@current_user', '@messenger']
plugin.manager.alias_type:
class: Drupal\pathauto\AliasTypeManager
parent: default_plugin_manager

View file

@ -104,7 +104,7 @@ class AliasCleaner implements AliasCleanerInterface {
// Trim duplicate, leading, and trailing separators. Do this before cleaning
// backslashes since a pattern like "[token1]/[token2]-[token3]/[token4]"
// could end up like "value1/-/value2" and if backslashes were cleaned first
// this would result in a duplicate blackslash.
// this would result in a duplicate backslash.
$output = $this->getCleanSeparators($output);
// Trim duplicate, leading, and trailing backslashes.
@ -247,7 +247,7 @@ class AliasCleaner implements AliasCleanerInterface {
// Get rid of words that are on the ignore list.
if ($this->cleanStringCache['ignore_words_regex']) {
$words_removed = $this->cleanStringCache['ignore_words_callback']($this->cleanStringCache['ignore_words_regex'], '', $output);
if (Unicode::strlen(trim($words_removed)) > 0) {
if (mb_strlen(trim($words_removed)) > 0) {
$output = $words_removed;
}
}
@ -260,7 +260,7 @@ class AliasCleaner implements AliasCleanerInterface {
// Optionally convert to lower case.
if ($this->cleanStringCache['lowercase']) {
$output = Unicode::strtolower($output);
$output = mb_strtolower($output);
}
// Shorten to a logical place based on word boundaries.
@ -335,7 +335,9 @@ class AliasCleaner implements AliasCleanerInterface {
public function cleanTokenValues(&$replacements, $data = array(), $options = array()) {
foreach ($replacements as $token => $value) {
// Only clean non-path tokens.
if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) {
$config = $this->configFactory->get('pathauto.settings');
$safe_tokens = implode('|', (array) $config->get('safe_tokens'));
if (!preg_match('/:(' . $safe_tokens . ')(:|\]$)/', $token)) {
$replacements[$token] = $this->cleanString($value, $options);
}
}

View file

@ -72,8 +72,8 @@ interface AliasCleanerInterface {
/**
* Return an array of arrays for punctuation values.
*
* Returns an array of arrays for punctuation values keyed by a name, including
* the value and a textual description.
* Returns an array of arrays for punctuation values keyed by a name,
* including the value and a textual description.
* Can and should be expanded to include "all" non text punctuation values.
*
* @return array

View file

@ -1,6 +1,7 @@
<?php
namespace Drupal\pathauto;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
@ -94,7 +95,6 @@ interface AliasStorageHelperInterface {
*/
public function loadBySourcePrefix($source);
/**
* Returns the count of url aliases for the source.
*

View file

@ -13,7 +13,8 @@ interface AliasTypeBatchUpdateInterface extends AliasTypeInterface {
* @param string $action
* One of:
* - 'create' to generate a URL alias for paths having none.
* - 'update' to recreate the URL alias for paths already having one, useful if the pattern changed.
* - 'update' to recreate the URL alias for paths already having one, useful
* if the pattern changed.
* - 'all' to do both actions above at the same time.
* @param array $context
* Batch context.

View file

@ -38,7 +38,7 @@ class AliasUniquifier implements AliasUniquifierInterface {
/**
* The route provider service.
*
* @var \Drupal\Core\Routing\RouteProviderInterface.
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
@ -60,6 +60,8 @@ class AliasUniquifier implements AliasUniquifierInterface {
* The module handler.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider service.
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* The alias manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageHelperInterface $alias_storage_helper, ModuleHandlerInterface $module_handler, RouteProviderInterface $route_provider, AliasManagerInterface $alias_manager) {
$this->configFactory = $config_factory;

View file

@ -1,6 +1,7 @@
<?php
namespace Drupal\pathauto;
use Drupal\Core\Language\LanguageInterface;
/**

View file

@ -21,9 +21,9 @@ class AliasType extends Plugin {
/**
* The human-readable name of the action plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;

View file

@ -6,6 +6,7 @@ use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
use Drupal\pathauto\PathautoPatternInterface;
@ -345,6 +346,17 @@ class PathautoPattern extends ConfigEntityBase implements PathautoPatternInterfa
$context_handler = \Drupal::service('context.handler');
$conditions = $this->getSelectionConditions();
foreach ($conditions as $condition) {
// As the context object is kept and only the value is switched out,
// it can over time grow to a huge number of cache contexts. Reset it
// if there are 100 cache tags to prevent cache tag merging getting too
// slow.
foreach ($condition->getContextDefinitions() as $name => $context_definition) {
if (count($condition->getContext($name)->getCacheTags()) > 100) {
$condition->setContext($name, new Context($context_definition));
}
}
if ($condition instanceof ContextAwarePluginInterface) {
try {
$context_handler->applyContextMapping($condition, $contexts);

View file

@ -13,11 +13,25 @@ use Drupal\pathauto\AliasTypeManager;
*/
class PathautoSettingsCacheTag implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The alias type manager.
*
* @var \Drupal\pathauto\AliasTypeManager
*/
protected $aliasTypeManager;
/**
* Constructs a PathautoSettingsCacheTag object.
*
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\pathauto\AliasTypeManager $alias_type_manager
* The alias type manager.
*/
public function __construct(EntityFieldManagerInterface $entity_field_manager, AliasTypeManager $alias_type_manager) {
$this->entityFieldManager = $entity_field_manager;

View file

@ -133,14 +133,14 @@ class PathautoAdminDelete extends FormBase {
}
else if ($delete_all) {
\Drupal::service('pathauto.alias_storage_helper')->deleteAll();
drupal_set_message($this->t('All of your path aliases have been deleted.'));
$this->messenger()->addMessage($this->t('All of your path aliases have been deleted.'));
}
else {
$storage_helper = \Drupal::service('pathauto.alias_storage_helper');
foreach (array_keys(array_filter($form_state->getValue(['delete', 'plugins']))) as $id) {
$alias_type = $this->aliasTypeManager->createInstance($id);
$storage_helper->deleteBySourcePrefix((string) $alias_type->getSourcePrefix());
drupal_set_message($this->t('All of your %label path aliases have been deleted.', ['%label' => $alias_type->getLabel()]));
$this->messenger()->addMessage($this->t('All of your %label path aliases have been deleted.', ['%label' => $alias_type->getLabel()]));
}
}
}
@ -168,17 +168,25 @@ class PathautoAdminDelete extends FormBase {
public static function batchFinished($success, $results, $operations) {
if ($success) {
if ($results['delete_all']) {
drupal_set_message(t('All of your automatically generated path aliases have been deleted.'));
\Drupal::service('messenger')
->addMessage(t('All of your automatically generated path aliases have been deleted.'));
}
else if (isset($results['deletions'])) {
foreach (array_values($results['deletions']) as $label) {
drupal_set_message(t('All of your automatically generated %label path aliases have been deleted.', ['%label' => $label]));
\Drupal::service('messenger')
->addMessage(t('All of your automatically generated %label path aliases have been deleted.', [
'%label' => $label,
]));
}
}
}
else {
$error_operation = reset($operations);
drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
\Drupal::service('messenger')
->addMessage(t('An error occurred while processing @operation with arguments : @args', [
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0]),
]));
}
}

View file

@ -149,15 +149,21 @@ class PathautoBulkUpdateForm extends FormBase {
public static function batchFinished($success, $results, $operations) {
if ($success) {
if ($results['updates']) {
drupal_set_message(\Drupal::translation()->formatPlural($results['updates'], 'Generated 1 URL alias.', 'Generated @count URL aliases.'));
\Drupal::service('messenger')->addMessage(\Drupal::translation()
->formatPlural($results['updates'], 'Generated 1 URL alias.', 'Generated @count URL aliases.'));
}
else {
drupal_set_message(t('No new URL aliases to generate.'));
\Drupal::service('messenger')
->addMessage(t('No new URL aliases to generate.'));
}
}
else {
$error_operation = reset($operations);
drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
\Drupal::service('messenger')
->addMessage(t('An error occurred while processing @operation with arguments : @args'), [
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0]),
]);
}
}

View file

@ -35,7 +35,7 @@ class PathautoSettingsForm extends ConfigFormBase {
protected $aliasTypeManager;
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, AliasTypeManager $alias_type_manager) {
parent::__construct($config_factory);
@ -45,7 +45,7 @@ class PathautoSettingsForm extends ConfigFormBase {
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
@ -195,14 +195,19 @@ class PathautoSettingsForm extends ConfigFormBase {
'#title' => $this->t('Strings to Remove'),
'#default_value' => $config->get('ignore_words'),
'#description' => $this->t('Words to strip out of the URL alias, separated by commas. Do not use this to remove punctuation.'),
'#wysiwyg' => FALSE,
);
$form['safe_tokens'] = array(
'#type' => 'textarea',
'#title' => $this->t('Safe tokens'),
'#default_value' => implode(', ', $config->get('safe_tokens')),
'#description' => $this->t('List of tokens that are safe to use in alias patterns and do not need to be cleaned. For example urls, aliases, machine names. Separated with a comma.'),
);
$form['punctuation'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => $this->t('Punctuation'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#open' => FALSE,
'#tree' => TRUE,
);
@ -241,7 +246,6 @@ class PathautoSettingsForm extends ConfigFormBase {
$form_state->cleanValues();
$original_entity_types = $config->get('enabled_entity_types');
foreach ($form_state->getValues() as $key => $value) {
if ($key == 'enabled_entity_types') {
$enabled_entity_types = [];
@ -256,6 +260,9 @@ class PathautoSettingsForm extends ConfigFormBase {
}
$value = $enabled_entity_types;
}
elseif ($key == 'safe_tokens') {
$value = array_filter(array_map('trim', explode(',', $value)));
}
$config->set($key, $value);
}
$config->save();

View file

@ -44,7 +44,9 @@ class PatternDisableForm extends EntityConfirmFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->disable()->save();
drupal_set_message($this->t('Disabled pattern %label.', array('%label' => $this->entity->label())));
$this->messenger()->addMessage($this->t('Disabled pattern %label.', [
'%label' => $this->entity->label(),
]));
$form_state->setRedirectUrl($this->getCancelUrl());
}

View file

@ -16,6 +16,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class PatternEditForm extends EntityForm {
/**
* The alias type manager.
*
* @var \Drupal\pathauto\AliasTypeManager
*/
protected $manager;
@ -33,11 +35,15 @@ class PatternEditForm extends EntityForm {
protected $entityTypeBundleInfo;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The language manager service.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
@ -58,9 +64,13 @@ class PatternEditForm extends EntityForm {
* PatternEditForm constructor.
*
* @param \Drupal\pathauto\AliasTypeManager $manager
* The alias type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
*/
function __construct(AliasTypeManager $manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager) {
$this->manager = $manager;
@ -70,7 +80,7 @@ class PatternEditForm extends EntityForm {
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
@ -199,7 +209,7 @@ class PatternEditForm extends EntityForm {
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\pathauto\PathautoPatternInterface $entity */
@ -259,11 +269,13 @@ class PatternEditForm extends EntityForm {
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
drupal_set_message($this->t('Pattern @label saved.', ['@label' => $this->entity->label()]));
$this->messenger()->addMessage($this->t('Pattern %label saved.', [
'%label' => $this->entity->label(),
]));
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
}

View file

@ -44,7 +44,9 @@ class PatternEnableForm extends EntityConfirmFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->enable()->save();
drupal_set_message($this->t('Enabled pattern %label.', array('%label' => $this->entity->label())));
$this->messenger()->addMessage($this->t('Enabled pattern %label.', [
'%label' => $this->entity->label(),
]));
$form_state->setRedirectUrl($this->getCancelUrl());
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\pathauto;
use Drupal\path\Plugin\Field\FieldType\PathFieldItemList;
class PathautoFieldItemList extends PathFieldItemList {
/**
* @{inheritdoc}
*/
protected function delegateMethod($method) {
// @todo Workaround until this is fixed, see
// https://www.drupal.org/project/drupal/issues/2946289.
$this->ensureComputedValue();
// Duplicate the logic instead of calling the parent due to the dynamic
// arguments.
$result = [];
$args = array_slice(func_get_args(), 1);
foreach ($this->list as $delta => $item) {
// call_user_func_array() is way slower than a direct call so we avoid
// using it if have no parameters.
$result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
}
return $result;
}
/**
* @{inheritdoc}
*/
protected function computeValue() {
parent::computeValue();
// For a new entity, default to creating a new alias.
if ($this->getEntity()->isNew()) {
$this->list[0]->set('pathauto', PathautoState::CREATE);
}
}
}

View file

@ -2,7 +2,6 @@
namespace Drupal\pathauto;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
@ -87,12 +86,16 @@ class PathautoGenerator implements PathautoGeneratorInterface {
protected $messenger;
/**
* The token entity mapper.
*
* @var \Drupal\token\TokenEntityMapperInterface
*/
protected $tokenEntityMapper;
/**
* @var Drupal\Core\Entity\EntityTypeManagerInterface
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
@ -115,10 +118,12 @@ class PathautoGenerator implements PathautoGeneratorInterface {
* The messenger service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager
* @param \Drupal\token\TokenEntityMapperInterface $token_entity_mapper
* The token entity mapper.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token, AliasCleanerInterface $alias_cleaner, AliasStorageHelperInterface $alias_storage_helper, AliasUniquifierInterface $alias_uniquifier, MessengerInterface $messenger, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mappper, EntityTypeManagerInterface $entity_type_manager) {
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token, AliasCleanerInterface $alias_cleaner, AliasStorageHelperInterface $alias_storage_helper, AliasUniquifierInterface $alias_uniquifier, MessengerInterface $messenger, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mapper, EntityTypeManagerInterface $entity_type_manager) {
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
$this->token = $token;
@ -127,7 +132,7 @@ class PathautoGenerator implements PathautoGeneratorInterface {
$this->aliasUniquifier = $alias_uniquifier;
$this->messenger = $messenger;
$this->stringTranslation = $string_translation;
$this->tokenEntityMapper = $token_entity_mappper;
$this->tokenEntityMapper = $token_entity_mapper;
$this->entityTypeManager = $entity_type_manager;
}
@ -165,8 +170,9 @@ class PathautoGenerator implements PathautoGeneratorInterface {
'bundle' => $entity->bundle(),
'language' => &$langcode,
);
// @todo Is still hook still useful?
$pattern_original = $pattern->getPattern();
$this->moduleHandler->alter('pathauto_pattern', $pattern, $context);
$pattern_altered = $pattern->getPattern();
// Special handling when updating an item which is already aliased.
$existing_alias = NULL;
@ -209,7 +215,7 @@ class PathautoGenerator implements PathautoGeneratorInterface {
$this->moduleHandler->alter('pathauto_alias', $alias, $context);
// If we have arrived at an empty string, discontinue.
if (!Unicode::strlen($alias)) {
if (!mb_strlen($alias)) {
return NULL;
}
@ -236,11 +242,19 @@ class PathautoGenerator implements PathautoGeneratorInterface {
'language' => $langcode,
);
return $this->aliasStorageHelper->save($path, $existing_alias, $op);
$return = $this->aliasStorageHelper->save($path, $existing_alias, $op);
// Because there is no way to set an altered pattern to not be cached,
// change it back to the original value.
if ($pattern_altered !== $pattern_original) {
$pattern->setPattern($pattern_original);
}
return $return;
}
/**
* Loads pathauto patterns for a given entity type ID
* Loads pathauto patterns for a given entity type ID.
*
* @param string $entity_type_id
* An entity type ID.
@ -335,7 +349,7 @@ class PathautoGenerator implements PathautoGeneratorInterface {
$result = $this->createEntityAlias($entity, $op);
}
catch (\InvalidArgumentException $e) {
drupal_set_message($e->getMessage(), 'error');
$this->messenger->addError($e->getMessage());
return NULL;
}

View file

@ -49,6 +49,7 @@ interface PathautoGeneratorInterface {
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* An entity.
*
* @return \Drupal\pathauto\PathautoPatternInterface|null
*/
public function getPatternByEntity(EntityInterface $entity);

View file

@ -45,14 +45,4 @@ class PathautoItem extends PathItem {
return !$this->alias && !$this->get('pathauto')->hasValue();
}
/**
* {@inheritdoc}
*/
public function applyDefaultValue($notify = TRUE) {
parent::applyDefaultValue($notify);
// Created fields default creating a new alias.
$this->setValue(array('pathauto' => PathautoState::CREATE), $notify);
return $this;
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Drupal\pathauto;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\TypedData\TypedData;
/**
@ -38,7 +40,7 @@ class PathautoState extends TypedData {
// If no value has been set or loaded yet, try to load a value if this
// entity has already been saved.
$this->value = \Drupal::keyValue($this->getCollection())
->get($this->parent->getEntity()->id());
->get(static::getPathautoStateKey($this->parent->getEntity()->id()));
// If it was not yet saved or no value was found, then set the flag to
// create the alias if there is a matching pattern.
if ($this->value === NULL) {
@ -72,10 +74,8 @@ class PathautoState extends TypedData {
* Persists the state.
*/
public function persist() {
\Drupal::keyValue($this->getCollection())->set(
$this->parent->getEntity()
->id(), $this->value
);
\Drupal::keyValue($this->getCollection())
->set(static::getPathautoStateKey($this->parent->getEntity()->id()), $this->getValue());
}
/**
@ -83,15 +83,77 @@ class PathautoState extends TypedData {
*/
public function purge() {
\Drupal::keyValue($this->getCollection())
->delete($this->parent->getEntity()->id());
->delete(static::getPathautoStateKey($this->parent->getEntity()->id()));
}
/**
* Returns the key value collection that should be used for the given entity.
*
* @return string
*/
protected function getCollection() {
return 'pathauto_state.' . $this->parent->getEntity()->getEntityTypeId();
}
/**
* Deletes the URL aliases for multiple entities of the same type.
*
* @param string $entity_type_id
* The entity type ID of entities being deleted.
* @param int[] $pids_by_id
* A list of path IDs keyed by entity ID.
*/
public static function bulkDelete($entity_type_id, array $pids_by_id) {
foreach ($pids_by_id as $id => $pid) {
// Some key-values store entries have computed keys.
$key = static::getPathautoStateKey($id);
if ($key !== $id) {
$pids_by_id[$key] = $pid;
unset($pids_by_id[$id]);
}
}
$states = \Drupal::keyValue("pathauto_state.$entity_type_id")
->getMultiple(array_keys($pids_by_id));
$pids = [];
foreach ($pids_by_id as $id => $pid) {
// Only delete aliases that were created by this module.
if (isset($states[$id]) && $states[$id] == PathautoState::CREATE) {
$pids[] = $pid;
}
}
\Drupal::service('pathauto.alias_storage_helper')->deleteMultiple($pids);
}
/**
* Gets the key-value store entry key for 'pathauto_state.*' collections.
*
* Normally we want to use the entity ID as key for 'pathauto_state.*'
* collection entries. But some entity types may use string IDs. When such IDs
* are exceeding 128 characters, which is the limit for the 'name' column in
* the {key_value} table, the insertion of the ID in {key_value} will fail.
* Thus we test if we can use the plain ID or we need to store a hashed
* version of the entity ID. Also, it is not possible to rely on the UUID as
* entity types might not have one or might use a non-standard format.
*
* The code is inspired by
* \Drupal\Core\Cache\DatabaseBackend::normalizeCid().
*
* @param int|string $entity_id
* The entity id for which to compute the key.
*
* @return int|string
* The key used to store the value in the key-value store.
*
* @see \Drupal\Core\Cache\DatabaseBackend::normalizeCid()
*/
public static function getPathautoStateKey($entity_id) {
$entity_id_is_ascii = mb_check_encoding($entity_id, 'ASCII');
if ($entity_id_is_ascii && strlen($entity_id) <= 128) {
// The original entity ID, if it's an ASCII of 128 characters or less.
return $entity_id;
}
return Crypt::hashBase64($entity_id);
}
}

View file

@ -26,6 +26,8 @@ class EntityAliasTypeDeriver extends DeriverBase implements ContainerDeriverInte
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
@ -44,7 +46,7 @@ class EntityAliasTypeDeriver extends DeriverBase implements ContainerDeriverInte
* The entity field manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @apram \Drupal\Token\TokenEntityMapperInterface $token_entity_mapper
* @param \Drupal\Token\TokenEntityMapperInterface $token_entity_mapper
* The token entity mapper.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mapper) {

View file

@ -11,6 +11,7 @@ use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\ContextAwarePluginBase;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\pathauto\AliasTypeBatchUpdateInterface;
use Drupal\pathauto\AliasTypeInterface;
use Drupal\pathauto\PathautoState;
@ -26,6 +27,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInterface, AliasTypeBatchUpdateInterface, ContainerFactoryPluginInterface {
use MessengerTrait;
/**
* The module handler service.
*
@ -151,12 +154,15 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
case 'create':
$query->isNull('ua.source');
break;
case 'update':
$query->isNotNull('ua.source');
break;
case 'all':
// Nothing to do. We want all paths.
break;
default:
// Unknown action. Abort!
return;
@ -182,7 +188,7 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
$updates = $this->bulkUpdate($ids);
$context['sandbox']['count'] += count($ids);
$context['sandbox']['current'] = max($ids);
$context['sandbox']['current'] = !empty($ids) ? max($ids) : 0;
$context['results']['updates'] += $updates;
$context['message'] = $this->t('Updated alias for %label @id.', array('%label' => $entity_type->getLabel(), '@id' => end($ids)));
@ -226,7 +232,7 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
$query->range(0, 100);
$pids_by_id = $query->execute()->fetchAllKeyed();
$this->bulkDelete($pids_by_id);
PathautoState::bulkDelete($this->getEntityTypeId(), $pids_by_id);
$context['sandbox']['count'] += count($pids_by_id);
$context['sandbox']['current'] = max($pids_by_id);
$context['results']['deletions'][] = $this->getLabel();
@ -255,7 +261,7 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
* An optional array of additional options.
*
* @return int
* The number of updated URL aliases.
* The number of updated URL aliases.
*/
protected function bulkUpdate(array $ids, array $options = array()) {
$options += array('message' => FALSE);
@ -274,7 +280,10 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
}
if (!empty($options['message'])) {
drupal_set_message(\Drupal::translation()->formatPlural(count($ids), 'Updated 1 %label URL alias.', 'Updated @count %label URL aliases.'), array('%label' => $this->getLabel()));
$this->messenger->addMessage($this->translationManager
->formatPlural(count($ids), 'Updated 1 %label URL alias.', 'Updated @count %label URL aliases.'), [
'%label' => $this->getLabel(),
]);
}
return $updates;
@ -285,19 +294,11 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
*
* @param int[] $pids_by_id
* A list of path IDs keyed by entity ID.
*
* @deprecated Use \Drupal\pathauto\PathautoState::bulkDelete() instead.
*/
protected function bulkDelete(array $pids_by_id) {
$collection = 'pathauto_state.' . $this->getEntityTypeId();
$states = $this->keyValue->get($collection)->getMultiple(array_keys($pids_by_id));
$pids = [];
foreach ($pids_by_id as $id => $pid) {
// Only delete aliases that were created by this module.
if (isset($states[$id]) && $states[$id] == PathautoState::CREATE) {
$pids[] = $pid;
}
}
\Drupal::service('pathauto.alias_storage_helper')->deleteMultiple($pids);
PathautoState::bulkDelete($this->getEntityTypeId(), $pids_by_id);
}
/**
@ -338,5 +339,4 @@ class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInt
return $this;
}
}

View file

@ -44,7 +44,7 @@ class PathautoBulkUpdateTest extends WebTestBase {
protected $patterns;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -103,7 +103,7 @@ class PathautoBulkUpdateTest extends WebTestBase {
$this->assertText('No new URL aliases to generate.');
$this->assertNoEntityAliasExists($new_node);
// Make sure existing aliases can be overriden.
// Make sure existing aliases can be overridden.
$this->drupalPostForm('admin/config/search/path/settings', ['update_action' => PathautoGeneratorInterface::UPDATE_ACTION_DELETE], t('Save configuration'));
// Patterns did not change, so no aliases should be regenerated.
@ -111,16 +111,17 @@ class PathautoBulkUpdateTest extends WebTestBase {
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
$this->assertText('No new URL aliases to generate.');
// Update the node pattern, and leave other patterns alone. Existing nodes should get a new alias,
// except the node above whose alias is manually set. Other aliases must be left alone.
// Update the node pattern, and leave other patterns alone. Existing nodes
// should get a new alias, except the node above whose alias is manually
// set. Other aliases must be left alone.
$this->patterns['node']->delete();
$this->patterns['node'] = $this->createPattern('node', '/archive/node-[node:nid]');
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
$this->assertText('Generated 5 URL aliases.');
// Prevent existing aliases to be overriden. The bulk generate page should only offer
// to create an alias for paths which have none.
// Prevent existing aliases to be overridden. The bulk generate page should
// only offer to create an alias for paths which have none.
$this->drupalPostForm('admin/config/search/path/settings', ['update_action' => PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW], t('Save configuration'));
$this->drupalGet('admin/config/search/path/update_bulk');

View file

@ -13,6 +13,7 @@ use Drupal\comment\Tests\CommentTestTrait;
class PathautoEnablingEntityTypesTest extends WebTestBase {
use PathautoTestHelperTrait;
use CommentTestTrait;
/**
@ -30,7 +31,7 @@ class PathautoEnablingEntityTypesTest extends WebTestBase {
protected $adminUser;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();

View file

@ -87,7 +87,21 @@ class PathautoLocaleTest extends WebTestBase {
* Test that patterns work on multilingual content.
*/
function testLanguagePatterns() {
$this->drupalLogin($this->rootUser);
// Allow other modules to add additional permissions for the admin user.
$permissions = array(
'administer pathauto',
'administer url aliases',
'create url aliases',
'bypass node access',
'access content overview',
'administer languages',
'translate any entity',
'administer content translation'
);
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add French language.
$edit = array(
@ -134,8 +148,9 @@ class PathautoLocaleTest extends WebTestBase {
'title[0][value]' => 'English node',
'langcode[0][value]' => 'en',
);
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$english_node = $this->drupalGetNodeByTitle('English node');
return;
$this->assertAlias('/node/' . $english_node->id(), '/the-articles/english-node', 'en');
$this->drupalGet('node/' . $english_node->id() . '/translations');
@ -143,7 +158,7 @@ class PathautoLocaleTest extends WebTestBase {
$edit = array(
'title[0][value]' => 'French node',
);
$this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)'));
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$this->rebuildContainer();
$english_node = $this->drupalGetNodeByTitle('English node');
$french_node = $english_node->getTranslation('fr');

View file

@ -49,9 +49,8 @@ class PathautoMassDeleteTest extends WebTestBase {
*/
protected $terms;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -136,14 +135,16 @@ class PathautoMassDeleteTest extends WebTestBase {
* Helper function to generate aliases.
*/
function generateAliases() {
// Delete all aliases to avoid duplicated aliases. They will be recreated below.
// Delete all aliases to avoid duplicated aliases. They will be recreated
// below.
$this->deleteAllAliases();
// We generate a bunch of aliases for nodes, users and taxonomy terms. If
// the entities are already created we just update them, otherwise we create
// them.
if (empty($this->nodes)) {
// Create a large number of nodes (100+) to make sure that the batch code works.
// Create a large number of nodes (100+) to make sure that the batch code
// works.
for ($i = 1; $i <= 105; $i++) {
// Set the alias of two nodes manually.
$settings = ($i > 103) ? ['path' => ['alias' => "/custom_alias_$i", 'pathauto' => PathautoState::SKIP]] : [];

View file

@ -1,6 +1,7 @@
<?php
namespace Drupal\pathauto\Tests;
use Drupal\pathauto\Entity\PathautoPattern;
use Drupal\node\Entity\Node;
use Drupal\pathauto\PathautoState;
@ -30,7 +31,7 @@ class PathautoNodeWebTest extends WebTestBase {
protected $adminUser;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -43,7 +44,6 @@ class PathautoNodeWebTest extends WebTestBase {
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer nodes',
'bypass node access',
'access content overview',
);
@ -57,14 +57,15 @@ class PathautoNodeWebTest extends WebTestBase {
* Tests editing nodes with different settings.
*/
function testNodeEditing() {
// Ensure that the Pathauto checkbox is checked by default on the node add form.
// Ensure that the Pathauto checkbox is checked by default on the node add
// form.
$this->drupalGet('node/add/page');
$this->assertFieldChecked('edit-path-0-pathauto');
// Create a node by saving the node form.
$title = ' Testing: node title [';
$automatic_alias = '/content/testing-node-title';
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save and publish'));
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save'));
$node = $this->drupalGetNodeByTitle($title);
// Look for alias generated in the form.
@ -82,7 +83,7 @@ class PathautoNodeWebTest extends WebTestBase {
'path[0][pathauto]' => FALSE,
'path[0][alias]' => $manual_alias,
);
$this->drupalPostForm($node->toUrl('edit-form'), $edit, t('Save and keep published'));
$this->drupalPostForm($node->toUrl('edit-form'), $edit, t('Save'));
$this->assertText(t('@type @title has been updated.', array('@type' => 'page', '@title' => $title)));
// Check that the automatic alias checkbox is now unchecked by default.
@ -91,7 +92,7 @@ class PathautoNodeWebTest extends WebTestBase {
$this->assertFieldByName('path[0][alias]', $manual_alias);
// Submit the node form with the default values.
$this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save and keep published'));
$this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save'));
$this->assertText(t('@type @title has been updated.', array('@type' => 'page', '@title' => $title)));
// Test that the old (automatic) alias has been deleted and only accessible
@ -109,7 +110,7 @@ class PathautoNodeWebTest extends WebTestBase {
'path[0][pathauto]' => TRUE,
'path[0][alias]' => '/should-not-get-created',
);
$this->drupalPostForm('node/add/page', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/page', $edit, t('Save'));
$this->assertNoAliasExists(array('alias' => 'should-not-get-created'));
$node = $this->drupalGetNodeByTitle($title);
$this->assertEntityAlias($node, '/content/automatic-title');
@ -130,7 +131,7 @@ class PathautoNodeWebTest extends WebTestBase {
$edit = array();
$edit['title'] = 'My test article';
$this->drupalCreateNode($edit);
//$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
//$this->drupalPostForm(NULL, $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title']);
// Pathauto checkbox should still not exist.
@ -271,7 +272,7 @@ class PathautoNodeWebTest extends WebTestBase {
'title[0][value]' => 'Sample article',
'path[0][alias]' => '/sample-article',
];
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$this->assertText(t('article Sample article has been created.'));
// Test the alias.

View file

@ -85,7 +85,7 @@ class PathautoSettingsFormWebTest extends WebTestBase {
);
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -97,7 +97,6 @@ class PathautoSettingsFormWebTest extends WebTestBase {
'notify of path changes',
'administer url aliases',
'create url aliases',
'administer nodes',
'bypass node access',
);
$this->adminUser = $this->drupalCreateUser($permissions);
@ -133,11 +132,11 @@ class PathautoSettingsFormWebTest extends WebTestBase {
$title = 'Verbose settings test';
$this->drupalGet('/node/add/article');
$this->assertFieldChecked('edit-path-0-pathauto');
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save and publish'));
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save'));
$this->assertText('Created new alias /content/verbose-settings-test for');
$node = $this->drupalGetNodeByTitle($title);
$this->drupalPostForm('/node/' . $node->id() . '/edit', array('title[0][value]' => 'Updated title'), t('Save and keep published'));
$this->drupalPostForm('/node/' . $node->id() . '/edit', array('title[0][value]' => 'Updated title'), t('Save'));
$this->assertText('Created new alias /content/updated-title for');
$this->assertText('replacing /content/verbose-settings-test.');
}

View file

@ -27,7 +27,7 @@ class PathautoTaxonomyWebTest extends WebTestBase {
protected $adminUser;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -45,7 +45,6 @@ class PathautoTaxonomyWebTest extends WebTestBase {
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
}
/**
* Basic functional testing of Pathauto with taxonomy terms.
*/

View file

@ -2,7 +2,6 @@
namespace Drupal\pathauto\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\BubbleableMetadata;
@ -34,7 +33,7 @@ trait PathautoTestHelperTrait {
$type = ($entity_type_id == 'forum') ? 'forum' : 'canonical_entities:' . $entity_type_id;
$pattern = PathautoPattern::create([
'id' => Unicode::strtolower($this->randomMachineName()),
'id' => mb_strtolower($this->randomMachineName()),
'type' => $type,
'pattern' => $pattern,
'weight' => $weight,
@ -51,7 +50,7 @@ trait PathautoTestHelperTrait {
* @param string $entity_type
* The entity type ID.
* @param string $bundle
* The bundle
* The bundle.
*/
protected function addBundleCondition(PathautoPatternInterface $pattern, $entity_type, $bundle) {
$plugin_id = $entity_type == 'node' ? 'node_type' : 'entity_bundle:' . $entity_type;
@ -142,10 +141,11 @@ trait PathautoTestHelperTrait {
/**
* @param array $values
*
* @return \Drupal\taxonomy\VocabularyInterface
*/
public function addVocabulary(array $values = array()) {
$name = Unicode::strtolower($this->randomMachineName(5));
$name = mb_strtolower($this->randomMachineName(5));
$values += array(
'name' => $name,
'vid' => $name,
@ -158,7 +158,7 @@ trait PathautoTestHelperTrait {
public function addTerm(VocabularyInterface $vocabulary, array $values = array()) {
$values += array(
'name' => Unicode::strtolower($this->randomMachineName(5)),
'name' => mb_strtolower($this->randomMachineName(5)),
'vid' => $vocabulary->id(),
);

View file

@ -2,6 +2,7 @@
namespace Drupal\pathauto\Tests;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
use Drupal\pathauto\Entity\PathautoPattern;
@ -29,7 +30,7 @@ class PathautoUiTest extends WebTestBase {
protected $adminUser;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -132,7 +133,8 @@ class PathautoUiTest extends WebTestBase {
// Edit workflow, set a new label and weight for the pattern.
$this->drupalPostForm('/admin/config/search/path/patterns', ['entities[page_pattern][weight]' => '4'], t('Save'));
$this->clickLink(t('Edit'));
$this->assertUrl('/admin/config/search/path/patterns/page_pattern');
$destination_query = ['query' => ['destination' => Url::fromRoute('entity.pathauto_pattern.collection')->toString()]];
$this->assertUrl('/admin/config/search/path/patterns/page_pattern', $destination_query);
$this->assertFieldByName('pattern', '[node:title]');
$this->assertFieldByName('label', 'Page pattern');
$this->assertFieldChecked('edit-status');
@ -148,7 +150,7 @@ class PathautoUiTest extends WebTestBase {
$this->drupalGet('/admin/config/search/path/patterns');
$this->assertNoLink(t('Enable'));
$this->clickLink(t('Disable'));
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/disable');
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/disable', $destination_query);
$this->drupalPostForm(NULL, [], t('Disable'));
$this->assertText('Disabled pattern Test.');
@ -167,7 +169,7 @@ class PathautoUiTest extends WebTestBase {
$this->drupalGet('/admin/config/search/path/patterns');
$this->assertNoLink(t('Disable'));
$this->clickLink(t('Enable'));
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/enable');
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/enable', $destination_query);
$this->drupalPostForm(NULL, [], t('Enable'));
$this->assertText('Enabled pattern Test.');
@ -178,7 +180,7 @@ class PathautoUiTest extends WebTestBase {
// Delete workflow.
$this->drupalGet('/admin/config/search/path/patterns');
$this->clickLink(t('Delete'));
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/delete');
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/delete', $destination_query);
$this->assertText(t('This action cannot be undone.'));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertText('The pathauto pattern Test has been deleted.');

View file

@ -1,7 +1,7 @@
<?php
namespace Drupal\pathauto\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
use Drupal\views\Views;
@ -29,7 +29,7 @@ class PathautoUserWebTest extends WebTestBase {
protected $adminUser;
/**
* {inheritdoc}
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
@ -47,7 +47,6 @@ class PathautoUserWebTest extends WebTestBase {
$this->createPattern('user', '/users/[user:name]');
}
/**
* Basic functional testing of Pathauto with users.
*/
@ -71,7 +70,6 @@ class PathautoUserWebTest extends WebTestBase {
$view->initDisplay();
$view->preview('page_1');
foreach ($view->result as $key => $row) {
if ($view->field['name']->getValue($row) == $account->getUsername()) {
break;
@ -85,7 +83,7 @@ class PathautoUserWebTest extends WebTestBase {
$this->drupalPostForm('admin/people', $edit, t('Apply to selected items'));
$this->assertText('Update URL alias was applied to 1 item.');
$this->assertEntityAlias($account, '/users/' . Unicode::strtolower($account->getUsername()));
$this->assertEntityAlias($account, '/users/' . mb_strtolower($account->getUsername()));
$this->assertEntityAlias($this->adminUser, '/user/' . $this->adminUser->id());
}

View file

@ -3,6 +3,7 @@
namespace Drupal\pathauto;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface as CoreMessengerInterface;
use Drupal\Core\Session\AccountInterface;
/**
@ -32,11 +33,26 @@ class VerboseMessenger implements MessengerInterface {
protected $account;
/**
* Creates a verbose messenger.
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $account) {
protected $messenger;
/**
* Creates a verbose messenger.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user account.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $account, CoreMessengerInterface $messenger) {
$this->configFactory = $config_factory;
$this->account = $account;
$this->messenger = $messenger;
}
/**
@ -54,7 +70,7 @@ class VerboseMessenger implements MessengerInterface {
}
if ($message) {
drupal_set_message($message);
$this->messenger->addMessage($message);
}
return TRUE;

View file

@ -0,0 +1,13 @@
name: 'Pathauto testing module'
type: module
# core: '8.x'
description: 'Pathauto for Entity with string ID.'
package: Testing
dependencies:
- token
# Information added by Drupal.org packaging script on 2018-09-08
version: '8.x-1.3'
core: '8.x'
project: 'pathauto'
datestamp: 1536407890

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\pathauto_string_id_test\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines a test entity with a string ID.
*
* @ContentEntityType(
* id = "pathauto_string_id_test",
* label = @Translation("Test entity with string ID"),
* handlers = {
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* base_table = "pathauto_string_id_test",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* },
* links = {
* "canonical" = "/pathauto_string_id_test/{pathauto_string_id_test}",
* },
* )
*/
class PathautoStringIdTest extends ContentEntityBase {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['id'] = BaseFieldDefinition::create('string')
->setLabel('ID')
->setReadOnly(TRUE)
// A bigger value will not be allowed to build the index.
->setSetting('max_length', 191);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel('Name');
$fields['path'] = BaseFieldDefinition::create('path')
->setLabel('Path')
->setComputed(TRUE);
return $fields;
}
}

View file

@ -7,8 +7,8 @@ package: Testing
dependencies:
- views
# Information added by Drupal.org packaging script on 2017-04-29
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-09-08
version: '8.x-1.3'
core: '8.x'
project: 'pathauto'
datestamp: 1493468049
datestamp: 1536407890

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\Tests\pathauto\Kernel;
use Drupal\Component\Serialization\PhpSerialize;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\KeyValueStore\KeyValueDatabaseFactory;
use Drupal\pathauto\PathautoState;
use Drupal\pathauto\Tests\PathautoTestHelperTrait;
use Drupal\KernelTests\KernelTestBase;
use Drupal\pathauto_string_id_test\Entity\PathautoStringIdTest;
/**
* Tests auto-aliasing of entities that use string IDs.
*
* @group pathauto
*/
class PathautoEntityWithStringIdTest extends KernelTestBase {
use PathautoTestHelperTrait;
/**
* The alias type plugin instance.
*
* @var \Drupal\pathauto\AliasTypeBatchUpdateInterface
*/
protected $aliasType;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'field',
'token',
'path',
'pathauto',
'pathauto_string_id_test',
];
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
// Kernel tests are using the 'keyvalue.memory' store but we want to test
// against the 'keyvalue.database'.
$container
->register('keyvalue.database', KeyValueDatabaseFactory::class)
->addArgument(new PhpSerialize())
->addArgument($container->get('database'))
->addTag('persist');
$container->setAlias('keyvalue', 'keyvalue.database');
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value']);
$this->installConfig(['system', 'pathauto']);
$this->installEntitySchema('pathauto_string_id_test');
$this->createPattern('pathauto_string_id_test', '/[pathauto_string_id_test:name]');
/** @var \Drupal\pathauto\AliasTypeManager $alias_type_manager */
$alias_type_manager = $this->container->get('plugin.manager.alias_type');
$this->aliasType = $alias_type_manager->createInstance('canonical_entities:pathauto_string_id_test');
}
/**
* Test aliasing entities with long string ID.
*
* @dataProvider entityWithStringIdProvider
*
* @param string|int $id
* The entity ID
* @param string $expected_key
* The expected key for 'pathauto_state.*' collections.
*/
public function testEntityWithStringId($id, $expected_key) {
$entity = PathautoStringIdTest::create([
'id' => $id,
'name' => $name = $this->randomMachineName(),
]);
$entity->save();
// Check that the path was generated.
$this->assertEntityAlias($entity, mb_strtolower("/$name"));
// Check that the path auto state was saved with the expected key.
$value = \Drupal::keyValue('pathauto_state.pathauto_string_id_test')->get($expected_key);
$this->assertEquals(PathautoState::CREATE, $value);
$context = [];
// Batch delete uses the key-value store collection 'pathauto_state.*. We
// test that after a bulk delete all aliases are removed. Running only once
// the batch delete process is enough as the batch size is 100.
$this->aliasType->batchDelete($context);
// Check that the paths were removed on batch delete.
$this->assertNoEntityAliasExists($entity, "/$name");
}
/**
* Provides test cases for ::testEntityWithStringId().
*
* @see \Drupal\Tests\pathauto\Kernel\PathautoEntityWithStringIdTest::testEntityWithStringId()
*/
public function entityWithStringIdProvider() {
return [
'ascii with less or equal 128 chars' => [
str_repeat('a', 128), str_repeat('a', 128)
],
'ascii with over 128 chars' => [
str_repeat('a', 191), Crypt::hashBase64(str_repeat('a', 191))
],
'non-ascii with less or equal 128 chars' => [
str_repeat('社', 128), Crypt::hashBase64(str_repeat('社', 128))
],
'non-ascii with over 128 chars' => [
str_repeat('社', 191), Crypt::hashBase64(str_repeat('社', 191))
],
'simulating an integer id' => [
123, '123'
],
];
}
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\pathauto\Kernel;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
@ -285,7 +284,7 @@ class PathautoKernelTest extends KernelTestBase {
$this->assertEntityAlias($node, '/content/second-title');
$this->assertNoAliasExists(array('alias' => '/content/first-title'));
// Test PATHAUTO_UPDATE_ACTION_LEAVE
// Test PATHAUTO_UPDATE_ACTION_LEAVE.
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_LEAVE);
$config->save();
$node->setTitle('Third title');
@ -323,8 +322,8 @@ class PathautoKernelTest extends KernelTestBase {
}
/**
* Test that \Drupal::service('pathauto.generator')->createEntityAlias() will not create an alias for a pattern
* that does not get any tokens replaced.
* Test that \Drupal::service('pathauto.generator')->createEntityAlias() will
* not create an alias for a pattern that does not get any tokens replaced.
*/
public function testNoTokensNoAlias() {
$this->installConfig(['filter']);
@ -365,12 +364,11 @@ class PathautoKernelTest extends KernelTestBase {
function testParentChildPathTokens() {
// First create a field which will be used to create the path. It must
// begin with a letter.
$this->installEntitySchema('taxonomy_term');
Vocabulary::create(['vid' => 'tags'])->save();
$fieldname = 'a' . Unicode::strtolower($this->randomMachineName());
$fieldname = 'a' . mb_strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create(['entity_type' => 'taxonomy_term', 'field_name' => $fieldname, 'type' => 'string']);
$field_storage->save();
$field = FieldConfig::create(['field_storage' => $field_storage, 'bundle' => 'tags']);
@ -391,11 +389,11 @@ class PathautoKernelTest extends KernelTestBase {
// Create the child term.
$child = Term::create(['vid' => 'tags', $fieldname => $this->randomMachineName(), 'parent' => $parent, 'name' => $this->randomMachineName()]);
$child->save();
$this->assertEntityAlias($child, '/' . Unicode::strtolower($parent->getName() . '/' . $child->$fieldname->value));
$this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
// Re-saving the parent term should not modify the child term's alias.
$parent->save();
$this->assertEntityAlias($child, '/' . Unicode::strtolower($parent->getName() . '/' . $child->$fieldname->value));
$this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
}
/**
@ -506,7 +504,7 @@ class PathautoKernelTest extends KernelTestBase {
}
/**
* Tests that enabled entity types genrates the necessary fields and plugins.
* Tests that enabled entity types generates the necessary fields and plugins.
*/
public function testSettingChangeInvalidatesCache() {
@ -547,6 +545,22 @@ class PathautoKernelTest extends KernelTestBase {
$this->assertEntityAlias($node1, '/content/default-revision');
}
/**
* Tests that the pathauto state property gets set to CREATED for new nodes.
*
* In some cases, this can trigger $node->path to be set up with no default
* value for the pathauto property.
*/
public function testCreateNodeWhileAccessingPath() {
$node = Node::create([
'type' => 'article',
'title' => 'TestAlias',
]);
$node->path->langcode;
$node->save();
$this->assertEntityAlias($node, '/content/testalias');
}
/**
* Creates a node programmatically.
*

View file

@ -39,6 +39,42 @@ class PathautoTokenTest extends KernelTestBase {
$alias_cleaner = \Drupal::service('pathauto.alias_cleaner');
$alias_cleaner->cleanTokenValues($replacements, $data, array());
$this->assertEqual($replacements['[array:join-path]'], 'test-first-arg/array-value');
// Test additional token cleaning and its configuration.
$safe_tokens = $this->config('pathauto.settings')->get('safe_tokens');
$safe_tokens[] = 'safe';
$this->config('pathauto.settings')
->set('safe_tokens', $safe_tokens)
->save();
$safe_tokens = [
'[example:path]',
'[example:url]',
'[example:url-brief]',
'[example:login-url]',
'[example:login-url:relative]',
'[example:url:relative]',
'[example:safe]',
];
$unsafe_tokens = [
'[example:path_part]',
'[example:something_url]',
'[example:unsafe]',
];
foreach ($safe_tokens as $token) {
$replacements = [
$token => 'this/is/a/path',
];
$alias_cleaner->cleanTokenValues($replacements);
$this->assertEquals('this/is/a/path', $replacements[$token], "Token $token cleaned.");
}
foreach ($unsafe_tokens as $token) {
$replacements = [
$token => 'This is not a / path',
];
$alias_cleaner->cleanTokenValues($replacements);
$this->assertEquals('not-path', $replacements[$token], "Token $token not cleaned.");
}
}
/**

View file

@ -1,59 +1,54 @@
<?php
namespace Drupal\Tests\pathauto\Unit {
namespace Drupal\Tests\pathauto\Unit;
use Drupal\pathauto\VerboseMessenger;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\pathauto\VerboseMessenger;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\pathauto\VerboseMessenger
* @group pathauto
*/
class VerboseMessengerTest extends UnitTestCase {
/**
* @coversDefaultClass \Drupal\pathauto\VerboseMessenger
* @group pathauto
* The messenger under test.
*
* @var \Drupal\pathauto\VerboseMessenger
*/
class VerboseMessengerTest extends UnitTestCase {
protected $messenger;
/**
* The messenger under test.
*
* @var \Drupal\pathauto\VerboseMessenger
*/
protected $messenger;
/**
* {@inheritdoc}
*/
protected function setUp() {
$config_factory = $this->getConfigFactoryStub(['pathauto.settings' => ['verbose' => TRUE]]);
$account = $this->createMock(AccountInterface::class);
$account->expects($this->once())
->method('hasPermission')
->withAnyParameters()
->willReturn(TRUE);
$messenger = $this->createMock(MessengerInterface::class);
/**
* {@inheritdoc}
*/
protected function setUp() {
$config_factory = $this->getConfigFactoryStub(array('pathauto.settings' => array('verbose' => TRUE)));
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
$account->expects($this->once())
->method('hasPermission')
->withAnyParameters()
->willReturn(TRUE);
$this->messenger = new VerboseMessenger($config_factory, $account);
}
/**
* Tests add messages.
* @covers ::addMessage
*/
public function testAddMessage() {
$this->assertTrue($this->messenger->addMessage("Test message"), "The message was added");
}
/**
* @covers ::addMessage
*/
public function testDoNotAddMessageWhileBulkupdate() {
$this->assertFalse($this->messenger->addMessage("Test message", "bulkupdate"), "The message was NOT added");
}
}
}
namespace {
// @todo Delete after https://drupal.org/node/1858196 is in.
if (!function_exists('drupal_set_message')) {
function drupal_set_message() {
}
$this->messenger = new VerboseMessenger($config_factory, $account, $messenger);
}
/**
* Tests add messages.
*
* @covers ::addMessage
*/
public function testAddMessage() {
$this->assertTrue($this->messenger->addMessage("Test message"), "The message was added");
}
/**
* @covers ::addMessage
*/
public function testDoNotAddMessageWhileBulkupdate() {
$this->assertFalse($this->messenger->addMessage("Test message", "bulkupdate"), "The message was NOT added");
}
}

View file

@ -10,6 +10,8 @@ REQUIRED:
The origin website.
$config['stage_file_proxy.settings']['origin'] = 'http://example.com'; // no trailing slash
// Drush variable set
drush config-set stage_file_proxy.settings origin http://example.com
If the site is using HTTP Basic Authentication (the browser popup for username
and password) you can embed those in the url. Be sure to URL encode any
@ -19,10 +21,21 @@ For example, setting a user name of "myusername" and password as, "letme&in" the
configuration would be the following:
$config['stage_file_proxy.settings']['origin'] = 'http://myusername:letme%26in@example.com';
// Drush variable set
drush config-set stage_file_proxy.settings origin http://myusername:letme%26in@example.com
OPTIONAL
========
$config['stage_file_proxy.settings']['verify'] = TRUE;
Default is TRUE.
If this is true (default) then the request will be done by doing the SSL
verification if the origin is using https.
$config['stage_file_proxy.settings']['use_imagecache_root'] = TRUE;
// Drush variable set
drush config-set stage_file_proxy.settings use_imagecache_root TRUE
Default is TRUE.
@ -33,6 +46,8 @@ imagecache handle it. This will speed up future imagecache requests for the
same original file.
$config['stage_file_proxy.settings']['hotlink'] = FALSE;
// Drush variable set
drush config-set stage_file_proxy.settings hotlink FALSE
Default is FALSE.
@ -41,6 +56,8 @@ local machine, it will just serve a 301 to the remote file and let the origin
webserver handle it.
$config['stage_file_proxy.settings']['origin_dir'] = 'sites/default/files';
// Drush variable set
drush config-set stage_file_proxy.settings origin_dir sites/default/files
Default is 'sites/default/files';

View file

@ -0,0 +1,61 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Stage File Proxy module saves you time and disk space by sending requests to
your development environment's files directory to the production environment and
making a copy of the production file in your development site. It makes it
easier to manage local development environments. This module should not be
installed on a server that faces the internet.
* For a full description of the module visit
https://www.drupal.org/project/stage_file_proxy
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/stage_file_proxy
REQUIREMENTS
------------
This module does not require any additional modules outside of Drupal core.
INSTALLATION
------------
Install the Stage File Proxy module as you would normally install a contributed
Drupal module. Visit https://www.drupal.org/node/1897420 for more information.
CONFIGURATION
-------------
1. Enable Stage File Proxy, either via "Extend" (/admin/modules) or via drush:
$ drush en --yes stage_file_proxy
2. Configure connection to the source. This is available via the UI, at
Configuration > Stage File Proxy Settings (admin/config/system/stage_file_proxy)
As this module should only be used on non-production sites, it is preferable to
configure this within your settings.php or settings.local.php file. Detailed
descriptions of each setting, and syntax for defining the configuration in code
is in INSTALL.md
MAINTAINERS
-----------
* Baris Wanschers (BarisW) - https://www.drupal.org/u/barisw
* Greg Knaddison (greggles) - https://www.drupal.org/u/greggles
* Rob Wilmshurst (robwilmshurst) - https://www.drupal.org/u/robwilmshurst
* netaustin - https://www.drupal.org/user/199298

View file

@ -2,3 +2,4 @@ hotlink: false
origin: ''
origin_dir: false
use_imagecache_root: true
verify: true

View file

@ -1,17 +1,19 @@
stage_file_proxy.settings:
type: mapping
type: config_object
label: 'Stage file proxy settings'
mapping:
hotlink:
type: booleam
type: boolean
label: "Hotlink"
origin:
type: string
label: "Origin"
origin_dir:
type: booelan
type: string
label: "Origin dir"
use_imagecache_root:
type: booelan
type: boolean
label: "Use imagecache root"
"
verify:
type: boolean
label: "Verify SSL"

View file

@ -55,7 +55,7 @@ class AlterExcludedPathsEvent extends Event {
* The excluded path string to add.
*/
public function addExcludedPath($excluded_path) {
$this->excludedPath[] = $excluded_path;
$this->excludedPaths[] = $excluded_path;
}
/**

View file

@ -6,12 +6,11 @@ use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Url;
use Psr\Log\LoggerInterface;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Drupal\stage_file_proxy\EventDispatcher\AlterExcludedPathsEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\stage_file_proxy\FetchManagerInterface;
/**
@ -29,14 +28,14 @@ class ProxySubscriber implements EventSubscriberInterface {
/**
* The logger.
*
* @var LoggerInterface
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The event dispatcher.
*
* @var ContainerAwareEventDispatcher
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
@ -45,12 +44,12 @@ class ProxySubscriber implements EventSubscriberInterface {
*
* @param \Drupal\stage_file_proxy\FetchManagerInterface $manager
* The manager used to fetch the file against.
*
* @param \Psr\Log\LoggerInterface $logger
* @param ContainerAwareEventDispatcher $event_dispatcher
* The logger interface.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(FetchManagerInterface $manager, LoggerInterface $logger, ContainerAwareEventDispatcher $event_dispatcher) {
public function __construct(FetchManagerInterface $manager, LoggerInterface $logger, EventDispatcherInterface $event_dispatcher) {
$this->manager = $manager;
$this->logger = $logger;
$this->eventDispatcher = $event_dispatcher;
@ -63,20 +62,34 @@ class ProxySubscriber implements EventSubscriberInterface {
* The Event to process.
*/
public function checkFileOrigin(GetResponseEvent $event) {
$file_dir = $this->manager->filePublicPath();
$uri = $event->getRequest()->getPathInfo();
$config = \Drupal::config('stage_file_proxy.settings');
// Get the origin server.
$server = $config->get('origin');
$uri = Unicode::substr($uri, 1);
if (strpos($uri, '' . $file_dir) !== 0) {
// Quit if no origin given.
if (!$server) {
return;
}
$alter_excluded_paths_event = new AlterExcludedPathsEvent(array());
$event = $this->eventDispatcher->dispatch('stage_file_proxy.alter_excluded_paths', $alter_excluded_paths_event);
$excluded_paths = $event->getExcludedPaths();
// Quit if we are the origin, ignore http(s).
if (preg_replace('#^[a-z]*://#u', '', $server) === $event->getRequest()->getHost()) {
return;
}
$file_dir = $this->manager->filePublicPath();
$request_path = $event->getRequest()->getPathInfo();
$request_path = Unicode::substr($request_path, 1);
if (strpos($request_path, '' . $file_dir) !== 0) {
return;
}
$alter_excluded_paths_event = new AlterExcludedPathsEvent([]);
$this->eventDispatcher->dispatch('stage_file_proxy.alter_excluded_paths', $alter_excluded_paths_event);
$excluded_paths = $alter_excluded_paths_event->getExcludedPaths();
foreach ($excluded_paths as $excluded_path) {
if (strpos($uri, $excluded_path) !== FALSE) {
if (strpos($request_path, $excluded_path) !== FALSE) {
return;
}
}
@ -84,50 +97,59 @@ class ProxySubscriber implements EventSubscriberInterface {
// Note if the origin server files location is different. This
// must be the exact path for the remote site's public file
// system path, and defaults to the local public file system path.
$remote_file_dir = trim(\Drupal::config('stage_file_proxy.settings')->get('origin_dir'));
$remote_file_dir = trim($config->get('origin_dir'));
if (!$remote_file_dir) {
$remote_file_dir = $file_dir;
}
$uri = rawurldecode($uri);
$relative_path = Unicode::substr($uri, Unicode::strlen($file_dir) + 1);
$request_path = rawurldecode($request_path);
// Path relative to file directory. Used for hotlinking.
$relative_path = Unicode::substr($request_path, Unicode::strlen($file_dir) + 1);
// If file is fetched and use_imagecache_root is set, original is used.
$fetch_path = $relative_path;
// Get the origin server.
$server = \Drupal::config('stage_file_proxy.settings')->get('origin');
if ($server) {
// Is this imagecache? Request the root file and let imagecache resize.
if (\Drupal::config('stage_file_proxy.settings')->get('origin') && $original_path = $this->manager->styleOriginalPath($relative_path, TRUE)) {
$relative_path = file_uri_target($original_path);
if (file_exists($original_path)) {
// Imagecache can generate it without our help.
return;
}
// Is this imagecache? Request the root file and let imagecache resize.
// We check this first so locally added files have precedence.
$original_path = $this->manager->styleOriginalPath($relative_path, TRUE);
if ($original_path) {
if (file_exists($original_path)) {
// Imagecache can generate it without our help.
return;
}
$query = \Drupal::request()->query->all();
$query_parameters = UrlHelper::filterQueryParameters($query);
if (\Drupal::config('stage_file_proxy.settings')->get('hotlink')) {
$location = Url::fromUri("$server/$remote_file_dir/$relative_path", array(
'query' => $query_parameters,
'absolute' => TRUE,
))->toString();
}
elseif ($this->manager->fetch($server, $remote_file_dir, $relative_path)) {
// Refresh this request & let the web server work out mime type, etc.
$location = Url::fromUri('base://' . $uri, array(
'query' => $query_parameters,
'absolute' => TRUE,
))->toString();
}
else {
$this->logger->error('Stage File Proxy encountered an unknown error by retrieving file @file', array('@file' => $server . '/' . UrlHelper::encodePath($remote_file_dir . '/' . $relative_path)));
throw new NotFoundHttpException();
if ($config->get('use_imagecache_root')) {
// Config says: Fetch the original.
$fetch_path = file_uri_target($original_path);
}
}
$query = \Drupal::request()->query->all();
$query_parameters = UrlHelper::filterQueryParameters($query);
$options = [
'verify' => \Drupal::config('stage_file_proxy.settings')->get('verify'),
];
if ($config->get('hotlink')) {
$location = Url::fromUri("$server/$remote_file_dir/$relative_path", [
'query' => $query_parameters,
'absolute' => TRUE,
])->toString();
}
elseif ($this->manager->fetch($server, $remote_file_dir, $fetch_path, $options)) {
// Refresh this request & let the web server work out mime type, etc.
$location = Url::fromUri('base://' . $request_path, [
'query' => $query_parameters,
'absolute' => TRUE,
])->toString();
// Avoid redirection caching in upstream proxies.
header("Cache-Control: must-revalidate, no-cache, post-check=0, pre-check=0, private");
}
else {
$this->logger->error('Stage File Proxy encountered an unknown error by retrieving file @file', ['@file' => $server . '/' . UrlHelper::encodePath($remote_file_dir . '/' . $relative_path)]);
}
if (isset($location)) {
header("Location: $location");
exit;
}
@ -139,9 +161,9 @@ class ProxySubscriber implements EventSubscriberInterface {
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
public static function getSubscribedEvents() {
// Priority 240 is after ban middleware but before page cache.
$events[KernelEvents::REQUEST][] = array('checkFileOrigin', 240);
$events[KernelEvents::REQUEST][] = ['checkFileOrigin', 240];
return $events;
}

View file

@ -3,31 +3,86 @@
namespace Drupal\stage_file_proxy;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StreamWrapper\PublicStream;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
/**
* Fetch manager.
*/
class FetchManager implements FetchManagerInterface {
public function __construct(ClientInterface $client) {
/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* The file system.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* {@inheritdoc}
*/
public function __construct(Client $client, FileSystemInterface $file_system) {
$this->client = $client;
$this->fileSystem = $file_system;
}
/**
* {@inheritdoc}
*/
public function fetch($server, $remote_file_dir, $relative_path) {
// Fetch remote file.
$url = $server . '/' . UrlHelper::encodePath($remote_file_dir . '/' . $relative_path);
$response = $this->client->get($url);
public function fetch($server, $remote_file_dir, $relative_path, array $options) {
try {
// Fetch remote file.
$url = $server . '/' . UrlHelper::encodePath($remote_file_dir . '/' . $relative_path);
$options['Connection'] = 'close';
$response = $this->client->get($url, $options);
$result = $response->getStatusCode();
if ($result != 200) {
\Drupal::logger('stage_file_proxy')->error('HTTP error @errorcode occurred when trying to fetch @remote.', [
'@errorcode' => $result,
'@remote' => $url,
]);
return FALSE;
}
if ($response->getStatusCode() == 200) {
// Prepare local target directory and save downloaded file.
$file_dir = $this->filePublicPath();
$target_dir = $file_dir . '/' . dirname($relative_path);
if (file_prepare_directory($target_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
file_put_contents($file_dir . '/' . $relative_path, $response->getBody(TRUE));
$destination = $file_dir . '/' . dirname($relative_path);
if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
\Drupal::logger('stage_file_proxy')->error('Unable to prepare local directory @path.', ['@path' => $destination]);
return FALSE;
}
$destination = str_replace('///', '//', "$destination/") . $this->fileSystem->basename($relative_path);
$response_headers = $response->getHeaders();
$content_length = array_shift($response_headers['Content-Length']);
$response_data = $response->getBody()->getContents();
if (isset($content_length) && strlen($response_data) != $content_length) {
\Drupal::logger('stage_file_proxy')->error('Incomplete download. Was expecting @content-length bytes, actually got @data-length.', [
'@content-length' => $content_length,
'@data-length' => $content_length,
]);
return FALSE;
}
if ($this->writeFile($destination, $response_data)) {
return TRUE;
}
\Drupal::logger('stage_file_proxy')->error('@remote could not be saved to @path.', ['@remote' => $url, '@path' => $destination]);
return FALSE;
}
catch (ClientException $e) {
// Do nothing.
}
return FALSE;
}
@ -43,7 +98,7 @@ class FetchManager implements FetchManagerInterface {
* {@inheritdoc}
*/
public function styleOriginalPath($uri, $style_only = TRUE) {
$scheme = file_uri_scheme($uri);
$scheme = $this->fileSystem->uriScheme($uri);
if ($scheme) {
$path = file_uri_target($uri);
}
@ -65,4 +120,72 @@ class FetchManager implements FetchManagerInterface {
return FALSE;
}
}
/**
* Use write & rename instead of write.
*
* Perform the replace operation. Since there could be multiple processes
* writing to the same file, the best option is to create a temporary file in
* the same directory and then rename it to the destination. A temporary file
* is needed if the directory is mounted on a separate machine; thus ensuring
* the rename command stays local.
*
* @param string $destination
* A string containing the destination location.
* @param string $data
* A string containing the contents of the file.
*
* @return bool
* True if write was successful. False if write or rename failed.
*/
protected function writeFile($destination, $data) {
// Get a temporary filename in the destination directory.
$dir = $this->fileSystem->dirname($destination) . '/';
$temporary_file = $this->fileSystem->tempnam($dir, 'stage_file_proxy_');
$temporary_file_copy = $temporary_file;
// Get the extension of the original filename and append it to the temp file
// name. Preserves the mime type in different stream wrapper
// implementations.
$parts = pathinfo($destination);
$extension = '.' . $parts['extension'];
if ($extension === '.gz') {
$parts = pathinfo($parts['filename']);
$extension = '.' . $parts['extension'] . $extension;
}
// Move temp file into the destination dir if not in there.
// Add the extension on as well.
$temporary_file = str_replace(substr($temporary_file, 0, strpos($temporary_file, 'stage_file_proxy_')), $dir, $temporary_file) . $extension;
// Preform the rename, adding the extension to the temp file.
if (!@rename($temporary_file_copy, $temporary_file)) {
// Remove if rename failed.
@unlink($temporary_file_copy);
return FALSE;
}
// Save to temporary filename in the destination directory.
$filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE);
// Perform the rename operation if the write succeeded.
if ($filepath) {
if (!@rename($filepath, $destination)) {
// Unlink and try again for windows. Rename on windows does not replace
// the file if it already exists.
@unlink($destination);
if (!@rename($filepath, $destination)) {
// Remove temporary_file if rename failed.
@unlink($filepath);
}
}
}
// Final check; make sure file exists & is not empty.
$result = FALSE;
if (file_exists($destination) & filesize($destination) != 0) {
$result = TRUE;
}
return $result;
}
}

View file

@ -2,12 +2,23 @@
namespace Drupal\stage_file_proxy;
use Drupal\Core\File\FileSystemInterface;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
/**
* Interface for FetchManager.
*/
interface FetchManagerInterface {
public function __construct(ClientInterface $client);
/**
* Constructs an FetchManager instance.
*
* @param \GuzzleHttp\Client $client
* The HTTP client.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system.
*/
public function __construct(Client $client, FileSystemInterface $file_system);
/**
* Downloads a remote file and saves it to the local files directory.
@ -18,11 +29,13 @@ interface FetchManagerInterface {
* The relative path to the files directory on the origin server.
* @param string $relative_path
* The path to the requested resource relative to the files directory.
* @param array $options
* Options for the request.
*
* @return bool
* Returns true if the content was downloaded, otherwise false.
*/
public function fetch($server, $remote_file_dir, $relative_path);
public function fetch($server, $remote_file_dir, $relative_path, array $options);
/**
* Helper to retrieve the file directory.
@ -33,14 +46,16 @@ interface FetchManagerInterface {
* Helper to retrieves original path for a styled image.
*
* @param string $uri
* A uri or path (may be prefixed with scheme)
* A uri or path (may be prefixed with scheme).
* @param bool $style_only
* Indicates if, the function should only return paths retrieved from style
* paths. Defaults to TRUE.
*
* @return bool|mixed|string
* A file URI pointing to the given original image.
* If $style_only is set to TRUE and $uri is no style-path, FALSE is returned.
* If $style_only is set to TRUE and $uri is no style-path, FALSE is
* returned.
*/
public function styleOriginalPath($uri, $style_only = TRUE);
}

View file

@ -6,6 +6,9 @@ use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Unicode;
/**
* Provide settings for Stage File Proxy.
*/
class SettingsForm extends ConfigFormBase {
/**
@ -30,11 +33,19 @@ class SettingsForm extends ConfigFormBase {
public function buildForm(array $form, FormStateInterface $form_state, $field_type = NULL) {
$config = $this->config('stage_file_proxy.settings');
$form['origin'] = array(
$form['origin'] = [
'#type' => 'textfield',
'#title' => t('The origin website.'),
'#title' => $this->t('The origin website.'),
'#default_value' => $config->get('origin'),
'#description' => t("The origin website. For example: 'http://example.com' with no trailing slash. If the site is using HTTP Basic Authentication (the browser popup for username and password) you can embed those in the url. Be sure to URL encode any special characters:<br/><br/>For example, setting a user name of 'myusername' and password as, 'letme&in' the configuration would be the following: <br/><br/>'http://myusername:letme%26in@example.com';"),
'#description' => $this->t("The origin website. For example: 'http://example.com' with no trailing slash. If the site is using HTTP Basic Authentication (the browser popup for username and password) you can embed those in the url. Be sure to URL encode any special characters:<br/><br/>For example, setting a user name of 'myusername' and password as, 'letme&in' the configuration would be the following: <br/><br/>'http://myusername:letme%26in@example.com';"),
'#required' => FALSE,
];
$form['verify'] = array(
'#type' => 'checkbox',
'#title' => t('Verify SSL.'),
'#default_value' => $config->get('verify'),
'#description' => t('If this is true (default) then the request will be done by doing the SSL verification if the origin is using https.'),
'#required' => FALSE,
);
@ -45,29 +56,29 @@ class SettingsForm extends ConfigFormBase {
$stage_file_proxy_origin_dir = \Drupal::service('site.path') . '/files';
}
}
$form['origin_dir'] = array(
$form['origin_dir'] = [
'#type' => 'textfield',
'#title' => t('The origin directory.'),
'#title' => $this->t('The origin directory.'),
'#default_value' => $stage_file_proxy_origin_dir,
'#description' => t('If this is set then Stage File Proxy will use a different path for the remote files. This is useful for multisite installations where the sites directory contains different names for each url. If this is not set, it defaults to the same path as the local site.'),
'#description' => $this->t('If this is set then Stage File Proxy will use a different path for the remote files. This is useful for multisite installations where the sites directory contains different names for each url. If this is not set, it defaults to the same path as the local site.'),
'#required' => FALSE,
);
];
$form['use_imagecache_root'] = array(
$form['use_imagecache_root'] = [
'#type' => 'checkbox',
'#title' => t('Imagecache Root.'),
'#title' => $this->t('Imagecache Root.'),
'#default_value' => $config->get('use_imagecache_root'),
'#description' => t("If this is true (default) then Stage File Proxy will look for /imagecache/ in the URL and determine the original file and request that rather than the processed file, then send a header to the browser to refresh the image and let imagecache handle it. This will speed up future imagecache requests for the same original file."),
'#description' => $this->t("If this is true (default) then Stage File Proxy will look for /imagecache/ in the URL and determine the original file and request that rather than the processed file, then send a header to the browser to refresh the image and let imagecache handle it. This will speed up future imagecache requests for the same original file."),
'#required' => FALSE,
);
];
$form['hotlink'] = array(
$form['hotlink'] = [
'#type' => 'checkbox',
'#title' => t('Hotlink.'),
'#title' => $this->t('Hotlink.'),
'#default_value' => $config->get('hotlink'),
'#description' => t("If this is true then Stage File Proxy will not transfer the remote file to the local machine, it will just serve a 301 to the remote file and let the origin webserver handle it."),
'#description' => $this->t("If this is true then Stage File Proxy will not transfer the remote file to the local machine, it will just serve a 301 to the remote file and let the origin webserver handle it."),
'#required' => FALSE,
);
];
return parent::buildForm($form, $form_state);
}
@ -75,7 +86,7 @@ class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
function validateForm(array &$form, FormStateInterface $form_state) {
public function validateForm(array &$form, FormStateInterface $form_state) {
$origin = $form_state->getValue('origin');
@ -95,12 +106,13 @@ class SettingsForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('stage_file_proxy.settings');
$keys = array(
$keys = [
'origin',
'origin_dir',
'use_imagecache_root',
'hotlink',
);
'verify',
];
foreach ($keys as $key) {
$config->set($key, $form_state->getValue($key));
}

View file

@ -2,12 +2,11 @@ name: Stage File Proxy
type: module
description: 'Proxies files from production server so you do not have to transfer them manually'
package: Development
# version: VERSION
# core: 8.x
configure: stage_file_proxy.admin_form
# Information added by Drupal.org packaging script on 2017-04-05
version: '8.x-1.0-alpha2'
# Information added by Drupal.org packaging script on 2017-07-12
version: '8.x-1.0-alpha3'
core: '8.x'
project: 'stage_file_proxy'
datestamp: 1491359585
datestamp: 1499900944

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Install file for stage_file_proxy module.
*/
/**
* Set verify option.
*/
function stage_file_proxy_update_8001() {
\Drupal::configFactory()->getEditable('stage_file_proxy.settings')->set('verify', TRUE)->save();
}

View file

@ -1,7 +1,7 @@
services:
stage_file_proxy.fetch_manager:
class: Drupal\stage_file_proxy\FetchManager
arguments: ['@http_client']
arguments: ['@http_client', '@file_system']
stage_file_proxy.subscriber:
class: Drupal\stage_file_proxy\EventSubscriber\ProxySubscriber

29
web/modules/contrib/token/README.md Normal file → Executable file
View file

@ -1,3 +1,13 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Recommended modules
* Installation
* Configuration
* Troubleshooting
* Maintainers
INTRODUCTION
------------
@ -9,20 +19,28 @@ INTRODUCTION
* To submit bug reports and feature suggestions, or to track changes:
https://drupal.org/project/issues/token
RECOMMENDED MODULES
-------------------
* No extra module is required.
INSTALLATION
------------
Install as usual, see
https://www.drupal.org/docs/8/extending-drupal-8/installing-contributed-modules-find-import-enable-configure-drupal-8 for further
information.
* Install as usual, see
https://www.drupal.org/docs/8/extending-drupal-8/installing-contributed-modules-find-import-enable-configure-drupal-8 for further
information.
CONFIGURATION
-------------
* No configuration is needed.
TROUBLESHOOTING
---------------
Token module doesn't provide any visible functions to the user on its own, it
just provides token handling services for other modules.
* Token module doesn't provide any visible functions to the user on its own, it
just provides token handling services for other modules.
MAINTAINERS
@ -31,3 +49,4 @@ MAINTAINERS
Current maintainers:
* Dave Reid (https://drupal.org/user/53892)
* Sascha Grossenbacher (Berdir) (https://www.drupal.org/user/214652)

View file

@ -1,5 +1,5 @@
(function ($) {
(function ($, Drupal, drupalSettings) {
'use strict';
@ -20,39 +20,74 @@
$('.token-click-insert .token-key', context).once('token-click-insert').each(function () {
var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function () {
if (typeof drupalSettings.tokenFocusedField == 'undefined') {
alert(Drupal.t('First click a text field to insert your tokens into.'));
var content = this.text;
// Always work in normal text areas that currently have focus.
if (drupalSettings.tokenFocusedField && (drupalSettings.tokenFocusedField.tokenDialogFocus || drupalSettings.tokenFocusedField.tokenHasFocus)) {
insertAtCursor(drupalSettings.tokenFocusedField, content);
}
// Direct tinyMCE support.
else if (typeof(tinyMCE) != 'undefined' && tinyMCE.activeEditor) {
tinyMCE.activeEditor.execCommand('mceInsertContent', false, content);
}
// Direct CKEditor support. Only works if the field currently has focus,
// which is unusual since the dialog is open.
else if (typeof(CKEDITOR) != 'undefined' && CKEDITOR.currentInstance) {
CKEDITOR.currentInstance.insertHtml(content);
}
// Direct CodeMirror support.
else if (typeof(CodeMirror) != 'undefined' && drupalSettings.tokenFocusedField && $(drupalSettings.tokenFocusedField).parents('.CodeMirror').length) {
var editor = $(drupalSettings.tokenFocusedField).parents('.CodeMirror')[0].CodeMirror;
editor.replaceSelection(content);
editor.focus();
}
// WYSIWYG support, should work in all editors if available.
else if (Drupal.wysiwyg && Drupal.wysiwyg.activeId) {
Drupal.wysiwyg.instances[Drupal.wysiwyg.activeId].insert(content)
}
// CKeditor module support.
else if (typeof(CKEDITOR) != 'undefined' && typeof(Drupal.ckeditorActiveId) != 'undefined') {
CKEDITOR.instances[Drupal.ckeditorActiveId].insertHtml(content);
}
else if (drupalSettings.tokenFocusedField) {
insertAtCursor(drupalSettings.tokenFocusedField, content);
}
else {
var myField = drupalSettings.tokenFocusedField;
var myValue = $(this).text();
// IE support.
if (document.selection) {
myField.focus();
var sel = document.selection.createRange();
sel.text = myValue;
}
// MOZILLA/NETSCAPE support.
else if (myField.selectionStart || myField.selectionStart === '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)
+ myValue
+ myField.value.substring(endPos, myField.value.length);
}
else {
myField.value += myValue;
}
$('html,body').animate({scrollTop: $(myField).offset().top}, 500);
alert(Drupal.t('First click a text field to insert your tokens into.'));
}
return false;
});
$(this).html(newThis);
});
function insertAtCursor(editor, content) {
// Record the current scroll position.
var scroll = editor.scrollTop;
// IE support.
if (document.selection) {
editor.focus();
var sel = document.selection.createRange();
sel.text = content;
}
// Mozilla/Firefox/Netscape 7+ support.
else if (editor.selectionStart || editor.selectionStart == '0') {
var startPos = editor.selectionStart;
var endPos = editor.selectionEnd;
editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length);
}
// Fallback, just add to the end of the content.
else {
editor.value += content;
}
// Ensure the textarea does not unexpectedly scroll.
editor.scrollTop = scroll;
}
}
};
})(jQuery, drupalSettings);
})(jQuery, Drupal, drupalSettings);

View file

@ -2,7 +2,6 @@
namespace Drupal\token\Controller;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Controller\ControllerBase;
use Drupal\token\TreeBuilderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -48,9 +47,9 @@ class TokenAutocompleteController extends ControllerBase {
public function autocomplete($token_type, $filter, Request $request) {
$filter = substr($filter, strrpos($filter, '['));
$matches = array();
$matches = [];
if (!Unicode::strlen($filter)) {
if (!mb_strlen($filter)) {
$matches["[{$token_type}:"] = 0;
}
else {

View file

@ -7,14 +7,14 @@ use Drupal\Core\Controller\ControllerBase;
/**
* Clears cache for tokens.
*/
class TokenCacheController extends ControllerBase {
class TokenCacheController extends ControllerBase {
/**
* Clear caches and redirect back to the frontpage.
*/
public function flush() {
token_clear_cache();
drupal_set_message(t('Token registry caches cleared.'));
$this->messenger()->addMessage($this->t('Token registry caches cleared.'));
return $this->redirect('<front>');
}

View file

@ -50,6 +50,17 @@ class TokenTreeController extends ControllerBase {
$build['#cache']['contexts'][] = 'url.query_args:options';
$build['#title'] = $this->t('Available tokens');
// If this is an AJAX/modal request, add a wrapping div to the contents so
// that Drupal.behaviors.tokenTree and Drupal.behaviors.tokenAttach can
// stil find the elements they need to.
// @see https://www.drupal.org/project/token/issues/2994671
// @see https://www.drupal.org/node/2940704
// @see http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/
if ($request->isXmlHttpRequest()) {
$build['#prefix'] = '<div>';
$build['#suffix'] = '</div>';
}
return $build;
}

View file

@ -71,4 +71,5 @@ class RouteSubscriber extends RouteSubscriberBase {
$events[RoutingEvents::ALTER] = array('onAlterRoutes', 100);
return $events;
}
}

View file

@ -1,6 +1,7 @@
<?php
namespace Drupal\token\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
@ -21,38 +22,38 @@ class TokenBlockTest extends TokenTestBase {
/**
* {@inheritdoc}
*/
public function setUp($modules = array()) {
public function setUp($modules = []) {
parent::setUp();
$this->admin_user = $this->drupalCreateUser(array('access content', 'administer blocks'));
$this->admin_user = $this->drupalCreateUser(['access content', 'administer blocks']);
$this->drupalLogin($this->admin_user);
}
public function testBlockTitleTokens() {
$label = 'tokenblock';
$bundle = BlockContentType::create(array(
$bundle = BlockContentType::create([
'id' => $label,
'label' => $label,
'revision' => FALSE
));
]);
$bundle->save();
$block_content = BlockContent::create(array(
$block_content = BlockContent::create([
'type' => $label,
'label' => '[current-page:title] block title',
'info' => 'Test token title block',
'body[value]' => 'This is the test token title block.',
));
]);
$block_content->save();
$block = $this->drupalPlaceBlock('block_content:' . $block_content->uuid(), array(
$block = $this->drupalPlaceBlock('block_content:' . $block_content->uuid(), [
'label' => '[user:name]',
));
$this->drupalGet($block->urlInfo());
]);
$this->drupalGet($block->toUrl());
// Ensure that the link to available tokens is present and correctly
// positioned.
$this->assertLink('Browse available tokens.');
$this->assertText('This field supports tokens. Browse available tokens.');
$this->drupalPostForm(NULL, array(), t('Save block'));
$this->drupalPostForm(NULL, [], t('Save block'));
// Ensure token validation is working on the block.
$this->assertText('Title is using the following invalid tokens: [user:name].');
@ -63,8 +64,8 @@ class TokenBlockTest extends TokenTestBase {
$block->save();
// Ensure that tokens are not double-escaped when output as a block title.
$this->drupalCreateContentType(array('type' => 'page'));
$node = $this->drupalCreateNode(array('title' => "Site's first node"));
$this->drupalCreateContentType(['type' => 'page']);
$node = $this->drupalCreateNode(['title' => "Site's first node"]);
$this->drupalGet('node/' . $node->id());
// The apostraphe should only be escaped once.
$this->assertRaw("Site&#039;s first node block title");

View file

@ -16,19 +16,19 @@ class TokenCurrentPageTest extends TokenTestBase {
*
* @var array
*/
public static $modules = array('node');
public static $modules = ['node'];
function testCurrentPageTokens() {
$tokens = array(
$tokens = [
'[current-page:title]' => t('Log in'),
'[current-page:url]' => Url::fromRoute('user.login', [], array('absolute' => TRUE))->toString(),
'[current-page:url:absolute]' => Url::fromRoute('user.login', [], array('absolute' => TRUE))->toString(),
'[current-page:url]' => Url::fromRoute('user.login', [], ['absolute' => TRUE])->toString(),
'[current-page:url:absolute]' => Url::fromRoute('user.login', [], ['absolute' => TRUE])->toString(),
'[current-page:url:relative]' => Url::fromRoute('user.login')->toString(),
'[current-page:url:path]' => '/user/login',
'[current-page:url:args:value:0]' => 'user',
'[current-page:url:args:value:1]' => 'login',
'[current-page:url:args:value:2]' => NULL,
'[current-page:url:unaliased]' => Url::fromRoute('user.login', [], array('absolute' => TRUE, 'alias' => TRUE))->toString(),
'[current-page:url:unaliased]' => Url::fromRoute('user.login', [], ['absolute' => TRUE, 'alias' => TRUE])->toString(),
'[current-page:page-number]' => 1,
'[current-page:query:foo]' => NULL,
'[current-page:query:bar]' => NULL,
@ -36,20 +36,20 @@ class TokenCurrentPageTest extends TokenTestBase {
'[current-page:arg:0]' => 'user',
'[current-page:arg:1]' => 'login',
'[current-page:arg:2]' => NULL,
);
];
$this->assertPageTokens('user/login', $tokens);
$this->drupalCreateContentType(array('type' => 'page'));
$node = $this->drupalCreateNode(array('title' => 'Node title', 'path' => array('alias' => '/node-alias')));
$tokens = array(
$this->drupalCreateContentType(['type' => 'page']);
$node = $this->drupalCreateNode(['title' => 'Node title', 'path' => ['alias' => '/node-alias']]);
$tokens = [
'[current-page:title]' => 'Node title',
'[current-page:url]' => $node->url('canonical', array('absolute' => TRUE)),
'[current-page:url:absolute]' => $node->url('canonical', array('absolute' => TRUE)),
'[current-page:url:relative]' => $node->url(),
'[current-page:url]' => $node->toUrl('canonical', ['absolute' => TRUE])->toString(),
'[current-page:url:absolute]' => $node->toUrl('canonical', ['absolute' => TRUE])->toString(),
'[current-page:url:relative]' => $node->toUrl()->toString(),
'[current-page:url:alias]' => '/node-alias',
'[current-page:url:args:value:0]' => 'node-alias',
'[current-page:url:args:value:1]' => NULL,
'[current-page:url:unaliased]' => $node->url('canonical', array('absolute' => TRUE, 'alias' => TRUE)),
'[current-page:url:unaliased]' => $node->toUrl('canonical', ['absolute' => TRUE, 'alias' => TRUE])->toString(),
'[current-page:url:unaliased:args:value:0]' => 'node',
'[current-page:url:unaliased:args:value:1]' => $node->id(),
'[current-page:url:unaliased:args:value:2]' => NULL,
@ -60,7 +60,7 @@ class TokenCurrentPageTest extends TokenTestBase {
'[current-page:arg:0]' => 'node',
'[current-page:arg:1]' => 1,
'[current-page:arg:2]' => NULL,
);
$this->assertPageTokens("/node/{$node->id()}", $tokens, array(), array('url_options' => array('query' => array('foo' => 'bar'))));
];
$this->assertPageTokens("/node/{$node->id()}", $tokens, [], ['url_options' => ['query' => ['foo' => 'bar']]]);
}
}

View file

@ -2,6 +2,8 @@
namespace Drupal\token\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;
use Drupal\node\Entity\Node;
use Drupal\file\Entity\File;
@ -41,50 +43,50 @@ class TokenFieldUiTest extends TokenTestBase {
]);
$node_type->save();
entity_create('field_storage_config', array(
FieldStorageConfig::create([
'field_name' => 'field_body',
'entity_type' => 'node',
'type' => 'text_with_summary',
))->save();
entity_create('field_config', array(
])->save();
FieldConfig::create([
'field_name' => 'field_body',
'label' => 'Body',
'entity_type' => 'node',
'bundle' => 'article',
))->save();
entity_create('field_storage_config', array(
])->save();
FieldStorageConfig::create([
'field_name' => 'field_image',
'entity_type' => 'node',
'type' => 'image',
))->save();
entity_create('field_config', array(
])->save();
FieldConfig::create([
'field_name' => 'field_image',
'label' => 'Image',
'entity_type' => 'node',
'bundle' => 'article',
))->save();
entity_create('field_storage_config', array(
])->save();
FieldStorageConfig::create([
'field_name' => 'field_image_2',
'entity_type' => 'node',
'type' => 'image',
))->save();
entity_create('field_config', array(
])->save();
FieldConfig::create([
'field_name' => 'field_image_2',
'label' => 'Image 2',
'entity_type' => 'node',
'bundle' => 'article',
))->save();
entity_create('field_storage_config', array(
])->save();
FieldStorageConfig::create([
'field_name' => 'multivalued_field_image',
'entity_type' => 'node',
'type' => 'image',
))->save();
entity_create('field_config', array(
])->save();
FieldConfig::create([
'field_name' => 'multivalued_field_image',
'label' => 'Multivalued field image',
'entity_type' => 'node',
'bundle' => 'article',
))->save();
])->save();
entity_get_form_display('node', 'article', 'default')
->setComponent('field_body', [

View file

@ -35,11 +35,11 @@ class TokenMenuTest extends TokenTestBase {
// Make sure we have a body field on the node type.
$this->drupalCreateContentType(['type' => 'page']);
// Add a menu.
$menu = entity_create('menu', array(
$menu = Menu::create([
'id' => 'main-menu',
'label' => 'Main menu',
'description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.',
));
]);
$menu->save();
// Place the menu block.
@ -47,25 +47,25 @@ class TokenMenuTest extends TokenTestBase {
// Add a root link.
/** @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent $root_link */
$root_link = entity_create('menu_link_content', array(
$root_link = MenuLinkContent::create([
'link' => ['uri' => 'internal:/admin'],
'title' => 'Administration',
'menu_name' => 'main-menu',
));
]);
$root_link->save();
// Add another link with the root link as the parent.
/** @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent $parent_link */
$parent_link = entity_create('menu_link_content', array(
$parent_link = MenuLinkContent::create([
'link' => ['uri' => 'internal:/admin/config'],
'title' => 'Configuration',
'menu_name' => 'main-menu',
'parent' => $root_link->getPluginId(),
));
]);
$parent_link->save();
// Test menu link tokens.
$tokens = array(
$tokens = [
'id' => $parent_link->getPluginId(),
'title' => 'Configuration',
'menu' => 'Main menu',
@ -73,13 +73,13 @@ class TokenMenuTest extends TokenTestBase {
'menu:machine-name' => $menu->id(),
'menu:description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.',
'menu:menu-link-count' => '2',
'menu:edit-url' => Url::fromRoute('entity.menu.edit_form', ['menu' => 'main-menu'], array('absolute' => TRUE))->toString(),
'url' => Url::fromRoute('system.admin_config', [], array('absolute' => TRUE))->toString(),
'url:absolute' => Url::fromRoute('system.admin_config', [], array('absolute' => TRUE))->toString(),
'url:relative' => Url::fromRoute('system.admin_config', [], array('absolute' => FALSE))->toString(),
'menu:edit-url' => Url::fromRoute('entity.menu.edit_form', ['menu' => 'main-menu'], ['absolute' => TRUE])->toString(),
'url' => Url::fromRoute('system.admin_config', [], ['absolute' => TRUE])->toString(),
'url:absolute' => Url::fromRoute('system.admin_config', [], ['absolute' => TRUE])->toString(),
'url:relative' => Url::fromRoute('system.admin_config', [], ['absolute' => FALSE])->toString(),
'url:path' => '/admin/config',
'url:alias' => '/admin/config',
'edit-url' => Url::fromRoute('entity.menu_link_content.canonical', ['menu_link_content' => $parent_link->id()], array('absolute' => TRUE))->toString(),
'edit-url' => Url::fromRoute('entity.menu_link_content.canonical', ['menu_link_content' => $parent_link->id()], ['absolute' => TRUE])->toString(),
'parent' => 'Administration',
'parent:id' => $root_link->getPluginId(),
'parent:title' => 'Administration',
@ -92,32 +92,32 @@ class TokenMenuTest extends TokenTestBase {
'root:id' => $root_link->getPluginId(),
'root:parent' => NULL,
'root:root' => NULL,
);
$this->assertTokens('menu-link', array('menu-link' => $parent_link), $tokens);
];
$this->assertTokens('menu-link', ['menu-link' => $parent_link], $tokens);
// Add a node.
$node = $this->drupalCreateNode();
// Allow main menu for this node type.
//$this->config('menu.entity.node.' . $node->getType())->set('available_menus', array('main-menu'))->save();
//$this->config('menu.entity.node.' . $node->getType())->set('available_menus', ['main-menu'])->save();
// Add a node menu link.
/** @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent $node_link */
$node_link = entity_create('menu_link_content', array(
'link' => ['uri' =>'entity:node/' . $node->id()],
$node_link = MenuLinkContent::create([
'link' => ['uri' => 'entity:node/' . $node->id()],
'title' => 'Node link',
'parent' => $parent_link->getPluginId(),
'menu_name' => 'main-menu',
));
]);
$node_link->save();
// Test [node:menu] tokens.
$tokens = array(
$tokens = [
'menu-link' => 'Node link',
'menu-link:id' => $node_link->getPluginId(),
'menu-link:title' => 'Node link',
'menu-link:menu' => 'Main menu',
'menu-link:url' => $node->url('canonical', ['absolute' => TRUE]),
'menu-link:url' => $node->toUrl('canonical', ['absolute' => TRUE])->toString(),
'menu-link:url:path' => '/node/' . $node->id(),
'menu-link:edit-url' => $node_link->url('edit-form', ['absolute' => TRUE]),
'menu-link:parent' => 'Configuration',
@ -127,12 +127,12 @@ class TokenMenuTest extends TokenTestBase {
'menu-link:parents:keys' => $root_link->getPluginId() . ', ' . $parent_link->getPluginId(),
'menu-link:root' => 'Administration',
'menu-link:root:id' => $root_link->getPluginId(),
);
$this->assertTokens('node', array('node' => $node), $tokens);
];
$this->assertTokens('node', ['node' => $node], $tokens);
// Reload the node which will not have $node->menu defined and re-test.
$loaded_node = Node::load($node->id());
$this->assertTokens('node', array('node' => $loaded_node), $tokens);
$this->assertTokens('node', ['node' => $loaded_node], $tokens);
// Regression test for http://drupal.org/node/1317926 to ensure the
// original node object is not changed when calling menu_node_prepare().
@ -148,11 +148,11 @@ class TokenMenuTest extends TokenTestBase {
'access administration pages',
]));
// Setup node type menu options.
$edit = array(
$edit = [
'menu_options[main-menu]' => 1,
'menu_options[main]' => 1,
'menu_parent' => 'main-menu:',
);
];
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
// Use a menu-link token in the body.
@ -164,7 +164,7 @@ class TokenMenuTest extends TokenTestBase {
'body[0][value]' => 'This is a [node:menu-link:title] token to the menu link title',
'menu[enabled]' => 1,
'menu[title]' => 'Test preview',
], t('Save and publish'));
], t('Save'));
$node = $this->drupalGetNodeByTitle('Node menu title test');
$this->assertEqual('This is a Test preview token to the menu link title', $node->body->value);
@ -173,7 +173,7 @@ class TokenMenuTest extends TokenTestBase {
$link = menu_ui_get_menu_link_defaults($node);
$this->drupalPostForm('admin/structure/menu/manage/main-menu', ['links[menu_plugin_id:' . $link['id'] . '][enabled]' => FALSE], t('Save'));
$this->assertText('Menu Main menu has been updated.');
$this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and keep published'));
$this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save'));
$this->assertNoLink('Test preview');
// Now test a parent link and token.
@ -186,7 +186,7 @@ class TokenMenuTest extends TokenTestBase {
$select = reset($selects);
$options = $this->getAllOptions($select);
// Filter to items with title containing 'Test preview'.
$options = array_filter($options, function(\SimpleXMLElement $item) {
$options = array_filter($options, function (\SimpleXMLElement $item) {
return strpos((string) $item[0], 'Test preview') !== FALSE;
});
$this->assertEqual(1, count($options));
@ -195,17 +195,17 @@ class TokenMenuTest extends TokenTestBase {
'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
'menu[enabled]' => 1,
'menu[title]' => 'Child link',
'menu[menu_parent]' => 'main-menu:' . $parent_link->getPluginId(),
], t('Save and publish'));
'menu[menu_parent]' => 'main-menu:' . $parent_link->getPluginId(),
], t('Save'));
$node = $this->drupalGetNodeByTitle('Node menu title parent path test');
$this->assertEqual('This is a /admin/config token to the menu link parent', $node->body->value);
// Now edit the node and update the parent and title.
$this->drupalPostForm('node/' . $node->id() . '/edit', [
'menu[menu_parent]' => 'main-menu:' . $node_link->getPluginId(),
'menu[menu_parent]' => 'main-menu:' . $node_link->getPluginId(),
'title[0][value]' => 'Node menu title edit parent path test',
'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
], t('Save and keep published'));
], t('Save'));
$node = $this->drupalGetNodeByTitle('Node menu title edit parent path test', TRUE);
$this->assertEqual(sprintf('This is a /node/%d token to the menu link parent', $loaded_node->id()), $node->body->value);
@ -218,7 +218,7 @@ class TokenMenuTest extends TokenTestBase {
$select = reset($selects);
$options = $this->getAllOptions($select);
// Filter to items with title containing 'Test preview'.
$options = array_filter($options, function(\SimpleXMLElement $item) {
$options = array_filter($options, function (\SimpleXMLElement $item) {
return strpos((string) $item[0], 'Child link') !== FALSE;
});
$this->assertEqual(1, count($options));
@ -229,7 +229,7 @@ class TokenMenuTest extends TokenTestBase {
'title[0][value]' => 'Node menu adding menu later test',
'body[0][value]' => 'Going to add a menu link on edit',
'menu[enabled]' => 0,
], t('Save and publish'));
], t('Save'));
$node = $this->drupalGetNodeByTitle('Node menu adding menu later test');
// Now edit it and add a menu item.
$this->drupalGet('node/' . $node->id() . '/edit');
@ -238,8 +238,8 @@ class TokenMenuTest extends TokenTestBase {
'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
'menu[enabled]' => 1,
'menu[title]' => 'Child link',
'menu[menu_parent]' => 'main-menu:' . $parent_link->getPluginId(),
], t('Save and keep published'));
'menu[menu_parent]' => 'main-menu:' . $parent_link->getPluginId(),
], t('Save'));
$node = $this->drupalGetNodeByTitle('Node menu adding menu later test', TRUE);
$this->assertEqual('This is a /admin/config token to the menu link parent', $node->body->value);
// And make sure the menu link exists with the right URI.
@ -260,18 +260,18 @@ class TokenMenuTest extends TokenTestBase {
'menu[enabled]' => 1,
'menu[title]' => 'menu link provided by node',
];
$this->drupalPostForm('node/add/page', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/page', $edit, t('Save'));
$this->assertText('page ' . $node_title . ' has been created');
$node = $this->drupalGetNodeByTitle($node_title);
$menu_ui_link1 = entity_create('menu_link_content', [
$menu_ui_link1 = MenuLinkContent::create([
'link' => ['uri' => 'entity:node/' . $node->id()],
'title' => 'menu link 1 provided by menu ui',
'menu_name' => 'main-menu',
]);
$menu_ui_link1->save();
$menu_ui_link2 = entity_create('menu_link_content', [
$menu_ui_link2 = MenuLinkContent::create([
'link' => ['uri' => 'entity:node/' . $node->id()],
'title' => 'menu link 2 provided by menu ui',
'menu_name' => 'main-menu',
@ -306,7 +306,7 @@ class TokenMenuTest extends TokenTestBase {
]);
$node_type->save();
$permissions = array(
$permissions = [
'access administration pages',
'administer content translation',
'administer content types',
@ -316,18 +316,18 @@ class TokenMenuTest extends TokenTestBase {
'edit any article content',
'translate any entity',
'administer menu',
);
];
$this->drupalLogin($this->drupalCreateUser($permissions));
// Enable translation for articles and menu links.
$this->drupalGet('admin/config/regional/content-language');
$edit = array(
$edit = [
'entity_types[node]' => TRUE,
'entity_types[menu_link_content]' => TRUE,
'settings[node][article][translatable]' => TRUE,
'settings[node][article][fields][title]' => TRUE,
'settings[menu_link_content][menu_link_content][translatable]' => TRUE,
);
];
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->assertText('Settings successfully updated.');
@ -411,10 +411,10 @@ class TokenMenuTest extends TokenTestBase {
// - parent
// - child-1
// - child-1-1
Menu::create(array(
Menu::create([
'id' => 'menu_test',
'label' => 'Test menu',
))->save();
])->save();
$base_options = [
'provider' => 'menu_test',
'menu_name' => 'menu_test',

View file

@ -16,6 +16,6 @@ abstract class TokenTestBase extends WebTestBase {
*
* @var array
*/
public static $modules = array('path', 'token', 'token_module_test');
public static $modules = ['path', 'token', 'token_module_test'];
}

View file

@ -11,57 +11,57 @@ use Drupal\Core\Render\BubbleableMetadata;
*/
trait TokenTestTrait {
function assertToken($type, array $data, $token, $expected, array $options = array()) {
return $this->assertTokens($type, $data, array($token => $expected), $options);
function assertToken($type, array $data, $token, $expected, array $options = []) {
return $this->assertTokens($type, $data, [$token => $expected], $options);
}
function assertTokens($type, array $data, array $tokens, array $options = array()) {
function assertTokens($type, array $data, array $tokens, array $options = []) {
$input = $this->mapTokenNames($type, array_keys($tokens));
$bubbleable_metadata = new BubbleableMetadata();
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
foreach ($tokens as $name => $expected) {
$token = $input[$name];
if (!isset($expected)) {
$this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
$this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token]));
}
elseif (!isset($replacements[$token])) {
$this->fail(t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
$this->fail(t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token]));
}
elseif (!empty($options['regex'])) {
$this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
$this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected]));
}
else {
$this->assertEqual($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
$this->assertEqual($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected]));
}
}
return $replacements;
}
function mapTokenNames($type, array $tokens = array()) {
$return = array();
function mapTokenNames($type, array $tokens = []) {
$return = [];
foreach ($tokens as $token) {
$return[$token] = "[$type:$token]";
}
return $return;
}
function assertNoTokens($type, array $data, array $tokens, array $options = array()) {
function assertNoTokens($type, array $data, array $tokens, array $options = []) {
$input = $this->mapTokenNames($type, $tokens);
$bubbleable_metadata = new BubbleableMetadata();
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
foreach ($tokens as $name) {
$token = $input[$name];
$this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
$this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token]));
}
}
function saveAlias($source, $alias, $language = Language::LANGCODE_NOT_SPECIFIED) {
$alias = array(
$alias = [
'source' => $source,
'alias' => $alias,
'language' => $language,
);
];
\Drupal::service('path.alias_storage')->save($alias['source'], $alias['alias']);
return $alias;
}
@ -74,22 +74,22 @@ trait TokenTestTrait {
/**
* Make a page request and test for token generation.
*/
function assertPageTokens($url, array $tokens, array $data = array(), array $options = array()) {
function assertPageTokens($url, array $tokens, array $data = [], array $options = []) {
if (empty($tokens)) {
return TRUE;
}
$token_page_tokens = array(
$token_page_tokens = [
'tokens' => $tokens,
'data' => $data,
'options' => $options,
);
];
\Drupal::state()->set('token_page_tokens', $token_page_tokens);
$options += array('url_options' => array());
$options += ['url_options' => []];
$this->drupalGet($url, $options['url_options']);
$this->refreshVariables();
$result = \Drupal::state()->get('token_page_tokens', array());
$result = \Drupal::state()->get('token_page_tokens', []);
if (!isset($result['values']) || !is_array($result['values'])) {
return $this->fail('Failed to generate tokens.');
@ -97,13 +97,13 @@ trait TokenTestTrait {
foreach ($tokens as $token => $expected) {
if (!isset($expected)) {
$this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", array('@token' => $token)));
$this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", ['@token' => $token]));
}
elseif (!isset($result['values'][$token])) {
$this->fail(t('Failed to generate token @token.', array('@token' => $token)));
$this->fail(t('Failed to generate token @token.', ['@token' => $token]));
}
else {
$this->assertIdentical($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected)));
$this->assertIdentical($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", ['@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected]));
}
}
}

View file

@ -16,7 +16,7 @@ class TokenURLTest extends TokenTestBase {
*
* @var array
*/
public static $modules = array('node');
public static $modules = ['node'];
/**
* {@inheritdoc}
@ -27,25 +27,25 @@ class TokenURLTest extends TokenTestBase {
}
function testURLTokens() {
$url = new Url('entity.node.canonical', array('node' => 1));
$tokens = array(
$url = new Url('entity.node.canonical', ['node' => 1]);
$tokens = [
'absolute' => $url->setAbsolute()->toString(),
'relative' => $url->setAbsolute(FALSE)->toString(),
'path' => '/first-node',
'brief' => preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString()),
'brief' => preg_replace(['!^https?://!', '!/$!'], '', $url->setAbsolute()->toString()),
'args:value:0' => 'first-node',
'args:value:1' => NULL,
'args:value:N' => NULL,
'unaliased' => $url->setAbsolute()->setOption('alias', TRUE)->toString(),
'unaliased:relative' => $url->setAbsolute(FALSE)->setOption('alias', TRUE)->toString(),
'unaliased:path' => '/node/1',
'unaliased:brief' => preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->setOption('alias', TRUE)->toString()),
'unaliased:brief' => preg_replace(['!^https?://!', '!/$!'], '', $url->setAbsolute()->setOption('alias', TRUE)->toString()),
'unaliased:args:value:0' => 'node',
'unaliased:args:value:1' => '1',
'unaliased:args:value:2' => NULL,
// Deprecated tokens.
'alias' => '/first-node',
);
$this->assertTokens('url', array('url' => new Url('entity.node.canonical', array('node' => 1))), $tokens);
];
$this->assertTokens('url', ['url' => new Url('entity.node.canonical', ['node' => 1])], $tokens);
}
}

View file

@ -2,6 +2,7 @@
namespace Drupal\token\Tests;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\field\Entity\FieldStorageConfig;
@ -24,7 +25,7 @@ class TokenUserTest extends TokenTestBase {
*
* @var array
*/
public static $modules = array('token_user_picture');
public static $modules = ['token_user_picture'];
/**
* {@inheritdoc}
@ -32,10 +33,17 @@ class TokenUserTest extends TokenTestBase {
public function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser(['administer users', 'administer account settings']);
$this->account = $this->drupalCreateUser([
'administer users',
'administer account settings',
'access content',
]);
$this->drupalLogin($this->account);
}
/**
* Tests the user releated tokens.
*/
public function testUserTokens() {
// Enable user pictures.
\Drupal::state()->set('user_pictures', 1);
@ -49,7 +57,7 @@ class TokenUserTest extends TokenTestBase {
// Add a user picture to the account.
$image = current($this->drupalGetTestFiles('image'));
$edit = array('files[user_picture_0]' => drupal_realpath($image->uri));
$edit = ['files[user_picture_0]' => \Drupal::service('file_system')->realpath($image->uri)];
$this->drupalPostForm('user/' . $this->account->id() . '/edit', $edit, t('Save'));
$storage = \Drupal::entityTypeManager()->getStorage('user');
@ -65,14 +73,14 @@ class TokenUserTest extends TokenTestBase {
];
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$user_tokens = array(
$user_tokens = [
'picture' => $renderer->renderPlain($picture),
'picture:fid' => $this->account->user_picture->target_id,
'picture:size-raw' => 125,
'ip-address' => NULL,
'roles' => implode(', ', $this->account->getRoles()),
);
$this->assertTokens('user', array('user' => $this->account), $user_tokens);
];
$this->assertTokens('user', ['user' => $this->account], $user_tokens);
// Remove the simpletest-created user role.
$roles = $this->account->getRoles();
@ -84,33 +92,36 @@ class TokenUserTest extends TokenTestBase {
$storage->resetCache();
$this->account = $storage->load($this->account->id());
$user_tokens = array(
$user_tokens = [
'picture' => NULL,
'picture:fid' => NULL,
'ip-address' => NULL,
'roles' => 'authenticated',
'roles:keys' => (string) DRUPAL_AUTHENTICATED_RID,
);
$this->assertTokens('user', array('user' => $this->account), $user_tokens);
'roles:keys' => AccountInterface::AUTHENTICATED_ROLE,
];
$this->assertTokens('user', ['user' => $this->account], $user_tokens);
// The ip address token should work for the current user token type.
$tokens = array(
$tokens = [
'ip-address' => \Drupal::request()->getClientIp(),
);
$this->assertTokens('current-user', array(), $tokens);
];
$this->assertTokens('current-user', [], $tokens);
$anonymous = new AnonymousUserSession();
$tokens = array(
$tokens = [
'roles' => 'anonymous',
'roles:keys' => (string) DRUPAL_ANONYMOUS_RID,
);
$this->assertTokens('user', array('user' => $anonymous), $tokens);
'roles:keys' => AccountInterface::ANONYMOUS_ROLE,
];
$this->assertTokens('user', ['user' => $anonymous], $tokens);
}
/**
* Test user account settings.
*/
public function testUserAccountSettings() {
$this->drupalGet('admin/config/people/accounts');
$this->assertText('The list of available tokens that can be used in e-mails is provided below.');
$this->assertLink('Browse available tokens.');
$this->assertLinkByHref('token/tree');
}
}
}

View file

@ -137,7 +137,7 @@ class TreeTest extends TokenTestBase {
protected function getTokenTreeUrl($options = []) {
$this->drupalGet('token_module_test/browse');
$this->assertTitle('Available Tokens | Drupal');
$links = $this->xpath('//a[contains(@href, :href)]/@href', array(':href' => 'token/tree'));
$links = $this->xpath('//a[contains(@href, :href)]/@href', [':href' => 'token/tree']);
$link = $this->getAbsoluteUrl((string) current($links));
if (!empty($options)) {
$options = Json::encode($options);

View file

@ -65,9 +65,9 @@ class Token extends TokenBase implements TokenInterface {
}
$this->tokenInfo = $token_info;
$this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, array(
$this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, [
static::TOKEN_INFO_CACHE_TAG,
));
]);
}
}
@ -136,7 +136,7 @@ class Token extends TokenBase implements TokenInterface {
*/
function getInvalidTokens($type, $tokens) {
$token_info = $this->getInfo();
$invalid_tokens = array();
$invalid_tokens = [];
foreach ($tokens as $token => $full_token) {
if (isset($token_info['tokens'][$type][$token])) {
@ -162,7 +162,7 @@ class Token extends TokenBase implements TokenInterface {
}
else {
// Recursively check the chained tokens.
$sub_tokens = $this->findWithPrefix(array($token => $full_token), $parts[0]);
$sub_tokens = $this->findWithPrefix([$token => $full_token], $parts[0]);
$invalid_tokens = array_merge($invalid_tokens, $this->getInvalidTokens($sub_token_info['type'], $sub_tokens));
}
}
@ -184,7 +184,7 @@ class Token extends TokenBase implements TokenInterface {
$valid_types = array_merge($valid_types, $this->getGlobalTokenTypes());
}
$invalid_tokens = array();
$invalid_tokens = [];
$value_tokens = is_string($value) ? $this->scan($value) : $value;
foreach ($value_tokens as $type => $tokens) {

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