Compare commits

..

14 Commits

Author SHA1 Message Date
Sebastiaan Janssen 5d5ce72178 Whitespace updates and use CleanFilename everywhere 2018-10-04 14:00:07 +02:00
Sebastiaan Janssen 871da7036f Puts CleanForXss back but replace backslashes with forward slashes so it doesn't mess with the JS 2018-10-04 13:59:36 +02:00
Kim Holzmann 9828e36e72 #3001 Styles cannot be saved
Fixed
can now create with folders.
Save file and changes.
rename fielname. (not copying)

and added a check for existing filename, to prevent owerwriting existing stylesheet file.
2018-10-03 22:24:00 +02:00
Kim Holzmann aa703caaf0 Revert "#3001 Styles cannot be saved - Not sure if oldName is legacy"
This reverts commit 362f9db6f8.
2018-10-03 21:06:43 +02:00
Kim Holzmann 362f9db6f8 #3001 Styles cannot be saved - Not sure if oldName is legacy
Works now -
But is not sure, is oldName used anymore, It fails when used, in both create new stylefile and save filestyle.
If oldName is legacy, I think we should clean up metode and the calls to it.
2018-10-03 03:52:34 +02:00
Sebastiaan Janssen c90e7735f9 Fix initial upload for svg and correct the way the cropper elements are hidden when cropping is not possible 2018-10-02 15:24:51 +02:00
Stephan eda6e084a7 Fix casing issue in member group service/repo 2018-10-01 14:37:40 +02:00
Sebastiaan Janssen 328b4b1556 Merge pull request #3077 from umbraco/abjerner-patch-1
Removed "debugger" statement from linkpicker overlay controller
2018-09-30 22:19:05 +02:00
Anders Bjerner fabab44342 Removed "debugger" statement from linkpicker overlay 2018-09-30 19:31:40 +02:00
agrath 63a2a155d1 User invite flow review (#3000) 2018-09-30 15:03:52 +02:00
KimHolzmann 8fad718347 #2916 Media picker searches entire media archive (#3061) 2018-09-30 14:08:01 +02:00
Sebastiaan Janssen 3879b81ed3 Merge pull request #3062 from markadrake/temp-3058
If TinyMCE has not loaded yet, return the initialized value.
2018-09-30 13:44:11 +02:00
Mark Drake c5de33dcae Added an active css class for Umbraco Tree items (#3056) 2018-09-30 13:38:19 +02:00
Mark Drake 12a9466cd7 if tinymce has not loaded yet, return the initialized value only 2018-09-27 17:03:24 -04:00
21 changed files with 199 additions and 74 deletions
@@ -284,7 +284,7 @@ namespace Umbraco.Core.Persistence.Repositories
var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase);
foreach (var toAssign in nonAssignedRoles)
{
var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId;
var groupId = rolesForNames.First(x => x.Text.InvariantEquals(toAssign)).NodeId;
Database.Insert(new Member2MemberGroupDto { Member = mId, MemberGroup = groupId });
}
}
@@ -236,7 +236,10 @@ namespace Umbraco.Core.Security
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<T, int>(dataProtectionProvider.Create("ASP.NET Identity"));
manager.UserTokenProvider = new DataProtectorTokenProvider<T, int>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromDays(3)
};
}
manager.UserLockoutEnabledByDefault = true;
@@ -748,6 +751,7 @@ namespace Umbraco.Core.Security
var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current);
return httpContext.GetCurrentRequestIpAddress();
}
}
}
@@ -630,7 +630,9 @@ namespace Umbraco.Core.Security
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
anythingChanged = true;
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
user.LastLoginDate = dt;
}
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
@@ -288,6 +288,29 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(2, membersInRole.Count());
}
[Test]
public void Associate_Members_To_Roles_With_Member_Id_Casing()
{
ServiceContext.MemberService.AddRole("MyTestRole1");
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
ServiceContext.MemberTypeService.Save(memberType);
var member1 = MockedMember.CreateSimpleMember(memberType, "test1", "test1@test.com", "pass", "test1");
ServiceContext.MemberService.Save(member1);
var member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2");
ServiceContext.MemberService.Save(member2);
// temp make sure they exist
Assert.IsNotNull(ServiceContext.MemberService.GetById(member1.Id));
Assert.IsNotNull(ServiceContext.MemberService.GetById(member2.Id));
ServiceContext.MemberService.AssignRoles(new[] { member1.Id, member2.Id }, new[] { "mytestrole1" });
var membersInRole = ServiceContext.MemberService.GetMembersInRole("MyTestRole1");
Assert.AreEqual(2, membersInRole.Count());
}
[Test]
public void Associate_Members_To_Roles_With_Member_Username()
{
@@ -1179,4 +1202,4 @@ namespace Umbraco.Tests.Services
}
}
}
}
@@ -14,7 +14,7 @@ angular.module("umbraco.directives")
scope: {
src: '=',
center: "=",
onImageLoaded: "="
onImageLoaded: "&"
},
link: function(scope, element, attrs) {
@@ -56,23 +56,17 @@ angular.module("umbraco.directives")
};
var setDimensions = function () {
if (scope.src.endsWith(".svg")) {
// svg files don't automatically get a size by
// loading them set a default size for now
$image.attr("width", "200");
$image.attr("height", "200");
// can't crop an svg file, don't show the focal point
$overlay.remove();
if (scope.isCroppable) {
scope.dimensions.width = $image.width();
scope.dimensions.height = $image.height();
if(scope.center){
scope.dimensions.left = scope.center.left * scope.dimensions.width -10;
scope.dimensions.top = scope.center.top * scope.dimensions.height -10;
}else{
scope.center = { left: 0.5, top: 0.5 };
}
}
scope.dimensions.width = $image.width();
scope.dimensions.height = $image.height();
if(scope.center){
scope.dimensions.left = scope.center.left * scope.dimensions.width -10;
scope.dimensions.top = scope.center.top * scope.dimensions.height -10;
}else{
scope.center = { left: 0.5, top: 0.5 };
}
};
var calculateGravity = function(offsetX, offsetY){
@@ -113,10 +107,32 @@ angular.module("umbraco.directives")
//// INIT /////
$image.load(function() {
$timeout(function() {
scope.isCroppable = true;
scope.hasDimensions = true;
if (scope.src !== "undefined") {
if (scope.src.endsWith(".svg")) {
scope.isCroppable = false;
scope.hasDimensions = false;
} else {
// From: https://stackoverflow.com/a/51789597/5018
var type = scope.src.substring(scope.src.indexOf("/") + 1, scope.src.indexOf(";base64"));
if (type.startsWith("svg")) {
scope.isCroppable = false;
scope.hasDimensions = false;
}
}
}
setDimensions();
scope.loaded = true;
if (angular.isFunction(scope.onImageLoaded)) {
scope.onImageLoaded();
scope.onImageLoaded(
{
"isCroppable": scope.isCroppable,
"hasDimensions": scope.hasDimensions
}
);
}
});
});
@@ -125,6 +125,14 @@ Use this directive to generate a thumbnail grid of media items.
i--;
}
if (scope.includeSubFolders !== 'true') {
if (item.parentId !== parseInt(scope.currentFolderId)) {
scope.items.splice(i, 1);
i--;
}
}
}
if (scope.items.length > 0) {
@@ -307,7 +315,9 @@ Use this directive to generate a thumbnail grid of media items.
itemMaxHeight: "@",
itemMinWidth: "@",
itemMinHeight: "@",
onlyImages: "@"
onlyImages: "@",
includeSubFolders: "@",
currentFolderId: "@"
},
link: link
};
@@ -29,6 +29,7 @@
function init() {
// Check if it is a new user
var inviteVal = $location.search().invite;
//1 = enter password, 2 = password set, 3 = invalid token
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {
$q.all([
@@ -58,6 +59,8 @@
$scope.inviteStep = Number(inviteVal);
});
} else if (inviteVal && inviteVal === "3") {
$scope.inviteStep = Number(inviteVal);
}
}
@@ -99,10 +99,18 @@
</umb-button>
</div>
</div>
</div>
<div ng-show="invitedUser == null && inviteStep === 3" ng-if="inviteStep === 3" class="umb-login-container">
<div class="form">
<h1 style="margin-bottom: 10px; text-align: left;">Hi there</h1>
<p style="line-height: 1.6; margin-bottom: 25px;">
<localize key="user_userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</localize>
</p>
<div ng-show="invitedUser == null" class="umb-login-container">
</div>
</div>
<div ng-show="invitedUser == null && !inviteStep" class="umb-login-container">
<div class="form">
<h1>{{greeting}}</h1>
@@ -119,8 +119,6 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
$scope.model.target.isMedia = true;
$scope.model.target.name = media.name;
$scope.model.target.url = mediaHelper.resolveFile(media);
debugger;
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
@@ -314,7 +314,7 @@ angular.module("umbraco")
function searchMedia() {
$scope.loading = true;
entityResource.getPagedDescendants($scope.startNodeId, "Media", $scope.searchOptions)
entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions)
.then(function(data) {
// update image data to work with image grid
angular.forEach(data.items,
@@ -11,7 +11,6 @@
<umb-load-indicator ng-if="loading">
</umb-load-indicator>
<div class="form-search">
<i class="icon-search"></i>
<input class="umb-search-field search-query -full-width-input"
@@ -21,6 +20,12 @@
ng-change="changeSearch()"
type="text"
no-dirty-check />
<br />
<label>
<localize key="general_includeFromsubFolders">Include subfolders in search</localize>
<input type="checkbox" ng-model="showChilds"
ng-change="changeSearch()">
</label>
</div>
<div class="upload-button">
@@ -83,7 +88,9 @@
item-max-height="150"
item-min-width="100"
item-min-height="100"
only-images={{onlyImages}}>
only-images={{onlyImages}}
include-sub-folders={{showChilds}}
current-Folder-id="{{currentFolder.id}}">
</umb-media-grid>
<div class="flex justify-center">
@@ -1,12 +1,14 @@
<div class="umb-cropper-gravity">
<div class="gravity-container" ng-show="loaded">
<div class="viewport">
<img ng-src="{{src}}" style="max-width: 100%; max-height: 100%" ng-click="setFocalPoint($event)" draggable="false" />
<div class="viewport">
<div class="overlay" ng-style="style()">
<img ng-show="isCroppable" ng-src="{{src}}" style="max-width: 100%; max-height: 100%" ng-click="setFocalPoint($event)" draggable="false"/>
<img ng-show="!isCroppable && !hasDimensions" ng-src="{{src}}" width="200" height="200" draggable="false" style="cursor: default;"/>
</div>
<div ng-show="isCroppable" ng-when="isCroppable" class="overlay" ng-style="style()">
</div>
</div>
</div>
</div>
</div>
@@ -72,8 +72,10 @@ angular.module('umbraco')
}
};
$scope.imageLoaded = function() {
$scope.imageLoaded = function (isCroppable, hasDimensions) {
$scope.imageIsLoaded = true;
$scope.isCroppable = isCroppable;
$scope.hasDimensions = hasDimensions;
};
//on image selected, update the cropper
@@ -114,7 +116,7 @@ angular.module('umbraco')
})
.run(function (mediaHelper, umbRequestHelper) {
if (mediaHelper && mediaHelper.registerFileResolver) {
//NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
// they contain different data structures so if we need to query against it we need to be aware of this.
mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) {
@@ -1,7 +1,6 @@
<div class="umb-editor umb-mediapicker"
ng-controller="Umbraco.PropertyEditors.ImageCropperController">
<span class="fileinput-button umb-upload-button-big"
style="margin-bottom: 5px;"
ng-hide="imageSrc">
@@ -42,12 +41,12 @@
<umb-image-gravity
src="imageSrc"
center="model.value.focalPoint"
on-image-loaded="imageLoaded">
on-image-loaded="imageLoaded(isCroppable, hasDimensions)">
</umb-image-gravity>
<a href class="btn btn-link btn-crop-delete" ng-click="clear()"><i class="icon-delete red"></i> <localize key="content_uploadClear">Remove file</localize></a>
</div>
<ul ng-if="!imageSrc.endsWith('.svg')" class="umb-sortable-thumbnails cropList clearfix">
<ul ng-if="isCroppable" class="umb-sortable-thumbnails cropList clearfix">
<li ng-repeat=" (key,value) in model.value.crops" ng-class="{'current':currentCrop.alias === value.alias}" ng-click="crop(value)">
<umb-image-thumbnail center="model.value.focalPoint"
@@ -377,7 +377,7 @@ angular.module("umbraco")
var unsubscribe = $scope.$on("formSubmitting", function () {
//TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
// we do parse it out on the server side but would be nice to do that on the client side before as well.
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
$scope.model.value = tinyMceEditor.getContent();
}
});
@@ -684,6 +684,7 @@
<key alias="embed">Embed</key>
<key alias="retrieve">Retrieve</key>
<key alias="selected">selected</key>
<key alias="includeFromsubFolders">Include subfolders in search</key>
</area>
<area alias="colors">
@@ -1864,7 +1865,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="goToProfile">Go to user profile</key>
<key alias="groupsHelp">Add groups to assign access and permissions</key>
<key alias="inviteAnotherUser">Invite another user</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
<key alias="language">Language</key>
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
<key alias="lastLockoutDate">Last lockout date</key>
@@ -1928,6 +1929,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="userInvited">has been invited</key>
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute youll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
<key alias="writer">Writer</key>
<key alias="translator">Translator</key>
@@ -684,6 +684,7 @@
<key alias="embed">Embed</key>
<key alias="retrieve">Retrieve</key>
<key alias="selected">selected</key>
<key alias="includeFromsubFolders">Include subfolders in search</key>
</area>
<area alias="colors">
<key alias="black">Black</key>
@@ -1857,7 +1858,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="goToProfile">Go to user profile</key>
<key alias="groupsHelp">Add groups to assign access and permissions</key>
<key alias="inviteAnotherUser">Invite another user</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
<key alias="language">Language</key>
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
<key alias="lastLockoutDate">Last lockout date</key>
@@ -1921,6 +1922,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="userInvited">has been invited</key>
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute youll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
<key alias="writer">Writer</key>
<key alias="translator">Translator</key>
@@ -226,7 +226,7 @@ namespace Umbraco.Web.Editors
switch (result)
{
case SignInStatus.Success:
//get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
UserManager.RaiseLoginSuccessEvent(user.Id);
@@ -425,6 +425,34 @@ namespace Umbraco.Web.Editors
}
}
//They've successfully set their password, we can now update their user account to be confirmed
//if user was only invited, then they have not been approved
//but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite)
//means we have verified their email
if (!UserManager.IsEmailConfirmed(model.UserId))
{
await UserManager.ConfirmEmailAsync(model.UserId, model.ResetCode);
}
//if the user is invited, enable their account on forgot password
var identityUser = await UserManager.FindByIdAsync(model.UserId);
//invited is not approved, never logged in, invited date present
/*
if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
return UserState.Invited;
*/
if (identityUser != null && !identityUser.IsApproved)
{
var user = Services.UserService.GetByUsername(identityUser.UserName);
//also check InvitedDate and never logged in, otherwise this would allow a disabled user to reactivate their account with a forgot password
if (user.LastLoginDate == default && user.InvitedDate != null)
{
user.IsApproved = true;
user.InvitedDate = null;
Services.UserService.Save(user);
}
}
UserManager.RaiseForgotPasswordChangedSuccessEvent(model.UserId);
return Request.CreateResponse(HttpStatusCode.OK);
}
@@ -524,4 +552,4 @@ namespace Umbraco.Web.Editors
}
}
}
}
@@ -76,6 +76,16 @@ namespace Umbraco.Web.Editors
[HttpGet]
public async Task<ActionResult> VerifyInvite(string invite)
{
//if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid
//you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get
//dumped at the default admin view with no detail
if(Security.IsAuthenticated())
{
AuthenticationManager.SignOut(
Core.Constants.Security.BackOfficeAuthenticationType,
Core.Constants.Security.BackOfficeExternalAuthenticationType);
}
if (invite == null)
{
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with invalid token: NULL");
@@ -119,16 +129,15 @@ namespace Umbraco.Web.Editors
if (result.Succeeded == false)
{
Logger.Warn<BackOfficeController>("Could not verify email, Error: " + string.Join(",", result.Errors) + ", Token: " + invite);
return RedirectToAction("Default");
return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3");
}
//sign the user in
AuthenticationManager.SignOut(
Core.Constants.Security.BackOfficeAuthenticationType,
Core.Constants.Security.BackOfficeExternalAuthenticationType);
DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc;
await SignInManager.SignInAsync(identityUser, false, false);
//reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change
identityUser.LastLoginDateUtc = previousLastLoginDate;
await UserManager.UpdateAsync(identityUser);
return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1");
}
@@ -39,13 +39,13 @@ namespace Umbraco.Web.WebServices
[HttpPost]
public JsonResult SavePartialViewMacro(string filename, string oldName, string contents)
{
var svce = (FileService) Services.FileService;
var svce = (FileService)Services.FileService;
return SavePartialView(svce,
filename, oldName, contents,
"MacroPartials/",
(s, n) => s.GetPartialViewMacro(n),
(s, v) => s.ValidatePartialViewMacro((PartialView) v),
(s, v) => s.ValidatePartialViewMacro((PartialView)v),
(s, v) => s.SavePartialViewMacro(v));
}
@@ -59,13 +59,13 @@ namespace Umbraco.Web.WebServices
[HttpPost]
public JsonResult SavePartialView(string filename, string oldName, string contents)
{
var svce = (FileService) Services.FileService;
var svce = (FileService)Services.FileService;
return SavePartialView(svce,
filename, oldName, contents,
"Partials/",
(s, n) => s.GetPartialView(n),
(s, v) => s.ValidatePartialView((PartialView) v),
(s, v) => s.ValidatePartialView((PartialView)v),
(s, v) => s.SavePartialView(v));
}
@@ -77,9 +77,7 @@ namespace Umbraco.Web.WebServices
Func<IFileService, IPartialView, Attempt<IPartialView>> save)
{
// sanitize input - partial view names have an extension
filename = filename
.Replace('\\', '/')
.TrimStart('/');
filename = CleanFilename(filename);
// sharing the editor with partial views & partial view macros,
// using path prefix to differenciate,
@@ -98,7 +96,7 @@ namespace Umbraco.Web.WebServices
oldname = oldname.TrimStart(pathPrefix);
}
var currentView = oldname.IsNullOrWhiteSpace()
var currentView = oldname.IsNullOrWhiteSpace()
? get(svce, filename)
: get(svce, oldname);
@@ -108,7 +106,7 @@ namespace Umbraco.Web.WebServices
currentView.Path = filename;
currentView.Content = contents;
Attempt<IPartialView> attempt;
@@ -166,7 +164,7 @@ namespace Umbraco.Web.WebServices
if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0))
{
t.MasterTemplate = Math.Max(masterTemplateId, 0);
pathChanged = true;
pathChanged = true;
}
}
catch (ArgumentException ex)
@@ -206,11 +204,9 @@ namespace Umbraco.Web.WebServices
public JsonResult SaveScript(string filename, string oldName, string contents)
{
// sanitize input - script names have an extension
filename = filename
.Replace('\\', '/')
.TrimStart('/');
filename = CleanFilename(filename);
var svce = (FileService) Services.FileService;
var svce = (FileService)Services.FileService;
var script = svce.GetScriptByName(oldName);
if (script == null)
script = new Script(filename);
@@ -223,7 +219,7 @@ namespace Umbraco.Web.WebServices
if (svce.ValidateScript(script) == false)
return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"),
new FileSecurityException("File '" + filename + "' is not a valid script file."));
svce.SaveScript(script);
}
catch (Exception e)
@@ -245,12 +241,18 @@ namespace Umbraco.Web.WebServices
public JsonResult SaveStylesheet(string filename, string oldName, string contents)
{
// sanitize input - stylesheet names have no extension
filename = filename
.Replace('\\', '/')
.TrimStart('/')
.EnsureEndsWith(".css");
var svce = (FileService)Services.FileService;
filename = CleanFilename(filename);
oldName = CleanFilename(oldName);
if (filename != oldName)
{
var stylesheetExists = svce.GetStylesheetByName(filename);
if (stylesheetExists != null)
return Failed(ui.Text("speechBubbles", "cssErrorText"), "A file named '" + filename + ".css' already exists.");
}
var svce = (FileService) Services.FileService;
var stylesheet = svce.GetStylesheetByName(oldName);
if (stylesheet == null)
stylesheet = new Stylesheet(filename);
@@ -281,6 +283,14 @@ namespace Umbraco.Web.WebServices
});
}
private static string CleanFilename(string filename)
{
return filename
.Replace('\\', '/')
.TrimStart('/')
.EnsureEndsWith(".css");
}
/// <summary>
/// Returns a successful message
/// </summary>
@@ -63,7 +63,7 @@ namespace umbraco.cms.presentation.settings.stylesheet
TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename).TrimEnd(".css");
// name derives from path, without the .css extension, clean for xss
NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/');
NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/').Replace("\\", "/");
if (IsPostBack == false)
{
@@ -154,4 +154,4 @@ namespace umbraco.cms.presentation.settings.stylesheet
protected global::umbraco.uicontrols.CodeArea editorSource;
}
}
}