Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge_v8_03-11-2021
# Conflicts: # src/Umbraco.Core/Configuration/GlobalSettings.cs # src/Umbraco.Core/Configuration/IGlobalSettings.cs # src/Umbraco.Core/Constants-AppSettings.cs # src/Umbraco.Tests/Umbraco.Tests.csproj # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.UI/umbraco/config/lang/en.xml # src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Editors/ContentTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs # tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/LanguageXmlTests.cs
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"CMS": {
|
||||
//#if (HasNoNodesViewPath || UseHttpsRedirect)
|
||||
"Global": {
|
||||
"SanitizeTinyMce": true,
|
||||
//#if (!HasNoNodesViewPath && UseHttpsRedirect)
|
||||
"UseHttps": true
|
||||
//#elseif (UseHttpsRedirect)
|
||||
@@ -25,6 +26,7 @@
|
||||
//#if (HasNoNodesViewPath)
|
||||
"NoNodesViewPath": "NO_NODES_VIEW_PATH_FROM_TEMPLATE"
|
||||
//#endif
|
||||
|
||||
},
|
||||
//#endif
|
||||
"Hosting": {
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
internal const bool StaticDisableElectionForSingleServer = false;
|
||||
internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml";
|
||||
internal const string StaticSqlWriteLockTimeOut = "00:00:05";
|
||||
internal const bool StaticSanitizeTinyMce = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the reserved URLs.
|
||||
@@ -157,6 +158,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
/// </summary>
|
||||
public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether TinyMCE scripting sanitization should be applied
|
||||
/// </summary>
|
||||
[DefaultValue(StaticSanitizeTinyMce)]
|
||||
public bool SanitizeTinyMce => StaticSanitizeTinyMce;
|
||||
|
||||
/// <summary>
|
||||
/// An int value representing the time in milliseconds to lock the database for a write operation
|
||||
/// </summary>
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
|
||||
var success = false;
|
||||
|
||||
// Access the site home page and check for the click-jack protection header or meta tag
|
||||
Uri url = _hostingEnvironment.ApplicationMainUrl;
|
||||
var url = _hostingEnvironment.ApplicationMainUrl.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -23,28 +23,28 @@ namespace Umbraco.Cms.Infrastructure.Search
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetBackOfficeFields() => _backOfficeFields;
|
||||
public virtual IEnumerable<string> GetBackOfficeFields() => _backOfficeFields;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetBackOfficeMembersFields() => _backOfficeMembersFields;
|
||||
public virtual IEnumerable<string> GetBackOfficeMembersFields() => _backOfficeMembersFields;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetBackOfficeMediaFields() => _backOfficeMediaFields;
|
||||
public virtual IEnumerable<string> GetBackOfficeMediaFields() => _backOfficeMediaFields;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetBackOfficeDocumentFields() => Enumerable.Empty<string>();
|
||||
public virtual IEnumerable<string> GetBackOfficeDocumentFields() => Enumerable.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISet<string> GetBackOfficeFieldsToLoad() => _backOfficeFieldsToLoad;
|
||||
public virtual ISet<string> GetBackOfficeFieldsToLoad() => _backOfficeFieldsToLoad;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISet<string> GetBackOfficeMembersFieldsToLoad() => _backOfficeMembersFieldsToLoad;
|
||||
public virtual ISet<string> GetBackOfficeMembersFieldsToLoad() => _backOfficeMembersFieldsToLoad;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISet<string> GetBackOfficeMediaFieldsToLoad() => _backOfficeMediaFieldsToLoad;
|
||||
public virtual ISet<string> GetBackOfficeMediaFieldsToLoad() => _backOfficeMediaFieldsToLoad;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISet<string> GetBackOfficeDocumentFieldsToLoad()
|
||||
public virtual ISet<string> GetBackOfficeDocumentFieldsToLoad()
|
||||
{
|
||||
var fields = _backOfficeDocumentFieldsToLoad;
|
||||
|
||||
|
||||
@@ -413,6 +413,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{"showAllowSegmentationForDocumentTypes", false},
|
||||
{"minimumPasswordLength", _memberPasswordConfigurationSettings.RequiredLength},
|
||||
{"minimumPasswordNonAlphaNum", _memberPasswordConfigurationSettings.GetMinNonAlphaNumericChars()},
|
||||
{"sanitizeTinyMce", _globalSettings.SanitizeTinyMce}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -590,36 +590,44 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
|
||||
var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads);
|
||||
var tempPath = Path.Combine(root,fileName);
|
||||
|
||||
using (var stream = System.IO.File.Create(tempPath))
|
||||
if (Path.GetFullPath(tempPath).StartsWith(Path.GetFullPath(root)))
|
||||
{
|
||||
formFile.CopyToAsync(stream).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
if (ext.InvariantEquals("udt"))
|
||||
{
|
||||
model.TempFileName = Path.Combine(root, fileName);
|
||||
|
||||
var xd = new XmlDocument
|
||||
using (var stream = System.IO.File.Create(tempPath))
|
||||
{
|
||||
XmlResolver = null
|
||||
};
|
||||
xd.Load(model.TempFileName);
|
||||
formFile.CopyToAsync(stream).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value;
|
||||
model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value;
|
||||
if (ext.InvariantEquals("udt"))
|
||||
{
|
||||
model.TempFileName = Path.Combine(root, fileName);
|
||||
|
||||
var xd = new XmlDocument
|
||||
{
|
||||
XmlResolver = null
|
||||
};
|
||||
xd.Load(model.TempFileName);
|
||||
|
||||
model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value;
|
||||
model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Notifications.Add(new BackOfficeNotification(
|
||||
_localizedTextService.Localize("speechBubbles","operationFailedHeader"),
|
||||
_localizedTextService.Localize("media","disallowedFileType"),
|
||||
NotificationStyle.Warning));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Notifications.Add(new BackOfficeNotification(
|
||||
_localizedTextService.Localize("speechBubbles","operationFailedHeader"),
|
||||
_localizedTextService.Localize("media","disallowedFileType"),
|
||||
_localizedTextService.Localize("speechBubbles", "operationFailedHeader"),
|
||||
_localizedTextService.Localize("media", "invalidFileName"),
|
||||
NotificationStyle.Warning));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return model;
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,58 @@
|
||||
|
||||
var currentOverlay = null;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#open
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Opens a new overlay.
|
||||
*
|
||||
* @param {object} overlay The rendering options for the overlay.
|
||||
* @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/default/default.html` if nothing is specified.
|
||||
* @param {string=} overlay.position The alias of the position of the overlay. Defaults to `center`.
|
||||
*
|
||||
* Custom positions can be added by adding a CSS rule for the the underlying CSS rule. Eg. for the position `center`, the corresponding `umb-overlay-center` CSS rule is defined as:
|
||||
*
|
||||
* <pre>
|
||||
* .umb-overlay.umb-overlay-center {
|
||||
* position: absolute;
|
||||
* width: 600px;
|
||||
* height: auto;
|
||||
* top: 50%;
|
||||
* left: 50%;
|
||||
* transform: translate(-50%,-50%);
|
||||
* border-radius: 3px;
|
||||
* }
|
||||
* </pre>
|
||||
* @param {string=} overlay.size Sets an alias for the size of the overlay to be opened. If set to `small` (default), an `umb-overlay--small` class name will be appended the the class list of the main overlay element in the DOM.
|
||||
*
|
||||
* Umbraco does not support any more sizes by default, but if you wish to introduce a `medium` size, you could do so by adding a CSS rule simlar to:
|
||||
*
|
||||
* <pre>
|
||||
* .umb-overlay-center.umb-overlay--medium {
|
||||
* width: 800px;
|
||||
* }
|
||||
* </pre>
|
||||
* @param {booean=} overlay.disableBackdropClick A boolean value indicating whether the click event on the backdrop should be disabled.
|
||||
* @param {string=} overlay.title The overall title of the overlay. The title will be omitted if not specified.
|
||||
* @param {string=} overlay.subtitle The sub title of the overlay. The sub title will be omitted if not specified.
|
||||
* @param {object=} overlay.itemDetails An item that will replace the header of the overlay.
|
||||
* @param {string=} overlay.itemDetails.icon The icon of the item - eg. `icon-book`.
|
||||
* @param {string=} overlay.itemDetails.title The title of the item.
|
||||
* @param {string=} overlay.itemDetails.description Sets the description of the item. *
|
||||
* @param {string=} overlay.submitButtonLabel The label of the submit button. To support localized values, it's recommended to use the `submitButtonLabelKey` instead.
|
||||
* @param {string=} overlay.submitButtonLabelKey The key to be used for the submit button label. Defaults to `general_submit` if not specified.
|
||||
* @param {string=} overlay.submitButtonState The state of the submit button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `init`, `busy", `success`, `error`.
|
||||
* @param {string=} overlay.submitButtonStyle The styling of the submit button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `success` if not specified specified.
|
||||
* @param {string=} overlay.hideSubmitButton A boolean value indicating whether the submit button should be hidden. Default is `false`.
|
||||
* @param {string=} overlay.disableSubmitButton A boolean value indicating whether the submit button should be disabled, preventing the user from submitting the overlay. Default is `false`.
|
||||
* @param {string=} overlay.closeButtonLabel The label of the close button. To support localized values, it's recommended to use the `closeButtonLabelKey` instead.
|
||||
* @param {string=} overlay.closeButtonLabelKey The key to be used for the close button label. Defaults to `general_close` if not specified.
|
||||
* @param {string=} overlay.submit A callback function that is invoked when the user submits the overlay.
|
||||
* @param {string=} overlay.close A callback function that is invoked when the user closes the overlay.
|
||||
*/
|
||||
function open(newOverlay) {
|
||||
|
||||
// prevent two open overlays at the same time
|
||||
@@ -49,6 +101,14 @@
|
||||
eventsService.emit("appState.overlay", overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#close
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Closes the current overlay.
|
||||
*/
|
||||
function close() {
|
||||
focusLockService.removeInertAttribute();
|
||||
|
||||
@@ -61,6 +121,16 @@
|
||||
eventsService.emit("appState.overlay", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#ysod
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Opens a new overlay with an error message.
|
||||
*
|
||||
* @param {object} error The error to be shown.
|
||||
*/
|
||||
function ysod(error) {
|
||||
const overlay = {
|
||||
view: "views/common/overlays/ysod/ysod.html",
|
||||
@@ -72,6 +142,36 @@
|
||||
open(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#confirm
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Opens a new overlay prompting the user to confirm the overlay.
|
||||
*
|
||||
* @param {object} overlay The options for the overlay.
|
||||
* @param {string=} overlay.confirmType The type of the confirm dialog, which helps define standard styling and labels of the overlay. Supported values are `delete` and `remove`.
|
||||
* @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified.
|
||||
* @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified.
|
||||
* @param {string=} overlay.confirmMessageStyle The styling of the confirm message. If `overlay.confirmType` is `delete`, the fallback value is `danger` - otherwise a message style isn't explicitly specified.
|
||||
* @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`.
|
||||
*
|
||||
* If not specified, the fallback value depends on the value specified for the `overlay.confirmType` parameter:
|
||||
*
|
||||
* - `delete`: fallback key is `danger`
|
||||
* - `remove`: fallback key is `primary`
|
||||
* - anything else: no fallback AKA default button style
|
||||
* @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label.
|
||||
*
|
||||
* If not specified, the fallback value depends on the value specified for the `overlay.confirmType` parameter:
|
||||
*
|
||||
* - `delete`: fallback key is `actions_delete`
|
||||
* - `remove`: fallback key is `actions_remove`
|
||||
* - anything else: fallback is `general_confirm`
|
||||
* @param {function=} overlay.close A callback function that is invoked when the user closes the overlay.
|
||||
* @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay.
|
||||
*/
|
||||
function confirm(overlay) {
|
||||
|
||||
if (!overlay.closeButtonLabelKey) overlay.closeButtonLabelKey = "general_cancel";
|
||||
@@ -99,11 +199,45 @@
|
||||
open(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#confirmDelete
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Opens a new overlay prompting the user to confirm the overlay. The overlay will have styling and labels useful for when the user needs to confirm a delete action.
|
||||
*
|
||||
* @param {object} overlay The options for the overlay.
|
||||
* @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified.
|
||||
* @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified.
|
||||
* @param {string=} overlay.confirmMessageStyle The styling of the confirm message. Defaults to `delete` if not specified specified.
|
||||
* @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `danger` if not specified specified.
|
||||
* @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label. Defaults to `actions_delete` if not specified.
|
||||
* @param {function=} overlay.close A callback function that is invoked when the user closes the overlay.
|
||||
* @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay.
|
||||
*/
|
||||
function confirmDelete(overlay) {
|
||||
overlay.confirmType = "delete";
|
||||
confirm(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.overlayService#confirmRemove
|
||||
* @methodOf umbraco.services.overlayService
|
||||
*
|
||||
* @description
|
||||
* Opens a new overlay prompting the user to confirm the overlay. The overlay will have styling and labels useful for when the user needs to confirm a remove action.
|
||||
*
|
||||
* @param {object} overlay The options for the overlay.
|
||||
* @param {string=} overlay.closeButtonLabelKey The key to be used for the cancel button label. Defaults to `general_cancel` if not specified.
|
||||
* @param {string=} overlay.view The URL to the view. Defaults to `views/common/overlays/confirm/confirm.html` if nothing is specified.
|
||||
* @param {string=} overlay.confirmMessageStyle The styling of the confirm message - eg. `danger`.
|
||||
* @param {string=} overlay.submitButtonStyle The styling of the confirm button. Possible values are inherited from the [umbButton directive](#/api/umbraco.directives.directive:umbButton) and are `primary`, `info`, `success`, `warning`, `danger`, `inverse`, `link` and `block`. Defaults to `primary` if not specified specified.
|
||||
* @param {string=} overlay.submitButtonLabelKey The key to be used for the confirm button label. Defaults to `actions_remove` if not specified.
|
||||
* @param {function=} overlay.close A callback function that is invoked when the user closes the overlay.
|
||||
* @param {function=} overlay.submit A callback function that is invoked when the user confirms the overlay.
|
||||
*/
|
||||
function confirmRemove(overlay) {
|
||||
overlay.confirmType = "remove";
|
||||
confirm(overlay);
|
||||
|
||||
@@ -1502,6 +1502,19 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){
|
||||
/** prevent injecting arbitrary JavaScript execution in on-attributes. */
|
||||
const allNodes = Array.prototype.slice.call(args.editor.dom.doc.getElementsByTagName("*"));
|
||||
allNodes.forEach(node => {
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
if(node.attributes[i].name.indexOf("on") === 0) {
|
||||
node.removeAttribute(node.attributes[i].name)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
args.editor.on('init', function (e) {
|
||||
@@ -1513,6 +1526,60 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
//enable browser based spell checking
|
||||
args.editor.getBody().setAttribute('spellcheck', true);
|
||||
|
||||
|
||||
/** Setup sanitization for preventing injecting arbitrary JavaScript execution in attributes:
|
||||
* https://github.com/advisories/GHSA-w7jx-j77m-wp65
|
||||
* https://github.com/advisories/GHSA-5vm8-hhgr-jcjp
|
||||
*/
|
||||
const uriAttributesToSanitize = ['src', 'href', 'data', 'background', 'action', 'formaction', 'poster', 'xlink:href'];
|
||||
const parseUri = function() {
|
||||
// Encapsulated JS logic.
|
||||
const safeSvgDataUrlElements = [ 'img', 'video' ];
|
||||
const scriptUriRegExp = /((java|vb)script|mhtml):/i;
|
||||
const trimRegExp = /[\s\u0000-\u001F]+/g;
|
||||
const isInvalidUri = (uri, tagName) => {
|
||||
if (/^data:image\//i.test(uri)) {
|
||||
return safeSvgDataUrlElements.indexOf(tagName) !== -1 && /^data:image\/svg\+xml/i.test(uri);
|
||||
} else {
|
||||
return /^data:/i.test(uri);
|
||||
}
|
||||
};
|
||||
|
||||
return function parseUri(uri, tagName) {
|
||||
uri = uri.replace(trimRegExp, '');
|
||||
try {
|
||||
// Might throw malformed URI sequence
|
||||
uri = decodeURIComponent(uri);
|
||||
} catch (ex) {
|
||||
// Fallback to non UTF-8 decoder
|
||||
uri = unescape(uri);
|
||||
}
|
||||
|
||||
if (scriptUriRegExp.test(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInvalidUri(uri, tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
}();
|
||||
|
||||
if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){
|
||||
args.editor.serializer.addAttributeFilter(uriAttributesToSanitize, function (nodes) {
|
||||
nodes.forEach(function(node) {
|
||||
node.attributes.forEach(function(attr) {
|
||||
const attrName = attr.name.toLowerCase();
|
||||
if(uriAttributesToSanitize.indexOf(attrName) !== -1) {
|
||||
attr.value = parseUri(attr.value, node.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//start watching the value
|
||||
startWatch();
|
||||
});
|
||||
|
||||
@@ -55,6 +55,11 @@ input.umb-table__input {
|
||||
color: @ui-disabled-type;
|
||||
}
|
||||
|
||||
.umb-table-head__icon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.umb-table-head__link {
|
||||
background: transparent;
|
||||
border: 0 none;
|
||||
@@ -111,7 +116,7 @@ input.umb-table__input {
|
||||
.umb-table-body .umb-table-row.-selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.umb-table-row.-selected,
|
||||
.umb-table-row.-selected,
|
||||
.umb-table-body .umb-table-row.-selectable:hover {
|
||||
&::before {
|
||||
content: "";
|
||||
@@ -226,7 +231,7 @@ input.umb-table__input {
|
||||
&.umb-table-body__checkicon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Table Row Styles
|
||||
@@ -309,8 +314,8 @@ input.umb-table__input {
|
||||
|
||||
.umb-table__loading-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -330,7 +335,7 @@ input.umb-table__input {
|
||||
}
|
||||
|
||||
.umb-table--condensed {
|
||||
|
||||
|
||||
.umb-table-cell:first-of-type:not(.not-fixed) {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
@@ -60,20 +60,20 @@
|
||||
ng-click="vm.selectAll($event)"
|
||||
ng-checked="vm.isSelectedAll()">
|
||||
</div>
|
||||
<div class="umb-table-cell umb-table__name">
|
||||
<div class="umb-table-cell umb-table__name">
|
||||
<button type="button"
|
||||
class="umb-table-head__link sortable"
|
||||
ng-click="setSort('name')">
|
||||
<localize key="general_name">Name</localize>
|
||||
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': isSortDirection('name', 'asc'), 'icon-navigation-down': isSortDirection('name', 'desc')}"></i>
|
||||
<umb-icon ng-attr-icon="{{isSortDirection('name', 'asc') && 'icon-navigation-up' || isSortDirection('name', 'desc') && 'icon-navigation-down'}}" class="umb-table-head__icon"></umb-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
<button type="button"
|
||||
class="umb-table-head__link sortable"
|
||||
ng-click="setSort('updateDate')">
|
||||
Last Updated
|
||||
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': isSortDirection('updateDate', 'asc'), 'icon-navigation-down': isSortDirection('updateDate', 'desc')}"></i>
|
||||
<localize key="general_lastUpdated">Last Updated</localize>
|
||||
<umb-icon ng-attr-icon="{{isSortDirection('updateDate', 'asc') && 'icon-navigation-up' || isSortDirection('updateDate', 'desc') && 'icon-navigation-down'}}" class="umb-table-head__icon"></umb-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
ng-show="item.isFolder"
|
||||
ng-class="{'-locked': item.selected || !item.file || !item.thumbnail}"
|
||||
ng-click="clickItemName(item, $event, $index)">
|
||||
</umb-icon>
|
||||
</umb-icon>
|
||||
<span data-src="{{item.value.src}}" class="item-name">{{item.name}}</span>
|
||||
</div>
|
||||
<div class="umb-table-cell">
|
||||
@@ -101,4 +101,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<umb-icon icon="icon-navigation" class="handle"></umb-icon>
|
||||
</td>
|
||||
<td>
|
||||
{{ph = placeholder(config);""}}
|
||||
{{ph = placeholder(config);hasTabsOrFirstRender = (elemTypeTabs[config.ncAlias].length || config.ncAlias=='');""}}
|
||||
<button type="button" class="btn-reset umb-nested-content__placeholder" ng-class="{'umb-nested-content__placeholder--selected':ph}" ng-click="openElemTypeModal($event, config)">
|
||||
<umb-node-preview ng-if="ph" icon="ph.icon" name="ph.name"></umb-node-preview>
|
||||
<localize key="content_nestedContentAddElementType" ng-if="!ph">Add element type</localize>
|
||||
@@ -30,9 +30,14 @@
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<select id="{{model.alias}}_tab_select"
|
||||
<select ng-show="hasTabsOrFirstRender" id="{{model.alias}}_tab_select"
|
||||
ng-options="t for t in elemTypeTabs[config.ncAlias]"
|
||||
ng-model="config.ncTabAlias" required></select>
|
||||
<span ng-show="!hasTabsOrFirstRender" class="red">
|
||||
<localize key="content_nestedContentNoGroups">
|
||||
The selected element type does not contain any supported groups (tabs are not supported by this editor, either change them to groups or use the Block List editor).
|
||||
</localize>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" ng-model="config.nameTemplate" />
|
||||
|
||||
@@ -34,7 +34,8 @@ a:hover {
|
||||
color: rgba(0, 0, 0, .8);
|
||||
}
|
||||
|
||||
.content p code {
|
||||
.content p code,
|
||||
.content li code {
|
||||
font-size: 85%;
|
||||
font-family: inherit;
|
||||
background-color: #f7f7f9;
|
||||
|
||||
@@ -282,6 +282,7 @@
|
||||
name. Use
|
||||
</key>
|
||||
<key alias="nestedContentTemplateHelpTextPart2">to display the item index</key>
|
||||
<key alias="nestedContentNoGroups">The selected element type does not contain any supported groups (tabs are not supported by this editor, either change them to groups or use the Block List editor).</key>
|
||||
<key alias="addTextBox">Add another text box</key>
|
||||
<key alias="removeTextBox">Remove this text box</key>
|
||||
<key alias="contentRoot">Content root</key>
|
||||
@@ -325,6 +326,7 @@
|
||||
<key alias="clickToUpload">Click to upload</key>
|
||||
<key alias="orClickHereToUpload">or click here to choose files</key>
|
||||
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
|
||||
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
|
||||
<key alias="maxFileSize">Max file size is</key>
|
||||
<key alias="mediaRoot">Media root</key>
|
||||
<key alias="createFolderFailed">Failed to create a folder under parent id %0%</key>
|
||||
@@ -848,6 +850,7 @@
|
||||
<key alias="avatar">Avatar for</key>
|
||||
<key alias="header">Header</key>
|
||||
<key alias="systemField">system field</key>
|
||||
<key alias="lastUpdated">Last Updated</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blue</key>
|
||||
@@ -1168,7 +1171,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
%6%
|
||||
|
||||
Have a nice day!
|
||||
|
||||
Cheers from the Umbraco robot
|
||||
]]></key>
|
||||
<key alias="mailBodyVariantSummary">The following languages have been modified %0%</key>
|
||||
@@ -1906,7 +1908,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
http://%3%
|
||||
|
||||
Have a nice day!
|
||||
|
||||
Cheers from the Umbraco robot
|
||||
]]></key>
|
||||
<key alias="noTranslators">No translator users found. Please create a translator user before you start sending
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
name. Use
|
||||
</key>
|
||||
<key alias="nestedContentTemplateHelpTextPart2">to display the item index</key>
|
||||
<key alias="nestedContentNoGroups">The selected element type does not contain any supported groups (tabs are not supported by this editor, either change them to groups or use the Block List editor).</key>
|
||||
<key alias="addTextBox">Add another text box</key>
|
||||
<key alias="removeTextBox">Remove this text box</key>
|
||||
<key alias="contentRoot">Content root</key>
|
||||
@@ -329,6 +330,7 @@
|
||||
<key alias="clickToUpload">Click to upload</key>
|
||||
<key alias="orClickHereToUpload">or click here to choose files</key>
|
||||
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
|
||||
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
|
||||
<key alias="maxFileSize">Max file size is</key>
|
||||
<key alias="mediaRoot">Media root</key>
|
||||
<key alias="moveToSameFolderFailed">Parent and destination folders cannot be the same</key>
|
||||
@@ -869,6 +871,7 @@
|
||||
<key alias="avatar">Avatar for</key>
|
||||
<key alias="header">Header</key>
|
||||
<key alias="systemField">system field</key>
|
||||
<key alias="lastUpdated">Last Updated</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="blue">Blue</key>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configurations
|
||||
{
|
||||
[TestFixture]
|
||||
public class LanguageXmlTests
|
||||
{
|
||||
[Test]
|
||||
public void Can_Load_Language_Xml_Files()
|
||||
{
|
||||
var languageDirectory = GetLanguageDirectory();
|
||||
var readFilesCount = 0;
|
||||
var xmlDocument = new XmlDocument();
|
||||
foreach (var languageFile in languageDirectory.EnumerateFiles("*.xml"))
|
||||
{
|
||||
// Load will throw an exception if the XML isn't valid.
|
||||
xmlDocument.Load(languageFile.FullName);
|
||||
readFilesCount++;
|
||||
}
|
||||
|
||||
// Ensure that at least one file was read.
|
||||
Assert.AreNotEqual(0, readFilesCount);
|
||||
}
|
||||
|
||||
private static DirectoryInfo GetLanguageDirectory()
|
||||
{
|
||||
var testDirectoryPathParts = Path.GetDirectoryName(TestContext.CurrentContext.TestDirectory)
|
||||
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var solutionDirectoryPathParts = testDirectoryPathParts
|
||||
.Take(Array.IndexOf(testDirectoryPathParts, "tests"));
|
||||
var languageFolderPathParts = new List<string>(solutionDirectoryPathParts);
|
||||
var additionalPathParts = new[] { "Umbraco.Web.UI", "umbraco", "config", "lang" };
|
||||
languageFolderPathParts.AddRange(additionalPathParts);
|
||||
|
||||
// Hack for build-server - when this path is generated in that envrionment it's missing the "src" folder.
|
||||
// Not sure why, but if it's missing we'll add it in the right place.
|
||||
if (!languageFolderPathParts.Contains("src"))
|
||||
{
|
||||
languageFolderPathParts.Insert(languageFolderPathParts.Count - additionalPathParts.Length, "src");
|
||||
}
|
||||
|
||||
return new DirectoryInfo(string.Join(Path.DirectorySeparatorChar.ToString(), languageFolderPathParts));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user