Compare commits

...

26 Commits

Author SHA1 Message Date
Niels Hartvig b0230a1586 Rename the javascriptlibrary so it's just a service, not a helperservice :) 2018-03-15 09:32:44 +01:00
Niels Hartvig 57426ae895 Feedback from review. Makes the moment service more generic and changes promises chaining 2018-03-14 11:10:01 +01:00
Niels Hartvig 6bafd3a254 Removes ugly non-existing comment :) 2018-03-13 16:39:01 +01:00
Niels Hartvig 564074a2bd Adds check for existing moment locales before attempting to download them 2018-03-13 16:19:51 +01:00
Kyle Weems 617bdb2bd1 Merge branch 'dev-v7' of git://github.com/umbraco/Umbraco-CMS into dev-v7 2018-03-07 10:21:11 -08:00
Kyle Weems 2c91f9bf14 Reduce the first load impact of Moment by moving from using moment-with-locales.js to moment.min.js and loading in the specific locale files when the app authorizes. http://issues.umbraco.org/issue/U4-11044 2018-03-07 10:19:11 -08:00
Robert f146262031 Merge pull request #2476 from barryokane/U4-9996
(uDuf) Prevent unsaved dialogue appearing when no changes made on FileUpload
2018-03-06 09:17:17 +01:00
Robert ddae0d01e3 Merge pull request #2460 from mwanchap/U4-10821
Fix for U4-10821, render alt text attribute from grid image editor (uduf hackathon)
2018-03-05 14:35:23 +01:00
Robert b9ee85dabc String ->string 2018-03-05 14:34:36 +01:00
Sebastiaan Janssen 29fa6061a9 Merge pull request #2496 from soreng/temp-U4-9992
U4-9992: Added nuget config to restore command
2018-03-02 11:56:39 +01:00
Søren Gregersen 8ee61312b6 Added nuget config to restore command 2018-03-02 11:47:57 +01:00
Sebastiaan Janssen cfd4b8c7bf Merge pull request #2495 from soreng/temp-U4-9992
U4-9992: Added usage of nuget config in environment setup
2018-03-02 11:45:42 +01:00
Sebastiaan Janssen 7ce3b6046c Merge pull request #2494 from Mivaweb/patch-1
Update NL.xml rectify and permissionDenied
2018-03-02 11:43:20 +01:00
Søren Gregersen 43d98c9682 Added usage of nuget config in environment setup 2018-03-02 11:42:41 +01:00
Michaël Vanbrabandt 62e4825a5e Update NL.xml rectify and permissionDenied 2018-03-02 10:47:15 +01:00
Sebastiaan Janssen 81d35c6079 Merge pull request #2491 from mortenbock/temp-U4-5614
U4-5614 - Handle invalid urls in extension check
2018-03-01 20:49:09 +01:00
Morten Bock 7e15077f96 Handle invalid urls in extension check 2018-03-01 20:18:18 +01:00
Sebastiaan Janssen 6543e3c058 Merge pull request #2489 from mortenbock/temp-U4-288
U4-288 - Allow relative urls in sheduled tasks
2018-03-01 19:47:31 +01:00
Morten Bock 7c73c379cf Use the UmbracoApplicationUrl as the base for sheduled task urls, in case they are relative. 2018-03-01 19:11:37 +01:00
Sebastiaan Janssen 4bc9d96bce Merge pull request #2488 from hemraker/temp-U4-10993
Fixed open doc type button
2018-03-01 18:09:48 +01:00
Rune Hem Strand 45fd54d046 Fixed open doc type button 2018-03-01 18:06:46 +01:00
Sebastiaan Jansssen 4f062306a6 U4-10891 Color Picker Cannot read property 'hasOwnProperty' of null
(cherry picked from commit 4a8730b925)
2018-03-01 16:47:28 +01:00
Sebastiaan Janssen 0beda4b790 Update CONTRIBUTING.md 2018-03-01 16:43:02 +01:00
Matt Wanchap da0a1c808a Added fallback to caption if no altText, as suggested 2018-02-28 11:06:59 +10:00
Barry 08965982fd Prevent unsaved dialogue appearing when no changes made on FileUpload property editor
Addresses this issue in media section (U4-9996) and content (U4-7484)
2018-02-25 05:50:10 +00:00
Matt Wanchap b37197f66a Fix for U4-10821, render alt text attribute from grid image editor, or empty string if null (for screen readers) 2018-02-22 11:46:23 +10:00
23 changed files with 507 additions and 350 deletions
+10 -1
View File
@@ -114,6 +114,15 @@ Some parts of our source code is over 10 years old now. And when we say "old", w
There's two big areas that you should know about:
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You may need to run the following commands to set up gulp properly:
```
npm cache clean
npm install -g bower
npm install -g gulp
npm install -g gulp-cli
npm install
gulp build
```
2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
@@ -171,4 +180,4 @@ Did something not work as expected? Try leaving a note in the ["Contributing to
## Credits
This contribution guide borrows heavily from the excellent work on [the Atom contribution guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). A big [#h5yr](http://h5yr.com/) to them!
This contribution guide borrows heavily from the excellent work on [the Atom contribution guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). A big [#h5yr](http://h5yr.com/) to them!
@@ -8,6 +8,7 @@ function Get-UmbracoBuildEnv
# store tools in the module's directory
# and cache them for two days
$path = "$PSScriptRoot\temp"
$src = "$PSScriptRoot\..\..\..\src"
$cache = 2
if (-not (test-path $path))
@@ -37,7 +38,7 @@ function Get-UmbracoBuildEnv
if (-not (test-path $sevenZip))
{
Write-Host "Download 7-Zip..."
&$nuget install 7-Zip.CommandLine -OutputDirectory $path -Verbosity quiet
&$nuget install 7-Zip.CommandLine -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
$dir = ls "$path\7-Zip.CommandLine.*" | sort -property Name -descending | select -first 1
$file = ls -path "$dir" -name 7za.exe -recurse
$file = ls -path "$dir" -name 7za.exe -recurse | select -first 1 #A select is because there is tools\7za.exe & tools\x64\7za.exe
@@ -54,7 +55,7 @@ function Get-UmbracoBuildEnv
if (-not (test-path $vswhere))
{
Write-Host "Download VsWhere..."
&$nuget install vswhere -OutputDirectory $path -Verbosity quiet
&$nuget install vswhere -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
$dir = ls "$path\vswhere.*" | sort -property Name -descending | select -first 1
$file = ls -path "$dir" -name vswhere.exe -recurse
mv "$dir\$file" $vswhere
@@ -70,7 +71,7 @@ function Get-UmbracoBuildEnv
if (-not (test-path $semver))
{
Write-Host "Download Semver..."
&$nuget install semver -OutputDirectory $path -Verbosity quiet
&$nuget install semver -configFile "$src\NuGet.config" -OutputDirectory $path -Verbosity quiet
$dir = ls "$path\semver.*" | sort -property Name -descending | select -first 1
$file = "$dir\lib\net452\Semver.dll"
if (-not (test-path $file))
@@ -445,7 +445,7 @@ function Restore-NuGet
Write-Host ">> Restore NuGet"
Write-Host "Logging to $tmp\nuget.restore.log"
&$uenv.NuGet restore "$src\Umbraco.sln" > "$tmp\nuget.restore.log"
&$uenv.NuGet restore "$src\Umbraco.sln" -configfile "$src\NuGet.config" > "$tmp\nuget.restore.log"
}
#
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Configuration;
@@ -26,6 +27,7 @@ namespace Umbraco.Core.IO
private ShadowWrapper _xsltFileSystem;
private ShadowWrapper _masterPagesFileSystem;
private ShadowWrapper _mvcViewsFileSystem;
private ShadowWrapper _javaScriptLibraryFileSystem;
#region Singleton & Constructor
@@ -113,6 +115,7 @@ namespace Umbraco.Core.IO
var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
var javaScriptLibraryFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib"));
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider);
@@ -123,6 +126,7 @@ namespace Umbraco.Core.IO
_xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider);
_masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider);
_javaScriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", ScopeProvider);
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
@@ -143,6 +147,7 @@ namespace Umbraco.Core.IO
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } }
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
internal IFileSystem2 JavaScriptLibraryFileSystem { get { return _javaScriptLibraryFileSystem; } }
public MediaFileSystem MediaFileSystem { get; private set; }
#endregion
+14 -5
View File
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core
{
@@ -142,10 +143,18 @@ namespace Umbraco.Core
/// <returns></returns>
internal static bool IsClientSideRequest(this Uri url)
{
var ext = Path.GetExtension(url.LocalPath);
if (ext.IsNullOrWhiteSpace()) return false;
var toInclude = new[] { ".aspx", ".ashx", ".asmx", ".axd", ".svc" };
return toInclude.Any(ext.InvariantEquals) == false;
try
{
var ext = Path.GetExtension(url.LocalPath);
if (ext.IsNullOrWhiteSpace()) return false;
var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"};
return toInclude.Any(ext.InvariantEquals) == false;
}
catch (ArgumentException ex)
{
LogHelper.Error(typeof(UriExtensions), "Failed to determine if request was client side", ex);
return false;
}
}
/// <summary>
@@ -311,4 +320,4 @@ namespace Umbraco.Core
return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped));
}
}
}
}
+46 -40
View File
@@ -93,53 +93,59 @@ namespace Umbraco.Tests.Routing
var result = uri.IsClientSideRequest();
Assert.AreEqual(assert, result);
}
[Test]
public void Is_Client_Side_Request_InvalidPath_ReturnFalse()
{
//This url is invalid. Default to false when the extension cannot be determined
var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\"");
var result = uri.IsClientSideRequest();
Assert.AreEqual(false, result);
}
//NOTE: This test shows how we can test most of the HttpModule, it however is testing a method that no longer exists and is testing too much,
// we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc...
// to ensure that each one is individually tested.
//[TestCase("/", 1046)]
//[TestCase("/home.aspx", 1046)]
//[TestCase("/home/sub1.aspx", 1173)]
//[TestCase("/home.aspx?altTemplate=blah", 1046)]
//public void Process_Front_End_Document_Request_Match_Node(string url, int nodeId)
//{
// var httpContextFactory = new FakeHttpContextFactory(url);
// var httpContext = httpContextFactory.HttpContext;
// var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new NullRoutesCache());
// var contentStore = new ContentStore(umbracoContext);
// var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
// umbracoContext.RoutingContext = new RoutingContext(
// new IPublishedContentLookup[] {new LookupByNiceUrl()},
// new DefaultLastChanceLookup(),
// contentStore,
// niceUrls);
// StateHelper.HttpContext = httpContext;
//NOTE: This test shows how we can test most of the HttpModule, it however is testing a method that no longer exists and is testing too much,
// we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc...
// to ensure that each one is individually tested.
// //because of so much dependency on the db, we need to create som stuff here, i originally abstracted out stuff but
// //was turning out to be quite a deep hole because ultimately we'd have to abstract the old 'Domain' and 'Language' classes
// Domain.MakeNew("Test.com", 1000, Language.GetByCultureCode("en-US").id);
//[TestCase("/", 1046)]
//[TestCase("/home.aspx", 1046)]
//[TestCase("/home/sub1.aspx", 1173)]
//[TestCase("/home.aspx?altTemplate=blah", 1046)]
//public void Process_Front_End_Document_Request_Match_Node(string url, int nodeId)
//{
// var httpContextFactory = new FakeHttpContextFactory(url);
// var httpContext = httpContextFactory.HttpContext;
// var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new NullRoutesCache());
// var contentStore = new ContentStore(umbracoContext);
// var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
// umbracoContext.RoutingContext = new RoutingContext(
// new IPublishedContentLookup[] {new LookupByNiceUrl()},
// new DefaultLastChanceLookup(),
// contentStore,
// niceUrls);
// //need to create a template with id 1045
// var template = Template.MakeNew("test", new User(0));
// StateHelper.HttpContext = httpContext;
// SetupUmbracoContextForTest(umbracoContext, template);
// //because of so much dependency on the db, we need to create som stuff here, i originally abstracted out stuff but
// //was turning out to be quite a deep hole because ultimately we'd have to abstract the old 'Domain' and 'Language' classes
// Domain.MakeNew("Test.com", 1000, Language.GetByCultureCode("en-US").id);
// _module.AssignDocumentRequest(httpContext, umbracoContext, httpContext.Request.Url);
// //need to create a template with id 1045
// var template = Template.MakeNew("test", new User(0));
// Assert.IsNotNull(umbracoContext.PublishedContentRequest);
// Assert.IsNotNull(umbracoContext.PublishedContentRequest.XmlNode);
// Assert.IsFalse(umbracoContext.PublishedContentRequest.IsRedirect);
// Assert.IsFalse(umbracoContext.PublishedContentRequest.Is404);
// Assert.AreEqual(umbracoContext.PublishedContentRequest.Culture, Thread.CurrentThread.CurrentCulture);
// Assert.AreEqual(umbracoContext.PublishedContentRequest.Culture, Thread.CurrentThread.CurrentUICulture);
// Assert.AreEqual(nodeId, umbracoContext.PublishedContentRequest.NodeId);
// SetupUmbracoContextForTest(umbracoContext, template);
//}
// _module.AssignDocumentRequest(httpContext, umbracoContext, httpContext.Request.Url);
// Assert.IsNotNull(umbracoContext.PublishedContentRequest);
// Assert.IsNotNull(umbracoContext.PublishedContentRequest.XmlNode);
// Assert.IsFalse(umbracoContext.PublishedContentRequest.IsRedirect);
// Assert.IsFalse(umbracoContext.PublishedContentRequest.Is404);
// Assert.AreEqual(umbracoContext.PublishedContentRequest.Culture, Thread.CurrentThread.CurrentCulture);
// Assert.AreEqual(umbracoContext.PublishedContentRequest.Culture, Thread.CurrentThread.CurrentUICulture);
// Assert.AreEqual(nodeId, umbracoContext.PublishedContentRequest.NodeId);
//}
}
}
}
}
+6 -1
View File
@@ -42,7 +42,12 @@
"codemirror"
],
"sources": {
"moment": "bower_components/moment/min/moment-with-locales.js",
"moment": [
"bower_components/moment/min/moment.min.js",
"bower_components/moment/min/moment-with-locales.js",
"bower_components/moment/min/moment-with-locales.min.js",
"bower_components/moment/locale/*.js"
],
"underscore": [
"bower_components/underscore/underscore-min.js",
"bower_components/underscore/underscore-min.map"
@@ -50,7 +50,7 @@
scope.openDocumentType = function (documentType) {
var url = "/settings/documenttypes/edit/" + documentType.id;
$location.path(url);
$location.url(url);
};
scope.updateTemplate = function (templateAlias) {
@@ -10,12 +10,12 @@ function noDirtyCheck() {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
elm.focus(function () {
scope.$watch(function() {
ctrl.$pristine = false;
});
});
var alwaysFalse = {
get: function () { return false; },
set: function () { }
};
Object.defineProperty(ctrl, '$pristine', alwaysFalse);
Object.defineProperty(ctrl, '$dirty', alwaysFalse);
}
};
@@ -0,0 +1,40 @@
(function () {
'use strict';
function javascriptLibraryService($q, $http, umbRequestHelper) {
var existingLocales = [];
function getSupportedLocalesForMoment() {
var deferred = $q.defer();
if (existingLocales.length === 0) {
umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"backOfficeAssetsApiBaseUrl",
"GetSupportedMomentLocales")),
'Failed to get cultures').then(function(locales) {
existingLocales = locales;
deferred.resolve(existingLocales);
});
} else {
deferred.resolve(existingLocales);
}
return deferred.promise;
}
var service = {
getSupportedLocalesForMoment: getSupportedLocalesForMoment
};
return service;
}
angular.module('umbraco.services').factory('javascriptLibraryService', javascriptLibraryService);
})();
@@ -1,285 +1,324 @@
angular.module('umbraco.services')
.factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) {
.factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) {
var currentUser = null;
var lastUserId = null;
var loginDialog = null;
var currentUser = null;
var lastUserId = null;
var loginDialog = null;
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
// this is used so that we know when to go and get the user's remaining seconds directly.
var lastServerTimeoutSet = null;
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
// this is used so that we know when to go and get the user's remaining seconds directly.
var lastServerTimeoutSet = null;
function openLoginDialog(isTimedOut) {
if (!loginDialog) {
loginDialog = dialogService.open({
function openLoginDialog(isTimedOut) {
if (!loginDialog) {
loginDialog = dialogService.open({
//very special flag which means that global events cannot close this dialog
manualClose: true,
//very special flag which means that global events cannot close this dialog
manualClose: true,
template: 'views/common/dialogs/login.html',
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: onLoginDialogClose,
dialogData: {
isTimedOut: isTimedOut
}
});
}
}
function onLoginDialogClose(success) {
loginDialog = null;
if (success) {
securityRetryQueue.retryAll(currentUser.name);
}
else {
securityRetryQueue.cancelAll();
$location.path('/');
}
}
/**
This methods will set the current user when it is resolved and
will then start the counter to count in-memory how many seconds they have
remaining on the auth session
*/
function setCurrentUser(usr) {
if (!usr.remainingAuthSeconds) {
throw "The user object is invalid, the remainingAuthSeconds is required.";
}
currentUser = usr;
lastServerTimeoutSet = new Date();
//start the timer
countdownUserTimeout();
}
/**
Method to count down the current user's timeout seconds,
this will continually count down their current remaining seconds every 5 seconds until
there are no more seconds remaining.
*/
function countdownUserTimeout() {
$timeout(function () {
if (currentUser) {
//countdown by 5 seconds since that is how long our timer is for.
currentUser.remainingAuthSeconds -= 5;
//if there are more than 30 remaining seconds, recurse!
if (currentUser.remainingAuthSeconds > 30) {
//we need to check when the last time the timeout was set from the server, if
// it has been more than 30 seconds then we'll manually go and retrieve it from the
// server - this helps to keep our local countdown in check with the true timeout.
if (lastServerTimeoutSet != null) {
var now = new Date();
var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
if (seconds > 30) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
template: 'views/common/dialogs/login.html',
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: onLoginDialogClose,
dialogData: {
isTimedOut: isTimedOut
}
});
}
}
}
//recurse the countdown!
countdownUserTimeout();
}
else {
function onLoginDialogClose(success) {
loginDialog = null;
//we are either timed out or very close to timing out so we need to show the login dialog.
if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
try {
//NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
// don't actually care about this result.
authResource.getRemainingTimeoutSeconds();
}
finally {
userAuthExpired();
}
});
if (success) {
securityRetryQueue.retryAll(currentUser.name);
}
else {
//we've got less than 30 seconds remaining so let's check the server
if (lastServerTimeoutSet != null) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
//recurse the countdown!
countdownUserTimeout();
securityRetryQueue.cancelAll();
$location.path('/');
}
}
}
}, 5000, //every 5 seconds
false); //false = do NOT execute a digest for every iteration
}
/** Called to update the current user's timeout */
function setUserTimeoutInternal(newTimeout) {
var asNumber = parseFloat(newTimeout);
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
currentUser.remainingAuthSeconds = newTimeout;
lastServerTimeoutSet = new Date();
}
}
/** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
function userAuthExpired(isLogout) {
//store the last user id and clear the user
if (currentUser && currentUser.id !== undefined) {
lastUserId = currentUser.id;
}
if (currentUser) {
currentUser.remainingAuthSeconds = 0;
}
lastServerTimeoutSet = null;
currentUser = null;
//broadcast a global event that the user is no longer logged in
eventsService.emit("app.notAuthenticated");
openLoginDialog(isLogout === undefined ? true : !isLogout);
}
// Register a handler for when an item is added to the retry queue
securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
if (securityRetryQueue.hasMore()) {
userAuthExpired();
}
});
return {
/** Internal method to display the login dialog */
_showLoginDialog: function () {
openLoginDialog();
},
/** Returns a promise, sends a request to the server to check if the current cookie is authorized */
isAuthenticated: function () {
//if we've got a current user then just return true
if (currentUser) {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
/**
This methods will set the current user when it is resolved and
will then start the counter to count in-memory how many seconds they have
remaining on the auth session
*/
function setCurrentUser(usr) {
if (!usr.remainingAuthSeconds) {
throw "The user object is invalid, the remainingAuthSeconds is required.";
}
currentUser = usr;
lastServerTimeoutSet = new Date();
//start the timer
countdownUserTimeout();
}
return authResource.isAuthenticated();
},
/** Returns a promise, sends a request to the server to validate the credentials */
authenticate: function (login, password) {
/**
Method to count down the current user's timeout seconds,
this will continually count down their current remaining seconds every 5 seconds until
there are no more seconds remaining.
*/
function countdownUserTimeout() {
return authResource.performLogin(login, password)
.then(this.setAuthenticationSuccessful);
},
setAuthenticationSuccessful: function (data) {
$timeout(function () {
//when it's successful, return the user data
setCurrentUser(data);
if (currentUser) {
//countdown by 5 seconds since that is how long our timer is for.
currentUser.remainingAuthSeconds -= 5;
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
//if there are more than 30 remaining seconds, recurse!
if (currentUser.remainingAuthSeconds > 30) {
//broadcast a global event
eventsService.emit("app.authenticated", result);
return result;
},
//we need to check when the last time the timeout was set from the server, if
// it has been more than 30 seconds then we'll manually go and retrieve it from the
// server - this helps to keep our local countdown in check with the true timeout.
if (lastServerTimeoutSet != null) {
var now = new Date();
var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
/** Logs the user out
*/
logout: function () {
if (seconds > 30) {
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
//done!
return null;
});
},
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
/** Refreshes the current user data with the data stored for the user on the server and returns it */
refreshCurrentUser: function() {
var deferred = $q.defer();
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
}
authResource.getCurrentUser()
.then(function (data) {
//recurse the countdown!
countdownUserTimeout();
}
else {
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
setCurrentUser(data);
//we are either timed out or very close to timing out so we need to show the login dialog.
if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
try {
//NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
// don't actually care about this result.
authResource.getRemainingTimeoutSeconds();
}
finally {
userAuthExpired();
}
});
}
else {
//we've got less than 30 seconds remaining so let's check the server
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
if (lastServerTimeoutSet != null) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
return deferred.promise;
},
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
var deferred = $q.defer();
//recurse the countdown!
countdownUserTimeout();
if (!currentUser) {
authResource.getCurrentUser()
.then(function (data) {
}
}
}
}, 5000, //every 5 seconds
false); //false = do NOT execute a digest for every iteration
}
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
/** Called to update the current user's timeout */
function setUserTimeoutInternal(newTimeout) {
if (args && args.broadcastEvent) {
//broadcast a global event, will inform listening controllers to load in the user specific data
var asNumber = parseFloat(newTimeout);
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
currentUser.remainingAuthSeconds = newTimeout;
lastServerTimeoutSet = new Date();
}
}
/** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
function userAuthExpired(isLogout) {
//store the last user id and clear the user
if (currentUser && currentUser.id !== undefined) {
lastUserId = currentUser.id;
}
if (currentUser) {
currentUser.remainingAuthSeconds = 0;
}
lastServerTimeoutSet = null;
currentUser = null;
//broadcast a global event that the user is no longer logged in
eventsService.emit("app.notAuthenticated");
openLoginDialog(isLogout === undefined ? true : !isLogout);
}
// Register a handler for when an item is added to the retry queue
securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
if (securityRetryQueue.hasMore()) {
userAuthExpired();
}
});
return {
/** Internal method to display the login dialog */
_showLoginDialog: function () {
openLoginDialog();
},
/** Returns a promise, sends a request to the server to check if the current cookie is authorized */
isAuthenticated: function () {
//if we've got a current user then just return true
if (currentUser) {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
}
return authResource.isAuthenticated();
},
/** Returns a promise, sends a request to the server to validate the credentials */
authenticate: function (login, password) {
return authResource.performLogin(login, password)
.then(this.setAuthenticationSuccessful);
},
setAuthenticationSuccessful: function (data) {
//when it's successful, return the user data
setCurrentUser(data);
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
//broadcast a global event
eventsService.emit("app.authenticated", result);
}
return result;
},
setCurrentUser(data);
/** Logs the user out
*/
logout: function () {
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
//done!
return null;
});
},
}
else {
deferred.resolve(currentUser);
}
/** Refreshes the current user data with the data stored for the user on the server and returns it */
refreshCurrentUser: function () {
var deferred = $q.defer();
return deferred.promise;
},
authResource.getCurrentUser()
.then(function (data) {
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
}
};
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
});
setCurrentUser(data);
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
return deferred.promise;
},
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
var deferred = $q.defer();
if (!currentUser) {
authResource.getCurrentUser()
.then(function (data) {
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
if (args && args.broadcastEvent) {
//broadcast a global event, will inform listening controllers to load in the user specific data
eventsService.emit("app.authenticated", result);
}
setCurrentUser(data);
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
}
else {
deferred.resolve(currentUser);
}
return deferred.promise;
},
/** Loads the Moment.js Locale for the current user. */
loadMomentLocaleForCurrentUser: function () {
var deferred = $q.defer();
function loadLocales(currentUser, supportedLocales) {
var locale = currentUser.locale.toLowerCase();
if (locale !== 'en-us') {
var localeUrls = [];
if (supportedLocales.indexOf(locale + '.js') > -1) {
localeUrls.push('lib/moment/' + locale + '.js');
}
if (locale.indexOf('-') > -1) {
var majorLocale = locale.split('-')[0] + '.js';
if (supportedLocales.indexOf(majorLocale) > -1) {
localeUrls.push('lib/moment/' + majorLocale);
}
}
assetsService.load(localeUrls).then(function () {
deferred.resolve(localeUrls);
});
} else {
deferred.resolve(['']);
}
}
var promises = {
currentUser: this.getCurrentUser(),
supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment()
}
$q.all(promises).then((values) => {
loadLocales(values.currentUser, values.supportedLocales);
});
return deferred.promise;
},
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
}
};
});
+18 -15
View File
@@ -18,23 +18,26 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
eventsService.on("app.authenticated", function(evt, data) {
assetsService._loadInitAssets().then(function() {
//Register all of the tours on the server
tourService.registerAllTours().then(function () {
appReady(data);
// Auto start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
if (introTour && introTour.disabled !== true && introTour.completed !== true) {
tourService.startTour(introTour);
}
// Loads the user's locale settings for Moment.
userService.loadMomentLocaleForCurrentUser().then(function() {
//Register all of the tours on the server
tourService.registerAllTours().then(function () {
appReady(data);
// Auto start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
if (introTour && introTour.disabled !== true && introTour.completed !== true) {
tourService.startTour(introTour);
}
});
}, function(){
appReady(data);
});
}, function(){
appReady(data);
});
});
});
@@ -15,7 +15,7 @@ function ColorPickerController($scope) {
$scope.toggleItem = function (color) {
var currentColor = $scope.model.value.hasOwnProperty("value")
var currentColor = ($scope.model.value && $scope.model.value.hasOwnProperty("value"))
? $scope.model.value.value
: $scope.model.value;
@@ -130,6 +130,9 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag
// in the description of this controller, it states that this value isn't actually used for persistence,
// but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation.
$scope.model.value = { selectedFiles: newVal.trimEnd(",") };
//need to explicity setDirty here as file upload field can't track dirty & we can't use the fileCount (hidden field/model)
$scope.propertyForm.$setDirty();
});
});
@@ -154,26 +157,24 @@ angular.module("umbraco")
.controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
.run(function(mediaHelper, umbRequestHelper, assetsService){
if (mediaHelper && mediaHelper.registerFileResolver) {
assetsService.load(["lib/moment/moment-with-locales.js"]).then(
function () {
//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.UploadField", function(property, entity, thumbnail){
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
return thumbnailUrl;
}
else {
return null;
}
}
else {
return property.value;
}
});
//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.UploadField", function(property, entity, thumbnail){
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
return thumbnailUrl;
}
else {
return null;
}
}
);
else {
return property.value;
}
});
}
});
@@ -31,6 +31,6 @@
</ul>
</div>
<input type="hidden" name="fileCount" ng-model="files.length" val-property-validator="validateMandatory"/>
<input type="hidden" name="fileCount" ng-model="files.length" val-property-validator="validateMandatory" no-dirty-check />
</div>
@@ -99,4 +99,4 @@ module.exports = function (config) {
'karma-phantomjs-launcher'
]
});
};
};
@@ -13,8 +13,10 @@
url += "&mode=crop";
}
}
var altText = Model.value.altText ?? Model.value.caption ?? string.Empty;
<img src="@url" alt="@Model.value.caption">
<img src="@url" alt="@altText">
if (Model.value.caption != null)
{
@@ -38,7 +38,7 @@
<key alias="update">Bijwerken</key>
</area>
<area alias="assignDomain">
<key alias="permissionDenied">Permission denied.</key>
<key alias="permissionDenied">Toegang geweigerd.</key>
<key alias="addNew">Nieuw domein toevoegen</key>
<key alias="remove">verwijderen</key>
<key alias="invalidNode">Ongeldige node.</key>
@@ -1379,10 +1379,10 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de
<key alias="httpsCheckEnableHttpsDescription">Zet in de appSettings van de web.config de umbracoSSL instelling op 'true'.</key>
<key alias="httpsCheckEnableHttpsSuccess">De appSetting 'umbracoUseSSL' is nu ingesteld op 'true', cookies zullen als 'secure' worden aangemerkt.</key>
<key alias="rectifyButton">Fix</key>
<key alias="cannotRectifyShouldNotEqual">Cannot fix a check with a value comparison type of 'ShouldNotEqual'.</key>
<key alias="cannotRectifyShouldEqualWithValue">Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value.</key>
<key alias="valueToRectifyNotProvided">Value to fix check not provided.</key>
<key alias="rectifyButton">Herstellen</key>
<key alias="cannotRectifyShouldNotEqual">Kan een controle met vergelijkingstype 'ShouldNotEqual' niet herstellen.</key>
<key alias="cannotRectifyShouldEqualWithValue">Kan een controle met vergelijkingstype 'ShouldNotEqual' en gedefinieerde waarde niet herstellen.</key>
<key alias="valueToRectifyNotProvided">Waarde om te herstellen niet gedefinieerd.</key>
<key alias="compilationDebugCheckSuccessMessage">Debug compiliate mode staat uit.</key>
<key alias="compilationDebugCheckErrorMessage">Debug compiliate mode staat momenteel aan. Wij raden aan deze instelling uit te zetten voor livegang.</key>
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Umbraco.Core.IO;
using Umbraco.Web.Mvc;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
public class BackOfficeAssetsController : UmbracoAuthorizedJsonController
{
[HttpGet]
public IEnumerable<string> GetSupportedMomentLocales()
{
var momentLocaleFolder = "moment";
var fileSystem = FileSystemProviderManager.Current.JavaScriptLibraryFileSystem;
var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList();
for (var i = 0; i < cultures.Count(); i++)
{
cultures[i] = cultures[i]
.Substring(cultures[i].IndexOf(momentLocaleFolder) + momentLocaleFolder.Length + 1);
}
return cultures;
}
}
}
@@ -268,6 +268,10 @@ namespace Umbraco.Web.Editors
{
"helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HelpController>(
controller => controller.GetContextHelpForPage("","",""))
},
{
"backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<BackOfficeAssetsController>(
controller => controller.GetSupportedMomentLocales())
}
}
},
+5 -1
View File
@@ -63,6 +63,10 @@ namespace Umbraco.Web.Scheduling
{
using (var wc = new HttpClient())
{
if (Uri.TryCreate(_appContext.UmbracoApplicationUrl, UriKind.Absolute, out var baseUri))
{
wc.BaseAddress = baseUri;
}
var request = new HttpRequestMessage(HttpMethod.Get, url);
//TODO: pass custom the authorization header, currently these aren't really secured!
@@ -122,4 +126,4 @@ namespace Umbraco.Web.Scheduling
get { return true; }
}
}
}
}
@@ -3,7 +3,7 @@
'lib/angular/1.1.5/angular.min.js',
'lib/underscore/underscore-min.js',
'lib/moment/moment-with-locales.js',
'lib/moment/moment.min.js',
'lib/jquery-ui/jquery-ui.min.js',
'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js',
+1
View File
@@ -316,6 +316,7 @@
<Compile Include="Cache\TemplateCacheRefresher.cs" />
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
<Compile Include="Cache\UserCacheRefresher.cs" />
<Compile Include="Editors\BackOfficeAssetsController .cs" />
<Compile Include="Features\DisabledFeatures.cs" />
<Compile Include="Editors\BackOfficeNotificationsController.cs" />
<Compile Include="Editors\BackOfficeServerVariables.cs" />