Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023

This commit is contained in:
Pantheon Automation 2015-09-04 13:20:09 -07:00 committed by Greg Anderson
parent 2720a9ec4b
commit f3791f1da3
1898 changed files with 54300 additions and 11481 deletions

View file

@ -342,6 +342,17 @@ block.settings.system_menu_block:*:
type: integer
label: 'Maximum number of levels'
block.settings.local_tasks_block:
type: block_settings
label: 'Tabs block'
mapping:
primary:
type: boolean
label: 'Whether primary tabs are shown'
secondary:
type: boolean
label: 'Whether secondary tabs are shown'
condition.plugin.request_path:
type: condition.plugin
mapping:

View file

@ -0,0 +1,43 @@
/**
* @file
* Styles for link buttons and action links.
*/
.action-links {
list-style: none;
padding: 0;
margin: 1em 0;
}
[dir="rtl"] .action-links {
/* This is required to win over specificity of [dir="rtl"] ul */
margin-right: 0;
}
.action-links li {
display: inline-block;
margin: 0 0.3em;
}
.action-links li:first-child {
margin-left: 0; /* LTR */
}
[dir="rtl"] .action-links li:first-child {
margin-left: 0.3em;
margin-right: 0;
}
.button-action {
display: inline-block;
line-height: 160%;
padding: 0.2em 0.5em 0.3em;
text-decoration: none;
}
.button-action:before {
content: '+';
font-weight: 900;
margin-left: -0.1em; /* LTR */
padding-right: 0.2em; /* LTR */
}
[dir="rtl"] .button-action:before {
margin-left: 0;
margin-right: -0.1em;
padding-left: 0.2em;
padding-right: 0;
}

View file

@ -0,0 +1,45 @@
/**
* @file
* Throbber.
*/
.ajax-progress {
display: inline-block;
padding: 1px 5px 2px 5px;
}
[dir="rtl"] .ajax-progress {
float: right;
}
.ajax-progress-throbber .throbber {
background: transparent url(../../../../misc/throbber-active.gif) no-repeat 0px center;
display: inline;
padding: 1px 5px 2px;
}
.ajax-progress-throbber .message {
display: inline;
padding: 1px 5px 2px;
}
tr .ajax-progress-throbber .throbber {
margin: 0 2px;
}
.ajax-progress-bar {
width: 16em;
}
/* Full screen throbber */
.ajax-progress-fullscreen {
/* Can't do center:50% middle: 50%, so approximate it for a typical window size. */
left: 49%;
position: fixed;
top: 48.5%;
z-index: 1000;
background-color: #232323;
background-image: url(../../../../misc/loading-small.gif);
background-position: center center;
background-repeat: no-repeat;
border-radius: 7px;
height: 24px;
opacity: 0.9;
padding: 4px;
width: 24px;
}

View file

@ -0,0 +1,32 @@
/**
* @file
* Alignment classes for text and block level elements.
*/
.text-align-left {
text-align: left;
}
.text-align-right {
text-align: right;
}
.text-align-center {
text-align: center;
}
.text-align-justify {
text-align: justify;
}
/**
* Alignment classes for block level elements (images, videos, blockquotes, etc.)
*/
.align-left {
float: left;
}
.align-right {
float: right;
}
.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}

View file

@ -0,0 +1,22 @@
/**
* @file
* Visual styles for animated throbber.
*
* @see autocomplete.js
*/
.js input.form-autocomplete {
background-image: url(../../../../misc/throbber-inactive.png);
background-position: 100% center; /* LTR */
background-repeat: no-repeat;
}
.js[dir="rtl"] input.form-autocomplete {
background-position: 0% center;
}
.js input.form-autocomplete.ui-autocomplete-loading {
background-image: url(../../../../misc/throbber-active.gif);
background-position: 100% center; /* LTR */
}
.js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 0% center;
}

View file

@ -0,0 +1,29 @@
/**
* @file
* Styles for breadcrumbs.
*/
.breadcrumb {
padding-bottom: 0.5em;
}
.breadcrumb ol {
margin: 0;
padding: 0;
}
[dir="rtl"] .breadcrumb ol {
/* This is required to win over specificity of [dir="rtl"] ol */
margin-right: 0;
}
.breadcrumb li {
display: inline;
list-style-type: none;
margin: 0;
padding: 0;
}
/* IE8 does not support :not() and :last-child. */
.breadcrumb li:before {
content: ' \BB ';
}
.breadcrumb li:first-child:before {
content: none;
}

View file

@ -0,0 +1,15 @@
/**
* @file
* Visual styles for buttons.
*/
.button,
.image-button {
margin-left: 1em;
margin-right: 1em;
}
.button:first-child,
.image-button:first-child {
margin-left: 0;
margin-right: 0;
}

View file

@ -0,0 +1,15 @@
/**
* @file
* Float clearing.
*
* Based on the micro clearfix hack by Nicolas Gallagher, with the :before
* pseudo selector removed to allow normal top margin collapse.
*
* @see http://nicolasgallagher.com/micro-clearfix-hack
*/
.clearfix:after {
content: "";
display: table;
clear: both;
}

View file

@ -0,0 +1,32 @@
/**
* @file
* Visual styles for collapsible fieldsets.
*/
.collapse-processed > summary {
padding-left: 0.5em;
padding-right: 0.5em;
}
.collapse-processed > summary:before {
background: url(../../../../misc/menu-expanded.png) 0px 100% no-repeat; /* LTR */
content: "";
float: left;
height: 1em;
width: 1em;
}
[dir="rtl"] .collapse-processed > summary:before {
background-position: 100% 100%;
float: right;
}
.collapse-processed:not([open]) > summary:before {
background-position: 25% 35%; /* LTR */
-ms-transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
[dir="rtl"] .collapse-processed:not([open]) > summary:before {
background-position: 75% 35%;
-ms-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}

View file

@ -0,0 +1,13 @@
/**
* @file
* Inline items.
*/
.container-inline div,
.container-inline label {
display: inline;
}
/* Details contents always need to be rendered as block. */
.container-inline .details-wrapper {
display: block;
}

View file

@ -0,0 +1,20 @@
/**
* @file
* Inline items.
*/
.container-inline label:after,
.container-inline .label:after {
content: ':';
}
.form-type-radios .container-inline label:after {
content: '';
}
.form-type-radios .container-inline .form-type-radio {
margin: 0 1em;
}
.container-inline .form-actions,
.container-inline.form-actions {
margin-top: 0;
margin-bottom: 0;
}

View file

@ -0,0 +1,10 @@
/**
* @file
* Collapsible details.
*
* @see collapse.js
*/
.js details:not([open]) .details-wrapper {
display: none;
}

View file

@ -0,0 +1,23 @@
/**
* @file
* Collapsible details.
*
* @see collapse.js
* @see http://nicolasgallagher.com/css-background-image-hacks/
*/
details {
border: 1px solid #ccc;
margin-top: 1em;
margin-bottom: 1em;
}
details > .details-wrapper {
padding: 0.5em 1.5em;
}
/* @todo Regression: The summary of uncollapsible details are no longer
vertically aligned with the .details-wrapper in browsers without native
details support. */
summary {
cursor: pointer;
padding: 0.2em 0.5em;
}

View file

@ -0,0 +1,46 @@
/**
* @file
* Visual styles for exposed filters.
*/
.exposed-filters .filters {
float: left; /* LTR */
margin-right: 1em; /* LTR */
}
[dir="rtl"] .exposed-filters .filters {
float: right;
margin-left: 1em;
margin-right: 0;
}
.exposed-filters .form-item {
margin: 0 0 0.1em 0;
padding: 0;
}
.exposed-filters .form-item label {
float: left; /* LTR */
font-weight: normal;
width: 10em;
}
[dir="rtl"] .exposed-filters .form-item label {
float: right;
}
.exposed-filters .form-select {
width: 14em;
}
/* Current filters */
.exposed-filters .current-filters {
margin-bottom: 1em;
}
.exposed-filters .current-filters .placeholder {
font-style: normal;
font-weight: bold;
}
.exposed-filters .additional-filters {
float: left; /* LTR */
margin-right: 1em; /* LTR */
}
[dir="rtl"] .exposed-filters .additional-filters {
float: right;
margin-left: 1em;
margin-right: 0;
}

View file

@ -0,0 +1,25 @@
/**
* @file
* Visual styles for fields.
*/
.field__label {
font-weight: bold;
}
.field--label-inline .field__label,
.field--label-inline .field__items {
float: left; /*LTR*/
}
.field--label-inline .field__label,
.field--label-inline > .field__item,
.field--label-inline .field__items {
padding-right: 0.5em;
}
[dir="rtl"] .field--label-inline .field__label,
[dir="rtl"] .field--label-inline .field__items {
padding-left: 0.5em;
padding-right: 0;
}
.field--label-inline .field__label::after {
content: ':';
}

View file

@ -0,0 +1,9 @@
/**
* @file
* Fieldgroup border reset.
*/
.fieldgroup {
border-width: 0;
padding: 0;
}

View file

@ -0,0 +1,98 @@
/**
* @file
* Visual styles for form components.
*/
form .field-multiple-table {
margin: 0;
}
form .field-multiple-table .field-multiple-drag {
width: 30px;
padding-right: 0; /*LTR*/
}
[dir="rtl"] form .field-multiple-table .field-multiple-drag {
padding-left: 0;
}
form .field-multiple-table .field-multiple-drag .tabledrag-handle {
padding-right: .5em; /*LTR*/
}
[dir="rtl"] form .field-multiple-table .field-multiple-drag .tabledrag-handle {
padding-left: .5em;
}
form .field-add-more-submit {
margin: .5em 0 0;
}
/**
* Markup generated by Form API.
*/
.form-item,
.form-actions {
margin-top: 1em;
margin-bottom: 1em;
}
tr.odd .form-item,
tr.even .form-item {
margin-top: 0;
margin-bottom: 0;
}
.form-composite > .fieldset-wrapper > .description,
.form-item .description {
font-size: 0.85em;
}
label.option {
display: inline;
font-weight: normal;
}
.form-composite > legend,
.label {
display:inline;
font-size: inherit;
font-weight: bold;
margin: 0;
padding: 0;
}
.form-checkboxes .form-item,
.form-radios .form-item {
margin-top: 0.4em;
margin-bottom: 0.4em;
}
.form-type-radio .description,
.form-type-checkbox .description {
margin-left: 2.4em;
}
.marker {
color: #e00;
}
.form-required:after {
content: '';
vertical-align: super;
display: inline-block;
/* Use a background image to prevent screen readers from announcing the text. */
background-image: url(../../../../misc/icons/ee0000/required.svg);
background-repeat: no-repeat;
background-size: 6px 6px;
width: 6px;
height: 6px;
margin: 0 0.3em;
}
abbr.tabledrag-changed,
abbr.ajax-changed {
border-bottom: none;
}
.form-item input.error,
.form-item textarea.error,
.form-item select.error {
border: 2px solid red;
}
/* Inline error messages. */
.form-item--error-message:before {
content: '';
display: inline-block;
height: 14px;
width: 14px;
vertical-align: sub;
background: url(../../../../misc/icons/e32700/error.svg) no-repeat;
background-size: contain;
}

View file

@ -0,0 +1,53 @@
/**
* @file
* Utility classes to hide elements in different ways.
*/
/**
* Hide elements from all users.
*
* Used for elements which should not be immediately displayed to any user. An
* example would be collapsible details that will be expanded with a click
* from a user. The effect of this class can be toggled with the jQuery show()
* and hide() functions.
*/
.hidden {
display: none;
}
/**
* Hide elements visually, but keep them available for screen readers.
*
* Used for information required for screen reader users to understand and use
* the site where visual display is undesirable. Information provided in this
* manner should be kept concise, to avoid unnecessary burden on the user.
* "!important" is used to prevent unintentional overrides.
*/
.visually-hidden {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
height: 1px;
width: 1px;
word-wrap: normal;
}
/**
* The .focusable class extends the .visually-hidden class to allow
* the element to be focusable when navigated to via the keyboard.
*/
.visually-hidden.focusable:active,
.visually-hidden.focusable:focus {
position: static !important;
clip: auto;
overflow: visible;
height: auto;
width: auto;
}
/**
* Hide visually and from screen readers, but maintain layout.
*/
.invisible {
visibility: hidden;
}

View file

@ -0,0 +1,21 @@
/**
* @file
* Visual styles for icons.
*/
.icon-help {
background: url(../../../../misc/help.png) 0 50% no-repeat; /* LTR */
padding: 1px 0 1px 20px; /* LTR */
}
[dir="rtl"] .icon-help {
background-position: 100% 50%;
padding: 1px 20px 1px 0;
}
.feed-icon {
background: url(../../../../misc/feed.svg) no-repeat;
overflow: hidden;
text-indent: -9999px;
display: block;
width: 16px;
height: 16px;
}

View file

@ -0,0 +1,29 @@
/**
* @file
* Visual styles for inline forms.
*/
.form--inline .form-item {
float: left; /* LTR */
margin-right: 0.5em; /* LTR */
}
[dir="rtl"] .form--inline .form-item {
float: right;
margin-right: 0;
margin-left: 0.5em;
}
.form--inline .form-item-separator {
margin-top: 2.3em;
margin-right: 1em; /* LTR */
margin-left: 0.5em; /* LTR */
}
[dir="rtl"] .form--inline .form-item-separator {
margin-right: 0.5em;
margin-left: 1em;
}
.form--inline .form-actions {
clear: left; /* LTR */
}
[dir="rtl"] .form--inline .form-actions {
clear: right;
}

View file

@ -0,0 +1,39 @@
/**
* @file
* Visual styles for item list.
*/
.item-list .title {
font-weight: bold;
}
.item-list ul {
margin: 0 0 0.75em 0;
padding: 0;
}
.item-list ul li {
margin: 0 0 0.25em 1.5em; /* LTR */
padding: 0;
}
[dir="rtl"] .item-list ul li {
margin: 0 1.5em 0.25em 0;
}
ul.item-list__comma-list {
display: inline;
}
ul.item-list__comma-list li {
display: inline;
list-style-type: none;
}
ul.item-list__comma-list,
ul.item-list__comma-list li,
[dir="rtl"] ul.item-list__comma-list,
[dir="rtl"] ul.item-list__comma-list li {
margin: 0;
padding: 0;
}
ul.item-list__comma-list li:after {
content: ", ";
}
ul.item-list__comma-list li:last-child:after {
content: "";
}

View file

@ -0,0 +1,22 @@
/**
* @file
* Utility classes to assist with Javascript functionality.
*/
/**
* For anything you want to hide on page load when JS is enabled, so
* that you can use the JS to control visibility and avoid flicker.
*/
.js .js-hide {
display: none;
}
/**
* For anything you want to show on page load only when JS is enabled.
*/
.js-show {
display: none;
}
.js .js-show {
display: block;
}

View file

@ -0,0 +1,16 @@
/**
* @file
* Style another element as a link.
*/
button.link {
background: transparent;
border: 0;
cursor: pointer;
margin: 0;
padding: 0;
font-size: 1em;
}
label button.link {
font-weight: bold;
}

View file

@ -0,0 +1,18 @@
/**
* @file
* Visual styles for links.
*/
ul.inline,
ul.links.inline {
display: inline;
padding-left: 0;
}
ul.inline li {
display: inline;
list-style-type: none;
padding: 0 0.5em;
}
ul.links a.is-active {
color: #000;
}

View file

@ -0,0 +1,34 @@
/**
* @file
* Visual styles for menu.
*/
ul.menu {
list-style: none outside;
margin-left: 1em; /* LTR */
padding: 0;
text-align: left; /* LTR */
}
[dir="rtl"] ul.menu {
margin-left: 0;
margin-right: 1em;
text-align: right;
}
.menu-item--expanded {
list-style-image: url(../../../../misc/menu-expanded.png);
list-style-type: circle;
}
.menu-item--collapsed {
list-style-image: url(../../../../misc/menu-collapsed.png); /* LTR */
list-style-type: disc;
}
[dir="rtl"] .menu-item--collapsed {
list-style-image: url(../../../../misc/menu-collapsed-rtl.png);
}
.menu-item {
padding-top: 0.2em;
margin: 0;
}
ul.menu a.is-active {
color: #000;
}

View file

@ -0,0 +1,72 @@
/**
* @file
* Styles for system messages.
*/
.messages {
background: no-repeat 10px 17px; /* LTR */
border: 1px solid;
border-width: 1px 1px 1px 0; /* LTR */
border-radius: 2px;
padding: 15px 20px 15px 35px; /* LTR */
word-wrap: break-word;
overflow-wrap: break-word;
}
[dir="rtl"] .messages {
border-width: 1px 0 1px 1px;
background-position: right 10px top 17px;
padding-left: 20px;
padding-right: 35px;
text-align: right;
}
.messages + .messages {
margin-top: 1.538em;
}
.messages__list {
list-style: none;
padding: 0;
margin: 0;
}
.messages__item + .messages__item {
margin-top: 0.769em;
}
/* See .color-success in Seven's colors.css */
.messages--status {
color: #325e1c;
background-color: #f3faef;
border-color: #c9e1bd #c9e1bd #c9e1bd transparent; /* LTR */
background-image: url(../../../../misc/icons/73b355/check.svg);
box-shadow: -8px 0 0 #77b259; /* LTR */
}
[dir="rtl"] .messages--status {
border-color: #c9e1bd transparent #c9e1bd #c9e1bd;
box-shadow: 8px 0 0 #77b259;
margin-left: 0;
}
/* See .color-warning in Seven's colors.css */
.messages--warning {
background-color: #fdf8ed;
background-image: url(../../../../misc/icons/e29700/warning.svg);
border-color: #f4daa6 #f4daa6 #f4daa6 transparent; /* LTR */
color: #734c00;
box-shadow: -8px 0 0 #e09600; /* LTR */
}
[dir="rtl"] .messages--warning {
border-color: #f4daa6 transparent #f4daa6 #f4daa6;
box-shadow: 8px 0 0 #e09600;
}
/* See .color-error in Seven's colors.css */
.messages--error {
background-color: #fcf4f2;
color: #a51b00;
background-image: url(../../../../misc/icons/e32700/error.svg);
border-color: #f9c9bf #f9c9bf #f9c9bf transparent; /* LTR */
box-shadow: -8px 0 0 #e62600; /* LTR */
}
[dir="rtl"] .messages--error {
border-color: #f9c9bf transparent #f9c9bf #f9c9bf;
box-shadow: 8px 0 0 #e62600;
}
.messages--error p.error {
color: #a51b00;
}

View file

@ -0,0 +1,12 @@
/**
* @file
* Markup generated by #type 'more_link'.
*/
.more-link {
display: block;
text-align: right; /* LTR */
}
[dir="rtl"] .more-link {
text-align: left;
}

View file

@ -0,0 +1,8 @@
/**
* @file
* Utility class to prevent text wrapping.
*/
.nowrap {
white-space: nowrap;
}

View file

@ -0,0 +1,16 @@
/**
* @file
* Visual styles for pager.
*/
.pager__items {
clear: both;
text-align: center;
}
.pager__item {
display: inline;
padding: 0.5em;
}
.pager__item.is-active {
font-weight: bold;
}

View file

@ -0,0 +1,8 @@
/*
* @file
* Contain positioned elements.
*/
.position-container {
position: relative;
}

View file

@ -0,0 +1,50 @@
/**
* @file
* Progress behavior.
*
* @see progress.js
*/
.progress {
position: relative;
}
.progress__track {
background-color: #fff;
border: 1px solid;
margin-top: 5px;
max-width: 100%;
min-width: 100px;
height: 16px;
}
.progress__bar {
background-color: #000;
height: 1.5em;
min-width: 3%;
max-width: 100%;
}
.progress__description,
.progress__percentage {
color: #555;
overflow: hidden;
font-size: .875em;
margin-top: 0.2em;
}
.progress__description {
float: left; /* LTR */
}
[dir="rtl"] .progress__description {
float: right;
}
.progress__percentage {
float: right; /* LTR */
}
[dir="rtl"] .progress__percentage {
float: left;
}
.progress--small .progress__track {
height: 7px;
}
.progress--small .progress__bar {
height: 7px;
background-size: 20px 20px;
}

View file

@ -0,0 +1,64 @@
/**
* @file
* Visual styles for progress bar.
*
* @see progress.js
*/
.progress__track {
border-color: #b3b3b3;
border-radius: 10em;
background-color: #f2f1eb;
background-image: -webkit-linear-gradient(#e7e7df, #f0f0f0);
background-image: linear-gradient(#e7e7df, #f0f0f0);
box-shadow: inset 0 1px 3px hsla(0, 0%, 0%, 0.16);
}
.progress__bar {
border: 1px #07629a solid;
background: #057ec9;
background-image:
-webkit-linear-gradient( top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15) ),
-webkit-linear-gradient( left top,
#0094f0 0%,
#0094f0 25%,
#007ecc 25%,
#007ecc 50%,
#0094f0 50%,
#0094f0 75%,
#0094f0 100% );
background-image:
-webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)), -webkit-linear-gradient(left top, #0094f0 0%, #0094f0 25%, #007ecc 25%, #007ecc 50%, #0094f0 50%, #0094f0 75%, #0094f0 100%);
background-image:
linear-gradient( to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15) ),
linear-gradient( to right bottom,
#0094f0 0%,
#0094f0 25%,
#007ecc 25%,
#007ecc 50%,
#0094f0 50%,
#0094f0 75%,
#0094f0 100% );
background-size: 40px 40px;
margin-top: -1px;
margin-left: -1px;
padding: 0 1px;
height: 16px;
border-radius: 10em;
-webkit-animation: animate-stripes 3s linear infinite;
-moz-animation: animate-stripes 3s linear infinite;
-webkit-transition: width 0.5s ease-out;
transition: width 0.5s ease-out;
}
/**
* Progress bar animations.
*/
@-webkit-keyframes animate-stripes {
0% {background-position: 0 0, 0 0;} 100% {background-position: 0 0, -80px 0;}
}
@-ms-keyframes animate-stripes {
0% {background-position: 0 0, 0 0;} 100% {background-position: 0 0, -80px 0;}
}
@keyframes animate-stripes {
0% {background-position: 0 0, 0 0;} 100% {background-position: 0 0, -80px 0;}
}

View file

@ -0,0 +1,15 @@
/*
* @file
* Utility class to remove browser styles, especially for button.
*/
.reset-appearance {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: 0 none;
background: transparent;
padding: 0;
margin: 0;
line-height: inherit;
}

View file

@ -0,0 +1,21 @@
/**
* @file
* Resizable textareas.
*/
.resize-none {
resize: none;
}
.resize-vertical {
resize: vertical;
min-height: 2em;
}
.resize-horizontal {
resize: horizontal;
max-width: 100%;
}
.resize-both {
resize: both;
max-width: 100%;
min-height: 2em;
}

View file

@ -0,0 +1,13 @@
/**
* @file
* Table header behavior.
*
* @see tableheader.js
*/
table.sticky-header {
background-color: #fff;
margin-top: 0;
z-index: 500;
top: 0;
}

View file

@ -0,0 +1,85 @@
/**
* @file
* Table drag behavior.
*
* @see tabledrag.js
*/
body.drag {
cursor: move;
}
tr.region-title {
font-weight: bold;
}
tr.region-message {
color: #999;
}
tr.region-populated {
display: none;
}
tr.add-new .tabledrag-changed {
display: none;
}
.draggable a.tabledrag-handle {
cursor: move;
float: left; /* LTR */
height: 1.7em;
margin-left: -1em; /* LTR */
overflow: hidden;
text-decoration: none;
}
[dir="rtl"] .draggable a.tabledrag-handle {
float: right;
margin-right: -1em;
margin-left: 0;
}
a.tabledrag-handle:hover {
text-decoration: none;
}
a.tabledrag-handle .handle {
background: url(../../../../misc/icons/787878/move.svg) no-repeat 6px 7px;
height: 14px;
margin: -0.4em 0.5em 0;
padding: 0.42em 0.5em;
width: 14px;
}
a.tabledrag-handle:hover .handle,
a.tabledrag-handle:focus .handle {
background-image: url(../../../../misc/icons/000000/move.svg);
}
.touch .draggable td {
padding: 0 10px;
}
.touch .draggable .menu-item__link {
display: inline-block;
padding: 10px 0;
}
.touch a.tabledrag-handle {
height: 44px;
width: 40px;
}
.touch a.tabledrag-handle .handle {
background-position: 40% 19px;
height: 21px;
}
.touch .draggable.drag a.tabledrag-handle .handle {
background-position: 50% -32px;
}
.tabledrag-toggle-weight-wrapper {
text-align: right; /* LTR */
}
[dir="rtl"] .tabledrag-toggle-weight-wrapper {
text-align: left;
}
.indentation {
float: left; /* LTR */
height: 1.7em;
margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */
padding: 0.42em 0 0.42em 0.6em; /* LTR */
width: 20px;
}
[dir="rtl"] .indentation {
float: right;
margin: -0.4em -0.4em -0.4em 0.2em;
padding: 0.42em 0.6em 0.42em 0;
}

View file

@ -0,0 +1,14 @@
/**
* @file
* Visual styles for table drag.
*/
tr.drag {
background-color: #fffff0;
}
tr.drag-previous {
background-color: #ffd;
}
body div.tabledrag-changed-warning {
margin-bottom: 0.5em;
}

View file

@ -0,0 +1,14 @@
/**
* @file
* Table select behavior.
*
* @see tableselect.js
*/
tr.selected td {
background: #ffc;
}
td.checkbox,
th.checkbox {
text-align: center;
}

View file

@ -0,0 +1,11 @@
/**
* @file
* Table sort indicator.
*/
th.is-active img {
display: inline;
}
td.is-active {
background-color: #ddd;
}

View file

@ -0,0 +1,33 @@
/**
* @file
* Visual styles for tabs.
*/
div.tabs {
margin: 1em 0;
}
ul.tabs {
list-style: none;
margin: 0 0 0.5em;
padding: 0;
}
.tabs > li {
display: inline-block;
margin-right: 0.3em; /* LTR */
}
[dir="rtl"] .tabs > li {
margin-left: 0.3em;
margin-right: 0;
}
.tabs a {
display: block;
padding: 0.2em 1em;
text-decoration: none;
}
.tabs a.is-active {
background-color: #eee;
}
.tabs a:focus,
.tabs a:hover {
background-color: #f5f5f5;
}

View file

@ -0,0 +1,11 @@
/**
* @file
* Visual styles for a resizable textarea.
*/
.form-textarea-wrapper textarea {
display: block;
margin: 0;
width: 100%;
box-sizing: border-box;
}

View file

@ -0,0 +1,18 @@
/**
* @file
* Visual styles for a nested tree child.
*/
div.tree-child {
background: url(../../../../misc/tree.png) no-repeat 11px center; /* LTR */
}
div.tree-child-last {
background: url(../../../../misc/tree-bottom.png) no-repeat 11px center; /* LTR */
}
[dir="rtl"] div.tree-child,
[dir="rtl"] div.tree-child-last {
background-position: -65px center;
}
div.tree-child-horizontal {
background: url(../../../../misc/tree.png) no-repeat -11px center;
}

View file

@ -208,7 +208,7 @@ small .admin-link:after {
display: block;
}
.system-status-report__status-icon--error:before {
background-image: url(../../../misc/icons/ea2800/error.svg);
background-image: url(../../../misc/icons/e32700/error.svg);
}
.system-status-report__status-icon--warning:before {
background-image: url(../../../misc/icons/e29700/warning.svg);

View file

@ -1,44 +1,3 @@
/**
* Inline diff metadata
*/
.diff-inline-metadata {
padding:4px;
border:1px solid #ddd;
background:#fff;
margin:0 0 10px;
}
.diff-inline-legend {
font-size:11px;
}
.diff-inline-legend span,
.diff-inline-legend label {
margin-right:5px;
}
/**
* Inline diff markup
*/
span.diff-deleted {
color:#ccc;
}
span.diff-deleted img {
border: solid 2px #ccc;
}
span.diff-changed {
background:#ffb;
}
span.diff-changed img {
border:solid 2px #ffb;
}
span.diff-added {
background:#cfc;
}
span.diff-added img {
border: solid 2px #cfc;
}
/**
* Traditional split diff theming
*/
@ -48,25 +7,8 @@ table.diff {
table-layout: fixed;
width: 100%;
}
table.diff .even, table.diff .odd {
background-color: inherit;
border: none;
}
table.diff .diff-prevlink {
text-align: left;
}
table.diff .diff-nextlink {
text-align: right;
}
table.diff .diff-section-title,
table.diff .diff-section-title {
background-color: #f0f0ff;
font-size: 0.83em;
font-weight: bold;
padding: 0.1em 1em;
}
table.diff .diff-context {
background-color: #fafafa;
background-color: #fafafa;
}
table.diff .diff-deletedline {
background-color: #ffa;
@ -80,13 +22,9 @@ table.diff .diffchange {
color: #f00;
font-weight: bold;
}
table.diff .diff-marker {
width: 1.4em;
}
table.diff .diff-content {
width: 50%;
}
table.diff th {
padding-right: inherit;
}

View file

@ -13,6 +13,9 @@
* Display the preview for date format entered.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attach behavior for previewing date formats on input elements.
*/
Drupal.behaviors.dateFormat = {
attach: function (context) {
@ -30,6 +33,7 @@
* Event handler that replaces date characters with value.
*
* @param {jQuery.Event} e
* The jQuery event triggered.
*/
function dateFormatHandler(e) {
var baseValue = $(e.target).val() || '';

View file

@ -11,11 +11,16 @@
var ids = [];
/**
* When a field is filled out, apply its value to other fields that will likely
* use the same value. In the installer this is used to populate the
* Attaches field copy behavior from input fields to other input fields.
*
* When a field is filled out, apply its value to other fields that will
* likely use the same value. In the installer this is used to populate the
* administrator email address with the same value as the site email address.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the field copy behavior to an input field.
*/
Drupal.behaviors.copyFieldValue = {
attach: function (context) {
@ -60,9 +65,11 @@
/**
* Handler for a Blur event on a source field.
*
* This event handler will trigger a 'value:copy' event on all dependent fields.
* This event handler will trigger a 'value:copy' event on all dependent
* fields.
*
* @param {jQuery.Event} e
* The event triggered.
*/
valueSourceBlurHandler: function (e) {
var value = $(e.target).val();

View file

@ -0,0 +1,19 @@
id: d6_system_date
label: Drupal 6 system date configuration
migration_tags:
- Drupal 6
source:
plugin: variable
variables:
- configurable_timezones
- date_first_day
- date_default_timezone
process:
'timezone/user/configurable': configurable_timezones
first_day: date_first_day
'timezone/default':
plugin: timezone
source: date_default_timezone
destination:
plugin: config
config_name: system.date

View file

@ -0,0 +1,13 @@
id: menu
label: Menus
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: menu
process:
id: menu_name
label: title
description: description
destination:
plugin: entity:menu

View file

@ -9,7 +9,6 @@ namespace Drupal\system\Controller;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface;
@ -61,13 +60,6 @@ class DbUpdateController extends ControllerBase {
*/
protected $account;
/**
* The entity definition update manager.
*
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
*/
protected $entityDefinitionUpdateManager;
/**
* The bare HTML page renderer.
*
@ -97,19 +89,16 @@ class DbUpdateController extends ControllerBase {
* The module handler.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entity_definition_update_manager
* The entity definition update manager.
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
* The bare HTML page renderer.
*/
public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, BareHtmlPageRendererInterface $bare_html_page_renderer) {
public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer) {
$this->root = $root;
$this->keyValueExpirableFactory = $key_value_expirable_factory;
$this->cache = $cache;
$this->state = $state;
$this->moduleHandler = $module_handler;
$this->account = $account;
$this->entityDefinitionUpdateManager = $entity_definition_update_manager;
$this->bareHtmlPageRenderer = $bare_html_page_renderer;
}
@ -124,7 +113,6 @@ class DbUpdateController extends ControllerBase {
$container->get('state'),
$container->get('module_handler'),
$container->get('current_user'),
$container->get('entity.definition_update_manager'),
$container->get('bare_html_page_renderer')
);
}
@ -160,13 +148,13 @@ class DbUpdateController extends ControllerBase {
$severity = drupal_requirements_severity($requirements);
if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) {
$regions['sidebar_first'] = $this->updateTasksList('requirements');
$output = $this->requirements($severity, $requirements);
$output = $this->requirements($severity, $requirements, $request);
}
else {
switch ($op) {
case 'selection':
$regions['sidebar_first'] = $this->updateTasksList('selection');
$output = $this->selection();
$output = $this->selection($request);
break;
case 'run':
@ -176,12 +164,12 @@ class DbUpdateController extends ControllerBase {
case 'info':
$regions['sidebar_first'] = $this->updateTasksList('info');
$output = $this->info();
$output = $this->info($request);
break;
case 'results':
$regions['sidebar_first'] = $this->updateTasksList('results');
$output = $this->results();
$output = $this->results($request);
break;
// Regular batch ops : defer to batch processing API.
@ -204,10 +192,13 @@ class DbUpdateController extends ControllerBase {
/**
* Returns the info database update page.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return array
* A render array.
*/
protected function info() {
protected function info(Request $request) {
// Change query-strings on css/js files to enforce reload for all users.
_drupal_flush_css_js();
// Flush the cache of all data for the update status module.
@ -233,12 +224,12 @@ class DbUpdateController extends ControllerBase {
'#markup' => '<p>' . $this->t('When you have performed the steps above, you may proceed.') . '</p>',
);
$url = new Url('system.db_update', array('op' => 'selection'));
$build['link'] = array(
'#type' => 'link',
'#title' => $this->t('Continue'),
'#attributes' => array('class' => array('button', 'button--primary')),
'#url' => $url,
// @todo Revisit once https://www.drupal.org/node/2548095 is in.
'#url' => Url::fromUri('base://selection'),
);
return $build;
}
@ -246,10 +237,13 @@ class DbUpdateController extends ControllerBase {
/**
* Renders a list of available database updates.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return array
* A render array.
*/
protected function selection() {
protected function selection(Request $request) {
// Make sure there is no stale theme registry.
$this->cache->deleteAll();
@ -320,29 +314,12 @@ class DbUpdateController extends ControllerBase {
drupal_set_message($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'), 'warning');
}
// If there are entity definition updates, display their summary.
if ($this->entityDefinitionUpdateManager->needsUpdates()) {
$entity_build = array();
$summary = $this->entityDefinitionUpdateManager->getChangeSummary();
foreach ($summary as $entity_type_id => $items) {
$entity_update_key = 'entity_type_updates_' . $entity_type_id;
$entity_build[$entity_update_key] = array(
'#theme' => 'item_list',
'#items' => $items,
'#title' => $entity_type_id . ' entity type',
);
$count++;
}
// Display these above the module updates, since they will be run first.
$build['start'] = $entity_build + $build['start'];
}
if (empty($count)) {
drupal_set_message($this->t('No pending updates.'));
unset($build);
$build['links'] = array(
'#theme' => 'links',
'#links' => $this->helpfulLinks(),
'#links' => $this->helpfulLinks($request),
);
// No updates to run, so caches won't get flushed later. Clear them now.
@ -364,7 +341,9 @@ class DbUpdateController extends ControllerBase {
else {
$build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates');
}
$url = new Url('system.db_update', array('op' => 'run'));
// @todo Simplify with https://www.drupal.org/node/2548095
$base_url = str_replace('/update.php', '', $request->getBaseUrl());
$url = (new Url('system.db_update', array('op' => 'run')))->setOption('base_url', $base_url);
$build['link'] = array(
'#type' => 'link',
'#title' => $this->t('Apply pending updates'),
@ -380,15 +359,21 @@ class DbUpdateController extends ControllerBase {
/**
* Displays results of the update script with any accompanying errors.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return array
* A render array.
*/
protected function results() {
protected function results(Request $request) {
// @todo Simplify with https://www.drupal.org/node/2548095
$base_url = str_replace('/update.php', '', $request->getBaseUrl());
// Report end result.
$dblog_exists = $this->moduleHandler->moduleExists('dblog');
if ($dblog_exists && $this->account->hasPermission('access site reports')) {
$log_message = $this->t('All errors have been <a href="@url">logged</a>.', array(
'@url' => Url::fromRoute('dblog.overview')->toString(TRUE)->getGeneratedUrl(),
'@url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(),
));
}
else {
@ -396,7 +381,7 @@ class DbUpdateController extends ControllerBase {
}
if (!empty($_SESSION['update_success'])) {
$message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href="@url">site</a>. Otherwise, you may need to update your database manually.', array('@url' => Url::fromRoute('<front>')->toString(TRUE)->getGeneratedUrl())) . ' ' . $log_message . '</p>';
$message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href="@url">site</a>. Otherwise, you may need to update your database manually.', array('@url' => Url::fromRoute('<front>')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl())) . ' ' . $log_message . '</p>';
}
else {
$last = reset($_SESSION['updates_remaining']);
@ -420,7 +405,7 @@ class DbUpdateController extends ControllerBase {
);
$build['links'] = array(
'#theme' => 'links',
'#links' => $this->helpfulLinks(),
'#links' => $this->helpfulLinks($request),
);
// Output a list of info messages.
@ -492,12 +477,18 @@ class DbUpdateController extends ControllerBase {
/**
* Renders a list of requirement errors or warnings.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return array
* A render array.
*/
public function requirements($severity, array $requirements) {
public function requirements($severity, array $requirements, Request $request) {
$options = $severity == REQUIREMENT_WARNING ? array('continue' => 1) : array();
$try_again_url = Url::fromRoute('system.db_update', $options)->toString(TRUE)->getGeneratedUrl();
// @todo Revisit once https://www.drupal.org/node/2548095 is in. Something
// like Url::fromRoute('system.db_update')->setOptions() should then be
// possible.
$try_again_url = Url::fromUri($request->getUriForPath(''))->setOptions(['query' => $options])->toString(TRUE)->getGeneratedUrl();
$build['status_report'] = array(
'#theme' => 'status_report',
@ -544,13 +535,13 @@ class DbUpdateController extends ControllerBase {
* The current request object.
*/
protected function triggerBatch(Request $request) {
// During the update, bring the site offline so that schema changes do not
// affect visiting users.
$maintenance_mode = $this->config('system.maintenance')->get('enabled');
if (isset($maintenance_mode)) {
$_SESSION['maintenance_mode'] = $maintenance_mode;
}
if (empty($_SESSION['maintenance_mode'])) {
$maintenance_mode = $this->state->get('system.maintenance_mode', FALSE);
// Store the current maintenance mode status in the session so that it can
// be restored at the end of the batch.
$_SESSION['maintenance_mode'] = $maintenance_mode;
// During the update, always put the site into maintenance mode so that
// in-progress schema changes do not affect visiting users.
if (empty($maintenance_mode)) {
$this->state->set('system.maintenance_mode', TRUE);
}
@ -584,16 +575,6 @@ class DbUpdateController extends ControllerBase {
}
}
// Lastly, perform entity definition updates, which will update storage
// schema if needed. If module update functions need to work with specific
// entity schema they should call the entity update service for the specific
// update themselves.
// @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate()
// @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate()
if ($this->entityDefinitionUpdateManager->needsUpdates()) {
$operations[] = array('update_entity_definitions', array());
}
$batch['operations'] = $operations;
$batch += array(
'title' => $this->t('Updating'),
@ -603,7 +584,8 @@ class DbUpdateController extends ControllerBase {
);
batch_set($batch);
return batch_process('update.php/results', Url::fromRoute('system.db_update', array('op' => 'start')));
// @todo Revisit once https://www.drupal.org/node/2548095 is in.
return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start'));
}
/**
@ -630,28 +612,33 @@ class DbUpdateController extends ControllerBase {
$_SESSION['updates_remaining'] = $operations;
// Now that the update is done, we can put the site back online if it was
// previously in maintenance mode.
if (isset($_SESSION['maintenance_mode'])) {
// previously not in maintenance mode.
if (empty($_SESSION['maintenance_mode'])) {
\Drupal::state()->set('system.maintenance_mode', FALSE);
unset($_SESSION['maintenance_mode']);
}
unset($_SESSION['maintenance_mode']);
}
/**
* Provides links to the homepage and administration pages.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return array
* An array of links.
*/
protected function helpfulLinks() {
protected function helpfulLinks(Request $request) {
// @todo Simplify with https://www.drupal.org/node/2548095
$base_url = str_replace('/update.php', '', $request->getBaseUrl());
$links['front'] = array(
'title' => $this->t('Front page'),
'url' => Url::fromRoute('<front>'),
'url' => Url::fromRoute('<front>')->setOption('base_url', $base_url),
);
if ($this->account->hasPermission('access administration pages')) {
$links['admin-pages'] = array(
'title' => $this->t('Administration pages'),
'url' => Url::fromRoute('system.admin'),
'url' => Url::fromRoute('system.admin')->setOption('base_url', $base_url),
);
}
return $links;

View file

@ -68,7 +68,7 @@ class DateFormatListBuilder extends ConfigEntityListBuilder {
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $this->getLabel($entity);
$row['label'] = $entity->label();
$row['pattern'] = $this->dateFormatter->format(REQUEST_TIME, $entity->id());
return $row + parent::buildRow($entity);
}

View file

@ -130,12 +130,11 @@ abstract class DateFormatFormBase extends EntityForm {
parent::validateForm($form, $form_state);
// The machine name field should already check to see if the requested
// machine name is available. Regardless of machine_name or human readable
// name, check to see if the provided pattern exists.
// machine name is available.
$pattern = trim($form_state->getValue('date_format_pattern'));
foreach ($this->dateFormatStorage->loadMultiple() as $format) {
if ($format->getPattern() == $pattern && ($this->entity->isNew() || $format->id() != $this->entity->id())) {
$form_state->setErrorByName('date_format_pattern', $this->t('This format already exists. Enter a unique format string.'));
if ($format->getPattern() == $pattern && ($format->id() == $this->entity->id())) {
drupal_set_message(t('The existing format/name combination has not been altered.'));
continue;
}
}

View file

@ -7,7 +7,6 @@
namespace Drupal\system\Form;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\UnmetDependenciesException;
@ -169,7 +168,7 @@ class ModulesListForm extends FormBase {
*/
public function buildForm(array $form, FormStateInterface $form_state) {
require_once DRUPAL_ROOT . '/core/includes/install.inc';
$distribution = SafeMarkup::checkPlain(drupal_install_profile_distribution_name());
$distribution = drupal_install_profile_distribution_name();
// Include system.admin.inc so we can use the sort callbacks.
$this->moduleHandler->loadInclude('system', 'inc', 'system.admin');

View file

@ -9,6 +9,7 @@ namespace Drupal\system;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\TitleResolverInterface;
@ -125,6 +126,7 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
$breadcrumb = new Breadcrumb();
$links = array();
// General path-based breadcrumbs. Use the actual request path, prior to
@ -139,17 +141,21 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
// /user is just a redirect, so skip it.
// @todo Find a better way to deal with /user.
$exclude['/user'] = TRUE;
// Because this breadcrumb builder is entirely path-based, vary by the
// 'url.path' cache context.
$breadcrumb->setCacheContexts(['url.path']);
while (count($path_elements) > 1) {
array_pop($path_elements);
// Copy the path elements for up-casting.
$route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude);
if ($route_request) {
$route_match = RouteMatch::createFromRequest($route_request);
$access = $this->accessManager->check($route_match, $this->currentUser);
if ($access) {
$access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
// The set of breadcrumb links depends on the access result, so merge
// the access result's cacheability metadata.
$breadcrumb = $breadcrumb->addCacheableDependency($access);
if ($access->isAllowed()) {
$title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject());
}
if ($access) {
if (!isset($title)) {
// Fallback to using the raw path component as the title if the
// route is missing a _title or _title_callback attribute.
@ -165,7 +171,8 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
// Add the Home link, except for the front page.
$links[] = Link::createFromRoute($this->t('Home'), '<front>');
}
return array_reverse($links);
return $breadcrumb->setLinks(array_reverse($links));
}
/**

View file

@ -77,20 +77,13 @@ class SystemBreadcrumbBlock extends BlockBase implements ContainerFactoryPluginI
$breadcrumb = $this->breadcrumbManager->build($this->routeMatch);
if (!empty($breadcrumb)) {
// $breadcrumb is expected to be an array of rendered breadcrumb links.
return array(
$build = [
'#theme' => 'breadcrumb',
'#links' => $breadcrumb,
);
'#links' => $breadcrumb->getLinks(),
];
$breadcrumb->applyTo($build);
return $build;
}
}
/**
* {@inheritdoc}
*
* @todo Make cacheable in https://www.drupal.org/node/2483183
*/
public function getCacheMaxAge() {
return 0;
}
}

View file

@ -164,9 +164,7 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
// @todo Add a url.path cache context in
// https://www.drupal.org/node/2521978.
$contexts[] = 'url';
$contexts[] = 'url.path';
return $contexts;
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @file
* Contains \Drupal\system\Plugin\migrate\destination\EntityDateFormat.
*/
namespace Drupal\system\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityConfigBase;
/**
* @MigrateDestination(
* id = "entity:date_format"
* )
*/
class EntityDateFormat extends EntityConfigBase {
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Datetime\DateFormatInterface $entity
* The date entity.
*/
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
if ($parents[0] == 'pattern') {
$entity->setPattern($value);
}
else {
parent::updateEntityProperty($entity, $parents, $value);
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\systeml\Plugin\migrate\process\d6\TimeZone.
*/
namespace Drupal\system\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Process the D6 Timezone offset into a D8 compatible timezone name.
*
* @MigrateProcessPlugin(
* id = "timezone"
* )
*/
class TimeZone extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$offset = $value;
// Convert the integer value of the offset (which can be either
// negative or positive) to a timezone name.
// Note: Daylight saving time is not to be used.
$timezone_name = timezone_name_from_abbr('', intval($offset), 0);
if (!$timezone_name) {
$timezone_name = 'UTC';
}
return $timezone_name;
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\system\Plugin\migrate\source\Menu.
*/
namespace Drupal\system\Plugin\migrate\source;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Menu source from database.
*
* @MigrateSource(
* id = "menu",
* source_provider = "menu"
* )
*/
class Menu extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('menu_custom', 'm')->fields('m');
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'menu_name' => $this->t('The menu name. Primary key.'),
'title' => $this->t('The human-readable name of the menu.'),
'description' => $this->t('A description of the menu'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['menu_name']['type'] = 'string';
return $ids;
}
}

View file

@ -119,7 +119,7 @@ class SystemManager {
usort($requirements, function($a, $b) {
if (!isset($a['weight'])) {
if (!isset($b['weight'])) {
return strcmp($a['title'], $b['title']);
return strcasecmp($a['title'], $b['title']);
}
return -$b['weight'];
}

View file

@ -48,6 +48,9 @@ class FormValuesTest extends AjaxTestBase {
// Verify that AJAX elements with invalid callbacks return error code 500.
// Ensure the test error log is empty before these tests.
$this->assertNoErrorsLogged();
// We don't need to check for the X-Drupal-Ajax-Token header with these
// invalid requests.
$this->assertAjaxHeader = FALSE;
foreach (array('null', 'empty', 'nonexistent') as $key) {
$element_name = 'select_' . $key . '_callback';
$edit = array(
@ -56,6 +59,8 @@ class FormValuesTest extends AjaxTestBase {
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, $element_name);
$this->assertResponse(500);
}
// Switch this back to the default.
$this->assertAjaxHeader = TRUE;
// The exceptions are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');

View file

@ -165,7 +165,7 @@ class FrameworkTest extends AjaxTestBase {
// the first AJAX command.
$this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name'])));
$expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE);
$this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.'));
$this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), 'The settings command was first.');
// Verify the expected CSS file was added, both to drupalSettings, and as
// the second AJAX command for inclusion into the HTML.

View file

@ -60,7 +60,7 @@ class MultiFormTest extends AjaxTestBase {
$field_name = 'field_ajax_test';
$form_xpath = '//form[starts-with(@id, "node-page-form")]';
$field_xpath = '//div[contains(@class, "field-name-field-ajax-test")]';
$field_xpath = '//div[contains(@class, "field--name-field-ajax-test")]';
$button_name = $field_name . '_add_more';
$button_value = t('Add another item');
$button_xpath_suffix = '//input[@name="' . $button_name . '"]';

View file

@ -61,4 +61,19 @@ class PageTest extends WebTestBase {
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
}
/**
* Tests that the progress messages are correct.
*/
function testBatchProgressMessages() {
// Go to the initial step only.
$this->maximumMetaRefreshCount = 0;
$this->drupalGet('batch-test/test-title');
$this->assertRaw('<div class="progress__description">Initializing.<br />&nbsp;</div>', 'Initial progress message appears correctly.');
$this->assertNoRaw('&amp;nbsp;', 'Initial progress message is not double escaped.');
// Now also go to the next step.
$this->maximumMetaRefreshCount = 1;
$this->drupalGet('batch-test/test-title');
$this->assertRaw('<div class="progress__description">Completed 1 of 1.</div>', 'Progress message for second step appears correctly.');
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Batch;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
@ -21,7 +22,7 @@ class ProcessingTest extends WebTestBase {
*
* @var array
*/
public static $modules = array('batch_test');
public static $modules = array('batch_test', 'test_page_test');
/**
* Tests batches triggered outside of form submission.
@ -34,6 +35,18 @@ class ProcessingTest extends WebTestBase {
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
}
/**
* Tests batches that redirect in the batch finished callback.
*/
function testBatchRedirectFinishedCallback() {
// Displaying the page triggers batch 1.
$this->drupalGet('batch-test/finish-redirect');
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch for step 2 performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
$this->assertText('Test page text.', 'Custom redirection after batch execution displays the correct page.');
$this->assertUrl(Url::fromRoute('test_page_test.test_page'));
}
/**
* Tests batches defined in a form submit handler.
*/
@ -244,28 +257,28 @@ class ProcessingTest extends WebTestBase {
switch ($id) {
case 'batch_0':
$messages[] = 'results for batch 0<br>none';
$messages[] = 'results for batch 0<div class="item-list"><ul><li>none</li></ul></div>';
break;
case 'batch_1':
$messages[] = 'results for batch 1<br>op 1: processed 10 elements';
$messages[] = 'results for batch 1<div class="item-list"><ul><li>op 1: processed 10 elements</li></ul></div>';
break;
case 'batch_2':
$messages[] = 'results for batch 2<br>op 2: processed 10 elements';
$messages[] = 'results for batch 2<div class="item-list"><ul><li>op 2: processed 10 elements</li></ul></div>';
break;
case 'batch_3':
$messages[] = 'results for batch 3<br>op 1: processed 10 elements<br>op 2: processed 10 elements';
$messages[] = 'results for batch 3<div class="item-list"><ul><li>op 1: processed 10 elements</li><li>op 2: processed 10 elements</li></ul></div>';
break;
case 'batch_4':
$messages[] = 'results for batch 4<br>op 1: processed 10 elements';
$messages[] = 'results for batch 4<div class="item-list"><ul><li>op 1: processed 10 elements</li></ul></div>';
$messages = array_merge($messages, $this->_resultMessages('batch_2'));
break;
case 'batch_5':
$messages[] = 'results for batch 5<br>op 5: processed 10 elements';
$messages[] = 'results for batch 5<div class="item-list"><ul><li>op 5: processed 10 elements</li></ul></div>';
break;
case 'chained':

View file

@ -24,23 +24,30 @@ class DrupalSetMessageTest extends WebTestBase {
public static $modules = array('system_test');
/**
* Tests setting messages and removing one before it is displayed.
* Tests drupal_set_message().
*/
function testSetRemoveMessages() {
function testDrupalSetMessage() {
// The page at system-test/drupal-set-message sets two messages and then
// removes the first before it is displayed.
$this->drupalGet('system-test/drupal-set-message');
$this->assertNoText('First message (removed).');
$this->assertText('Second message (not removed).');
}
$this->assertRaw(t('Second message with <em>markup!</em> (not removed).'));
/**
* Tests setting duplicated messages.
*/
function testDuplicatedMessages() {
$this->drupalGet('system-test/drupal-set-message');
// Ensure duplicate messages are handled as expected.
$this->assertUniqueText('Non Duplicated message');
$this->assertNoUniqueText('Duplicated message');
// Ensure SafeString objects are rendered as expected.
$this->assertRaw('SafeString with <em>markup!</em>');
$this->assertUniqueText('SafeString with markup!');
$this->assertRaw('SafeString2 with <em>markup!</em>');
// Ensure when the same message is of different types it is not duplicated.
$this->assertUniqueText('Non duplicate SafeString / string.');
$this->assertNoUniqueText('Duplicate SafeString / string.');
// Ensure that strings that are not marked as safe are escaped.
$this->assertEscaped('<em>This<span>markup will be</span> escaped</em>.');
}
}

View file

@ -185,9 +185,9 @@ class AttachedAssetsTest extends KernelTestBase {
$rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
$this->assertTrue(
count($rendered_footer_js) == 2
&& substr($rendered_footer_js[0]['#value'], 0, 20) === 'var drupalSettings ='
&& $rendered_footer_js[0]['#attributes']['data-drupal-selector'] === 'drupal-settings-json'
&& substr($rendered_footer_js[1]['#attributes']['src'], 0, 7) === 'http://',
'There are 2 JavaScript assets in the footer: one with drupalSettings, one with the sole aggregated JavaScript asset.'
'There are 2 JavaScript assets in the footer: one with drupal settings, one with the sole aggregated JavaScript asset.'
);
}
@ -206,9 +206,9 @@ class AttachedAssetsTest extends KernelTestBase {
$rendered_js = $this->renderer->renderPlain($js_render_array);
// Parse the generated drupalSettings <script> back to a PHP representation.
$startToken = 'drupalSettings = ';
$startToken = '{';
$endToken = '}';
$start = strpos($rendered_js, $startToken) + strlen($startToken);
$start = strpos($rendered_js, $startToken);
$end = strrpos($rendered_js, $endToken);
$json = Unicode::substr($rendered_js, $start, $end - $start + 1);
$parsed_settings = Json::decode($json);

View file

@ -7,7 +7,7 @@
namespace Drupal\system\Tests\Common;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\simpletest\KernelTestBase;
@ -46,12 +46,12 @@ class RenderElementTypesTest extends KernelTestBase {
$actual_html = (string) \Drupal::service('renderer')->renderRoot($elements);
$out = '<table><tr>';
$out .= '<td valign="top"><pre>' . SafeMarkup::checkPlain($expected_html) . '</pre></td>';
$out .= '<td valign="top"><pre>' . SafeMarkup::checkPlain($actual_html) . '</pre></td>';
$out .= '<td valign="top"><pre>' . Html::escape($expected_html) . '</pre></td>';
$out .= '<td valign="top"><pre>' . Html::escape($actual_html) . '</pre></td>';
$out .= '</tr></table>';
$this->verbose($out);
$this->assertIdentical($actual_html, $expected_html, SafeMarkup::checkPlain($message));
$this->assertIdentical($actual_html, $expected_html, Html::escape($message));
}
/**
@ -91,8 +91,6 @@ class RenderElementTypesTest extends KernelTestBase {
'#type' => 'html_tag',
'#tag' => 'meta',
'#value' => 'ignored',
'#value_prefix' => 'ignored',
'#value_suffix' => 'ignored',
'#attributes' => array(
'name' => 'description',
'content' => 'Drupal test',
@ -104,12 +102,10 @@ class RenderElementTypesTest extends KernelTestBase {
'#type' => 'html_tag',
'#tag' => 'section',
'#value' => 'value',
'#value_prefix' => 'value_prefix|',
'#value_suffix' => '|value_suffix',
'#attributes' => array(
'class' => array('unicorns'),
),
), '<section class="unicorns">value_prefix|value|value_suffix</section>' . "\n", "#type 'html_tag', non-void element renders properly");
), '<section class="unicorns">value</section>' . "\n", "#type 'html_tag', non-void element renders properly");
// Test empty void element tag.
$this->assertElements(array(

View file

@ -7,6 +7,8 @@
namespace Drupal\system\Tests\Common;
use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
@ -24,6 +26,24 @@ class RenderWebTest extends WebTestBase {
*/
public static $modules = array('common_test');
/**
* Asserts the cache context for the wrapper format is always present.
*/
function testWrapperFormatCacheContext() {
$this->drupalGet('common-test/type-link-active-class');
$this->assertIdentical(0, strpos($this->getRawContent(), "<!DOCTYPE html>\n<html"));
$this->assertIdentical('text/html; charset=UTF-8', $this->drupalGetHeader('Content-Type'));
$this->assertTitle('Test active link class | Drupal');
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
$this->drupalGet('common-test/type-link-active-class', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'json']]);
$this->assertIdentical('application/json', $this->drupalGetHeader('Content-Type'));
$json = Json::decode($this->getRawContent());
$this->assertEqual(['content', 'title'], array_keys($json));
$this->assertIdentical('Test active link class', $json['title']);
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
}
/**
* Tests rendering form elements without passing through
* \Drupal::formBuilder()->doBuildForm().

View file

@ -44,7 +44,7 @@ class SimpleTestErrorCollectorTest extends WebTestBase {
if (count($this->collectedErrors) == 3) {
$this->assertError($this->collectedErrors[0], 'Notice', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Undefined variable: bananas');
$this->assertError($this->collectedErrors[1], 'Warning', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Division by zero');
$this->assertError($this->collectedErrors[2], 'User warning', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Drupal is awesome');
$this->assertError($this->collectedErrors[2], 'User warning', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Drupal &amp; awesome');
}
else {
// Give back the errors to the log report.

View file

@ -7,7 +7,7 @@
namespace Drupal\system\Tests\Common;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
@ -38,7 +38,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
$request->query->replace(array());
\Drupal::getContainer()->get('request_stack')->push($request);
$ts = tablesort_init($headers);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Simple table headers sorted correctly.');
// Test with simple table headers plus $_GET parameters that should _not_
@ -51,7 +51,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
));
\Drupal::getContainer()->get('request_stack')->push($request);
$ts = tablesort_init($headers);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Simple table headers plus non-overriding $_GET parameters sorted correctly.');
// Test with simple table headers plus $_GET parameters that _should_
@ -67,7 +67,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
$expected_ts['sort'] = 'desc';
$expected_ts['query'] = array('alpha' => 'beta');
$ts = tablesort_init($headers);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Simple table headers plus $_GET parameters sorted correctly.');
// Test complex table headers.
@ -99,7 +99,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
'sort' => 'desc',
'query' => array(),
);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Complex table headers sorted correctly.');
// Test complex table headers plus $_GET parameters that should _not_
@ -118,7 +118,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
'sort' => 'asc',
'query' => array(),
);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Complex table headers plus non-overriding $_GET parameters sorted correctly.');
// Test complex table headers plus $_GET parameters that _should_
@ -139,7 +139,7 @@ class TableSortExtenderUnitTest extends KernelTestBase {
'query' => array('alpha' => 'beta'),
);
$ts = tablesort_init($headers);
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => Html::escape(var_export($ts, TRUE)))));
$this->assertEqual($ts, $expected_ts, 'Complex table headers plus $_GET parameters sorted correctly.');
}
}

View file

@ -33,14 +33,14 @@ class UrlTest extends WebTestBase {
// Test \Drupal::l().
$text = $this->randomMachineName();
$path = "<SCRIPT>alert('XSS')</SCRIPT>";
$encoded_path = "3CSCRIPT%3Ealert%28%27XSS%27%29%3C/SCRIPT%3E";
$link = \Drupal::l($text, Url::fromUserInput('/' . $path));
$sanitized_path = check_url(Url::fromUri('base:' . $path)->toString());
$this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by _l().', array('@path' => $path)));
$this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by _l().', array('@path' => $path)));
// Test \Drupal\Core\Url.
$link = Url::fromUri('base:' . $path)->toString();
$sanitized_path = check_url(Url::fromUri('base:' . $path)->toString());
$this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
$this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
}
/**

View file

@ -56,6 +56,8 @@ class XssUnitTest extends KernelTestBase {
$expected_plain = 'http://www.example.com/?x=1&y=2';
$expected_html = 'http://www.example.com/?x=1&amp;y=2';
$this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
$this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\Url::stripDangerousProtocols() filters a URL and returns plain text.');
$this->assertIdentical(UrlHelper::filterBadProtocol($url), $expected_html, '\Drupal\Component\Utility\UrlHelper::filterBadProtocol() filters a URL and encodes it for HTML.');
$this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() filters a URL and returns plain text.');
}
}

View file

@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\SchemaException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\simpletest\KernelTestBase;
@ -99,7 +100,7 @@ class SchemaTest extends KernelTestBase {
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, FALSE, 'Fake index does not exists');
// Add index.
db_add_index('test_table', 'test_field', array('test_field'));
db_add_index('test_table', 'test_field', array('test_field'), $table_specification);
// Test for created index and test for the boolean result of indexExists().
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, TRUE, 'Index created.');
@ -295,6 +296,43 @@ class SchemaTest extends KernelTestBase {
);
db_create_table('test_table_index_length', $table_specification);
$schema_object = Database::getConnection()->schema();
// Ensure expected exception thrown when adding index with missing info.
$expected_exception_message = "MySQL needs the 'test_field_text' field specification in order to normalize the 'test_regular' index";
$missing_field_spec = $table_specification;
unset($missing_field_spec['fields']['test_field_text']);
try {
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $missing_field_spec);
$this->fail('SchemaException not thrown when adding index with missing information.');
}
catch (SchemaException $e) {
$this->assertEqual($expected_exception_message, $e->getMessage());
}
// Add a separate index.
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $table_specification);
$table_specification_with_new_index = $table_specification;
$table_specification_with_new_index['indexes']['test_separate'] = [['test_field_text', 200]];
// Ensure that the exceptions of addIndex are thrown as expected.
try {
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $table_specification);
$this->fail('\Drupal\Core\Database\SchemaObjectExistsException exception missed.');
}
catch (SchemaObjectExistsException $e) {
$this->pass('\Drupal\Core\Database\SchemaObjectExistsException thrown when index already exists.');
}
try {
$schema_object->addIndex('test_table_non_existing', 'test_separate', [['test_field_text', 200]], $table_specification);
$this->fail('\Drupal\Core\Database\SchemaObjectDoesNotExistException exception missed.');
}
catch (SchemaObjectDoesNotExistException $e) {
$this->pass('\Drupal\Core\Database\SchemaObjectDoesNotExistException thrown when index already exists.');
}
// Get index information.
$results = db_query('SHOW INDEX FROM {test_table_index_length}');
$expected_lengths = array(
@ -316,11 +354,14 @@ class SchemaTest extends KernelTestBase {
'test_field_string_ascii_long' => 200,
'test_field_string_short' => NULL,
),
'test_separate' => array(
'test_field_text' => 191,
),
);
// Count the number of columns defined in the indexes.
$column_count = 0;
foreach ($table_specification['indexes'] as $index) {
foreach ($table_specification_with_new_index['indexes'] as $index) {
foreach ($index as $field) {
$column_count++;
}
@ -657,4 +698,63 @@ class SchemaTest extends KernelTestBase {
// Clean-up.
db_drop_table($table_name);
}
/**
* Tests the findTables() method.
*/
public function testFindTables() {
// We will be testing with three tables, two of them using the default
// prefix and the third one with an individually specified prefix.
// Set up a new connection with different connection info.
$connection_info = Database::getConnectionInfo();
// Add per-table prefix to the second table.
$new_connection_info = $connection_info['default'];
$new_connection_info['prefix']['test_2_table'] = $new_connection_info['prefix']['default'] . '_shared_';
Database::addConnectionInfo('test', 'default', $new_connection_info);
Database::setActiveConnection('test');
// Create the tables.
$table_specification = [
'description' => 'Test table.',
'fields' => [
'id' => [
'type' => 'int',
'default' => NULL,
],
],
];
Database::getConnection()->schema()->createTable('test_1_table', $table_specification);
Database::getConnection()->schema()->createTable('test_2_table', $table_specification);
Database::getConnection()->schema()->createTable('the_third_table', $table_specification);
// Check the "all tables" syntax.
$tables = Database::getConnection()->schema()->findTables('%');
sort($tables);
$expected = [
// The 'config' table is added by
// \Drupal\simpletest\KernelTestBase::containerBuild().
'config',
'test_1_table',
// This table uses a per-table prefix, yet it is returned as un-prefixed.
'test_2_table',
'the_third_table',
];
$this->assertEqual($tables, $expected, 'All tables were found.');
// Check the restrictive syntax.
$tables = Database::getConnection()->schema()->findTables('test_%');
sort($tables);
$expected = [
'test_1_table',
'test_2_table',
];
$this->assertEqual($tables, $expected, 'Two tables were found.');
// Go back to the initial connection.
Database::setActiveConnection('default');
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Database;
use Drupal\Core\Database\InvalidQueryException;
use Drupal\Core\Database\Database;
/**
* Tests the Select query builder.
@ -57,10 +58,47 @@ class SelectTest extends DatabaseTestBase {
$records = $result->fetchAll();
$query = (string) $query;
$expected = "/* Testing query comments SELECT nid FROM {node}; -- */";
$expected = "/* Testing query comments * / SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
$this->assertEqual(count($records), 4, 'Returned the correct number of rows.');
$this->assertNotIdentical(FALSE, strpos($query, $expected), 'The flattened query contains the sanitised comment string.');
$connection = Database::getConnection();
foreach ($this->makeCommentsProvider() as $test_set) {
list($expected, $comments) = $test_set;
$this->assertEqual($expected, $connection->makeComment($comments));
}
}
/**
* Provides expected and input values for testVulnerableComment().
*/
function makeCommentsProvider() {
return [
[
'/* */ ',
[''],
],
// Try and close the comment early.
[
'/* Exploit * / DROP TABLE node; -- */ ',
['Exploit */ DROP TABLE node; --'],
],
// Variations on comment closing.
[
'/* Exploit * / * / DROP TABLE node; -- */ ',
['Exploit */*/ DROP TABLE node; --'],
],
[
'/* Exploit * * // DROP TABLE node; -- */ ',
['Exploit **// DROP TABLE node; --'],
],
// Try closing the comment in the second string which is appended.
[
'/* Exploit * / DROP TABLE node; --; Another try * / DROP TABLE node; -- */ ',
['Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'],
],
];
}
/**

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Database\UpsertTest.
*/
namespace Drupal\system\Tests\Database;
use Drupal\Core\Database\Database;
/**
* Tests the Upsert query builder.
*
* @group Database
*/
class UpsertTest extends DatabaseTestBase {
/**
* Confirms that we can upsert (update-or-insert) records successfully.
*/
public function testUpsert() {
$connection = Database::getConnection();
$num_records_before = $connection->query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$upsert = $connection->upsert('test_people')
->key('job')
->fields(['job', 'age', 'name']);
// Add a new row.
$upsert->values([
'job' => 'Presenter',
'age' => 31,
'name' => 'Tiffany',
]);
// Update an existing row.
$upsert->values([
'job' => 'Speaker',
// The initial age was 30.
'age' => 32,
'name' => 'Meredith',
]);
$upsert->execute();
$num_records_after = $connection->query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, 'Rows were inserted and updated properly.');
$person = $connection->query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
$this->assertEqual($person->age, 31, 'Age set correctly.');
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$person = $connection->query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->job, 'Speaker', 'Job was not changed.');
$this->assertEqual($person->age, 32, 'Age updated correctly.');
$this->assertEqual($person->name, 'Meredith', 'Name was not changed.');
}
}

View file

@ -55,13 +55,11 @@ class DrupalKernelTest extends KernelTestBase {
* A request object to use in booting the kernel.
* @param array $modules_enabled
* A list of modules to enable on the kernel.
* @param bool $read_only
* Build the kernel in a read only state.
*
* @return \Drupal\Core\DrupalKernel
* New kernel for testing.
*/
protected function getTestKernel(Request $request, array $modules_enabled = NULL, $read_only = FALSE) {
protected function getTestKernel(Request $request, array $modules_enabled = NULL) {
// Manually create kernel to avoid replacing settings.
$class_loader = require DRUPAL_ROOT . '/autoload.php';
$kernel = DrupalKernel::createFromRequest($request, $class_loader, 'testing');
@ -72,11 +70,6 @@ class DrupalKernelTest extends KernelTestBase {
}
$kernel->boot();
if ($read_only) {
$php_storage = Settings::get('php_storage');
$php_storage['service_container']['class'] = 'Drupal\Component\PhpStorage\FileReadOnlyStorage';
$this->settingsSet('php_storage', $php_storage);
}
return $kernel;
}
@ -98,24 +91,19 @@ class DrupalKernelTest extends KernelTestBase {
$kernel = $this->getTestKernel($request);
$container = $kernel->getContainer();
$refClass = new \ReflectionClass($container);
$is_compiled_container =
$refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$is_compiled_container = !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// Verify that the list of modules is the same for the initial and the
// compiled container.
$module_list = array_keys($container->get('module_handler')->getModuleList());
$this->assertEqual(array_values($modules_enabled), $module_list);
// Now use the read-only storage implementation, simulating a "production"
// environment.
$container = $this->getTestKernel($request, NULL, TRUE)
// Get the container another time, simulating a "production" environment.
$container = $this->getTestKernel($request, NULL)
->getContainer();
$refClass = new \ReflectionClass($container);
$is_compiled_container =
$refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$is_compiled_container = !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// Verify that the list of modules is the same for the initial and the
@ -137,16 +125,16 @@ class DrupalKernelTest extends KernelTestBase {
// Add another module so that we can test that the new module's bundle is
// registered to the new container.
$modules_enabled['service_provider_test'] = 'service_provider_test';
$this->getTestKernel($request, $modules_enabled, TRUE);
$this->getTestKernel($request, $modules_enabled);
// Instantiate it a second time and we should still get a ContainerBuilder
// class because we are using the read-only PHP storage.
$kernel = $this->getTestKernel($request, $modules_enabled, TRUE);
// Instantiate it a second time and we should not get a ContainerBuilder
// class because we are loading the container definition from cache.
$kernel = $this->getTestKernel($request, $modules_enabled);
$container = $kernel->getContainer();
$refClass = new \ReflectionClass($container);
$is_container_builder = $refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_container_builder, 'Container is a builder');
$this->assertFalse($is_container_builder, 'Container is not a builder');
// Assert that the new module's bundle was registered to the new container.
$this->assertTrue($container->has('service_provider_test_class'), 'Container has test service');

View file

@ -9,7 +9,7 @@ namespace Drupal\system\Tests\Entity;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Site\Settings;
use Drupal\system\Controller\EntityAutocompleteController;
@ -65,8 +65,8 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
// We should get both entities in a JSON encoded string.
$input = '10/';
$data = $this->getAutocompleteResult($input);
$this->assertIdentical($data[0]['label'], SafeMarkup::checkPlain($entity_1->name->value), 'Autocomplete returned the first matching entity');
$this->assertIdentical($data[1]['label'], SafeMarkup::checkPlain($entity_2->name->value), 'Autocomplete returned the second matching entity');
$this->assertIdentical($data[0]['label'], Html::escape($entity_1->name->value), 'Autocomplete returned the first matching entity');
$this->assertIdentical($data[1]['label'], Html::escape($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label that matches the first entity.
// We should only get the first entity in a JSON encoded string.
@ -74,7 +74,7 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
$data = $this->getAutocompleteResult($input);
$target = array(
'value' => $entity_1->name->value . ' (1)',
'label' => SafeMarkup::checkPlain($entity_1->name->value),
'label' => Html::escape($entity_1->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns only the expected matching entity.');
@ -82,7 +82,7 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
// the first entity is already typed in the autocomplete (tags) widget.
$input = $entity_1->name->value . ' (1), 10/17';
$data = $this->getAutocompleteResult($input);
$this->assertIdentical($data[0]['label'], SafeMarkup::checkPlain($entity_2->name->value), 'Autocomplete returned the second matching entity');
$this->assertIdentical($data[0]['label'], Html::escape($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label with both a comma and a slash.
$input = '"label with, and / t';
@ -92,7 +92,7 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
$n = Tags::encode($n);
$target = array(
'value' => $n,
'label' => SafeMarkup::checkPlain($entity_3->name->value),
'label' => Html::escape($entity_3->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns an entity label containing a comma and a slash.');
}

View file

@ -9,6 +9,7 @@ namespace Drupal\system\Tests\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
@ -337,6 +338,7 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
// The default cache contexts for rendered entities.
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$entity_cache_contexts = $default_cache_contexts;
$page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
// Cache tags present on every rendered page.
// 'user.permissions' is a required cache context, and responses that vary
@ -428,7 +430,7 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
$this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->pass("Test listing containing referenced entity.", 'Debug');
@ -438,7 +440,7 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
// Verify that after modifying the referenced entity, there is a cache miss
@ -509,13 +511,12 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
}
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
if ($bundle_entity_type !== 'bundle') {
if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
// Verify that after modifying the corresponding bundle entity, there is a
// cache miss for both the referencing entity, and the listing of
// referencing entities, but not for any other routes.
$this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
$bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
$bundle_entity = entity_load($bundle_entity_type_id, $this->entity->bundle());
$bundle_entity->save();
$this->verifyPageCache($referencing_entity_url, 'MISS');
$this->verifyPageCache($listing_url, 'MISS');

View file

@ -125,6 +125,17 @@ trait EntityDefinitionTestTrait {
$this->addBaseField('text');
}
/**
* Promotes a field to an entity key.
*/
protected function makeBaseFieldEntityKey() {
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
$entity_keys = $entity_type->getKeys();
$entity_keys['new_base_field'] = 'new_base_field';
$entity_type->set('entity_keys', $entity_keys);
$this->state->set('entity_test_update.entity_type', $entity_type);
}
/**
* Removes the new base field from the 'entity_test_update' entity type.
*/

View file

@ -7,16 +7,17 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\FieldStorageDefinition;
/**
* Tests EntityDefinitionUpdateManager functionality.
@ -508,6 +509,18 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
// Run the update and ensure the index is deleted.
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
// Test that composite indexes are handled correctly when dropping and
// re-creating one of their columns.
$this->addEntityIndex();
$this->entityDefinitionUpdateManager->applyUpdates();
$storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('name', 'entity_test_update');
$this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
$this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
$this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('name', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created again.');
}
/**
@ -609,21 +622,178 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
* Tests ::applyEntityUpdate() and ::applyFieldUpdate().
*/
public function testSingleActionCalls() {
// Ensure that the methods return FALSE when called with bogus information.
$this->assertFalse($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'foo'), 'Calling applyEntityUpdate() with a non-existent entity returns FALSE.');
$this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'foo', 'bar'), 'Calling applyFieldUpdate() with a non-existent entity returns FALSE.');
$this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update', 'bar'), 'Calling applyFieldUpdate() with a non-existent field returns FALSE.');
$this->assertFalse($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update'), 'Calling applyEntityUpdate() with an $op that is not applicable to the entity type returns FALSE.');
$this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_DELETED, 'entity_test_update', 'new_base_field'), 'Calling applyFieldUpdate() with an $op that is not applicable to the field returns FALSE.');
$db_schema = $this->database->schema();
// Ensure that a non-existing entity type cannot be installed.
$message = 'A non-existing entity type cannot be installed';
try {
$this->entityDefinitionUpdateManager->installEntityType(new ContentEntityType(['id' => 'foo']));
$this->fail($message);
}
catch (PluginNotFoundException $e) {
$this->pass($message);
}
// Ensure that a field cannot be installed on non-existing entity type.
$message = 'A field cannot be installed on a non-existing entity type';
try {
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'foo', 'entity_test', $storage_definition);
$this->fail($message);
}
catch (PluginNotFoundException $e) {
$this->pass($message);
}
// Ensure that a non-existing field cannot be installed.
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'bar'), "A non-existing field cannot be installed.");
// Ensure that installing an existing entity type is a no-op.
$entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
$this->entityDefinitionUpdateManager->installEntityType($entity_type);
$this->assertTrue($db_schema->tableExists('entity_test_update'), 'Installing an existing entity type is a no-op');
// Create a new base field.
$this->addRevisionableBaseField();
$this->assertTrue($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update', 'new_base_field'), 'Calling applyFieldUpdate() correctly returns TRUE.');
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
// Ensure that installing an existing entity type is a no-op.
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), 'Installing an existing entity type is a no-op');
// Update an existing field schema.
$this->modifyBaseField();
$storage_definition = BaseFieldDefinition::create('text')
->setName('new_base_field')
->setTargetEntityTypeId('entity_test_update')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "Previous schema for 'new_base_field' no longer exists.");
$this->assertTrue(
$db_schema->fieldExists('entity_test_update', 'new_base_field__value') && $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
"New schema for 'new_base_field' has been created."
);
// Drop an existing field schema.
$this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
$this->assertFalse(
$db_schema->fieldExists('entity_test_update', 'new_base_field__value') || $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
"The schema for 'new_base_field' has been dropped."
);
// Make the entity type revisionable.
$this->updateEntityTypeToRevisionable();
$this->assertTrue($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_UPDATED, 'entity_test_update'), 'Calling applyEntityUpdate() correctly returns TRUE.');
$this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
$this->assertFalse($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' does not exist before applying the update.");
$entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
$entity_type->set('entity_keys', $keys);
$this->entityDefinitionUpdateManager->updateEntityType($entity_type);
$this->assertTrue($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
}
/**
* Ensures that a new field and index on a shared table are created.
*
* @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema
*/
public function testCreateFieldAndIndexOnSharedTable() {
$this->addBaseField();
$this->addBaseFieldIndex();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), "New index 'entity_test_update_field__new_base_field' has been created on the 'entity_test_update' table.");
// Check index size in for MySQL.
if (Database::getConnection()->driver() == 'mysql') {
$result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update_field__new_base_field\' and column_name = \'new_base_field\'')->fetchObject();
$this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
}
}
/**
* Ensures that a new entity level index is created when data exists.
*
* @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate
*/
public function testCreateIndexUsingEntityStorageSchemaWithData() {
// Save an entity.
$name = $this->randomString();
$storage = $this->entityManager->getStorage('entity_test_update');
$entity = $storage->create(array('name' => $name));
$entity->save();
// Create an index.
$indexes = array(
'entity_test_update__type_index' => array('type'),
);
$this->state->set('entity_test_update.additional_entity_indexes', $indexes);
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__type_index'), "New index 'entity_test_update__type_index' has been created on the 'entity_test_update' table.");
// Check index size in for MySQL.
if (Database::getConnection()->driver() == 'mysql') {
$result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject();
$this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
}
}
/**
* Tests updating a base field when it has existing data.
*/
public function testBaseFieldEntityKeyUpdateWithExistingData() {
// Add the base field and run the update.
$this->addBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the base field populated.
$this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
// Save an entity with the base field not populated.
/** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
$entity = $this->entityManager->getStorage('entity_test_update')->create();
$entity->save();
// Promote the base field to an entity key. This will trigger the addition
// of a NOT NULL constraint.
$this->makeBaseFieldEntityKey();
// Try to apply the update and verify they fail since we have a NULL value.
$message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail($message);
}
catch (EntityStorageException $e) {
$this->pass($message);
}
// Check that the update is correctly applied when no NULL data is left.
$entity->set('new_base_field', $this->randomString());
$entity->save();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->pass('The update is correctly performed when no NULL data exists.');
// Check that the update actually applied a NOT NULL constraint.
$entity->set('new_base_field', NULL);
$message = 'The NOT NULL constraint was correctly applied.';
try {
$entity->save();
$this->fail($message);
}
catch (EntityStorageException $e) {
$this->pass($message);
}
}
}

View file

@ -677,6 +677,7 @@ class EntityFieldTest extends EntityUnitTestBase {
$node = entity_create('node', array(
'type' => 'page',
'uid' => $user->id(),
'title' => $this->randomString(),
));
$reference->setValue($node);
$violations = $reference->validate();
@ -699,6 +700,7 @@ class EntityFieldTest extends EntityUnitTestBase {
$node = entity_create('node', array(
'type' => 'article',
'uid' => $user->id(),
'title' => $this->randomString(),
));
$node->save();
$reference->setValue($node);

View file

@ -222,7 +222,7 @@ class EntityQueryTest extends EntityUnitTestBase {
$query = $this->factory->get('entity_test_mulrev');
$group_blue = $query->andConditionGroup()->condition("$figures.color", array('blue'), 'IN');
$group_red = $query->andConditionGroup()->condition("$figures.color", array('red'), 'IN');
$query
$this->queryResults = $query
->condition($group_blue)
->condition($group_red)
->sort('id')
@ -554,7 +554,7 @@ class EntityQueryTest extends EntityUnitTestBase {
}
}
}
$this->assertTrue($ok, format_string("$i is after all entities in bundle2"));
$this->assertTrue($ok, "$i is after all entities in bundle2");
}
}

View file

@ -8,7 +8,7 @@
namespace Drupal\system\Tests\Entity\EntityReferenceSelection;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Language\LanguageInterface;
use Drupal\comment\CommentInterface;
use Drupal\simpletest\WebTestBase;
@ -110,7 +110,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
$node = entity_create('node', $values);
$node->save();
$nodes[$key] = $node;
$node_labels[$key] = SafeMarkup::checkPlain($node->label());
$node_labels[$key] = Html::escape($node->label());
}
// Test as a non-admin.
@ -241,7 +241,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
$account = $values;
}
$users[$key] = $account;
$user_labels[$key] = SafeMarkup::checkPlain($account->getUsername());
$user_labels[$key] = Html::escape($account->getUsername());
}
// Test as a non-admin.
@ -416,7 +416,7 @@ class EntityReferenceSelectionAccessTest extends WebTestBase {
$comment = entity_create('comment', $values);
$comment->save();
$comments[$key] = $comment;
$comment_labels[$key] = SafeMarkup::checkPlain($comment->label());
$comment_labels[$key] = Html::escape($comment->label());
}
// Test as a non-admin.

View file

@ -7,7 +7,7 @@
namespace Drupal\system\Tests\Entity\EntityReferenceSelection;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
@ -93,7 +93,7 @@ class EntityReferenceSelectionSortTest extends EntityUnitTestBase {
$node = Node::create($values);
$node->save();
$nodes[$key] = $node;
$node_labels[$key] = SafeMarkup::checkPlain($node->label());
$node_labels[$key] = Html::escape($node->label());
}
$selection_options = array(

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\EntityRevisionTranslationTest.
*/
namespace Drupal\system\Tests\Entity;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests proper revision propagation of entities.
*
* @group Entity
*/
class EntityRevisionTranslationTest extends EntityUnitTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('de')->save();
$this->installEntitySchema('entity_test_mulrev');
}
/**
* Tests if the translation object has the right revision id after new revision.
*/
public function testNewRevisionAfterTranslation() {
$user = $this->createUser();
// Create a test entity.
$entity = EntityTestMulRev::create([
'name' => $this->randomString(),
'user_id' => $user->id(),
'language' => 'en',
]);
$entity->save();
$old_rev_id = $entity->getRevisionId();
$translation = $entity->addTranslation('de');
$translation->setNewRevision();
$translation->save();
$this->assertTrue($translation->getRevisionId() > $old_rev_id, 'The saved translation in new revision has a newer revision id.');
$this->assertTrue($this->reloadEntity($entity)->getRevisionId() > $old_rev_id, 'The entity from the storage has a newer revision id.');
}
}

View file

@ -106,6 +106,7 @@ class EntityTranslationFormTest extends WebTestBase {
// Create a body translation and check the form language.
$langcode2 = $this->langcodes[1];
$node->getTranslation($langcode2)->title->value = $this->randomString();
$node->getTranslation($langcode2)->body->value = $this->randomMachineName(16);
$node->getTranslation($langcode2)->setOwnerId($web_user->id());
$node->save();

View file

@ -39,6 +39,7 @@ class EntityViewControllerTest extends WebTestBase {
$this->entities[] = $entity_test;
}
$this->drupalLogin($this->drupalCreateUser(['view test entity']));
}
/**
@ -46,12 +47,9 @@ class EntityViewControllerTest extends WebTestBase {
*/
function testEntityViewController() {
$get_label_markup = function($label) {
return '<h1><div class="field field-entity-test--name field-name-name field-type-string field-label-hidden">
<div class="field-items">
<div class="field-item">' . $label . '</div>
</div>
</div>
</h1>';
return '<h1>
<div class="field field--name-name field--type-string field--label-hidden field__item">' . $label . '</div>
</h1>';
};
foreach ($this->entities as $entity) {

View file

@ -81,12 +81,11 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$this->verifyPageCache($entity_url, 'HIT');
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
if ($bundle_entity_type !== 'bundle') {
if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
// Verify that after modifying the corresponding bundle entity, there is a
// cache miss.
$this->pass("Test modification of entity's bundle entity.", 'Debug');
$bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
$bundle_entity = entity_load($bundle_entity_type_id, $this->entity->bundle());
$bundle_entity->save();
$this->verifyPageCache($entity_url, 'MISS');

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\Update\SqlContentEntityStorageSchemaIndexFilledTest.
*/
namespace Drupal\system\Tests\Entity\Update;
/**
* Runs SqlContentEntityStorageSchemaIndexTest with a dump filled with content.
*
* @group Entity
*/
class SqlContentEntityStorageSchemaIndexFilledTest extends SqlContentEntityStorageSchemaIndexTest {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
parent::setDatabaseDumpFiles();
$this->databaseDumpFiles[0] = __DIR__ . '/../../../../tests/fixtures/update/drupal-8.filled.standard.php.gz';
}
}

View file

@ -19,25 +19,16 @@ class SqlContentEntityStorageSchemaIndexTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['update_order_test'];
/**
* {@inheritdoc}
*/
public function setUp() {
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
parent::setUp();
}
/**
* Tests entity and field schema database updates and execution order.
*/
public function testIndex() {
// Enable the hook implementations in the update_order_test module.
\Drupal::state()->set('update_order_test', TRUE);
// The initial Drupal 8 database dump before any updates does not include
// the entity ID in the entity field data table indices that were added in
// https://www.drupal.org/node/2261669.
@ -45,38 +36,12 @@ class SqlContentEntityStorageSchemaIndexTest extends UpdatePathTestBase {
$this->assertFalse(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode does not exist prior to running updates.');
$this->assertFalse(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode does not exist prior to running updates.');
// Running database updates should automatically update the entity schemata
// to add the indices from https://www.drupal.org/node/2261669.
// Running database updates should update the entity schemata to add the
// indices from https://www.drupal.org/node/2261669.
$this->runUpdates();
$this->assertFalse(db_index_exists('node_field_data', 'node__default_langcode'), 'Index node__default_langcode properly removed.');
$this->assertTrue(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode properly created on the node_field_data table.');
$this->assertTrue(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode properly created on the user_field_data table.');
// Ensure that hook_update_N() implementations were in the expected order
// relative to the entity and field updates. The expected order is:
// 1. Initial Drupal 8.0.0-beta12 installation with no indices.
// 2. update_order_test_update_8001() is invoked.
// 3. update_order_test_update_8002() is invoked.
// 4. update_order_test_update_8002() explicitly applies the updates for
// the update_order_test_field storage. See update_order_test.module.
// 5. update_order_test_update_8002() explicitly applies the updates for
// the node entity type indices listed above.
// 6. The remaining entity schema updates are applied automatically after
// all update hook implementations have run, which applies the user
// index update.
$this->assertTrue(\Drupal::state()->get('update_order_test_update_8001', FALSE), 'Index node__default_langcode still existed during update_order_test_update_8001(), indicating that it ran before the entity type updates.');
// Node updates were run during update_order_test_update_8002().
$this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_node__default_langcode', TRUE), 'The node__default_langcode index was removed during update_order_test_update_8002().');
$this->assertTrue(\Drupal::state()->get('update_order_test_update_8002_node__id__default_langcode__langcode', FALSE), 'The node__id__default_langcode__langcode index was created during update_order_test_update_8002().');
// Ensure that the base field created by update_order_test_update_8002() is
// created when we expect.
$this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_update_order_test_before', TRUE), 'The update_order_test field was not been created on Node before update_order_test_update_8002().');
$this->assertTrue(\Drupal::state()->get('update_order_test_update_8002_update_order_test_after', FALSE), 'The update_order_test field was created on Node by update_order_test_update_8002().');
// User update were not run during update_order_test_update_8002().
$this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_user__id__default_langcode__langcode', TRUE));
}
}

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\Update\UpdateApiEntityDefinitionUpdateTest.
*/
namespace Drupal\system\Tests\Entity\Update;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\simpletest\WebTestBase;
use Drupal\system\Tests\Update\DbUpdatesTrait;
/**
* Tests performing entity updates through the Update API.
*
* @group Entity
*/
class UpdateApiEntityDefinitionUpdateTest extends WebTestBase {
use DbUpdatesTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['entity_test'];
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity definition update manager.
*
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
*/
protected $updatesManager;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->entityManager = $this->container->get('entity.manager');
$this->updatesManager = $this->container->get('entity.definition_update_manager');
$admin = $this->drupalCreateUser([], FALSE, TRUE);
$this->drupalLogin($admin);
}
/**
* Tests that individual updates applied sequentially work as expected.
*/
public function testSingleUpdates() {
// Create a test entity.
$user_ids = [mt_rand(), mt_rand()];
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => $user_ids]);
$entity->save();
// Check that only a single value is stored for 'user_id'.
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
// Make 'user_id' multiple by running updates.
$this->enableUpdates('entity_test', 'entity_definition_updates', 8001);
$this->runUpdates();
// Check that data was correctly migrated.
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
// Store multiple data and check it is correctly stored.
$entity->user_id = $user_ids;
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 2);
$this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
$this->assertEqual($entity->user_id[1]->target_id, $user_ids[1]);
// Make 'user_id' single again by running updates.
$this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
$this->runUpdates();
// Check that data was correctly migrated/dropped.
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
}
/**
* Tests that multiple updates applied in bulk work as expected.
*/
public function testMultipleUpdates() {
// Create a test entity.
$user_ids = [mt_rand(), mt_rand()];
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => $user_ids]);
$entity->save();
// Check that only a single value is stored for 'user_id'.
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
// Make 'user_id' multiple and then single again by running updates.
$this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
$this->runUpdates();
// Check that data was correctly migrated back and forth.
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
// Check that only a single value is stored for 'user_id' again.
$entity->user_id = $user_ids;
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertEqual(count($entity->user_id), 1);
$this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
}
/**
* Tests that entity updates are correctly reported in the status report page.
*/
function testStatusReport() {
// Create a test entity.
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => mt_rand()]);
$entity->save();
// Check that the status report initially displays no error.
$this->drupalGet('admin/reports/status');
$this->assertNoRaw('Out of date');
$this->assertNoRaw('Mismatch detected');
// Enable an entity update and check that we have a dedicated status report
// item.
$this->container->get('state')->set('entity_test.remove_name_field', TRUE);
$this->drupalGet('admin/reports/status');
$this->assertNoRaw('Out of date');
$this->assertRaw('Mismatch detected');
// Enable a db update and check that now the entity update status report
// item is no longer displayed. We assume an update function will fix the
// mismatch.
$this->enableUpdates('entity_test', 'status_report', 8001);
$this->drupalGet('admin/reports/status');
$this->assertRaw('Out of date');
$this->assertNoRaw('Mismatch detected');
// Run db updates and check that entity updates were not applied.
$this->runUpdates();
$this->drupalGet('admin/reports/status');
$this->assertNoRaw('Out of date');
$this->assertRaw('Mismatch detected');
// Check that en exception would be triggered when trying to apply them with
// existing data.
$message = 'Entity updates cannot run if entity data exists.';
try {
$this->updatesManager->applyUpdates();
$this->fail($message);
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass($message);
}
// Check the status report is the same after trying to apply updates.
$this->drupalGet('admin/reports/status');
$this->assertNoRaw('Out of date');
$this->assertRaw('Mismatch detected');
// Delete entity data, enable a new update, run updates again and check that
// entity updates were not applied even when no data exists.
$entity->delete();
$this->enableUpdates('entity_test', 'status_report', 8002);
$this->runUpdates();
$this->drupalGet('admin/reports/status');
$this->assertNoRaw('Out of date');
$this->assertRaw('Mismatch detected');
}
/**
* Reloads the specified entity.
*
* @param \Drupal\entity_test\Entity\EntityTest $entity
* An entity object.
*
* @return \Drupal\entity_test\Entity\EntityTest
* The reloaded entity object.
*/
protected function reloadEntity(EntityTest $entity) {
$this->entityManager->useCaches(FALSE);
$this->entityManager->getStorage('entity_test')->resetCache([$entity->id()]);
return EntityTest::load($entity->id());
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Extension\UpdaterTest.
*/
namespace Drupal\system\Tests\Extension;
use Drupal\simpletest\KernelTestBase;
use Drupal\Core\Updater\Updater;
/**
* Tests InfoParser class and exception.
*
* Files for this test are stored in core/modules/system/tests/fixtures and end
* with .info.txt instead of info.yml in order not not be considered as real
* extensions.
*
* @group Extension
*/
class UpdaterTest extends KernelTestBase {
/**
* Tests project and child project showing correct title.
*
* @see https://drupal.org/node/2409515
*/
public function testGetProjectTitleWithChild() {
// Get the project title from it's directory. If it can't find the title
// it will choose the first project title in the directory.
$directory = \Drupal::root() . '/core/modules/system/tests/modules/module_handler_test_multiple';
$title = Updater::getProjectTitle($directory);
$this->assertEqual('module handler test multiple', $title);
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains Drupal\system\Tests\Form\ElementsAccessTest.
*/
namespace Drupal\system\Tests\Form;
use Drupal\simpletest\WebTestBase;
/**
* Tests access control for form elements.
*
* @group Form
*/
class ElementsAccessTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('form_test');
/**
* Ensures that child values are still processed when #access = FALSE.
*/
public function testAccessFalse() {
$this->drupalPostForm('form_test/vertical-tabs-access', NULL, t('Submit'));
$this->assertNoText(t('This checkbox inside a vertical tab does not have its default value.'));
$this->assertNoText(t('This textfield inside a vertical tab does not have its default value.'));
$this->assertNoText(t('This checkbox inside a fieldset does not have its default value.'));
$this->assertNoText(t('This checkbox inside a container does not have its default value.'));
$this->assertNoText(t('This checkbox inside a nested container does not have its default value.'));
$this->assertNoText(t('This checkbox inside a vertical tab whose fieldset access is allowed does not have its default value.'));
$this->assertText(t('The form submitted correctly.'));
}
}

View file

@ -53,16 +53,16 @@ class ElementsLabelsTest extends WebTestBase {
// Exercise various defaults for textboxes and modifications to ensure
// appropriate override and correct behavior.
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required" and @class="form-required"]/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required" and @class="js-form-required form-required"]/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
$this->assertTrue(isset($elements[0]), 'Label precedes textfield, with required marker inside label.');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::label[@for="edit-form-textfield-test-no-title-required" and @class="form-required"]');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::label[@for="edit-form-textfield-test-no-title-required" and @class="js-form-required form-required"]');
$this->assertTrue(isset($elements[0]), 'Label tag with required marker precedes required textfield with no title.');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title-invisible"]/preceding-sibling::label[@for="edit-form-textfield-test-title-invisible" and @class="visually-hidden"]');
$this->assertTrue(isset($elements[0]), 'Label preceding field and label class is visually-hidden.');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::span[@class="form-required"]');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::span[@class="js-form-required form-required"]');
$this->assertFalse(isset($elements[0]), 'No required marker on non-required field.');
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title-after"]/following-sibling::label[@for="edit-form-textfield-test-title-after" and @class="option"]');

View file

@ -8,9 +8,11 @@
namespace Drupal\system\Tests\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\form_test\Form\FormTestDisabledElementsForm;
use Drupal\simpletest\WebTestBase;
use Drupal\user\RoleInterface;
@ -97,7 +99,7 @@ class FormTest extends WebTestBase {
$elements['file']['empty_values'] = $empty_strings;
// Regular expression to find the expected marker on required elements.
$required_marker_preg = '@<.*?class=".*?form-required.*?">@';
$required_marker_preg = '@<.*?class=".*?js-form-required.*form-required.*?">@';
// Go through all the elements and all the empty values for them.
foreach ($elements as $type => $data) {
foreach ($data['empty_values'] as $key => $empty) {
@ -231,6 +233,67 @@ class FormTest extends WebTestBase {
$this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.');
}
/**
* Tests that input is retained for safe elements even with an invalid token.
*
* Submits a test form containing several types of form elements.
*/
public function testInputWithInvalidToken() {
// We need to be logged in to have CSRF tokens.
$account = $this->createUser();
$this->drupalLogin($account);
// Submit again with required fields set but an invalid form token and
// verify that all the values are retained.
$edit = array(
'textfield' => $this->randomString(),
'checkboxes[bar]' => TRUE,
'select' => 'bar',
'radios' => 'foo',
'form_token' => 'invalid token',
);
$this->drupalPostForm(Url::fromRoute('form_test.validate_required'), $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
// Verify that input elements retained the posted values.
$this->assertFieldByName('textfield', $edit['textfield']);
$this->assertNoFieldChecked('edit-checkboxes-foo');
$this->assertFieldChecked('edit-checkboxes-bar');
$this->assertOptionSelected('edit-select', 'bar');
$this->assertFieldChecked('edit-radios-foo');
// Check another form that has a textarea input.
$edit = array(
'textfield' => $this->randomString(),
'textarea' => $this->randomString() . "\n",
'form_token' => 'invalid token',
);
$this->drupalPostForm(Url::fromRoute('form_test.required'), $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
$this->assertFieldByName('textfield', $edit['textfield']);
$this->assertFieldByName('textarea', $edit['textarea']);
// Check another form that has a number input.
$edit = array(
'integer_step' => mt_rand(1, 100),
'form_token' => 'invalid token',
);
$this->drupalPostForm(Url::fromRoute('form_test.number'), $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
$this->assertFieldByName('integer_step', $edit['integer_step']);
// Check a form with a Url field
$edit = array(
'url' => $this->randomString(),
'form_token' => 'invalid token',
);
$this->drupalPostForm(Url::fromRoute('form_test.url'), $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
$this->assertFieldByName('url', $edit['url']);
}
/**
* Tests validation for required textfield element without title.
*
@ -533,7 +596,7 @@ class FormTest extends WebTestBase {
// All the elements should be marked as disabled, including the ones below
// the disabled container.
$actual_count = count($disabled_elements);
$expected_count = 41;
$expected_count = 42;
$this->assertEqual($actual_count, $expected_count, SafeMarkup::format('Found @actual elements with disabled property (expected @expected).', array(
'@actual' => count($disabled_elements),
'@expected' => $expected_count,
@ -616,7 +679,7 @@ class FormTest extends WebTestBase {
$path = strtr($path, array('!type' => $type));
// Verify that the element exists.
$element = $this->xpath($path, array(
':name' => SafeMarkup::checkPlain($name),
':name' => Html::escape($name),
':div-class' => $class,
':value' => isset($item['#value']) ? $item['#value'] : '',
));

View file

@ -97,7 +97,7 @@ class RebuildTest extends WebTestBase {
// field items in the field for which we just added an item.
$this->drupalGet('node/add/page');
$this->drupalPostAjaxForm(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), NULL, array(), array(), 'node-page-form');
$this->assert(count($this->xpath('//div[contains(@class, "field-name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
$this->assert(count($this->xpath('//div[contains(@class, "field--name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
// Submit the form with the non-Ajax "Save" button, leaving the title field
// blank to trigger a validation error, and ensure that a validation error

View file

@ -7,7 +7,6 @@
namespace Drupal\system\Tests\Form;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
@ -292,11 +291,12 @@ class ValidationTest extends WebTestBase {
// Gather the element for checking the jump link section.
$error_links[] = \Drupal::l($message['title'], Url::fromRoute('<none>', [], ['fragment' => 'edit-' . str_replace('_', '-', $message['key']), 'external' => TRUE]));
}
$top_message = \Drupal::translation()->formatPlural(count($error_links), '1 error has been found: !errors', '@count errors have been found: !errors', [
'!errors' => SafeMarkup::set(implode(', ', $error_links))
]);
$top_message = \Drupal::translation()->formatPlural(count($error_links), '1 error has been found:', '@count errors have been found:');
$this->assertRaw($top_message);
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
foreach ($error_links as $error_link) {
$this->assertRaw($error_link);
}
$this->assertNoText('An illegal choice has been detected. Please contact the site administrator.');
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\HttpKernel\HeadersResponseCodeRenderTest.
*/
namespace Drupal\system\Tests\HttpKernel;
use Drupal\simpletest\WebTestBase;
/**
* Tests rendering headers and response codes.
*
* @group Routing
*/
class HeadersResponseCodeRenderTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('httpkernel_test');
/**
* Tests the rendering of an array-based header and response code.
*/
public function testHeaderResponseCode() {
$this->drupalGet('/httpkernel-test/teapot');
$this->assertResponse(418);
$this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
$this->assertHeader('X-Test-Teapot-Replace', 'Teapot replaced');
$this->assertHeader('X-Test-Teapot-No-Replace', 'This value is not replaced,This one is added');
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Installer\InstallerDatabaseErrorMessagesTest.
*/
namespace Drupal\system\Tests\Installer;
use Drupal\Core\Database\Database;
use Drupal\simpletest\InstallerTestBase;
/**
* Tests the installer with database errors.
*
* @group Installer
*/
class InstallerDatabaseErrorMessagesTest extends InstallerTestBase {
/**
* @{inheritdoc}
*/
protected function setUpSettings() {
// We are creating a table here to force an error in the installer because
// it will try and create the drupal_install_test table as this is part of
// the standard database tests performed by the installer in
// Drupal\Core\Database\Install\Tasks.
Database::getConnection('default')->query('CREATE TABLE {drupal_install_test} (id int NULL)');
parent::setUpSettings();
}
/**
* @{inheritdoc}
*/
protected function setUpSite() {
// This step should not appear as we had a failure on the settings screen.
}
/**
* Verifies that the error message in the settings step is correct.
*/
public function testSetUpSettingsErrorMessage() {
$this->assertRaw('<ul><li>Failed to <strong>CREATE</strong> a test table');
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Core\Database\Database;
use Drupal\simpletest\InstallerTestBase;
use Drupal\user\Entity\User;
@ -45,6 +46,26 @@ class InstallerTranslationTest extends InstallerTestBase {
$this->assertEqual($direction, 'ltr');
}
/**
* @{inheritdoc}
*/
protected function setUpSettings() {
// We are creating a table here to force an error in the installer because
// it will try and create the drupal_install_test table as this is part of
// the standard database tests performed by the installer in
// Drupal\Core\Database\Install\Tasks.
Database::getConnection('default')->query('CREATE TABLE {drupal_install_test} (id int NULL)');
parent::setUpSettings();
// Ensure that the error message translation is working.
$this->assertRaw('Beheben Sie alle Probleme unten, um die Installation fortzusetzen. Informationen zur Konfiguration der Datenbankserver finden Sie in der <a href="https://www.drupal.org/getting-started/install">Installationshandbuch</a>, oder kontaktieren Sie Ihren Hosting-Anbieter.');
$this->assertRaw('<strong>CREATE</strong> ein Test-Tabelle auf Ihrem Datenbankserver mit dem Befehl <em class="placeholder">CREATE TABLE {drupal_install_test} (id int NULL)</em> fehlgeschlagen.');
// Now do it successfully.
Database::getConnection('default')->query('DROP TABLE {drupal_install_test}');
parent::setUpSettings();
}
/**
* Verifies the expected behaviors of the installation result.
*/
@ -127,6 +148,12 @@ msgstr "Save and continue $langcode"
msgid "Anonymous"
msgstr "Anonymous $langcode"
msgid "Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="https://www.drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider."
msgstr "Beheben Sie alle Probleme unten, um die Installation fortzusetzen. Informationen zur Konfiguration der Datenbankserver finden Sie in der <a href="https://www.drupal.org/getting-started/install">Installationshandbuch</a>, oder kontaktieren Sie Ihren Hosting-Anbieter."
msgid "Failed to <strong>CREATE</strong> a test table on your database server with the command %query. The server reports the following message: %error.<p>Are you sure the configured username has the necessary permissions to create tables in the database?</p>"
msgstr "<strong>CREATE</strong> ein Test-Tabelle auf Ihrem Datenbankserver mit dem Befehl %query fehlgeschlagen."
ENDPO;
}

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