diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs index e50322d95c..1e10d13890 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs @@ -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 _libFileSystem; #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 libFileSystem = 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); + _libFileSystem = new ShadowWrapper(libFileSystem, "Lib", ScopeProvider); // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again MediaFileSystem = GetFileSystemProvider(); @@ -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; } } + public IFileSystem2 LibFileSystem { get { return _libFileSystem; } } public MediaFileSystem MediaFileSystem { get; private set; } #endregion diff --git a/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js new file mode 100644 index 0000000000..2bf506352d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js @@ -0,0 +1,42 @@ +(function () { + 'use strict'; + + function momenthelperService($q, $http, umbRequestHelper) { + + var existingLocales = []; + + function getSupportedLocales() { + var deferred = $q.defer(); + + if (existingLocales.length === 0) { + umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "momentApiBaseUrl", + "GetSupportedLocales")), + 'Failed to get cultures').then(function(locales) { + existingLocales = locales; + deferred.resolve(existingLocales); + }); + } else { + deferred.resolve(existingLocales); + } + + return deferred.promise; + } + + //////////// + + var service = { + getSupportedLocales: getSupportedLocales + }; + + return service; + + } + + angular.module('umbraco.services').factory('momenthelperService', momenthelperService); + + +})(); + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 12d7a75a77..9beb4dc0af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,309 +1,319 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, momenthelperService) { - 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; - } - 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; - }, - - /** Logs the user out - */ - logout: function () { - - return authResource.performLogout() - .then(function (data) { - userAuthExpired(); - //done! - return 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(); - - authResource.getCurrentUser() - .then(function (data) { - - 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; - }, + /** + 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(); + } - /** Loads the Moment.js Locale for the current user. */ - loadMomentLocaleForCurrentUser: function () { - var deferred = $q.defer(); + /** + 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() { - var supportedLocales = []; + $timeout(function () { - this.getCurrentUser() - .then(function (user) { - var locale = user.locale.toLowerCase(); - if (locale !== 'en-us') { - var localeUrls = ['lib/moment/' + locale + '.js']; - if (locale.indexOf('-') > -1) { - localeUrls.push('lib/moment/' + locale.split('-')[0] + '.js') + 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); + }); + }); + } + } + + //recurse the countdown! + countdownUserTimeout(); + } + else { + + //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 + + 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(); + + } } - assetsService.load(localeUrls).then(function() { - deferred.resolve(localeUrls); - }); - } else { - deferred.resolve(['']); } - }); - return deferred.promise; - }, + }, 5000, //every 5 seconds + false); //false = do NOT execute a digest for every iteration + } - /** 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); - } - }; + /** 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; + } + 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; + }, + + /** Logs the user out + */ + logout: function () { + + return authResource.performLogout() + .then(function (data) { + userAuthExpired(); + //done! + return 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(); + + authResource.getCurrentUser() + .then(function (data) { + + 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(); + + + this.getCurrentUser() + .then(function(user) { + var locale = user.locale.toLowerCase(); + + momenthelperService.getSupportedLocales().then(function(supportedLocales) { + + 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(['']); + } + }); + }); + 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); + } + }; + + }); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index d401c1c266..857929d63b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -268,6 +268,10 @@ namespace Umbraco.Web.Editors { "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetContextHelpForPage("","","")) + }, + { + "momentApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSupportedLocales()) } } }, diff --git a/src/Umbraco.Web/Editors/MomentController.cs b/src/Umbraco.Web/Editors/MomentController.cs new file mode 100644 index 0000000000..c5bc305d9d --- /dev/null +++ b/src/Umbraco.Web/Editors/MomentController.cs @@ -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 MomentController : UmbracoAuthorizedJsonController + { + + [HttpGet] + public IEnumerable GetSupportedLocales() + { + var momentLocaleFolder = "moment"; + var fileSystem = FileSystemProviderManager.Current.LibFileSystem; + 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; + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c6d528bb3d..e77ffd1677 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -316,6 +316,7 @@ +