diff --git a/src/Umbraco.Web.UI.Client/src/common/security/_module.js b/src/Umbraco.Web.UI.Client/src/common/security/_module.js
index 15a7663d9b..c8289c754e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/security/_module.js
+++ b/src/Umbraco.Web.UI.Client/src/common/security/_module.js
@@ -1,4 +1,4 @@
-// Based loosely around work by Witold Szczerba - https://github.com/witoldsz/angular-http-auth
-angular.module('umbraco.security', [
- 'umbraco.security.retryQueue',
- 'umbraco.security.interceptor']);
\ No newline at end of file
+//TODO: This is silly and unecessary to have a separate module for this
+angular.module('umbraco.security.retryQueue', []);
+angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']);
+angular.module('umbraco.security', ['umbraco.security.retryQueue', 'umbraco.security.interceptor']);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js
index e971719398..28d91dd610 100644
--- a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js
+++ b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js
@@ -1,3 +1,4 @@
+//TODO: This is silly and unecessary to have a separate module for this
angular.module('umbraco.security.retryQueue', [])
// This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel.
diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js
similarity index 96%
rename from src/Umbraco.Web.UI.Client/src/common/security/interceptor.js
rename to src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js
index 2b757707ba..b80754ef67 100644
--- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js
+++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js
@@ -1,95 +1,95 @@
-angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue'])
- // This http interceptor listens for authentication successes and failures
- .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) {
- return function(promise) {
-
- return promise.then(
- function(originalResponse) {
- // Intercept successful requests
-
- //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it
- //expires. Then we'll update the user in the user service accordingly.
- var headers = originalResponse.headers();
- if (headers["x-umb-user-seconds"]) {
- // We must use $injector to get the $http service to prevent circular dependency
- var userService = $injector.get('userService');
- userService.setUserTimeout(headers["x-umb-user-seconds"]);
- }
-
- return promise;
- }, function(originalResponse) {
- // Intercept failed requests
-
- //Here we'll check if we should ignore the error, this will be based on an original header set
- var headers = originalResponse.config ? originalResponse.config.headers : {};
- if (headers["x-umb-ignore-error"] === "ignore") {
- //exit/ignore
- return promise;
- }
- var filtered = _.find(requestInterceptorFilter(), function(val) {
- return originalResponse.config.url.indexOf(val) > 0;
- });
- if (filtered) {
- return promise;
- }
-
- //A 401 means that the user is not logged in
- if (originalResponse.status === 401) {
-
- // The request bounced because it was not authorized - add a new request to the retry queue
- promise = queue.pushRetryFn('unauthorized-server', function retryRequest() {
- // We must use $injector to get the $http service to prevent circular dependency
- return $injector.get('$http')(originalResponse.config);
- });
- }
- else if (originalResponse.status === 404) {
-
- //a 404 indicates that the request was not found - this could be due to a non existing url, or it could
- //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it
-
- var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + "";
- if (originalResponse.data && originalResponse.data.ExceptionMessage) {
- errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + "";
- }
- if (originalResponse.config.data) {
- errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
- }
-
- notifications.error(
- "Request error",
- errMsg);
-
- }
- else if (originalResponse.status === 403) {
- //if the status was a 403 it means the user didn't have permission to do what the request was trying to do.
- //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was
- //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper,
- // or completely custom for services calling resources.
-
- //http://issues.umbraco.org/issue/U4-2749
-
- //It was decided to just put these messages into the normal status messages.
-
- var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + "";
- if (originalResponse.config.data) {
- msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
- }
-
- notifications.error(
- "Authorization error",
- msg);
- }
-
- return promise;
- });
- };
- }])
-
- .value('requestInterceptorFilter', function() {
- return ["www.gravatar.com"];
- })
-
- // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
- .config(['$httpProvider', function ($httpProvider) {
- $httpProvider.responseInterceptors.push('securityInterceptor');
+angular.module('umbraco.security.interceptor')
+ // This http interceptor listens for authentication successes and failures
+ .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) {
+ return function(promise) {
+
+ return promise.then(
+ function(originalResponse) {
+ // Intercept successful requests
+
+ //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it
+ //expires. Then we'll update the user in the user service accordingly.
+ var headers = originalResponse.headers();
+ if (headers["x-umb-user-seconds"]) {
+ // We must use $injector to get the $http service to prevent circular dependency
+ var userService = $injector.get('userService');
+ userService.setUserTimeout(headers["x-umb-user-seconds"]);
+ }
+
+ return promise;
+ }, function(originalResponse) {
+ // Intercept failed requests
+
+ //Here we'll check if we should ignore the error, this will be based on an original header set
+ var headers = originalResponse.config ? originalResponse.config.headers : {};
+ if (headers["x-umb-ignore-error"] === "ignore") {
+ //exit/ignore
+ return promise;
+ }
+ var filtered = _.find(requestInterceptorFilter(), function(val) {
+ return originalResponse.config.url.indexOf(val) > 0;
+ });
+ if (filtered) {
+ return promise;
+ }
+
+ //A 401 means that the user is not logged in
+ if (originalResponse.status === 401) {
+
+ // The request bounced because it was not authorized - add a new request to the retry queue
+ promise = queue.pushRetryFn('unauthorized-server', function retryRequest() {
+ // We must use $injector to get the $http service to prevent circular dependency
+ return $injector.get('$http')(originalResponse.config);
+ });
+ }
+ else if (originalResponse.status === 404) {
+
+ //a 404 indicates that the request was not found - this could be due to a non existing url, or it could
+ //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it
+
+ var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + "";
+ if (originalResponse.data && originalResponse.data.ExceptionMessage) {
+ errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + "";
+ }
+ if (originalResponse.config.data) {
+ errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
+ }
+
+ notifications.error(
+ "Request error",
+ errMsg);
+
+ }
+ else if (originalResponse.status === 403) {
+ //if the status was a 403 it means the user didn't have permission to do what the request was trying to do.
+ //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was
+ //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper,
+ // or completely custom for services calling resources.
+
+ //http://issues.umbraco.org/issue/U4-2749
+
+ //It was decided to just put these messages into the normal status messages.
+
+ var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + "";
+ if (originalResponse.config.data) {
+ msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
+ }
+
+ notifications.error(
+ "Authorization error",
+ msg);
+ }
+
+ return promise;
+ });
+ };
+ }])
+
+ .value('requestInterceptorFilter', function() {
+ return ["www.gravatar.com"];
+ })
+
+ // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
+ .config(['$httpProvider', function ($httpProvider) {
+ $httpProvider.responseInterceptors.push('securityInterceptor');
}]);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
index 30815027b0..be5e2e1232 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
@@ -58,6 +58,44 @@ function versionHelper() {
}
angular.module('umbraco.services').factory('versionHelper', versionHelper);
+function dateHelper() {
+
+ return {
+
+ convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) {
+
+ //get the formatted offset time in HH:mm (server time offset is in minutes)
+ var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+ moment()
+ .startOf('day')
+ .minutes(Math.abs(serverOffsetMinutes))
+ .format('HH:mm');
+
+ var server = moment.utc(momentLocal).zone(formattedOffset);
+ return server.format(format ? format : "YYYY-MM-DD HH:mm:ss");
+ },
+
+ convertToLocalMomentTime: function (strVal, serverOffsetMinutes) {
+
+ //get the formatted offset time in HH:mm (server time offset is in minutes)
+ var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+ moment()
+ .startOf('day')
+ .minutes(Math.abs(serverOffsetMinutes))
+ .format('HH:mm');
+
+ //convert to the iso string format
+ var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset;
+
+ //create a moment with the iso format which will include the offset with the correct time
+ // then convert it to local time
+ return moment.parseZone(isoFormat).local();
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('dateHelper', dateHelper);
+
function packageHelper(assetsService, treeService, eventsService, $templateCache) {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less
index f9dedf5902..d046ac7104 100644
--- a/src/Umbraco.Web.UI.Client/src/less/forms.less
+++ b/src/Umbraco.Web.UI.Client/src/less/forms.less
@@ -9,7 +9,7 @@
small.umb-detail,
label small, .guiDialogTiny {
- color: #b3b3b3 !important;
+ color: @grayMed !important;
text-decoration: none;
display: block;
font-weight: normal;
diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less
index 83b61e48a5..aab0c5987c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/panel.less
+++ b/src/Umbraco.Web.UI.Client/src/less/panel.less
@@ -286,14 +286,14 @@
// form styles
.umb-dialog .muted,
.umb-panel .muted {
- color: @grayLight;
+ color: @grayMed;
}
.umb-dialog a.muted:hover,
.umb-dialog a.muted:focus,
.umb-panel a.muted:hover,
.umb-panel a.muted:focus {
- color: darken(@grayLight, 10%);
+ color: darken(@grayMed, 10%);
}
.umb-dialog .text-warning,
diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less
index 5bddbd8022..8ebb0f08ef 100644
--- a/src/Umbraco.Web.UI.Client/src/less/variables.less
+++ b/src/Umbraco.Web.UI.Client/src/less/variables.less
@@ -14,7 +14,7 @@
@grayDarker: #222;
@grayDark: #343434;
@gray: #555;
-@grayMed: #999;
+@grayMed: #7f7f7f;
@grayLight: #d9d9d9;
@grayLighter: #f8f8f8;
@white: #fff;
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
index 693bd49ec6..2c3495120a 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js
@@ -1,4 +1,4 @@
-function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element) {
+function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
//setup the default config
var config = {
@@ -22,6 +22,8 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
$scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
}
+
+
$scope.hasDatetimePickerValue = $scope.model.value ? true : false;
$scope.datetimePickerValue = null;
@@ -43,20 +45,46 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
if (e.date && e.date.isValid()) {
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
$scope.hasDatetimePickerValue = true;
- $scope.datetimePickerValue = e.date.format($scope.model.config.format);
- $scope.model.value = $scope.datetimePickerValue;
+ $scope.datetimePickerValue = e.date.format($scope.model.config.format);
}
else {
$scope.hasDatetimePickerValue = false;
$scope.datetimePickerValue = null;
}
-
+
+ setModelValue();
+
if (!$scope.model.config.pickTime) {
$element.find("div:first").datetimepicker("hide", 0);
}
});
}
+ //sets the scope model value accordingly - this is the value to be sent up to the server and depends on
+ // if the picker is configured to offset time. We always format the date/time in a specific format for sending
+ // to the server, this is different from the format used to display the date/time.
+ function setModelValue() {
+ if ($scope.hasDatetimePickerValue) {
+ var elementData = $element.find("div:first").data().DateTimePicker;
+ if ($scope.model.config.pickTime) {
+ //check if we are supposed to offset the time
+ if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
+ $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
+ $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
+ }
+ else {
+ $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss");
+ }
+ }
+ else {
+ $scope.model.value = elementData.getDate().format("YYYY-MM-DD");
+ }
+ }
+ else {
+ $scope.model.value = null;
+ }
+ }
+
var picker = null;
$scope.clearDate = function() {
@@ -66,6 +94,21 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
}
+ $scope.serverTime = null;
+ $scope.serverTimeNeedsOffsetting = false;
+ if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
+ // Will return something like 120
+ var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
+
+ // Will return something like -120
+ var localOffset = new Date().getTimezoneOffset();
+
+ // If these aren't equal then offsetting is needed
+ // note the minus in front of serverOffset needed
+ // because C# and javascript return the inverse offset
+ $scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset);
+ }
+
//get the current user to see if we can localize this picker
userService.getCurrentUser().then(function (user) {
@@ -97,8 +140,17 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
});
if ($scope.hasDatetimePickerValue) {
- //assign value to plugin/picker
- var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment();
+ var dateVal;
+ //check if we are supposed to offset the time
+ if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset) {
+ //get the local time offset from the server
+ dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
+ $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
+ }
+ else {
+ //create a normal moment , no offset required
+ var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment();
+ }
element.datetimepicker("setValue", dateVal);
$scope.datetimePickerValue = dateVal.format($scope.model.config.format);
@@ -117,18 +169,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
- if ($scope.hasDatetimePickerValue) {
- var elementData = $element.find("div:first").data().DateTimePicker;
- if ($scope.model.config.pickTime) {
- $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss");
- }
- else {
- $scope.model.value = elementData.getDate().format("YYYY-MM-DD");
- }
- }
- else {
- $scope.model.value = null;
- }
+ setModelValue();
});
//unbind doc click event!
$scope.$on('$destroy', function () {
@@ -142,17 +183,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
});
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
- if ($scope.hasDatetimePickerValue) {
- if ($scope.model.config.pickTime) {
- $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss");
- }
- else {
- $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD");
- }
- }
- else {
- $scope.model.value = null;
- }
+ setModelValue();
});
//unbind doc click event!
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
index e6d49e5c6c..003e2ada60 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html
@@ -18,6 +18,10 @@
{{datePickerForm.datepicker.errorMsg}}
Invalid date
+
+
+