first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
/**
* @file
* Styling for the shortcut module icons.
*/
/**
* Toolbar tab icon.
*/
.toolbar-bar .toolbar-icon-shortcut::before {
background-image: url(../../../misc/icons/bebebe/star.svg);
}
.toolbar-bar .toolbar-icon-shortcut:active::before,
.toolbar-bar .toolbar-icon-shortcut.is-active::before {
background-image: url(../../../misc/icons/ffffff/star.svg);
}
/**
* Add/remove links.
*/
.shortcut-action__icon {
display: inline-block;
width: 20px;
height: 20px;
vertical-align: -2px;
background: transparent url(../images/favstar.svg) no-repeat left top;
}
[dir="rtl"] .shortcut-action__icon {
background-image: url(../images/favstar-rtl.svg);
}
.shortcut-action--add:hover .shortcut-action__icon,
.shortcut-action--add:focus .shortcut-action__icon {
background-position: -20px top;
}
.shortcut-action--remove .shortcut-action__icon {
background-position: -40px top;
}
.shortcut-action--remove:focus .shortcut-action__icon,
.shortcut-action--remove:hover .shortcut-action__icon {
background-position: -60px top;
}

View File

@@ -0,0 +1,59 @@
/**
* @file
* Styling for the shortcut module.
*/
/**
* Toolbar.
*/
.toolbar .toolbar-tray-vertical .edit-shortcuts {
padding: 1em;
text-align: right; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-tray-vertical .edit-shortcuts {
text-align: left;
}
.toolbar .toolbar-tray-horizontal .edit-shortcuts {
float: right; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal .edit-shortcuts {
float: left;
}
/**
* Add/remove links.
*/
.shortcut-action {
display: inline-block;
margin-left: 0.3em; /* LTR */
}
[dir="rtl"] .shortcut-action {
margin-right: 0.3em;
margin-left: 0;
}
.shortcut-action__message {
display: inline-block;
margin-left: 0.3em; /* LTR */
padding: 0 5px;
-webkit-transition: all 200ms ease-out;
transition: all 200ms ease-out;
-webkit-transform: translateY(-12px);
transform: translateY(-12px);
opacity: 0;
color: #fff;
border-radius: 5px;
background: #000;
background: rgba(0, 0, 0, 0.5);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
[dir="rtl"] .shortcut-action__message {
margin-right: 0.3em;
margin-left: 0;
}
.shortcut-action:hover .shortcut-action__message,
.shortcut-action:focus .shortcut-action__message {
-webkit-transform: translateY(-2px);
transform: translateY(-2px);
opacity: 1;
}

View File

@@ -0,0 +1,19 @@
---
label: 'Creating and using shortcut administrative links'
related:
- core.ui_components
---
<h2>{% trans %}Goal{% endtrans %}</h2>
<p>{% trans %}Create, view, and use a set of shortcuts to access administrative pages.{% endtrans %}</p>
<h2>{% trans %}What are shortcuts?{% endtrans %}</h2>
<p>{% trans %}<em>Shortcuts</em> are quick links to administrative pages; they are managed by the core Shortcut module. A site can have one or more <em>shortcut sets</em>, which can be shared by one or more users (by default, there is only one set shared by all users); each set contains a limited number of shortcuts. Users need <em>Use shortcuts</em> permission to view shortcuts; <em>Edit current shortcut set</em> permission to add, delete, or edit the shortcuts in the set assigned to them; and <em>Select any shortcut set</em> permission to select a different shortcut set when editing their user profile. There is also an <em>Administer shortcuts</em> permission, which allows an administrator to do any of these actions, as well as select shortcut sets for other users.{% endtrans %}</p>
<h2>{% trans %}Steps{% endtrans %}</h2>
<ol>
<li>{% trans %}Make sure that the core Shortcut module is installed, and that you have a role with <em>Edit current shortcut set</em> or <em>Administer shortcuts</em> permission. Also, make sure that a toolbar module is installed (either the core Toolbar module or a contributed module replacement).{% endtrans %}</li>
<li>{% trans %}Navigate to an administrative page that you want in your shortcut list.{% endtrans %}</li>
<li>{% trans %}Click the shortcut link to add the page to your shortcut list -- in the core Claro administrative theme, the link looks like a star, and is displayed next to the page title. However, if the page is already in your shortcut set, clicking the shortcut link will remove it from your shortcut set.{% endtrans %}</li>
<li>{% trans %}Repeat until all the desired links have been added to your shortcut set.{% endtrans %}</li>
<li>{% trans %}Click <em>Shortcuts</em> in the toolbar to display your shortcuts, and verify that the list is complete.{% endtrans %}</li>
<li>{% trans %}Optionally, click <em>Edit shortcuts</em> at the right end of the shortcut list (left end in right-to-left languages), to remove links or change their order.{% endtrans %}</li>
<li>{% trans %}Click any link in the shortcut bar to go directly to the administrative page.{% endtrans %}</li>
</ol>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="80" height="20" xmlns="http://www.w3.org/2000/svg">
<g>
<g id="svg_1">
<path id="svg_2" d="m70.39463,2.259c-0.149605,-0.385 -0.395561,-0.385 -0.545135,0l-1.966644,5.049c-0.149582,0.385 -0.611374,0.699 -1.024971,0.699l-1.756836,0.169c-0.413605,0 -0.494911,0.219001 -0.180702,0.487l1.315094,0.981c0.31424,0.268 0.498962,0.816 0.408607,1.22l-1.477745,6.562c-0.090355,0.403 0.118454,0.546999 0.464806,0.319l3.862999,-2.539001c0.346359,-0.228001 0.910545,-0.228001 1.256889,0l3.863014,2.539001c0.346344,0.227999 0.555161,0.084 0.464806,-0.319l-1.051086,-4.707001c-0.090355,-0.403 0.094368,-0.952 0.408577,-1.22l3.531731,-3.006c0.314209,-0.268 0.232903,-0.487 -0.180702,-0.487l-4.398079,0c-0.413612,0 -0.875404,-0.314 -1.024986,-0.698999l-1.969635,-5.048z" fill="#FEF6A8"/>
<polygon id="svg_3" points="62.545124552483685,5 65.1010474534105,7.546999931335449 67.6569842932513,5 69.07749901699208,6.414999961853027 66.52157611606526,8.961000442504883 69.07448821157232,11.505000114440918 67.65596675253073,12.918999671936035 65.10203711630311,10.375 62.54711781718288,12.918999671936035 61.12760669524869,11.505000114440918 63.682525994368916,8.960000038146973 61.12559949163551,6.414000034332275 " fill="#807640"/>
<path id="svg_4" d="m64.930397,13.897s0.133507,-0.516001 0.653519,-0.349c0.518013,0.165999 0.303192,0.674 0.303192,0.674l-0.721832,3.204c-0.061234,0.272999 0.01609,0.427999 0.178696,0.427999c0.078323,0 0.174698,-0.035 0.28614,-0.107l3.863007,-2.539c0.174667,-0.115 0.401566,-0.172 0.62944,-0.172s0.45475,0.057 0.627426,0.172l3.863022,2.539c0.111427,0.072001 0.209808,0.107 0.28611,0.107c0.164627,0 0.240929,-0.153999 0.178696,-0.427999l-1.051094,-4.707001c-0.090347,-0.401999 0.094368,-0.951 0.409584,-1.219l3.531731,-3.007c0.315224,-0.269 0.232903,-0.487 -0.180702,-0.487l-4.400085,0c-0.413628,0 -0.874405,-0.314 -1.024986,-0.698999l-1.966637,-5.049c-0.075302,-0.192 -0.174683,-0.287 -0.273064,-0.287s-0.197769,0.096 -0.272064,0.288l-0.462784,1.158s-0.256004,0.409 -0.637497,0.219c-0.441711,-0.221 -0.227875,-0.66 -0.227875,-0.66l0.390511,-1.078c0.337311,-0.861 1.01194,-0.927 1.209709,-0.927s0.872391,0.066 1.20768,0.926l1.968658,5.049l0.109421,0.062l4.378006,-0.001c0.937645,0 1.234795,0.609 1.306076,0.796c0.06826,0.187 0.23893,0.844 -0.472839,1.451l-3.533722,3.007c-0.037148,0.041 -0.086334,0.186 -0.078308,0.25l1.049088,4.698999c0.133507,0.596001 -0.051216,0.984001 -0.229912,1.205002c-0.225868,0.278999 -0.563187,0.438999 -0.929596,0.438999c-0.280106,0 -0.563194,-0.092001 -0.839264,-0.273001l-3.863014,-2.539l0,0.002001l-0.074287,-0.008001l-0.088341,0.014l-3.85096,2.531c-0.274071,0.182001 -0.559174,0.273001 -0.839264,0.273001c-0.364418,0 -0.703735,-0.16 -0.927589,-0.438999c-0.178711,-0.221001 -0.362411,-0.609001 -0.230904,-1.205002l0.74691,-3.312999z" fill="#80722D"/>
</g>
<g id="svg_5">
<path id="svg_6" d="m54.758999,18.355c-0.18,0 -0.368999,-0.063999 -0.560997,-0.191l-3.848003,-2.539c-0.083,-0.055 -0.215,-0.088 -0.350998,-0.088s-0.268002,0.033 -0.351002,0.088l-3.848,2.539c-0.190998,0.127001 -0.381001,0.191 -0.561001,0.191c-0.211998,0 -0.406998,-0.091999 -0.535,-0.251999c-0.108997,-0.136002 -0.219997,-0.383001 -0.130997,-0.784l1.046997,-4.708c0.049999,-0.222 -0.070999,-0.584 -0.243,-0.731l-3.516998,-3.006001c-0.451,-0.386 -0.378002,-0.756 -0.326,-0.896999c0.053001,-0.141 0.237,-0.47 0.831001,-0.47l4.380997,0c0.206001,0 0.480003,-0.188 0.556,-0.38l1.961002,-5.049c0.213001,-0.549 0.586998,-0.608 0.737,-0.608s0.523998,0.059 0.737,0.607l1.959,5.049c0.075001,0.191 0.350002,0.38 0.556,0.38l4.382999,0c0.594002,0 0.778,0.329 0.831001,0.47c0.052002,0.142 0.125,0.512 -0.326,0.897l-3.517998,3.006c-0.173,0.147 -0.294003,0.51 -0.244003,0.731l1.047001,4.708001c0.089001,0.400999 -0.021,0.646999 -0.131001,0.783998c-0.127998,0.162001 -0.322998,0.253 -0.535,0.253z" fill="#FEF6A8"/>
<path id="svg_7" d="m50,1.97c0.098999,0 0.195999,0.096 0.271,0.289l1.959,5.049c0.149002,0.385 0.609001,0.699 1.021,0.699l4.382999,0c0.412003,0 0.493,0.219 0.18,0.487l-3.516998,3.006c-0.313,0.268 -0.497002,0.816 -0.407001,1.22l1.047001,4.707999c0.061001,0.273001 -0.014999,0.428001 -0.178001,0.428001c-0.077,0 -0.174,-0.035002 -0.285,-0.108002l-3.848,-2.539c-0.173,-0.113 -0.398998,-0.171 -0.625999,-0.171s-0.452999,0.058 -0.625999,0.171l-3.848,2.539c-0.111,0.073 -0.208,0.108002 -0.285,0.108002c-0.163002,0 -0.238003,-0.154001 -0.178001,-0.428001l1.047001,-4.707999c0.09,-0.403 -0.094002,-0.952001 -0.407001,-1.22l-3.517998,-3.006c-0.313,-0.268001 -0.232002,-0.487 0.18,-0.487l4.380997,0c0.412003,0 0.872002,-0.314 1.021,-0.699l1.961002,-5.049c0.076,-0.193 0.173,-0.289 0.271999,-0.289m0,-1c-0.198002,0 -0.869999,0.067 -1.203999,0.927l-1.961002,5.049l-0.108997,0.062l-4.360001,-0.001c-0.934002,0 -1.231003,0.61 -1.299999,0.797c-0.069,0.187 -0.239002,0.844 0.470997,1.451l3.518002,3.005c0.039001,0.043 0.085999,0.186 0.078999,0.25l-1.045998,4.701c-0.132,0.594999 0.051998,0.983 0.229,1.205c0.223999,0.278999 0.560997,0.438999 0.924999,0.438999c0.279999,0 0.562,-0.093 0.835999,-0.272999l3.848,-2.539001l0.000999,0.001001l0.074001,-0.007l0.089001,0.014l3.834,2.531c0.274002,0.181 0.556999,0.272999 0.835999,0.272999c0.364002,0 0.701,-0.16 0.925003,-0.438999c0.177998,-0.222 0.361,-0.610001 0.229,-1.206001l-1.046001,-4.706999c-0.006001,-0.058001 0.041,-0.2 0.085999,-0.248l3.512001,-3c0.709999,-0.607 0.540001,-1.264 0.471001,-1.451c-0.068001,-0.187 -0.366001,-0.797 -1.299999,-0.797l-4.382999,0l-0.098003,-0.08l-1.949997,-5.03c-0.336002,-0.86 -1.007999,-0.927 -1.206001,-0.927z" fill="#80722D"/>
</g>
<g id="svg_8">
<path id="svg_9" d="m24.614159,14.96s0.134195,-0.516 0.656843,-0.349c0.520624,0.166 0.304714,0.674 0.304714,0.674l-0.481285,2.142c-0.06155,0.273001 0.016146,0.427999 0.179598,0.427999c0.078697,0 0.175564,-0.035 0.287556,-0.107l3.882521,-2.539c0.175562,-0.115 0.403591,-0.172 0.632626,-0.172s0.457062,0.057 0.630606,0.172l3.88253,2.539c0.111988,0.072001 0.210865,0.107 0.287552,0.107c0.165478,0 0.242153,-0.153999 0.179596,-0.427999l-1.056396,-4.707c-0.090809,-0.402 0.094849,-0.951 0.411667,-1.219l3.549557,-3.007c0.316818,-0.269 0.234085,-0.487 -0.181614,-0.487l-4.422318,0c-0.415699,0 -0.878819,-0.314 -1.030159,-0.699l-1.976582,-5.049c-0.075674,-0.193 -0.17556,-0.288 -0.274439,-0.288s-0.198767,0.096 -0.27343,0.288l-1.389357,3.538s-0.257284,0.409 -0.640696,0.219c-0.443943,-0.221 -0.229034,-0.66 -0.229034,-0.66l0.612446,-1.658l0.703257,-1.801c0.340021,-0.86 1.018047,-0.926 1.216814,-0.926s0.876799,0.066 1.213793,0.926l1.9786,5.049l0.10997,0.062l4.400127,-0.001c0.942379,0 1.241035,0.609 1.312668,0.796c0.068615,0.187 0.240135,0.844 -0.475224,1.451l-3.551575,3.007c-0.037338,0.041 -0.086777,0.186 -0.078701,0.25l1.054375,4.698999c0.134193,0.596001 -0.051464,0.984001 -0.231056,1.205002c-0.22702,0.278999 -0.566032,0.438999 -0.934299,0.438999c-0.281509,0 -0.566044,-0.092001 -0.843502,-0.273001l-3.882523,-2.539l0,0.002001l-0.074667,-0.008001l-0.088793,0.014l-3.870409,2.531c-0.275454,0.182001 -0.562002,0.273001 -0.843506,0.273001c-0.366257,0 -0.707291,-0.16 -0.932289,-0.438999c-0.179598,-0.221001 -0.364237,-0.609001 -0.232063,-1.205002l0.506502,-2.249999z" fill="#5A563B" opacity="0.7"/>
<polygon id="svg_10" points="20.99598980255405,7.960000038146973 24.022905373148888,7.960000038146973 24.022905373148888,4.960000038146973 26.040840745155492,4.960000038146973 26.040840745155492,7.960000038146973 29.06775631575033,7.960000038146973 29.06775631575033,9.958999633789062 26.040840745155492,9.958999633789062 26.040840745155492,12.960000038146973 24.022905373148888,12.960000038146973 24.022905373148888,9.958999633789062 20.99598980255405,9.958999633789062 " fill="#807640"/>
</g>
<path id="svg_11" d="m10,1.97c0.098,0 0.197,0.096 0.271,0.289l1.959,5.049c0.149,0.385 0.609,0.699 1.021001,0.699l4.383,0c0.411999,0 0.493,0.219 0.179998,0.487l-3.516999,3.006c-0.313,0.268 -0.497,0.816 -0.407,1.22l1.047,4.707c0.061,0.273001 -0.015,0.427999 -0.178,0.427999c-0.077,0 -0.174,-0.035 -0.285,-0.108l-3.848,-2.539c-0.172,-0.114 -0.399,-0.171 -0.626,-0.171s-0.454,0.057 -0.626,0.171l-3.848,2.539c-0.111,0.073 -0.208,0.108 -0.285,0.108c-0.163,0 -0.239,-0.153999 -0.178,-0.427999l1.047,-4.707c0.09,-0.403 -0.094,-0.951 -0.407,-1.22l-3.518,-3.006c-0.313,-0.268001 -0.233,-0.487 0.18,-0.487l4.382,0c0.413,0 0.872,-0.314 1.021,-0.699l1.96,-5.049c0.075001,-0.193 0.173,-0.289 0.272,-0.289m0,-1c-0.198,0 -0.87,0.067 -1.204,0.927l-1.96,5.049l-0.109,0.062l-4.362,-0.001c-0.934,0 -1.231,0.61 -1.3,0.796c-0.069,0.187 -0.239,0.844 0.47,1.451l3.519,3.007c0.038,0.041 0.086,0.185 0.079,0.249001l-1.046,4.699999c-0.133,0.595001 0.051,0.983002 0.229,1.205002c0.224,0.278999 0.561,0.438999 0.925,0.438999c0.28,0 0.562,-0.093 0.836,-0.274l3.848,-2.538l0,0.000999l0.075,-0.007l0.088,0.014l3.834,2.531c0.274,0.181 0.557,0.273001 0.836,0.273001c0.363999,0 0.700999,-0.16 0.924999,-0.438999c0.178,-0.222 0.361001,-0.610001 0.229,-1.206001l-1.047,-4.705999c-0.006,-0.058001 0.041,-0.2 0.086,-0.248l3.511999,-3c0.710001,-0.607 0.540001,-1.264 0.471001,-1.451c-0.068001,-0.187 -0.365999,-0.797 -1.299999,-0.797l-4.383,0l-0.098001,-0.08l-1.95,-5.03c-0.333,-0.86 -1.005,-0.927 -1.203,-0.927z" fill="#5A563B" opacity="0.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,26 @@
id: d7_shortcut
label: Shortcut links
migration_tags:
- Drupal 7
- Content
source:
plugin: d7_shortcut
constants:
uri_scheme: 'internal:/'
process:
shortcut_set:
plugin: migration_lookup
migration: d7_shortcut_set
source: menu_name
title: link_title
weight: weight
link:
plugin: concat
source:
- 'constants/uri_scheme'
- link_path
destination:
plugin: entity:shortcut
migration_dependencies:
required:
- d7_shortcut_set

View File

@@ -0,0 +1,23 @@
id: d7_shortcut_set
label: Shortcut sets
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_shortcut_set
process:
id:
-
plugin: static_map
bypass: true
source: set_name
map:
shortcut-set-1: default
-
plugin: machine_name
field: id
# @see shortcut.schema.yml
replace_pattern: /[^a-z0-9-]+/
label: title
destination:
plugin: entity:shortcut_set

View File

@@ -0,0 +1,27 @@
id: d7_shortcut_set_users
label: Shortcut set user mapping
# This configuration migration depends on the d7_user content migration.
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_shortcut_set_users
process:
uid:
-
plugin: migration_lookup
migration: d7_user
source: uid
-
plugin: skip_on_empty
method: row
set_name:
plugin: migration_lookup
migration: d7_shortcut_set
source: set_name
destination:
plugin: shortcut_set_users
migration_dependencies:
required:
- d7_shortcut_set
- d7_user

View File

@@ -0,0 +1,3 @@
finished:
7:
shortcut: shortcut

View File

@@ -0,0 +1,45 @@
<?php
/**
* @file
* Hooks provided by the Shortcut module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Return the name of a default shortcut set for the provided user account.
*
* This hook allows modules to define default shortcut sets for a particular
* user that differ from the site-wide default (for example, a module may want
* to define default shortcuts on a per-role basis).
*
* The default shortcut set is used only when the user does not have any other
* shortcut set explicitly assigned to them.
*
* Note that only one default shortcut set can exist per user, so when multiple
* modules implement this hook, the last (i.e., highest weighted) module which
* returns a valid shortcut set name will prevail.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account whose default shortcut set is being requested.
*
* @return string
* The name of the shortcut set that this module recommends for that user, if
* there is one.
*/
function hook_shortcut_default_set(\Drupal\Core\Session\AccountInterface $account) {
// Use a special set of default shortcuts for administrators only.
$roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadByProperties(['is_admin' => TRUE]);
$user_admin_roles = array_intersect(array_keys($roles), $account->getRoles());
if ($user_admin_roles) {
return 'admin-shortcuts';
}
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,13 @@
name: Shortcut
type: module
description: 'Allows users to create sets of shortcuts within the site.'
package: Core
# version: VERSION
configure: entity.shortcut_set.collection
dependencies:
- drupal:link
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,75 @@
<?php
/**
* @file
* Install, update and uninstall functions for the shortcut module.
*/
/**
* Implements hook_schema().
*/
function shortcut_schema() {
$schema['shortcut_set_users'] = [
'description' => 'Maps users to shortcut sets.',
'fields' => [
'uid' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The {users}.uid for this set.',
],
'set_name' => [
'type' => 'varchar_ascii',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => "The {shortcut_set}.set_name that will be displayed for this user.",
],
],
'primary key' => ['uid'],
'indexes' => [
'set_name' => ['set_name'],
],
'foreign keys' => [
'set_user' => [
'table' => 'users',
'columns' => ['uid' => 'uid'],
],
'set_name' => [
'table' => 'shortcut_set',
'columns' => ['set_name' => 'set_name'],
],
],
];
return $schema;
}
/**
* Implements hook_install().
*/
function shortcut_install() {
// Theme settings are not configuration entities and cannot depend on modules
// so to set a module-specific setting, we need to set it with logic.
if (\Drupal::service('theme_handler')->themeExists('claro')) {
\Drupal::configFactory()
->getEditable("claro.settings")
->set('third_party_settings.shortcut.module_link', TRUE)
->save(TRUE);
}
}
/**
* Implements hook_uninstall().
*/
function shortcut_uninstall() {
// Theme settings are not configuration entities and cannot depend on modules
// so to unset a module-specific setting, we need to unset it with logic.
if (\Drupal::service('theme_handler')->themeExists('claro')) {
\Drupal::configFactory()
->getEditable("claro.settings")
->clear('third_party_settings.shortcut')
->save(TRUE);
}
}

View File

@@ -0,0 +1,6 @@
drupal.shortcut:
version: VERSION
css:
theme:
css/shortcut.theme.css: {}
css/shortcut.icons.theme.css: {}

View File

@@ -0,0 +1,10 @@
shortcut_set_add_local_action:
route_name: shortcut.set_add
title: 'Add shortcut set'
appears_on:
- entity.shortcut_set.collection
shortcut.link_add:
route_name: shortcut.link_add
title: 'Add shortcut'
appears_on:
- entity.shortcut_set.customize_form

View File

@@ -0,0 +1,5 @@
entity.shortcut_set.collection:
title: Shortcuts
description: 'Add and modify shortcut sets.'
route_name: entity.shortcut_set.collection
parent: system.admin_config_ui

View File

@@ -0,0 +1,19 @@
shortcut.set_switch:
route_name: shortcut.set_switch
base_route: entity.user.canonical
title: 'Shortcuts'
entity.shortcut_set.customize_form:
title: 'List links'
route_name: entity.shortcut_set.customize_form
base_route: entity.shortcut_set.customize_form
entity.shortcut_set.edit_form:
title: 'Edit set name'
route_name: entity.shortcut_set.edit_form
base_route: entity.shortcut_set.customize_form
weight: 10
entity.shortcut.canonical:
route_name: entity.shortcut.canonical
base_route: entity.shortcut.canonical
title: Edit

View File

@@ -0,0 +1,403 @@
<?php
/**
* @file
* Allows users to manage customizable lists of shortcut links.
*/
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\shortcut\ShortcutSetInterface;
/**
* Implements hook_help().
*/
function shortcut_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.shortcut':
$output = '<h2>' . t('About') . '</h2>';
$output .= '<p>' . t('The Shortcut module allows users to create sets of <em>shortcut</em> links to commonly-visited pages of the site. Shortcuts are contained within <em>sets</em>. Each user with <em>Select any shortcut set</em> permission can select a shortcut set created by anyone at the site. For more information, see the <a href=":shortcut">online documentation for the Shortcut module</a>.', [':shortcut' => 'https://www.drupal.org/docs/8/core/modules/shortcut']) . '</p>';
$output .= '<h2>' . t('Uses') . '</h2>';
$output .= '<dl><dt>' . t('Administering shortcuts') . '</dt>';
$output .= '<dd>' . t('Users with the <em>Administer shortcuts</em> permission can manage shortcut sets and edit the shortcuts within sets from the <a href=":shortcuts">Shortcuts administration page</a>.', [':shortcuts' => Url::fromRoute('entity.shortcut_set.collection')->toString()]) . '</dd>';
$output .= '<dt>' . t('Choosing shortcut sets') . '</dt>';
$output .= '<dd>' . t('Users with permission to switch shortcut sets can choose a shortcut set to use from the Shortcuts tab of their user account page.') . '</dd>';
$output .= '<dt>' . t('Adding and removing shortcuts') . '</dt>';
$output .= '<dd>' . t('The Shortcut module creates an add/remove link for each page on your site; the link lets you add or remove the current page from the currently-enabled set of shortcuts (if your theme displays it and you have permission to edit your shortcut set). The core Claro administration theme displays this link next to the page title, as a gray or yellow star. If you click on the gray star, you will add that page to your preferred set of shortcuts. If the page is already part of your shortcut set, the link will be a yellow star, and will allow you to remove the current page from your shortcut set.') . '</dd>';
$output .= '<dt>' . t('Displaying shortcuts') . '</dt>';
$output .= '<dd>' . t('You can display your shortcuts by enabling the <em>Shortcuts</em> block on the <a href=":blocks">Blocks administration page</a>. Certain administrative modules also display your shortcuts; for example, the core <a href=":toolbar-help">Toolbar module</a> provides a corresponding menu link.', [':blocks' => (\Drupal::moduleHandler()->moduleExists('block')) ? Url::fromRoute('block.admin_display')->toString() : '#', ':toolbar-help' => (\Drupal::moduleHandler()->moduleExists('toolbar')) ? Url::fromRoute('help.page', ['name' => 'toolbar'])->toString() : '#']) . '</dd>';
$output .= '</dl>';
return $output;
case 'entity.shortcut_set.collection':
case 'shortcut.set_add':
case 'entity.shortcut_set.edit_form':
$user = \Drupal::currentUser();
if ($user->hasPermission('access shortcuts') && $user->hasPermission('switch shortcut sets')) {
$output = '<p>' . t('Define which shortcut set you are using on the <a href=":shortcut-link">Shortcuts tab</a> of your account page.', [':shortcut-link' => Url::fromRoute('shortcut.set_switch', ['user' => $user->id()])->toString()]) . '</p>';
return $output;
}
}
}
/**
* Access callback for editing a shortcut set.
*
* @param Drupal\shortcut\ShortcutSetInterface $shortcut_set
* (optional) The shortcut set to be edited. If not set, the current user's
* shortcut set will be used.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
function shortcut_set_edit_access(?ShortcutSetInterface $shortcut_set = NULL) {
$account = \Drupal::currentUser();
// Shortcut administrators can edit any set.
if ($account->hasPermission('administer shortcuts')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Sufficiently-privileged users can edit their currently displayed shortcut
// set, but not other sets. They must also be able to access shortcuts.
$may_edit_current_shortcut_set = $account->hasPermission('customize shortcut links') && $account->hasPermission('access shortcuts');
if ($may_edit_current_shortcut_set && isset($shortcut_set)) {
$displayed_shortcut_set = \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDisplayedToUser($account);
$may_edit_current_shortcut_set = ($shortcut_set == $displayed_shortcut_set);
}
$result = AccessResult::allowedIf($may_edit_current_shortcut_set)->cachePerPermissions();
if (!$result->isAllowed()) {
$result->setReason("The shortcut set must be the currently displayed set for the user and the user must have 'access shortcuts' AND 'customize shortcut links' permissions.");
}
return $result;
}
/**
* Access callback for switching the shortcut set assigned to a user account.
*
* @param object $account
* (optional) The user account whose shortcuts will be switched. If not set,
* permissions will be checked for switching the logged-in user's own
* shortcut set.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
function shortcut_set_switch_access($account = NULL) {
$user = \Drupal::currentUser();
if ($user->hasPermission('administer shortcuts')) {
// Administrators can switch anyone's shortcut set.
return AccessResult::allowed()->cachePerPermissions();
}
if (!$user->hasPermission('access shortcuts')) {
// The user has no permission to use shortcuts.
return AccessResult::neutral()->cachePerPermissions();
}
if (!$user->hasPermission('switch shortcut sets')) {
// The user has no permission to switch anyone's shortcut set.
return AccessResult::neutral()->cachePerPermissions();
}
// Users with the 'switch shortcut sets' permission can switch their own
// shortcuts sets.
if (!isset($account)) {
return AccessResult::allowed()->cachePerPermissions();
}
elseif ($user->id() == $account->id()) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
}
// No opinion.
return AccessResult::neutral()->cachePerPermissions();
}
/**
* Returns the current displayed shortcut set for the provided user account.
*
* @param $account
* (optional) The user account whose shortcuts will be returned. Defaults to
* the currently logged-in user.
*
* @return \Drupal\shortcut\ShortcutSetInterface
* An object representing the shortcut set that should be displayed to the
* current user. If the user does not have an explicit shortcut set defined,
* the default set is returned.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\shortcut\ShortcutSetStorageInterface::getDisplayedToUser()
* instead.
*
* @see https://www.drupal.org/node/3427050
*/
function shortcut_current_displayed_set($account = NULL) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\shortcut\ShortcutSetStorageInterface::getDisplayedToUser() instead. See https://www.drupal.org/node/3427050', E_USER_DEPRECATED);
$shortcut_sets = &drupal_static(__FUNCTION__, []);
$user = \Drupal::currentUser();
if (!isset($account)) {
$account = $user;
}
// Try to return a shortcut set from the static cache.
if (isset($shortcut_sets[$account->id()])) {
return $shortcut_sets[$account->id()];
}
// If none was found, try to find a shortcut set that is explicitly assigned
// to this user.
$shortcut_set = \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDisplayedToUser($account);
$shortcut_sets[$account->id()] = $shortcut_set;
return $shortcut_set;
}
/**
* Returns the default shortcut set for a given user account.
*
* @param object $account
* (optional) The user account whose default shortcut set will be returned.
* If not provided, the function will return the currently logged-in user's
* default shortcut set.
*
* @return \Drupal\shortcut\ShortcutSetInterface|null
* An object representing the default shortcut set.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\shortcut\ShortcutSetStorageInterface::getDefaultSet() instead.
*
* @see https://www.drupal.org/node/3427050
*/
function shortcut_default_set($account = NULL) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\shortcut\ShortcutSetStorageInterface::getDefaultSet() instead. See https://www.drupal.org/node/3427050', E_USER_DEPRECATED);
$user = \Drupal::currentUser();
if (!isset($account)) {
$account = $user;
}
return \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDefaultSet($account);
}
/**
* Returns an array of shortcut links, suitable for rendering.
*
* @param \Drupal\shortcut\ShortcutSetInterface $shortcut_set
* (optional) An object representing the set whose links will be displayed.
* If not provided, the user's current set will be displayed.
*
* @return \Drupal\shortcut\ShortcutInterface[]
* An array of shortcut links, in the format returned by the menu system.
*/
function shortcut_renderable_links($shortcut_set = NULL) {
$shortcut_links = [];
if (!isset($shortcut_set)) {
$account = \Drupal::currentUser();
$shortcut_set = \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDisplayedToUser($account);
}
$cache_tags = [];
foreach ($shortcut_set->getShortcuts() as $shortcut) {
$shortcut = \Drupal::service('entity.repository')->getTranslationFromContext($shortcut);
$url = $shortcut->getUrl();
if ($url->access()) {
$links[$shortcut->id()] = [
'type' => 'link',
'title' => $shortcut->label(),
'url' => $shortcut->getUrl(),
];
$cache_tags = Cache::mergeTags($cache_tags, $shortcut->getCacheTags());
}
}
if (!empty($links)) {
$shortcut_links = [
'#theme' => 'links__toolbar_shortcuts',
'#links' => $links,
'#attributes' => [
'class' => ['toolbar-menu'],
],
'#cache' => [
'tags' => $cache_tags,
],
];
}
return $shortcut_links;
}
/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function shortcut_preprocess_block(&$variables) {
if ($variables['configuration']['provider'] == 'shortcut') {
$variables['attributes']['role'] = 'navigation';
}
}
/**
* Implements hook_preprocess_HOOK() for page title templates.
*/
function shortcut_preprocess_page_title(&$variables) {
// Only display the shortcut link if the user has the ability to edit
// shortcuts, the feature is enabled for the current theme and if the page's
// actual content is being shown (for example, we do not want to display it on
// "access denied" or "page not found" pages).
if (shortcut_set_edit_access()->isAllowed() && theme_get_setting('third_party_settings.shortcut.module_link') && !\Drupal::request()->attributes->has('exception')) {
$link = Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath();
$route_match = \Drupal::routeMatch();
// Replicate template_preprocess_html()'s processing to get the title in
// string form, so we can set the default name for the shortcut.
$name = $variables['title'] ?? '';
if (is_array($name)) {
$name = \Drupal::service('renderer')->render($name);
}
$query = [
'link' => $link,
'name' => trim(strip_tags($name)),
];
$shortcut_set = \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDisplayedToUser(\Drupal::currentUser());
// Pages with the add or remove shortcut button need cache invalidation when
// a shortcut is added, edited, or removed.
$cacheability_metadata = CacheableMetadata::createFromRenderArray($variables);
$cacheability_metadata->addCacheTags(\Drupal::entityTypeManager()->getDefinition('shortcut')->getListCacheTags());
$cacheability_metadata->applyTo($variables);
// Check if $link is already a shortcut and set $link_mode accordingly.
$shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $shortcut_set->id()]);
/** @var \Drupal\shortcut\ShortcutInterface $shortcut */
foreach ($shortcuts as $shortcut) {
if (($shortcut_url = $shortcut->getUrl()) && $shortcut_url->isRouted() && $shortcut_url->getRouteName() == $route_match->getRouteName() && $shortcut_url->getRouteParameters() == $route_match->getRawParameters()->all()) {
$shortcut_id = $shortcut->id();
break;
}
}
$link_mode = isset($shortcut_id) ? "remove" : "add";
if ($link_mode == "add") {
$link_text = shortcut_set_switch_access()->isAllowed() ? t('Add to %shortcut_set shortcuts', ['%shortcut_set' => $shortcut_set->label()]) : t('Add to shortcuts');
$route_name = 'shortcut.link_add_inline';
$route_parameters = ['shortcut_set' => $shortcut_set->id()];
}
else {
$query['id'] = $shortcut_id;
$link_text = shortcut_set_switch_access()->isAllowed() ? t('Remove from %shortcut_set shortcuts', ['%shortcut_set' => $shortcut_set->label()]) : t('Remove from shortcuts');
$route_name = 'entity.shortcut.link_delete_inline';
$route_parameters = ['shortcut' => $shortcut_id];
}
$query += \Drupal::destination()->getAsArray();
$variables['title_suffix']['add_or_remove_shortcut'] = [
'#attached' => [
'library' => [
'shortcut/drupal.shortcut',
],
],
'#type' => 'link',
'#title' => new FormattableMarkup('<span class="shortcut-action__icon"></span><span class="shortcut-action__message">@text</span>', ['@text' => $link_text]),
'#url' => Url::fromRoute($route_name, $route_parameters),
'#options' => ['query' => $query],
'#attributes' => [
'class' => [
'shortcut-action',
'shortcut-action--' . $link_mode,
],
],
];
}
}
/**
* Implements hook_toolbar().
*/
function shortcut_toolbar() {
$user = \Drupal::currentUser();
$items = [];
$items['shortcuts'] = [
'#cache' => [
'contexts' => [
'user.permissions',
],
],
];
if ($user->hasPermission('access shortcuts')) {
$shortcut_set = \Drupal::entityTypeManager()
->getStorage('shortcut_set')
->getDisplayedToUser($user);
$items['shortcuts'] += [
'#type' => 'toolbar_item',
'tab' => [
'#type' => 'link',
'#title' => t('Shortcuts'),
'#url' => $shortcut_set->toUrl('collection'),
'#attributes' => [
'title' => t('Shortcuts'),
'class' => ['toolbar-icon', 'toolbar-icon-shortcut'],
],
],
'tray' => [
'#heading' => t('User-defined shortcuts'),
'children' => [
'#lazy_builder' => ['shortcut.lazy_builders:lazyLinks', []],
'#create_placeholder' => TRUE,
'#cache' => [
'keys' => ['shortcut_set_toolbar_links'],
'contexts' => ['user'],
],
'#lazy_builder_preview' => [
'#markup' => '<a href="#" class="toolbar-tray-lazy-placeholder-link">&nbsp;</a>',
],
],
],
'#weight' => -10,
'#attached' => [
'library' => [
'shortcut/drupal.shortcut',
],
],
];
}
return $items;
}
/**
* Implements hook_themes_installed().
*/
function shortcut_themes_installed($theme_list) {
// Theme settings are not configuration entities and cannot depend on modules
// so to set a module-specific setting, we need to set it with logic.
if (in_array('claro', $theme_list, TRUE)) {
\Drupal::configFactory()->getEditable("claro.settings")
->set('third_party_settings.shortcut.module_link', TRUE)
->save(TRUE);
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function shortcut_user_delete(EntityInterface $entity) {
// Clean up shortcut set mapping of removed user account.
\Drupal::entityTypeManager()->getStorage('shortcut_set')->unassignUser($entity);
}

View File

@@ -0,0 +1,10 @@
administer shortcuts:
title: 'Administer shortcuts'
customize shortcut links:
title: 'Edit current shortcut set'
description: 'Editing the current shortcut set will affect other users if that set has been assigned to or selected by other users. Granting "Select any shortcut set" permission along with this permission will grant permission to edit any shortcut set.'
switch shortcut sets:
title: 'Select any shortcut set'
description: 'From all shortcut sets, select one to be own active set. Without this permission, an administrator selects shortcut sets for users.'
access shortcuts:
title: 'Use shortcuts'

View File

@@ -0,0 +1,102 @@
entity.shortcut_set.delete_form:
path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/delete'
defaults:
_entity_form: 'shortcut_set.delete'
_title: 'Delete shortcut set'
requirements:
_entity_access: 'shortcut_set.delete'
entity.shortcut_set.collection:
path: '/admin/config/user-interface/shortcut'
defaults:
_entity_list: 'shortcut_set'
_title: 'Shortcuts'
requirements:
_permission: 'administer shortcuts'
shortcut.set_add:
path: '/admin/config/user-interface/shortcut/add-set'
defaults:
_entity_form: 'shortcut_set.add'
_title: 'Add shortcut set'
requirements:
_entity_create_access: 'shortcut_set'
entity.shortcut_set.edit_form:
path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}'
defaults:
_entity_form: 'shortcut_set.edit'
_title: 'Edit shortcut set'
requirements:
_entity_access: 'shortcut_set.update'
shortcut.link_add_inline:
path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link-inline'
defaults:
_controller: '\Drupal\shortcut\Controller\ShortcutSetController::addShortcutLinkInline'
requirements:
_entity_access: 'shortcut_set.update'
_csrf_token: 'TRUE'
entity.shortcut_set.customize_form:
path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/customize'
defaults:
_entity_form: 'shortcut_set.customize'
_title: 'List links'
requirements:
_entity_access: 'shortcut_set.update'
shortcut.link_add:
path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link'
defaults:
_controller: '\Drupal\shortcut\Controller\ShortcutController::addForm'
_title: 'Add link'
requirements:
_entity_create_access: 'shortcut:{shortcut_set}'
entity.shortcut.canonical:
path: '/admin/config/user-interface/shortcut/link/{shortcut}'
defaults:
_entity_form: 'shortcut.default'
_title: 'Edit'
requirements:
_entity_access: 'shortcut.update'
shortcut: \d+
entity.shortcut.edit_form:
path: '/admin/config/user-interface/shortcut/link/{shortcut}'
defaults:
_entity_form: 'shortcut.default'
_title: 'Edit'
requirements:
_entity_access: 'shortcut.update'
shortcut: \d+
entity.shortcut.link_delete_inline:
path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete-inline'
defaults:
_controller: '\Drupal\shortcut\Controller\ShortcutController::deleteShortcutLinkInline'
requirements:
_entity_access: 'shortcut.delete'
_csrf_token: 'TRUE'
shortcut: \d+
entity.shortcut.delete_form:
path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete'
defaults:
_entity_form: 'shortcut.delete'
_title: 'Delete'
requirements:
_entity_access: 'shortcut.delete'
shortcut: \d+
shortcut.set_switch:
path: '/user/{user}/shortcuts'
defaults:
_form: '\Drupal\shortcut\Form\SwitchShortcutSet'
_title: 'Shortcuts'
requirements:
_custom_access: '\Drupal\shortcut\Form\SwitchShortcutSet::checkAccess'
options:
_admin_route: TRUE
user: \d+

View File

@@ -0,0 +1,5 @@
services:
shortcut.lazy_builders:
class: Drupal\shortcut\ShortcutLazyBuilders
arguments: ['@renderer', '@entity_type.manager', '@current_user']
Drupal\shortcut\ShortcutLazyBuilders: '@shortcut.lazy_builders'

View File

@@ -0,0 +1,52 @@
<?php
namespace Drupal\shortcut\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\shortcut\ShortcutSetInterface;
use Drupal\shortcut\ShortcutInterface;
/**
* Provides route responses for taxonomy.module.
*/
class ShortcutController extends ControllerBase {
/**
* Returns a form to add a new shortcut to a given set.
*
* @param \Drupal\shortcut\ShortcutSetInterface $shortcut_set
* The shortcut set this shortcut will be added to.
*
* @return array
* The shortcut add form.
*/
public function addForm(ShortcutSetInterface $shortcut_set) {
$shortcut = $this->entityTypeManager()->getStorage('shortcut')->create(['shortcut_set' => $shortcut_set->id()]);
return $this->entityFormBuilder()->getForm($shortcut, 'add');
}
/**
* Deletes the selected shortcut.
*
* @param \Drupal\shortcut\ShortcutInterface $shortcut
* The shortcut to delete.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the previous location or the front page when destination
* is not set.
*/
public function deleteShortcutLinkInline(ShortcutInterface $shortcut) {
$label = $shortcut->label();
try {
$shortcut->delete();
$this->messenger()->addStatus($this->t('The shortcut %title has been deleted.', ['%title' => $label]));
}
catch (\Exception $e) {
$this->messenger()->addStatus($this->t('Unable to delete the shortcut for %title.', ['%title' => $label]), 'error');
}
return $this->redirect('<front>');
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Drupal\shortcut\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\shortcut\ShortcutSetInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Builds the page for administering shortcut sets.
*/
class ShortcutSetController extends ControllerBase {
/**
* The path validator.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Creates a new ShortcutSetController instance.
*
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator.
*/
public function __construct(PathValidatorInterface $path_validator) {
$this->pathValidator = $path_validator;
}
/**
* Creates a new link in the provided shortcut set.
*
* @param \Drupal\shortcut\ShortcutSetInterface $shortcut_set
* The shortcut set to add a link to.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response to the front page, or the previous location.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Request $request) {
$link = $request->query->get('link');
$name = $request->query->get('name');
if (parse_url($link, PHP_URL_SCHEME) === NULL && $this->pathValidator->isValid($link)) {
$shortcut = $this->entityTypeManager()->getStorage('shortcut')->create([
'title' => $name,
'shortcut_set' => $shortcut_set->id(),
'link' => [
'uri' => 'internal:/' . $link,
],
]);
try {
$shortcut->save();
$this->messenger()->addStatus($this->t('Added a shortcut for %title.', ['%title' => $shortcut->label()]));
}
catch (\Exception $e) {
$this->messenger()->addError($this->t('Unable to add a shortcut for %title.', ['%title' => $shortcut->label()]));
}
return $this->redirect('<front>');
}
throw new AccessDeniedHttpException();
}
}

View File

@@ -0,0 +1,191 @@
<?php
namespace Drupal\shortcut\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\link\LinkItemInterface;
use Drupal\shortcut\ShortcutInterface;
/**
* Defines the shortcut entity class.
*
* @property \Drupal\link\LinkItemInterface $link
*
* @ContentEntityType(
* id = "shortcut",
* label = @Translation("Shortcut link"),
* label_collection = @Translation("Shortcut links"),
* label_singular = @Translation("shortcut link"),
* label_plural = @Translation("shortcut links"),
* label_count = @PluralTranslation(
* singular = "@count shortcut link",
* plural = "@count shortcut links",
* ),
* bundle_label = @Translation("Shortcut set"),
* handlers = {
* "access" = "Drupal\shortcut\ShortcutAccessControlHandler",
* "form" = {
* "default" = "Drupal\shortcut\ShortcutForm",
* "add" = "Drupal\shortcut\ShortcutForm",
* "edit" = "Drupal\shortcut\ShortcutForm",
* "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm"
* },
* },
* base_table = "shortcut",
* data_table = "shortcut_field_data",
* translatable = TRUE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "shortcut_set",
* "label" = "title",
* "langcode" = "langcode",
* },
* links = {
* "canonical" = "/admin/config/user-interface/shortcut/link/{shortcut}",
* "delete-form" = "/admin/config/user-interface/shortcut/link/{shortcut}/delete",
* "edit-form" = "/admin/config/user-interface/shortcut/link/{shortcut}",
* },
* list_cache_tags = { "config:shortcut_set_list" },
* bundle_entity_type = "shortcut_set"
* )
*/
class Shortcut extends ContentEntityBase implements ShortcutInterface {
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->get('title')->value;
}
/**
* {@inheritdoc}
*/
public function setTitle($link_title) {
$this->set('title', $link_title);
return $this;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->get('weight')->value;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->set('weight', $weight);
return $this;
}
/**
* {@inheritdoc}
*/
public function getUrl() {
return $this->link->first()->getUrl();
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
// handles the regular cases. The Shortcut entity has one special case: a
// newly created shortcut is *also* added to a shortcut set, so we must
// invalidate the associated shortcut set's cache tag.
if (!$update) {
Cache::invalidateTags($this->getCacheTagsToInvalidate());
}
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['id']->setDescription(t('The ID of the shortcut.'));
$fields['uuid']->setDescription(t('The UUID of the shortcut.'));
$fields['shortcut_set']->setLabel(t('Shortcut set'))
->setDescription(t('The bundle of the shortcut.'));
$fields['langcode']->setDescription(t('The language code of the shortcut.'));
$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of the shortcut.'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => -10,
'settings' => [
'size' => 40,
],
]);
$fields['weight'] = BaseFieldDefinition::create('integer')
->setLabel(t('Weight'))
->setDescription(t('Weight among shortcuts in the same shortcut set.'));
$fields['link'] = BaseFieldDefinition::create('link')
->setLabel(t('Path'))
->setDescription(t('The location this shortcut points to.'))
->setRequired(TRUE)
->setSettings([
'link_type' => LinkItemInterface::LINK_INTERNAL,
'title' => DRUPAL_DISABLED,
])
->setDisplayOptions('form', [
'type' => 'link_default',
'weight' => 0,
])
->setDisplayConfigurable('form', TRUE);
return $fields;
}
/**
* {@inheritdoc}
*/
public function getCacheTagsToInvalidate() {
return $this->shortcut_set->entity->getCacheTags();
}
/**
* Sort shortcut objects.
*
* Callback for uasort().
*
* @param \Drupal\shortcut\ShortcutInterface $a
* First item for comparison.
* @param \Drupal\shortcut\ShortcutInterface $b
* Second item for comparison.
*
* @return int
* The comparison result for uasort().
*/
public static function sort(ShortcutInterface $a, ShortcutInterface $b) {
$a_weight = $a->getWeight();
$b_weight = $b->getWeight();
if ($a_weight == $b_weight) {
return strnatcasecmp($a->getTitle(), $b->getTitle());
}
return $a_weight <=> $b_weight;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Drupal\shortcut\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\shortcut\ShortcutSetInterface;
/**
* Defines the Shortcut set configuration entity.
*
* @ConfigEntityType(
* id = "shortcut_set",
* label = @Translation("Shortcut set"),
* label_collection = @Translation("Shortcut sets"),
* label_singular = @Translation("shortcut set"),
* label_plural = @Translation("shortcut sets"),
* label_count = @PluralTranslation(
* singular = "@count shortcut set",
* plural = "@count shortcut sets",
* ),
* handlers = {
* "storage" = "Drupal\shortcut\ShortcutSetStorage",
* "access" = "Drupal\shortcut\ShortcutSetAccessControlHandler",
* "list_builder" = "Drupal\shortcut\ShortcutSetListBuilder",
* "form" = {
* "default" = "Drupal\shortcut\ShortcutSetForm",
* "add" = "Drupal\shortcut\ShortcutSetForm",
* "edit" = "Drupal\shortcut\ShortcutSetForm",
* "customize" = "Drupal\shortcut\Form\SetCustomize",
* "delete" = "Drupal\shortcut\Form\ShortcutSetDeleteForm"
* }
* },
* config_prefix = "set",
* bundle_of = "shortcut",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* links = {
* "customize-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}/customize",
* "delete-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}/delete",
* "edit-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}",
* "collection" = "/admin/config/user-interface/shortcut",
* },
* config_export = {
* "id",
* "label",
* }
* )
*/
class ShortcutSet extends ConfigEntityBundleBase implements ShortcutSetInterface {
/**
* The machine name for the configuration entity.
*
* @var string
*/
protected $id;
/**
* The human-readable name of the configuration entity.
*
* @var string
*/
protected $label;
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
if (!$update && !$this->isSyncing()) {
// Save a new shortcut set with links copied from the user's default set.
$default_set = $storage->getDefaultSet(\Drupal::currentUser());
// This is the default set, do not copy shortcuts.
if ($default_set->id() != $this->id()) {
foreach ($default_set->getShortcuts() as $shortcut) {
$shortcut = $shortcut->createDuplicate();
$shortcut->enforceIsNew();
$shortcut->shortcut_set->target_id = $this->id();
$shortcut->save();
}
}
}
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
foreach ($entities as $entity) {
$storage->deleteAssignedShortcutSets($entity);
// Next, delete the shortcuts for this set.
$shortcut_ids = \Drupal::entityQuery('shortcut')
->accessCheck(FALSE)
->condition('shortcut_set', $entity->id(), '=')
->execute();
$controller = \Drupal::entityTypeManager()->getStorage('shortcut');
$entities = $controller->loadMultiple($shortcut_ids);
$controller->delete($entities);
}
}
/**
* {@inheritdoc}
*/
public function resetLinkWeights() {
$weight = -50;
foreach ($this->getShortcuts() as $shortcut) {
$shortcut->setWeight(++$weight);
$shortcut->save();
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getShortcuts() {
$shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $this->id()]);
uasort($shortcuts, ['\Drupal\shortcut\Entity\Shortcut', 'sort']);
return $shortcuts;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Drupal\shortcut\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
/**
* Builds the shortcut set customize form.
*
* @internal
*/
class SetCustomize extends EntityForm {
/**
* The entity being used by this form.
*
* @var \Drupal\shortcut\ShortcutSetInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['shortcuts'] = [
'#tree' => TRUE,
'#weight' => -20,
];
$form['shortcuts']['links'] = [
'#type' => 'table',
'#header' => [$this->t('Name'), $this->t('Weight'), $this->t('Operations')],
'#empty' => $this->t('No shortcuts available. <a href=":link">Add a shortcut</a>', [':link' => Url::fromRoute('shortcut.link_add', ['shortcut_set' => $this->entity->id()])->toString()]),
'#attributes' => ['id' => 'shortcuts'],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'shortcut-weight',
],
],
];
foreach ($this->entity->getShortcuts() as $shortcut) {
$id = $shortcut->id();
$url = $shortcut->getUrl();
if (!$url->access()) {
continue;
}
$form['shortcuts']['links'][$id]['#attributes']['class'][] = 'draggable';
$form['shortcuts']['links'][$id]['name'] = [
'#type' => 'link',
'#title' => $shortcut->getTitle(),
'#url' => $url,
'#options' => $url->getOptions(),
];
$form['shortcuts']['links'][$id]['#weight'] = $shortcut->getWeight();
$form['shortcuts']['links'][$id]['weight'] = [
'#type' => 'weight',
'#title' => $this->t('Weight for @title', ['@title' => $shortcut->getTitle()]),
'#title_display' => 'invisible',
'#default_value' => $shortcut->getWeight(),
'#attributes' => ['class' => ['shortcut-weight']],
];
$links['edit'] = [
'title' => $this->t('Edit'),
'url' => $shortcut->toUrl(),
];
$links['delete'] = [
'title' => $this->t('Delete'),
'url' => $shortcut->toUrl('delete-form'),
];
$form['shortcuts']['links'][$id]['operations'] = [
'#type' => 'operations',
'#links' => $links,
'#access' => $url->access(),
];
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
// Only includes a Save action for the entity, no direct Delete button.
return [
'submit' => [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#access' => (bool) Element::getVisibleChildren($form['shortcuts']['links']),
'#submit' => ['::submitForm', '::save'],
],
];
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
foreach ($this->entity->getShortcuts() as $shortcut) {
$weight = $form_state->getValue(['shortcuts', 'links', $shortcut->id(), 'weight']);
$shortcut->setWeight($weight);
$shortcut->save();
}
$this->messenger()->addStatus($this->t('The shortcut set has been updated.'));
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Drupal\shortcut\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Url;
/**
* Builds the shortcut link deletion form.
*
* @internal
*/
class ShortcutDeleteForm extends ContentEntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'shortcut_confirm_delete';
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.shortcut_set.customize_form', [
'shortcut_set' => $this->entity->bundle(),
]);
}
/**
* {@inheritdoc}
*/
protected function getRedirectUrl() {
return $this->getCancelUrl();
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Drupal\shortcut\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\shortcut\ShortcutSetStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Database\Connection;
/**
* Builds the shortcut set deletion form.
*
* @internal
*/
class ShortcutSetDeleteForm extends EntityDeleteForm {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The shortcut storage.
*
* @var \Drupal\shortcut\ShortcutSetStorageInterface
*/
protected $storage;
/**
* Constructs a ShortcutSetDeleteForm object.
*/
public function __construct(Connection $database, ShortcutSetStorageInterface $storage) {
$this->database = $database;
$this->storage = $storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('database'),
$container->get('entity_type.manager')->getStorage('shortcut_set')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Find out how many users are directly assigned to this shortcut set, and
// make a message.
$number = $this->storage->countAssignedUsers($this->entity);
$info = '';
if ($number) {
$info .= '<p>' . $this->formatPlural($number,
'1 user has chosen or been assigned to this shortcut set.',
'@count users have chosen or been assigned to this shortcut set.') . '</p>';
}
// Also, if a module implements hook_shortcut_default_set(), it's possible
// that this set is being used as a default set. Add a message about that too.
if ($this->moduleHandler->hasImplementations('shortcut_default_set')) {
$info .= '<p>' . $this->t('If you have chosen this shortcut set as the default for some or all users, they may also be affected by deleting it.') . '</p>';
}
$form['info'] = [
'#markup' => $info,
];
return parent::buildForm($form, $form_state);
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Drupal\shortcut\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\shortcut\ShortcutSetStorageInterface;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Builds the shortcut set switch form.
*
* @internal
*/
class SwitchShortcutSet extends FormBase {
/**
* The account the shortcut set is for.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The shortcut set storage.
*
* @var \Drupal\shortcut\ShortcutSetStorageInterface
*/
protected $shortcutSetStorage;
/**
* Constructs a SwitchShortcutSet object.
*
* @param \Drupal\shortcut\ShortcutSetStorageInterface $shortcut_set_storage
* The shortcut set storage.
*/
public function __construct(ShortcutSetStorageInterface $shortcut_set_storage) {
$this->shortcutSetStorage = $shortcut_set_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')->getStorage('shortcut_set')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'shortcut_set_switch';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ?UserInterface $user = NULL) {
$account = $this->currentUser();
$this->user = $user;
// Prepare the list of shortcut sets.
$options = array_map(function (ShortcutSet $set) {
return $set->label();
}, $this->shortcutSetStorage->loadMultiple());
$current_set = $this->shortcutSetStorage->getDisplayedToUser($this->user);
// Only administrators can add shortcut sets.
$add_access = $account->hasPermission('administer shortcuts');
if ($add_access) {
$options['new'] = $this->t('New set');
}
$account_is_user = $this->user->id() == $account->id();
if (count($options) > 1) {
$form['set'] = [
'#type' => 'radios',
'#title' => $account_is_user ? $this->t('Choose a set of shortcuts to use') : $this->t('Choose a set of shortcuts for this user'),
'#options' => $options,
'#default_value' => $current_set->id(),
];
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#description' => $this->t('The new set is created by copying links from your default shortcut set.'),
'#access' => $add_access,
'#states' => [
'visible' => [
':input[name="set"]' => ['value' => 'new'],
],
'required' => [
':input[name="set"]' => ['value' => 'new'],
],
],
];
$form['id'] = [
'#type' => 'machine_name',
'#machine_name' => [
'exists' => [$this, 'exists'],
'replace_pattern' => '[^a-z0-9-]+',
'replace' => '-',
],
// This ID could be used for menu name.
'#maxlength' => 23,
'#states' => [
'required' => [
':input[name="set"]' => ['value' => 'new'],
],
],
'#required' => FALSE,
];
if (!$account_is_user) {
$default_set = $this->shortcutSetStorage->getDefaultSet($this->user);
$form['new']['#description'] = $this->t('The new set is created by copying links from the %default set.', ['%default' => $default_set->label()]);
}
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Change set'),
];
}
else {
// There is only 1 option, so output a message in the $form array.
$form['info'] = [
'#markup' => '<p>' . $this->t('You are currently using the %set-name shortcut set.', ['%set-name' => $current_set->label()]) . '</p>',
];
}
return $form;
}
/**
* Determines if a shortcut set exists already.
*
* @param string $id
* The set ID to check.
*
* @return bool
* TRUE if the shortcut set exists, FALSE otherwise.
*/
public function exists($id) {
return (bool) $this->shortcutSetStorage->getQuery()
->condition('id', $id)
->execute();
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('set') == 'new') {
// Check to prevent creating a shortcut set with an empty title.
if (trim($form_state->getValue('label')) == '') {
$form_state->setErrorByName('label', $this->t('The new set label is required.'));
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$account = $this->currentUser();
$account_is_user = $this->user->id() == $account->id();
if ($form_state->getValue('set') == 'new') {
// Save a new shortcut set with links copied from the user's default set.
/** @var \Drupal\shortcut\Entity\ShortcutSet $set */
$set = $this->shortcutSetStorage->create([
'id' => $form_state->getValue('id'),
'label' => $form_state->getValue('label'),
]);
$set->save();
$replacements = [
'%user' => $this->user->label(),
'%set_name' => $set->label(),
':switch-url' => Url::fromRoute('<current>')->toString(),
];
if ($account_is_user) {
// Only administrators can create new shortcut sets, so we know they have
// access to switch back.
$this->messenger()->addStatus($this->t('You are now using the new %set_name shortcut set. You can edit it from this page or <a href=":switch-url">switch back to a different one.</a>', $replacements));
}
else {
$this->messenger()->addStatus($this->t('%user is now using a new shortcut set called %set_name. You can edit it from this page.', $replacements));
}
$form_state->setRedirect(
'entity.shortcut_set.customize_form',
['shortcut_set' => $set->id()]
);
}
else {
// Switch to a different shortcut set.
/** @var \Drupal\shortcut\Entity\ShortcutSet $set */
$set = $this->shortcutSetStorage->load($form_state->getValue('set'));
$replacements = [
'%user' => $this->user->getDisplayName(),
'%set_name' => $set->label(),
];
$this->messenger()->addStatus($account_is_user ? $this->t('You are now using the %set_name shortcut set.', $replacements) : $this->t('%user is now using the %set_name shortcut set.', $replacements));
}
// Assign the shortcut set to the provided user account.
$this->shortcutSetStorage->assignUser($set, $this->user);
}
/**
* Checks access for the shortcut set switch form.
*
* @param \Drupal\user\UserInterface $user
* (optional) The owner of the shortcut set.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function checkAccess(?UserInterface $user = NULL) {
return shortcut_set_switch_access($user);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\shortcut\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Provides a 'Shortcut' block.
*/
#[Block(
id: "shortcuts",
admin_label: new TranslatableMarkup("Shortcuts"),
category: new TranslatableMarkup("Menus")
)]
class ShortcutsBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return [
'#lazy_builder' => ['shortcut.lazy_builders:lazyLinks', [FALSE]],
'#create_placeholder' => TRUE,
'#cache' => [
'keys' => ['shortcut_set_block_links'],
'contexts' => ['user'],
],
];
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'access shortcuts');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Drupal\shortcut\Plugin\migrate\destination;
use Drupal\migrate\Attribute\MigrateDestination;
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\migrate\destination\EntityConfigBase;
/**
* Migration destination for shortcut set entity.
*/
#[MigrateDestination('entity:shortcut_set')]
class EntityShortcutSet extends EntityConfigBase {
/**
* {@inheritdoc}
*/
protected function getEntity(Row $row, array $old_destination_id_values) {
$entity = parent::getEntity($row, $old_destination_id_values);
// Set the "syncing" flag to TRUE, to avoid duplication of default
// shortcut links
$entity->setSyncing(TRUE);
return $entity;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Drupal\shortcut\Plugin\migrate\destination;
use Drupal\migrate\Attribute\MigrateDestination;
use Drupal\shortcut\ShortcutSetStorageInterface;
use Drupal\user\Entity\User;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\migrate\destination\DestinationBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
/**
* Migration destination for shortcut_set_users.
*/
#[MigrateDestination('shortcut_set_users')]
class ShortcutSetUsers extends DestinationBase implements ContainerFactoryPluginInterface {
/**
* The shortcut set storage handler.
*
* @var \Drupal\shortcut\ShortcutSetStorageInterface
*/
protected $shortcutSetStorage;
/**
* Constructs an entity destination plugin.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration.
* @param \Drupal\shortcut\ShortcutSetStorageInterface $shortcut_set_storage
* The shortcut_set entity storage handler.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ShortcutSetStorageInterface $shortcut_set_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->shortcutSetStorage = $shortcut_set_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity_type.manager')->getStorage('shortcut_set')
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'set_name' => [
'type' => 'string',
],
'uid' => [
'type' => 'integer',
],
];
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'uid' => 'The users.uid for this set.',
'source' => 'The shortcut_set.set_name that will be displayed for this user.',
];
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = []) {
/** @var \Drupal\shortcut\ShortcutSetInterface $set */
$set = $this->shortcutSetStorage->load($row->getDestinationProperty('set_name'));
/** @var \Drupal\user\UserInterface $account */
$account = User::load($row->getDestinationProperty('uid'));
$this->shortcutSetStorage->assignUser($set, $account);
return [$set->id(), $account->id()];
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Drupal\shortcut\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
// cspell:ignore mlid
/**
* Drupal 7 shortcut links source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_shortcut",
* source_module = "shortcut"
* )
*/
class Shortcut extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('menu_links', 'ml')
->fields('ml', ['mlid', 'menu_name', 'link_path', 'link_title', 'weight'])
->condition('hidden', '0')
->condition('menu_name', 'shortcut-set-%', 'LIKE')
->orderBy('ml.mlid');
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'mlid' => $this->t("The menu.mlid primary key for this menu link (= shortcut link)."),
'menu_name' => $this->t("The menu name (= set name) for this shortcut link."),
'link_path' => $this->t("The link for this shortcut."),
'link_title' => $this->t("The title for this shortcut."),
'weight' => $this->t("The weight for this shortcut"),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['mlid']['type'] = 'integer';
return $ids;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Drupal\shortcut\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 shortcut_set source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_shortcut_set",
* source_module = "shortcut"
* )
*/
class ShortcutSet extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('shortcut_set', 'ss')->fields('ss');
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'set_name' => $this->t("The name under which the set's links are stored."),
'title' => $this->t("The title of the set."),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['set_name']['type'] = 'string';
return $ids;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Drupal\shortcut\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 shortcut_set_users source from database.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_shortcut_set_users",
* source_module = "shortcut"
* )
*/
class ShortcutSetUsers extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('shortcut_set_users', 'ssu')->fields('ssu');
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'uid' => $this->t('The users.uid for this set.'),
'set_name' => $this->t('The shortcut_set.set_name that will be displayed for this user.'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'set_name' => [
'type' => 'string',
],
'uid' => [
'type' => 'integer',
],
];
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the access control handler for the shortcut entity type.
*
* @see \Drupal\shortcut\Entity\Shortcut
*/
class ShortcutAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
/**
* The shortcut_set storage.
*
* @var \Drupal\shortcut\ShortcutSetStorageInterface
*/
protected $shortcutSetStorage;
/**
* Constructs a ShortcutAccessControlHandler object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\shortcut\ShortcutSetStorageInterface $shortcut_set_storage
* The shortcut_set storage.
*/
public function __construct(EntityTypeInterface $entity_type, ShortcutSetStorageInterface $shortcut_set_storage) {
parent::__construct($entity_type);
$this->shortcutSetStorage = $shortcut_set_storage;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager')->getStorage('shortcut_set')
);
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($shortcut_set = $this->shortcutSetStorage->load($entity->bundle())) {
return shortcut_set_edit_access($shortcut_set);
}
// @todo Fix this bizarre code: how can a shortcut exist without a shortcut
// set? The above if-test is unnecessary. See https://www.drupal.org/node/2339903.
return AccessResult::neutral()->addCacheableDependency($entity);
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
if ($shortcut_set = $this->shortcutSetStorage->load($entity_bundle)) {
return shortcut_set_edit_access($shortcut_set);
}
// @todo Fix this bizarre code: how can a shortcut exist without a shortcut
// set? The above if-test is unnecessary. See https://www.drupal.org/node/2339903.
return AccessResult::neutral();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
/**
* Form handler for the shortcut entity forms.
*
* @internal
*/
class ShortcutForm extends ContentEntityForm {
/**
* The entity being used by this form.
*
* @var \Drupal\shortcut\ShortcutInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['#attached']['library'][] = 'core/drupal.form';
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$entity = $this->entity;
$status = $entity->save();
$url = $entity->getUrl();
// There's an edge case where a user can have permission to
// 'link to any content', but has no right to access the linked page. So we
// check the access before showing the link.
if ($url->access()) {
$view_link = Link::fromTextAndUrl($entity->getTitle(), $url)->toString();
}
else {
$view_link = $entity->getTitle();
}
if ($status == SAVED_UPDATED) {
$message = $this->t('The shortcut %link has been updated.', ['%link' => $view_link]);
}
else {
$message = $this->t('Added a shortcut for %title.', ['%title' => $view_link]);
}
$this->messenger()->addStatus($message);
$form_state->setRedirect(
'entity.shortcut_set.customize_form',
['shortcut_set' => $entity->bundle()]
);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Entity\ContentEntityInterface;
/**
* Provides an interface defining a shortcut entity.
*/
interface ShortcutInterface extends ContentEntityInterface {
/**
* Returns the title of this shortcut.
*
* @return string
* The title of this shortcut.
*/
public function getTitle();
/**
* Sets the title of this shortcut.
*
* @param string $title
* The title of this shortcut.
*
* @return $this
* The called shortcut entity.
*/
public function setTitle($title);
/**
* Returns the weight among shortcuts with the same depth.
*
* @return int
* The shortcut weight.
*/
public function getWeight();
/**
* Sets the weight among shortcuts with the same depth.
*
* @param int $weight
* The shortcut weight.
*
* @return $this
* The called shortcut entity.
*/
public function setWeight($weight);
/**
* Returns the URL object pointing to the configured route.
*
* @return \Drupal\Core\Url
* The URL object.
*/
public function getUrl();
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
/**
* Lazy builders for the shortcut module.
*/
class ShortcutLazyBuilders implements TrustedCallbackInterface {
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new ShortcutLazyBuilders object.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface|null $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Session\AccountInterface|null $currentUser
* The current user.
*/
public function __construct(RendererInterface $renderer, protected ?EntityTypeManagerInterface $entityTypeManager, protected ?AccountInterface $currentUser) {
$this->renderer = $renderer;
if (!isset($this->entityTypeManager)) {
@trigger_error('Calling ' . __METHOD__ . '() without the $entityTypeManager argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3427050', E_USER_DEPRECATED);
$this->entityTypeManager = \Drupal::entityTypeManager();
}
if (!isset($this->currentUser)) {
@trigger_error('Calling ' . __METHOD__ . '() without the $currentUser argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3427050', E_USER_DEPRECATED);
$this->currentUser = \Drupal::currentUser();
}
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['lazyLinks'];
}
/**
* #lazy_builder callback; builds shortcut toolbar links.
*
* @param bool $show_configure_link
* Boolean to indicate whether to include the configure link or not.
*
* @return array
* A renderable array of shortcut links.
*/
public function lazyLinks(bool $show_configure_link = TRUE) {
$shortcut_set = $this->entityTypeManager->getStorage('shortcut_set')
->getDisplayedToUser($this->currentUser);
$links = shortcut_renderable_links();
$configure_link = NULL;
if ($show_configure_link && shortcut_set_edit_access($shortcut_set)->isAllowed()) {
$configure_link = [
'#type' => 'link',
'#title' => t('Edit shortcuts'),
'#url' => Url::fromRoute('entity.shortcut_set.customize_form', ['shortcut_set' => $shortcut_set->id()]),
'#options' => ['attributes' => ['class' => ['edit-shortcuts']]],
];
}
$build = [
'shortcuts' => $links,
'configure' => $configure_link,
];
$this->renderer->addCacheableDependency($build, $shortcut_set);
return $build;
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the access control handler for the shortcut set entity type.
*
* @see \Drupal\shortcut\Entity\ShortcutSet
*/
class ShortcutSetAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
/**
* Constructs a ShortcutSetAccessControlHandler object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct(EntityTypeInterface $entity_type, protected EntityTypeManagerInterface $entityTypeManager) {
parent::__construct($entity_type);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager'),
);
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
switch ($operation) {
case 'view':
return AccessResult::allowedIfHasPermission($account, 'access shortcuts');
case 'update':
if ($account->hasPermission('administer shortcuts')) {
return AccessResult::allowed()->cachePerPermissions();
}
if (!$account->hasPermission('access shortcuts')) {
return AccessResult::neutral()->cachePerPermissions();
}
$shortcut_set_storage = $this->entityTypeManager->getStorage('shortcut_set');
return AccessResult::allowedIf($account->hasPermission('customize shortcut links') && $entity == $shortcut_set_storage->getDisplayedToUser($account))->cachePerPermissions()->addCacheableDependency($entity);
case 'delete':
return AccessResult::allowedIf($account->hasPermission('administer shortcuts') && $entity->id() != 'default')->cachePerPermissions();
default:
// No opinion.
return AccessResult::neutral();
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, ['access shortcuts', 'customize shortcut links'], 'AND'));
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form handler for the shortcut set entity edit forms.
*
* @internal
*/
class ShortcutSetForm extends BundleEntityFormBase {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$entity = $this->entity;
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Set name'),
'#description' => $this->t('The new set is created by copying links from your default shortcut set.'),
'#required' => TRUE,
'#default_value' => $entity->label(),
];
$form['id'] = [
'#type' => 'machine_name',
'#machine_name' => [
'exists' => '\Drupal\shortcut\Entity\ShortcutSet::load',
'source' => ['label'],
'replace_pattern' => '[^a-z0-9-]+',
'replace' => '-',
],
'#default_value' => $entity->id(),
// This id could be used for menu name.
'#maxlength' => 23,
];
$form['actions']['submit']['#value'] = $this->t('Create new set');
return $this->protectBundleIdElement($form);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$entity = $this->entity;
$is_new = !$entity->getOriginalId();
$entity->save();
if ($is_new) {
$this->messenger()->addStatus($this->t('The %set_name shortcut set has been created. You can edit it from this page.', ['%set_name' => $entity->label()]));
}
else {
$this->messenger()->addStatus($this->t('Updated set name to %set-name.', ['%set-name' => $entity->label()]));
}
$form_state->setRedirectUrl($this->entity->toUrl('customize-form'));
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a shortcut set entity.
*/
interface ShortcutSetInterface extends ConfigEntityInterface {
/**
* Resets the link weights in a shortcut set to match their current order.
*
* This function can be used, for example, when a new shortcut link is added
* to the set. If the link is added to the end of the array and this function
* is called, it will force that link to display at the end of the list.
*
* @return $this
* The shortcut set.
*/
public function resetLinkWeights();
/**
* Returns all the shortcuts from a shortcut set sorted correctly.
*
* @return \Drupal\shortcut\ShortcutInterface[]
* An array of shortcut entities.
*/
public function getShortcuts();
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
/**
* Defines a class to build a listing of shortcut set entities.
*
* @see \Drupal\shortcut\Entity\ShortcutSet
*/
class ShortcutSetListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['name'] = t('Name');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Edit shortcut set');
}
$operations['list'] = [
'title' => t('List links'),
'url' => $entity->toUrl('customize-form'),
];
return $operations;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['name'] = $entity->label();
return $row + parent::buildRow($entity);
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace Drupal\shortcut;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a storage for shortcut_set entities.
*/
class ShortcutSetStorage extends ConfigEntityStorage implements ShortcutSetStorageInterface {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a ShortcutSetStorageController object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
* The entity info for the entity type.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
* The memory cache.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(EntityTypeInterface $entity_info, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, Connection $connection) {
parent::__construct($entity_info, $config_factory, $uuid_service, $language_manager, $memory_cache);
$this->moduleHandler = $module_handler;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
return new static(
$entity_info,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('module_handler'),
$container->get('language_manager'),
$container->get('entity.memory_cache'),
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
// First, delete any user assignments for this set, so that each of these
// users will go back to using whatever default set applies.
$this->connection->delete('shortcut_set_users')
->condition('set_name', $entity->id())
->execute();
}
/**
* {@inheritdoc}
*/
public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
$current_shortcut_set = $this->getDisplayedToUser($account);
$this->connection->merge('shortcut_set_users')
->key('uid', $account->id())
->fields(['set_name' => $shortcut_set->id()])
->execute();
if ($current_shortcut_set instanceof ShortcutSetInterface) {
Cache::invalidateTags($current_shortcut_set->getCacheTagsToInvalidate());
}
}
/**
* {@inheritdoc}
*/
public function unassignUser($account) {
$current_shortcut_set = $this->getDisplayedToUser($account);
$deleted = $this->connection->delete('shortcut_set_users')
->condition('uid', $account->id())
->execute();
if ($current_shortcut_set instanceof ShortcutSetInterface) {
Cache::invalidateTags($current_shortcut_set->getCacheTagsToInvalidate());
}
return (bool) $deleted;
}
/**
* {@inheritdoc}
*/
public function getAssignedToUser($account) {
$query = $this->connection->select('shortcut_set_users', 'ssu');
$query->fields('ssu', ['set_name']);
$query->condition('ssu.uid', $account->id());
return $query->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function getDisplayedToUser(AccountInterface $account): ShortcutSetInterface {
if ($set_name = $this->getAssignedToUser($account)) {
return $this->load($set_name);
}
return $this->getDefaultSet($account);
}
/**
* {@inheritdoc}
*/
public function countAssignedUsers(ShortcutSetInterface $shortcut_set) {
return Database::getConnection()->query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE [set_name] = :name', [':name' => $shortcut_set->id()])->fetchField();
}
/**
* {@inheritdoc}
*/
public function getDefaultSet(AccountInterface $account) {
// Allow modules to return a default shortcut set name. Since we can only
// have one, we allow the last module which returns a valid result to take
// precedence. If no module returns a valid set, fall back on the site-wide
// default, which is the lowest-numbered shortcut set.
$suggestions = array_reverse($this->moduleHandler->invokeAll('shortcut_default_set', [$account]));
$suggestions[] = 'default';
$shortcut_set = NULL;
foreach ($suggestions as $name) {
if ($shortcut_set = $this->load($name)) {
break;
}
}
return $shortcut_set;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Drupal\shortcut;
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for shortcut_set entity storage classes.
*/
interface ShortcutSetStorageInterface extends ConfigEntityStorageInterface {
/**
* Assigns a user to a particular shortcut set.
*
* @param \Drupal\shortcut\ShortcutSetInterface $shortcut_set
* An object representing the shortcut set.
* @param $account
* A user account that will be assigned to use the set.
*/
public function assignUser(ShortcutSetInterface $shortcut_set, $account);
/**
* Un-assigns a user from any shortcut set they may have been assigned to.
*
* The user will go back to using whatever default set applies.
*
* @param $account
* A user account that will be removed from the shortcut set assignment.
*
* @return bool
* TRUE if the user was previously assigned to a shortcut set and has been
* successfully removed from it. FALSE if the user was already not assigned
* to any set.
*/
public function unassignUser($account);
/**
* Delete shortcut sets assigned to users.
*
* @param \Drupal\shortcut\ShortcutSetInterface $entity
* Delete the user assigned sets belonging to this shortcut.
*/
public function deleteAssignedShortcutSets(ShortcutSetInterface $entity);
/**
* Get the name of the set assigned to this user.
*
* @param \Drupal\user\Entity\User $account
* The user account.
*
* @return string
* The name of the shortcut set assigned to this user.
*/
public function getAssignedToUser($account);
/**
* Gets the shortcut set to be displayed for a given user account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account whose default shortcut set will be returned.
*
* @return \Drupal\shortcut\ShortcutSetInterface
* An object representing the default shortcut set.
*/
public function getDisplayedToUser(AccountInterface $account): ShortcutSetInterface;
/**
* Get the number of users who have this set assigned to them.
*
* @param \Drupal\shortcut\ShortcutSetInterface $shortcut_set
* The shortcut to count the users assigned to.
*
* @return int
* The number of users who have this set assigned to them.
*/
public function countAssignedUsers(ShortcutSetInterface $shortcut_set);
/**
* Gets the default shortcut set for a given user account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account whose default shortcut set will be returned.
*
* @return \Drupal\shortcut\ShortcutSetInterface
* An object representing the default shortcut set.
*/
public function getDefaultSet(AccountInterface $account);
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for shortcut.
*
* @group shortcut
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonAnonTest extends ShortcutResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonBasicAuthTest extends ShortcutResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonCookieTest extends ShortcutResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for Shortcut entity.
*/
abstract class ShortcutResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['comment', 'shortcut'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'shortcut';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* The Shortcut entity.
*
* @var \Drupal\shortcut\ShortcutInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['access shortcuts', 'customize shortcut links']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create shortcut.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => t('Comments'),
'weight' => -20,
'link' => [
'uri' => 'internal:/admin/content/comment',
'options' => [
'fragment' => 'new',
],
],
]);
$shortcut->save();
return $shortcut;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'id' => [
[
'value' => (int) $this->entity->id(),
],
],
'title' => [
[
'value' => 'Comments',
],
],
'shortcut_set' => [
[
'target_id' => 'default',
'target_type' => 'shortcut_set',
'target_uuid' => ShortcutSet::load('default')->uuid(),
],
],
'link' => [
[
'uri' => 'internal:/admin/content/comment',
'title' => NULL,
'options' => [
'fragment' => 'new',
],
],
],
'weight' => [
[
'value' => -20,
],
],
'langcode' => [
[
'value' => 'en',
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'title' => [
[
'value' => 'Comments',
],
],
'link' => [
[
'uri' => 'internal:/',
],
],
'shortcut_set' => [
[
'target_id' => 'default',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
return "The shortcut set must be the currently displayed set for the user and the user must have 'access shortcuts' AND 'customize shortcut links' permissions.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ShortcutSetJsonAnonTest extends ShortcutSetResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ShortcutSetJsonBasicAuthTest extends ShortcutSetResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ShortcutSetJsonCookieTest extends ShortcutSetResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
/**
* ResourceTestBase for ShortcutSet entity.
*/
abstract class ShortcutSetResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['shortcut'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'shortcut_set';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* The ShortcutSet entity.
*
* @var \Drupal\shortcut\ShortcutSetInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['access shortcuts']);
break;
case 'POST':
case 'PATCH':
$this->grantPermissionsToTestedRole(['access shortcuts', 'customize shortcut links']);
break;
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer shortcuts']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$set = ShortcutSet::create([
'id' => 'llama-set',
'label' => 'Llama Set',
]);
$set->save();
return $set;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'id' => 'llama-set',
'uuid' => $this->entity->uuid(),
'label' => 'Llama Set',
'status' => TRUE,
'langcode' => 'en',
'dependencies' => [],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
return "The 'access shortcuts' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutSetXmlAnonTest extends ShortcutSetResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutSetXmlBasicAuthTest extends ShortcutSetResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutSetXmlCookieTest extends ShortcutSetResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutXmlAnonTest extends ShortcutResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutXmlBasicAuthTest extends ShortcutResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ShortcutXmlCookieTest extends ShortcutResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,397 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Url;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
/**
* Tests the Shortcut entity's cache tags.
*
* @group shortcut
*/
class ShortcutCacheTagsTest extends EntityCacheTagsTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'toolbar',
'shortcut',
'test_page_test',
'block',
];
/**
* User with permission to administer shortcuts.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $adminUser;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'access shortcuts',
'administer site configuration',
'administer shortcuts',
'administer themes',
]);
// Give anonymous users permission to customize shortcut links, so that we
// can verify the cache tags of cached versions of shortcuts.
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
$user_role->grantPermission('customize shortcut links');
$user_role->grantPermission('access shortcuts');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" shortcut.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => 'Llama',
'weight' => 0,
'link' => [['uri' => 'internal:/admin']],
]);
$shortcut->save();
return $shortcut;
}
/**
* Tests that when creating a shortcut, the shortcut set tag is invalidated.
*/
public function testEntityCreation(): void {
$cache_bin = $this->getRenderCacheBackend();
// Create a cache entry that is tagged with a shortcut set cache tag.
$cache_tags = ['config:shortcut.set.default'];
$cacheability = new CacheableMetadata();
$cacheability->addCacheTags($cache_tags);
$cache_bin->set(['foo'], 'bar', $cacheability, $cacheability);
// Verify a cache hit.
$this->verifyRenderCache(['foo'], $cache_tags, $cacheability);
// Now create a shortcut entity in that shortcut set.
$this->createEntity();
// Verify a cache miss.
$this->assertFalse($cache_bin->get(['foo'], $cacheability), 'Creating a new shortcut invalidates the cache tag of the shortcut set.');
}
/**
* Tests visibility and cacheability of shortcuts in the toolbar.
*/
public function testToolbar(): void {
$this->drupalPlaceBlock('page_title_block', ['id' => 'title']);
$test_page_url = Url::fromRoute('test_page_test.test_page');
$this->verifyPageCache($test_page_url, 'MISS');
$this->verifyPageCache($test_page_url, 'HIT');
// Ensure that without enabling the shortcuts-in-page-title-link feature
// in the theme, the shortcut_list cache tag is not added to the page.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/system/cron');
$expected_cache_tags = [
'block_view',
'config:block.block.title',
'config:block_list',
'config:shortcut.set.default',
'config:system.menu.admin',
'config:system.theme',
'CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form',
'rendered',
];
$this->assertCacheTags($expected_cache_tags);
\Drupal::configFactory()
->getEditable('stark.settings')
->set('third_party_settings.shortcut.module_link', TRUE)
->save(TRUE);
// Add cron to the default shortcut set, now the shortcut list cache tag
// is expected.
$this->drupalGet('admin/config/system/cron');
$this->clickLink('Add to Default shortcuts');
$expected_cache_tags[] = 'config:shortcut_set_list';
$this->assertCacheTags($expected_cache_tags);
// Verify that users without the 'access shortcuts' permission can't see the
// shortcuts.
$this->drupalLogin($this->drupalCreateUser(['access toolbar']));
$this->assertSession()->linkNotExists('Shortcuts');
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
// Verify that users without the 'administer site configuration' permission
// can't see the cron shortcut but can see shortcuts toolbar tab.
$this->drupalLogin($this->drupalCreateUser([
'access toolbar',
'access shortcuts',
]));
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Shortcuts');
$this->assertSession()->linkNotExists('Cron');
// Create a role with access to shortcuts as well as the necessary
// permissions to see specific shortcuts.
$site_configuration_role = $this->drupalCreateRole([
'access toolbar',
'access shortcuts',
'administer site configuration',
'access administration pages',
]);
// Create two different users with the same role to assert that the second
// user has a cache hit despite the user cache context, as
// the returned cache contexts include those from lazy-builder content.
$site_configuration_user1 = $this->drupalCreateUser();
$site_configuration_user1->addRole($site_configuration_role)->save();
$site_configuration_user2 = $this->drupalCreateUser();
$site_configuration_user2->addRole($site_configuration_role)->save();
$this->drupalLogin($site_configuration_user1);
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertCacheContexts(['session', 'user', 'url.query_args:_wrapper_format']);
$this->assertSession()->linkExists('Shortcuts');
$this->assertSession()->linkExists('Cron');
$this->drupalLogin($site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertCacheContexts(['session', 'user', 'url.query_args:_wrapper_format']);
$this->assertSession()->linkExists('Shortcuts');
$this->assertSession()->linkExists('Cron');
// Add another shortcut.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => 'Llama',
'weight' => 0,
'link' => [['uri' => 'internal:/admin/config']],
]);
$shortcut->save();
// The shortcuts are displayed in a lazy builder, so the page is still a
// cache HIT but shows the new shortcut immediately.
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('Llama');
// Update the shortcut title and assert that it is updated.
$shortcut->set('title', 'Alpaca');
$shortcut->save();
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('Alpaca');
// Delete the shortcut and assert that the link is gone.
$shortcut->delete();
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkNotExists('Alpaca');
// Add a new Shortcut Set with a single link.
$new_set = ShortcutSet::create([
'id' => 'llama-set',
'label' => 'Llama Set',
]);
$new_set->save();
$new_shortcut = Shortcut::create([
'shortcut_set' => 'llama-set',
'title' => 'New Llama',
'weight' => 0,
'link' => [['uri' => 'internal:/admin/config']],
]);
$new_shortcut->save();
// Assign the new shortcut set to user 2 and confirm that links are
// changed automatically.
\Drupal::entityTypeManager()
->getStorage('shortcut_set')
->assignUser($new_set, $site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('New Llama');
// Confirm that links for user 1 have not been affected.
$this->drupalLogin($site_configuration_user1);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkNotExists('New Llama');
// Confirm that removing assignment automatically changes the links too.
$this->drupalLogin($site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('New Llama');
\Drupal::entityTypeManager()
->getStorage('shortcut_set')
->unassignUser($site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkNotExists('New Llama');
// Confirm that deleting a shortcut set automatically changes the links too.
\Drupal::entityTypeManager()
->getStorage('shortcut_set')
->assignUser($new_set, $site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('New Llama');
\Drupal::entityTypeManager()
->getStorage('shortcut_set')
->delete([$new_set]);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkNotExists('New Llama');
}
/**
* Tests visibility and cacheability of shortcuts in the block.
*/
public function testBlock(): void {
$this->drupalPlaceBlock('page_title_block', ['id' => 'title']);
$this->drupalPlaceBlock('shortcuts', [
'id' => 'shortcuts',
'label' => 'Shortcuts Block',
]);
$test_page_url = Url::fromRoute('test_page_test.test_page');
$this->verifyPageCache($test_page_url, 'MISS');
$this->verifyPageCache($test_page_url, 'HIT');
// Ensure that without enabling the shortcuts-in-page-title-link feature
// in the theme, the shortcut_list cache tag is not added to the page.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/system/cron');
$expected_cache_tags = [
'CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form',
'block_view',
'config:block.block.shortcuts',
'config:block.block.title',
'config:block_list',
'config:shortcut.set.default',
'config:system.menu.admin',
'config:system.theme',
'rendered',
];
$this->assertCacheTags($expected_cache_tags);
\Drupal::configFactory()
->getEditable('stark.settings')
->set('third_party_settings.shortcut.module_link', TRUE)
->save(TRUE);
// Add cron to the default shortcut set, now the shortcut list cache tag
// is expected.
$this->drupalGet('admin/config/system/cron');
$this->clickLink('Add to Default shortcuts');
$expected_cache_tags[] = 'config:shortcut_set_list';
$this->assertCacheTags($expected_cache_tags);
// Verify that users without the 'access shortcuts' permission can't see the
// shortcuts.
$this->drupalLogin($this->drupalCreateUser());
$this->assertSession()->pageTextNotContains('Shortcuts Block');
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
// Verify that users without the 'administer site configuration' permission
// can't see the cron shortcut but can see the block.
$this->drupalLogin($this->drupalCreateUser([
'access shortcuts',
]));
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->pageTextContains('Shortcuts Block');
$this->assertSession()->linkNotExists('Cron');
// Create a role with access to shortcuts as well as the necessary
// permissions to see specific shortcuts.
$site_configuration_role = $this->drupalCreateRole([
'access shortcuts',
'administer site configuration',
'access administration pages',
]);
// Create two different users with the same role to assert that the second
// user has a cache hit despite the user cache context, as
// the returned cache contexts include those from lazy-builder content.
$site_configuration_user1 = $this->drupalCreateUser();
$site_configuration_user1->addRole($site_configuration_role)->save();
$site_configuration_user2 = $this->drupalCreateUser();
$site_configuration_user2->addRole($site_configuration_role)->save();
$this->drupalLogin($site_configuration_user1);
$this->verifyDynamicPageCache($test_page_url, 'MISS');
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertCacheContexts(['user', 'url.query_args:_wrapper_format']);
$this->assertSession()->pageTextContains('Shortcuts Block');
$this->assertSession()->linkExists('Cron');
$this->drupalLogin($site_configuration_user2);
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertCacheContexts(['user', 'url.query_args:_wrapper_format']);
$this->assertSession()->pageTextContains('Shortcuts Block');
$this->assertSession()->linkExists('Cron');
// Add another shortcut.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => 'Llama',
'weight' => 0,
'link' => [['uri' => 'internal:/admin/config']],
]);
$shortcut->save();
// The shortcuts are displayed in a lazy builder, so the page is still a
// cache HIT but shows the new shortcut immediately.
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('Llama');
// Update the shortcut title and assert that it is updated.
$shortcut->set('title', 'Alpaca');
$shortcut->save();
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkExists('Alpaca');
// Delete the shortcut and assert that the link is gone.
$shortcut->delete();
$this->verifyDynamicPageCache($test_page_url, 'HIT');
$this->assertSession()->linkExists('Cron');
$this->assertSession()->linkNotExists('Alpaca');
}
}

View File

@@ -0,0 +1,498 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Url;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\Tests\block\Functional\AssertBlockAppearsTrait;
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
use Drupal\views\Entity\View;
/**
* Create, view, edit, delete, and change shortcut links.
*
* @group shortcut
* @group #slow
*/
class ShortcutLinksTest extends ShortcutTestBase {
use AssertBlockAppearsTrait;
use PathAliasTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['router_test', 'views', 'block'];
/**
* {@inheritdoc}
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'administer shortcuts',
'view the administration theme',
'access content overview',
'administer users',
'administer site configuration',
'administer content types',
'create article content',
'create page content',
'edit any article content',
'edit any page content',
'administer blocks',
'access shortcuts',
]);
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests that creating a shortcut works properly.
*/
public function testShortcutLinkAdd(): void {
$set = $this->set;
// Create an alias for the node so we can test aliases.
$path_alias = $this->createPathAlias('/node/' . $this->node->id(), '/' . $this->randomMachineName(8));
// Create some paths to test.
$test_cases = [
'/',
'/admin',
'/admin/config/system/site-information',
'/node/' . $this->node->id() . '/edit',
$path_alias->getAlias(),
'/router_test/test2',
'/router_test/test3/value',
];
$test_cases_non_access = [
'/admin',
'/admin/config/system/site-information',
];
// Test the add shortcut form UI. Test that the base field description is
// there.
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link');
$this->assertSession()->pageTextContains('The location this shortcut points to.');
// Check that each new shortcut links where it should.
foreach ($test_cases as $test_path) {
$title = $this->randomMachineName();
$form_data = [
'title[0][value]' => $title,
'link[0][uri]' => $test_path,
];
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link');
$this->submitForm($form_data, 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Added a shortcut for ' . $title . '.');
$saved_set = ShortcutSet::load($set->id());
$paths = $this->getShortcutInformation($saved_set, 'link');
$this->assertContains('internal:' . $test_path, $paths, 'Shortcut created: ' . $test_path);
if (in_array($test_path, $test_cases_non_access)) {
$this->assertSession()->linkNotExists($title, new FormattableMarkup('Shortcut link %url not accessible on the page.', ['%url' => $test_path]));
}
else {
$this->assertSession()->linkExists($title, 0, new FormattableMarkup('Shortcut link %url found on the page.', ['%url' => $test_path]));
}
}
$saved_set = ShortcutSet::load($set->id());
// Test that saving and re-loading a shortcut preserves its values.
$shortcuts = $saved_set->getShortcuts();
foreach ($shortcuts as $entity) {
// Test the node routes with parameters.
$entity->save();
$loaded = Shortcut::load($entity->id());
$this->assertEquals($entity->link->uri, $loaded->link->uri);
$this->assertEquals($entity->link->options, $loaded->link->options);
}
// Log in as non admin user, to check that access is checked when creating
// shortcuts.
$this->drupalLogin($this->shortcutUser);
$title = $this->randomMachineName();
$form_data = [
'title[0][value]' => $title,
'link[0][uri]' => '/admin',
];
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link');
$this->submitForm($form_data, 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains("The path '/admin' is inaccessible.");
$form_data = [
'title[0][value]' => $title,
'link[0][uri]' => '/node',
];
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link');
$this->submitForm($form_data, 'Save');
$this->assertSession()->linkExists($title, 0, 'Shortcut link found on the page.');
// Create a new shortcut set and add a link to it.
$this->drupalLogin($this->adminUser);
$edit = [
'label' => $this->randomMachineName(),
'id' => $this->randomMachineName(),
];
$this->drupalGet('admin/config/user-interface/shortcut/add-set');
$this->submitForm($edit, 'Save');
$title = $this->randomMachineName();
$form_data = [
'title[0][value]' => $title,
'link[0][uri]' => '/admin',
];
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $edit['id'] . '/add-link');
$this->submitForm($form_data, 'Save');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests that the "add to shortcut" and "remove from shortcut" links work.
*/
public function testShortcutQuickLink(): void {
\Drupal::service('theme_installer')->install(['claro']);
$this->config('system.theme')->set('admin', 'claro')->save();
$this->config('node.settings')->set('use_admin_theme', '1')->save();
$this->container->get('router.builder')->rebuild();
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/system/cron');
// Test the "Add to shortcuts" link.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains('Added a shortcut for Cron.');
$this->assertSession()->linkExists('Cron', 0, 'Shortcut link found on page');
$this->drupalGet('admin/structure');
$this->assertSession()->linkExists('Cron', 0, 'Shortcut link found on different page');
// Test the "Remove from shortcuts" link.
$this->clickLink('Cron');
$this->clickLink('Remove from Default shortcuts');
$this->assertSession()->pageTextContains('The shortcut Cron has been deleted.');
$this->assertSession()->linkNotExists('Cron', 'Shortcut link removed from page');
$this->drupalGet('admin/structure');
$this->assertSession()->linkNotExists('Cron', 'Shortcut link removed from different page');
$this->drupalGet('admin/people');
// Test the "Add to shortcuts" link for a page generated by views.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains('Added a shortcut for People.');
$this->assertShortcutQuickLink('Remove from Default shortcuts');
// Test the "Remove from shortcuts" link for a page generated by views.
$this->clickLink('Remove from Default shortcuts');
$this->assertSession()->pageTextContains('The shortcut People has been deleted.');
$this->assertShortcutQuickLink('Add to Default shortcuts');
// Test two pages which use same route name but different route parameters.
$this->drupalGet('node/add/page');
// Add Shortcut for Basic Page.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains('Added a shortcut for Create Basic page.');
// Assure that Article does not have its shortcut indicated as set.
$this->drupalGet('node/add/article');
$this->assertSession()->elementNotExists('xpath', "//a[normalize-space()='Remove from Default shortcuts']");
// Add Shortcut for Article.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains('Added a shortcut for Create Article.');
$this->config('system.theme')->set('default', 'claro')->save();
$this->drupalGet('node/' . $this->node->id());
$title = $this->node->getTitle();
// Test the "Add to shortcuts" link for node view route.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains("Added a shortcut for $title.");
$this->assertShortcutQuickLink('Remove from Default shortcuts');
// Test the "Remove from shortcuts" link for node view route.
$this->clickLink('Remove from Default shortcuts');
$this->assertSession()->pageTextContains("The shortcut $title has been deleted.");
$this->assertShortcutQuickLink('Add to Default shortcuts');
\Drupal::service('module_installer')->install(['block_content']);
$this->adminUser
->addRole($this->drupalCreateRole(['administer block types']))
->save();
BlockContentType::create([
'id' => 'basic',
'label' => 'Basic block',
'revision' => FALSE,
])->save();
// Test page with HTML tags in title.
$this->drupalGet('admin/structure/block-content/manage/basic');
$page_title = "Edit Basic block block type";
$this->assertSession()->pageTextContains($page_title);
// Add shortcut to this page.
$this->clickLink('Add to Default shortcuts');
$this->assertSession()->pageTextContains("Added a shortcut for {$page_title}.");
}
/**
* Tests that shortcut links can be renamed.
*/
public function testShortcutLinkRename(): void {
$set = $this->set;
// Attempt to rename shortcut link.
$new_link_name = $this->randomMachineName();
$shortcuts = $set->getShortcuts();
$shortcut = reset($shortcuts);
$this->drupalGet('admin/config/user-interface/shortcut/link/' . $shortcut->id());
$this->submitForm(['title[0][value]' => $new_link_name], 'Save');
$saved_set = ShortcutSet::load($set->id());
$titles = $this->getShortcutInformation($saved_set, 'title');
$this->assertContains($new_link_name, $titles, 'Shortcut renamed: ' . $new_link_name);
$this->assertSession()->linkExists($new_link_name, 0, 'Renamed shortcut link appears on the page.');
$this->assertSession()->pageTextContains('The shortcut ' . $new_link_name . ' has been updated.');
}
/**
* Tests that changing the path of a shortcut link works.
*/
public function testShortcutLinkChangePath(): void {
$set = $this->set;
// Tests changing a shortcut path.
$new_link_path = '/admin/config';
$shortcuts = $set->getShortcuts();
$shortcut = reset($shortcuts);
$this->drupalGet('admin/config/user-interface/shortcut/link/' . $shortcut->id());
$this->submitForm([
'title[0][value]' => $shortcut->getTitle(),
'link[0][uri]' => $new_link_path,
], 'Save');
$saved_set = ShortcutSet::load($set->id());
$paths = $this->getShortcutInformation($saved_set, 'link');
$this->assertContains('internal:' . $new_link_path, $paths, 'Shortcut path changed: ' . $new_link_path);
$this->assertSession()->linkByHrefExists($new_link_path, 0, 'Shortcut with new path appears on the page.');
$this->assertSession()->pageTextContains('The shortcut ' . $shortcut->getTitle() . ' has been updated.');
}
/**
* Tests that changing the route of a shortcut link works.
*/
public function testShortcutLinkChangeRoute(): void {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content');
$this->assertSession()->statusCodeEquals(200);
// Disable the view.
View::load('content')->disable()->save();
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
$router_builder = \Drupal::service('router.builder');
$router_builder->rebuildIfNeeded();
$this->drupalGet('admin/content');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests deleting a shortcut link.
*/
public function testShortcutLinkDelete(): void {
$set = $this->set;
$shortcuts = $set->getShortcuts();
$shortcut = reset($shortcuts);
$this->drupalGet('admin/config/user-interface/shortcut/link/' . $shortcut->id() . '/delete');
$this->submitForm([], 'Delete');
$saved_set = ShortcutSet::load($set->id());
$ids = $this->getShortcutInformation($saved_set, 'id');
$this->assertNotContains($shortcut->id(), $ids, 'Successfully deleted a shortcut.');
// Delete all the remaining shortcut links.
$storage = \Drupal::entityTypeManager()->getStorage('shortcut');
$storage->delete($storage->loadMultiple(array_filter($ids)));
// Get the front page to check that no exceptions occur.
$this->drupalGet('');
}
/**
* Tests that the add shortcut link is not displayed for 404/403 errors.
*
* Tests that the "Add to shortcuts" link is not displayed on a page not
* found or a page the user does not have access to.
*/
public function testNoShortcutLink(): void {
// Change to a theme that displays shortcuts.
\Drupal::service('theme_installer')->install(['claro']);
$this->config('system.theme')
->set('default', 'claro')
->save();
$this->drupalGet('page-that-does-not-exist');
// Test that add to shortcuts link is not shown on a page not found.
$this->assertSession()->elementNotExists('xpath', '//a[contains(@class, "shortcut-action--add")]');
// The user does not have access to this path.
$this->drupalGet('admin/modules');
$this->assertSession()->elementNotExists('xpath', '//a[contains(@class, "shortcut-action--add")]');
// Verify that the testing mechanism works by verifying the shortcut link
// appears on admin/content.
$this->drupalGet('admin/content');
$this->assertSession()->elementExists('xpath', '//a[contains(@class, "shortcut-action--remove")]');
// Verify that the shortcut link appears on routing only pages.
$this->drupalGet('router_test/test2');
$this->assertSession()->elementExists('xpath', '//a[contains(@class, "shortcut-action--add")]');
}
/**
* Tests that the 'access shortcuts' permissions works properly.
*/
public function testAccessShortcutsPermission(): void {
// Change to a theme that displays shortcuts.
\Drupal::service('theme_installer')->install(['claro']);
$this->config('system.theme')
->set('default', 'claro')
->save();
// Add cron to the default shortcut set.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/system/cron');
$this->clickLink('Add to Default shortcuts');
// Verify that users without the 'access shortcuts' permission can't see the
// shortcuts.
$this->drupalLogin($this->drupalCreateUser(['access toolbar']));
$this->assertSession()->linkNotExists('Shortcuts', 'Shortcut link not found on page.');
// Verify that users without the 'administer site configuration' permission
// can't see the cron shortcuts but can see shortcuts.
$this->drupalLogin($this->drupalCreateUser([
'access toolbar',
'access shortcuts',
]));
$this->assertSession()->linkExists('Shortcuts');
$this->assertSession()->linkNotExists('Cron', 'Cron shortcut link not found on page.');
// Verify that users with the 'access shortcuts' permission can see the
// shortcuts.
$this->drupalLogin($this->drupalCreateUser([
'access toolbar', 'access shortcuts', 'administer site configuration',
]));
$this->clickLink('Shortcuts');
$this->assertSession()->linkExists('Cron', 0, 'Cron shortcut link found on page.');
$this->verifyAccessShortcutsPermissionForEditPages();
}
/**
* Tests the shortcuts are correctly ordered by weight in the toolbar.
*/
public function testShortcutLinkOrder(): void {
// Ensure to give permissions to access the shortcuts.
$this->drupalLogin($this->drupalCreateUser([
'access toolbar',
'access shortcuts',
'access content overview',
'administer content types',
]));
$this->drupalGet(Url::fromRoute('<front>'));
$shortcuts = $this->cssSelect('#toolbar-item-shortcuts-tray .toolbar-menu a');
$this->assertEquals('Add content', $shortcuts[0]->getText());
$this->assertEquals('All content', $shortcuts[1]->getText());
foreach ($this->set->getShortcuts() as $shortcut) {
$shortcut->setWeight($shortcut->getWeight() * -1)->save();
}
$this->drupalGet(Url::fromRoute('<front>'));
$shortcuts = $this->cssSelect('#toolbar-item-shortcuts-tray .toolbar-menu a');
$this->assertEquals('All content', $shortcuts[0]->getText());
$this->assertEquals('Add content', $shortcuts[1]->getText());
}
/**
* Tests the 'access shortcuts' permission for shortcut set administration.
*/
private function verifyAccessShortcutsPermissionForEditPages() {
// Create a user with customize links and switch sets permissions but
// without the 'access shortcuts' permission.
$test_permissions = [
'customize shortcut links',
'switch shortcut sets',
];
$no_access_user = $this->drupalCreateUser($test_permissions);
$this->drupalLogin($no_access_user);
// Verify that set administration pages are inaccessible without the
// 'access shortcuts' permission.
$this->drupalGet('admin/config/user-interface/shortcut/manage/default/customize');
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet('admin/config/user-interface/shortcut/manage/default');
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet('user/' . $no_access_user->id() . '/shortcuts');
$this->assertSession()->statusCodeEquals(403);
}
/**
* Tests the 'access shortcuts' permission with the shortcut block.
*/
public function testShortcutBlockAccess(): void {
// Creates a block instance and place in a region through api.
$block = $this->drupalPlaceBlock('shortcuts');
// Verify that users with the 'access shortcuts' permission can see the
// shortcut block.
$this->drupalLogin($this->shortcutUser);
$this->drupalGet('');
$this->assertBlockAppears($block);
$this->drupalLogout();
// Verify that users without the 'access shortcuts' permission can see the
// shortcut block.
$this->drupalLogin($this->drupalCreateUser([]));
$this->drupalGet('');
$this->assertNoBlockAppears($block);
}
/**
* Passes if a shortcut quick link with the specified label is found.
*
* An optional link index may be passed.
*
* @param string $label
* Text between the anchor tags.
* @param int $index
* Link position counting from zero.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use new FormattableMarkup() to embed variables in the message text, not
* t(). If left blank, a default message will be displayed.
*
* @internal
*/
protected function assertShortcutQuickLink(string $label, int $index = 0, string $message = ''): void {
$links = $this->xpath('//a[normalize-space()=:label]', [':label' => $label]);
$message = ($message ? $message : (string) new FormattableMarkup('Shortcut quick link with label %label found.', ['%label' => $label]));
$this->assertArrayHasKey($index, $links, $message);
}
}

View File

@@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\shortcut\Entity\ShortcutSet;
/**
* Create, view, edit, delete, and change shortcut sets.
*
* @group shortcut
*/
class ShortcutSetsTest extends ShortcutTestBase {
/**
* Modules to enable.
*
* @var string[]
*/
protected static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Tests creating a shortcut set.
*/
public function testShortcutSetAdd(): void {
$this->drupalGet('admin/config/user-interface/shortcut');
$this->clickLink('Add shortcut set');
$edit = [
'label' => $this->randomMachineName(),
'id' => $this->randomMachineName(),
];
$this->submitForm($edit, 'Save');
$new_set = $this->container->get('entity_type.manager')->getStorage('shortcut_set')->load($edit['id']);
$this->assertSame($edit['id'], $new_set->id(), 'Successfully created a shortcut set.');
$this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts');
// Verify that generated shortcut set was listed as a choice on the user
// account page.
$this->assertSession()->pageTextContains($new_set->label());
}
/**
* Tests editing a shortcut set.
*/
public function testShortcutSetEdit(): void {
$set = $this->set;
$shortcuts = $set->getShortcuts();
// Visit the shortcut set edit admin ui.
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/customize');
// Test for the page title.
$this->assertSession()->titleEquals('List links | Drupal');
// Test for the table.
$this->assertSession()->elementExists('xpath', '//div[@class="layout-content"]//table');
// Test the table header.
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/thead/tr/th', 3);
// Test the contents of each th cell.
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[1]', 'Name');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[2]', 'Weight');
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[3]', 'Operations');
// Look for test shortcuts in the table.
$weight = count($shortcuts);
$edit = [];
foreach ($shortcuts as $shortcut) {
$title = $shortcut->getTitle();
// Confirm that a link to the shortcut is found within the table.
$this->assertSession()->linkExists($title);
// Look for a test shortcut weight select form element.
$this->assertSession()->fieldExists('shortcuts[links][' . $shortcut->id() . '][weight]');
// Change the weight of the shortcut.
$edit['shortcuts[links][' . $shortcut->id() . '][weight]'] = $weight;
$weight--;
}
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("The shortcut set has been updated.");
\Drupal::entityTypeManager()->getStorage('shortcut')->resetCache();
// Check to ensure that the shortcut weights have changed and that
// ShortcutSet::.getShortcuts() returns shortcuts in the new order.
$this->assertSame(array_reverse(array_keys($shortcuts)), array_keys($set->getShortcuts()));
}
/**
* Tests switching a user's own shortcut set.
*/
public function testShortcutSetSwitchOwn(): void {
$new_set = $this->generateShortcutSet($this->randomMachineName());
// Attempt to switch the default shortcut set to the newly created shortcut
// set.
$this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts');
$this->submitForm(['set' => $new_set->id()], 'Change set');
$this->assertSession()->statusCodeEquals(200);
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$current_set = $shortcut_set_storage->getDisplayedToUser($this->adminUser);
$this->assertSame($current_set->id(), $new_set->id(), 'Successfully switched own shortcut set.');
}
/**
* Tests switching another user's shortcut set.
*/
public function testShortcutSetAssign(): void {
$new_set = $this->generateShortcutSet($this->randomMachineName());
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$shortcut_set_storage->assignUser($new_set, $this->shortcutUser);
$current_set = $shortcut_set_storage->getDisplayedToUser($this->shortcutUser);
$this->assertSame($current_set->id(), $new_set->id(), "Successfully switched another user's shortcut set.");
}
/**
* Tests switching a user's shortcut set and creating one at the same time.
*/
public function testShortcutSetSwitchCreate(): void {
$edit = [
'set' => 'new',
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
];
$this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts');
$this->submitForm($edit, 'Change set');
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$current_set = $shortcut_set_storage->getDisplayedToUser($this->adminUser);
$this->assertNotEquals($this->set->id(), $current_set->id(), 'A shortcut set can be switched to at the same time as it is created.');
$this->assertEquals($edit['label'], $current_set->label(), 'The new set is correctly assigned to the user.');
}
/**
* Tests switching a user's shortcut set without providing a new set name.
*/
public function testShortcutSetSwitchNoSetName(): void {
$edit = ['set' => 'new'];
$this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts');
$this->submitForm($edit, 'Change set');
$this->assertSession()->pageTextContains('The new set label is required.');
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$current_set = $shortcut_set_storage->getDisplayedToUser($this->adminUser);
$this->assertEquals($this->set->id(), $current_set->id(), 'Attempting to switch to a new shortcut set without providing a set name does not succeed.');
$field = $this->assertSession()->fieldExists('label');
$this->assertTrue($field->hasClass('error'));
}
/**
* Tests renaming a shortcut set.
*/
public function testShortcutSetRename(): void {
$set = $this->set;
$new_label = $this->randomMachineName();
$this->drupalGet('admin/config/user-interface/shortcut');
$this->clickLink('Edit shortcut set');
$this->submitForm(['label' => $new_label], 'Save');
$set = ShortcutSet::load($set->id());
$this->assertSame($new_label, $set->label(), 'Shortcut set has been successfully renamed.');
}
/**
* Tests un-assigning a shortcut set.
*/
public function testShortcutSetUnassign(): void {
$new_set = $this->generateShortcutSet($this->randomMachineName());
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$shortcut_set_storage->assignUser($new_set, $this->shortcutUser);
$shortcut_set_storage->unassignUser($this->shortcutUser);
$current_set = $shortcut_set_storage->getDisplayedToUser($this->shortcutUser);
$default_set = $shortcut_set_storage->getDefaultSet($this->shortcutUser);
$this->assertSame($default_set->id(), $current_set->id(), "Successfully unassigned another user's shortcut set.");
}
/**
* Tests assign clearing on user removal.
*/
public function testShortcutSetUnassignOnUserRemoval(): void {
$new_set = $this->generateShortcutSet($this->randomMachineName());
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$shortcut_set_storage->assignUser($new_set, $this->shortcutUser);
$this->shortcutUser->delete();
$current_set = $shortcut_set_storage->getDisplayedToUser($this->shortcutUser);
$default_set = $shortcut_set_storage->getDefaultSet($this->shortcutUser);
$this->assertSame($default_set->id(), $current_set->id(), "Successfully cleared assigned shortcut set for removed user.");
}
/**
* Tests deleting a shortcut set.
*/
public function testShortcutSetDelete(): void {
$new_set = $this->generateShortcutSet($this->randomMachineName());
$this->drupalGet('admin/config/user-interface/shortcut/manage/' . $new_set->id() . '/delete');
$this->submitForm([], 'Delete');
$sets = ShortcutSet::loadMultiple();
$this->assertFalse(isset($sets[$new_set->id()]), 'Successfully deleted a shortcut set.');
}
/**
* Tests deleting the default shortcut set.
*/
public function testShortcutSetDeleteDefault(): void {
$this->drupalGet('admin/config/user-interface/shortcut/manage/default/delete');
$this->assertSession()->statusCodeEquals(403);
}
/**
* Tests creating a new shortcut set with a defined set name.
*/
public function testShortcutSetCreateWithSetName(): void {
$random_name = $this->randomMachineName();
$new_set = $this->generateShortcutSet($random_name, $random_name);
$sets = ShortcutSet::loadMultiple();
$this->assertTrue(isset($sets[$random_name]), 'Successfully created a shortcut set with a defined set name.');
$this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts');
// Verify that generated shortcut set was listed as a choice on the user
// account page.
$this->assertSession()->pageTextContains($new_set->label());
}
}

View File

@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\shortcut\ShortcutSetInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Defines base class for shortcut test cases.
*/
abstract class ShortcutTestBase extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'toolbar', 'shortcut'];
/**
* User with permission to administer shortcuts.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* User with permission to use shortcuts, but not administer them.
*
* @var \Drupal\user\UserInterface
*/
protected $shortcutUser;
/**
* Generic node used for testing.
*
* @var \Drupal\node\NodeInterface
*/
protected $node;
/**
* Site-wide default shortcut set.
*
* @var \Drupal\shortcut\ShortcutSetInterface
*/
protected $set;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
if ($this->profile != 'standard') {
// Create Basic page and Article node types.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Populate the default shortcut set.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => t('Add content'),
'weight' => -20,
'link' => [
'uri' => 'internal:/node/add',
],
]);
$shortcut->save();
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => t('All content'),
'weight' => -19,
'link' => [
'uri' => 'internal:/admin/content',
],
]);
$shortcut->save();
}
// Create users.
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
'administer shortcuts',
'view the administration theme',
'create article content',
'create page content',
'access content overview',
'administer users',
'link to any page',
'edit any article content',
]);
$this->shortcutUser = $this->drupalCreateUser([
'customize shortcut links',
'switch shortcut sets',
'access shortcuts',
'access content',
]);
// Create a node.
$this->node = $this->drupalCreateNode(['type' => 'article']);
// Log in as admin and grab the default shortcut set.
$this->drupalLogin($this->adminUser);
$this->set = ShortcutSet::load('default');
\Drupal::entityTypeManager()->getStorage('shortcut_set')->assignUser($this->set, $this->adminUser);
}
/**
* Creates a generic shortcut set.
*/
public function generateShortcutSet($label = '', $id = NULL) {
$set = ShortcutSet::create([
'id' => $id ?? $this->randomMachineName(),
'label' => empty($label) ? $this->randomString() : $label,
]);
$set->save();
return $set;
}
/**
* Extracts information from shortcut set links.
*
* @param \Drupal\shortcut\ShortcutSetInterface $set
* The shortcut set object to extract information from.
* @param string $key
* The array key indicating what information to extract from each link:
* - 'title': Extract shortcut titles.
* - 'link': Extract shortcut paths.
* - 'id': Extract the shortcut ID.
*
* @return array
* Array of the requested information from each link.
*/
public function getShortcutInformation(ShortcutSetInterface $set, $key) {
$info = [];
\Drupal::entityTypeManager()->getStorage('shortcut')->resetCache();
foreach ($set->getShortcuts() as $shortcut) {
if ($key == 'link') {
$info[] = $shortcut->link->uri;
}
else {
$info[] = $shortcut->{$key}->value;
}
}
return $info;
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Functional;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Language\Language;
/**
* Tests the shortcut translation UI.
*
* @group Shortcut
*/
class ShortcutTranslationUITest extends ContentTranslationUITestBase {
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'user', 'url.path', 'url.query_args', 'url.site'];
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'language',
'content_translation',
'link',
'shortcut',
'toolbar',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->entityTypeId = 'shortcut';
$this->bundle = 'default';
parent::setUp();
$this->doSetup();
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), ['access shortcuts', 'administer shortcuts', 'access toolbar']);
}
/**
* {@inheritdoc}
*/
protected function createEntity($values, $langcode, $bundle_name = NULL) {
$values['link']['uri'] = 'internal:/user';
return parent::createEntity($values, $langcode, $bundle_name);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return ['title' => [['value' => $this->randomMachineName()]]] + parent::getNewEntityValues($langcode);
}
protected function doTestBasicTranslation() {
parent::doTestBasicTranslation();
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
if ($entity->hasTranslation($langcode)) {
$language = new Language(['id' => $langcode]);
// Request the front page in this language and assert that the right
// translation shows up in the shortcut list with the right path.
$this->drupalGet('<front>', ['language' => $language]);
$expected_path = \Drupal::urlGenerator()->generateFromRoute('user.page', [], ['language' => $language]);
$label = $entity->getTranslation($langcode)->label();
$this->assertSession()->elementExists('xpath', "//nav[contains(@class, 'toolbar-lining')]/ul[@class='toolbar-menu']/li/a[contains(@href, '{$expected_path}') and normalize-space(text())='{$label}']");
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = ['language' => $languages[$langcode]];
$url = $entity->toUrl('edit-form', $options);
$this->drupalGet($url);
$this->assertSession()->pageTextContains("{$entity->getTranslation($langcode)->label()} [{$languages[$langcode]->getName()} translation]");
}
}
}
/**
* Tests the basic translation workflow.
*/
protected function doTestTranslationChanged() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$this->assertFalse(
$entity instanceof EntityChangedInterface,
"$this->entityTypeId is not implementing EntityChangedInterface."
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Migrate;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
use Drupal\migrate_drupal\Tests\StubTestTrait;
/**
* Test stub creation for shortcut entities.
*
* @group shortcut
*/
class MigrateShortcutStubTest extends MigrateDrupalTestBase {
use StubTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['shortcut', 'link'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('shortcut');
// Make sure the 'default' shortcut_set is installed.
$this->installConfig(['shortcut']);
}
/**
* Tests creation of shortcut stubs.
*/
public function testStub(): void {
$this->performStubTest('shortcut');
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Migrate\d7;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\shortcut\ShortcutSetInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Test shortcut_set migration to ShortcutSet entities.
*
* @group shortcut
*/
class MigrateShortcutSetTest extends MigrateDrupal7TestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'link',
'field',
'shortcut',
'menu_link_content',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('shortcut');
$this->installEntitySchema('menu_link_content');
$this->executeMigration('d7_shortcut_set');
$this->executeMigration('d7_menu');
$this->executeMigration('d7_shortcut');
}
/**
* Tests the shortcut set migration.
*/
public function testShortcutSetMigration(): void {
$this->assertEntity('default', 'Default', 2);
$this->assertEntity('shortcut-set-2', 'Alternative shortcut set', 2);
}
/**
* Asserts various aspects of a shortcut set entity.
*
* @param string $id
* The expected shortcut set ID.
* @param string $label
* The expected shortcut set label.
* @param int $expected_size
* The number of shortcuts expected to be in the set.
*
* @internal
*/
protected function assertEntity(string $id, string $label, int $expected_size): void {
$shortcut_set = ShortcutSet::load($id);
$this->assertInstanceOf(ShortcutSetInterface::class, $shortcut_set);
/** @var \Drupal\shortcut\ShortcutSetInterface $shortcut_set */
$this->assertSame($id, $shortcut_set->id());
$this->assertSame($label, $shortcut_set->label());
// Check the number of shortcuts in the set.
$shortcuts = $shortcut_set->getShortcuts();
$this->assertCount($expected_size, $shortcuts);
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Migrate\d7;
use Drupal\user\Entity\User;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Test shortcut_set_users migration.
*
* @group shortcut
*/
class MigrateShortcutSetUsersTest extends MigrateDrupal7TestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'link',
'field',
'shortcut',
'menu_link_content',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('shortcut');
$this->installEntitySchema('menu_link_content');
$this->installSchema('shortcut', ['shortcut_set_users']);
$this->migrateUsers(FALSE);
$this->executeMigration('d7_shortcut_set');
$this->executeMigration('d7_menu');
$this->executeMigration('d7_shortcut');
$this->executeMigration('d7_shortcut_set_users');
}
/**
* Tests the shortcut set migration.
*/
public function testShortcutSetUsersMigration(): void {
// Check if migrated user has correct migrated shortcut set assigned.
$account = User::load(2);
$shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set');
$shortcut_set = $shortcut_set_storage->getDisplayedToUser($account);
/** @var \Drupal\shortcut\ShortcutSetInterface $shortcut_set */
$this->assertSame('shortcut-set-2', $shortcut_set->id());
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Migrate\d7;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\ShortcutInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Test shortcut menu links migration to Shortcut entities.
*
* @group shortcut
*/
class MigrateShortcutTest extends MigrateDrupal7TestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'link',
'field',
'shortcut',
'menu_link_content',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('shortcut');
$this->installEntitySchema('menu_link_content');
$this->executeMigration('d7_shortcut_set');
$this->executeMigration('d7_menu');
$this->executeMigration('d7_shortcut');
}
/**
* Asserts various aspects of a shortcut entity.
*
* @param int $id
* The shortcut ID.
* @param string $title
* The expected title of the shortcut.
* @param int $weight
* The expected weight of the shortcut.
* @param string $url
* The expected URL of the shortcut.
*
* @internal
*/
protected function assertEntity(int $id, string $title, int $weight, string $url): void {
$shortcut = Shortcut::load($id);
$this->assertInstanceOf(ShortcutInterface::class, $shortcut);
/** @var \Drupal\shortcut\ShortcutInterface $shortcut */
$this->assertSame($title, $shortcut->getTitle());
$this->assertSame($weight, (int) $shortcut->getWeight());
$this->assertSame($url, $shortcut->getUrl()->toString());
}
/**
* Tests the shortcut migration.
*/
public function testShortcutMigration(): void {
// Check if the 4 shortcuts were migrated correctly.
$this->assertEntity(1, 'Add content', -20, '/node/add');
$this->assertEntity(2, 'Find content', -19, '/admin/content');
$this->assertEntity(3, 'Help', -49, '/admin/help');
$this->assertEntity(4, 'People', -50, '/admin/people');
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 ShortcutSet source plugin.
*
* @covers Drupal\shortcut\Plugin\migrate\source\d7\ShortcutSet
*
* @group shortcut
*/
class ShortcutSetTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['shortcut', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['shortcut_set'] = [
[
'set_name' => 'shortcut-set-2',
'title' => 'Alternative shortcut set',
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set'];
return $tests;
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 ShortcutSetUsers source plugin.
*
* @covers Drupal\shortcut\Plugin\migrate\source\d7\ShortcutSetUsers
*
* @group shortcut
*/
class ShortcutSetUsersTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['shortcut', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['shortcut_set_users'] = [
[
'uid' => '2',
'set_name' => 'shortcut-set-2',
],
];
// The expected results are identical to the source data.
$tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set_users'];
return $tests;
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore mlid plid
/**
* Tests D7 Shortcut source plugin.
*
* @covers Drupal\shortcut\Plugin\migrate\source\d7\Shortcut
*
* @group shortcut
*/
class ShortcutTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['shortcut', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['menu_links'] = [
[
'menu_name' => 'shortcut-set-2',
'mlid' => '473',
'plid' => '0',
'link_path' => 'admin/people',
'router_path' => 'admin/people',
'link_title' => 'People',
'options' => 'a:0:{}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '-50',
'depth' => '1',
'customized' => '0',
'p1' => '473',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'mlid' => '473',
'menu_name' => 'shortcut-set-2',
'link_path' => 'admin/people',
'link_title' => 'People',
'weight' => '-50',
],
];
return $tests;
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests shortcut_install() and shortcut_uninstall().
*
* @group shortcut
*/
class ShortcutClaroIntegrationTest extends KernelTestBase {
protected static $modules = ['system'];
/**
* Tests shortcut_install() and shortcut_uninstall().
*/
public function testInstallUninstall(): void {
// Install claro.
\Drupal::service('theme_installer')->install(['claro']);
$this->assertNull($this->config('claro.settings')->get('third_party_settings.shortcut'), 'There are no shortcut settings in claro.settings.');
\Drupal::service('module_installer')->install(['shortcut']);
$this->assertTrue($this->config('claro.settings')->get('third_party_settings.shortcut.module_link'), 'The shortcut module_link setting is in claro.settings.');
\Drupal::service('module_installer')->uninstall(['shortcut']);
$this->assertNull($this->config('claro.settings')->get('third_party_settings.shortcut'), 'There are no shortcut settings in claro.settings.');
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Kernel;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
use Drupal\shortcut\Entity\ShortcutSet;
/**
* Tests validation of shortcut_set entities.
*
* @group shortcut
*/
class ShortcutSetValidationTest extends ConfigEntityValidationTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['link', 'shortcut'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('shortcut');
$this->installEntitySchema('shortcut');
$this->entity = ShortcutSet::create([
'id' => 'test-shortcut-set',
'label' => 'Test',
]);
$this->entity->save();
}
/**
* Shortcut set IDs are atypical: they allow dashes and disallow underscores.
*/
public static function providerInvalidMachineNameCharacters(): array {
$cases = parent::providerInvalidMachineNameCharacters();
// Remove the existing test case that verifies a machine name containing
// dashes is invalid.
self::assertSame(['dash-separated', FALSE], $cases['INVALID: dash separated']);
unset($cases['INVALID: dash separated']);
// And instead add a test case that verifies it is allowed for shortcut
// sets.
$cases['VALID: dash separated'] = ['dash-separated', TRUE];
// Remove the existing test case that verifies a machine name containing
// underscores is valid.
self::assertSame(['underscore_separated', TRUE], $cases['VALID: underscore separated']);
unset($cases['VALID: underscore separated']);
// And instead add a test case that verifies it is disallowed for shortcut
// sets.
$cases['INVALID: underscore separated'] = ['underscore_separated', FALSE];
return $cases;
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\shortcut\Unit\Menu;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
/**
* Tests existence of shortcut local tasks.
*
* @group shortcut
*/
class ShortcutLocalTasksTest extends LocalTaskIntegrationTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->directoryList = [
'shortcut' => 'core/modules/shortcut',
'user' => 'core/modules/user',
];
parent::setUp();
// Add services required for user local tasks.
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
$entity_type_manager->expects($this->any())
->method('getDefinitions')
->willReturn([]);
$this->container->set('entity_type.manager', $entity_type_manager);
$this->container->set('string_translation', $this->getStringTranslationStub());
}
/**
* Checks shortcut listing local tasks.
*
* @dataProvider getShortcutPageRoutes
*/
public function testShortcutPageLocalTasks($route): void {
$tasks = [
0 => ['shortcut.set_switch', 'entity.user.canonical', 'entity.user.edit_form'],
];
$this->assertLocalTasks($route, $tasks);
}
/**
* Provides a list of routes to test.
*/
public static function getShortcutPageRoutes() {
return [
['entity.user.canonical'],
['entity.user.edit_form'],
['shortcut.set_switch'],
];
}
}