text: (function () {
      function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale, localStorageService, tourService) {
          //the null is important because we do an explicit bool check on this in the view
          //the avatar is by default the umbraco logo    
          $scope.authenticated = null;
          $scope.avatar = [
              { value: 'assets/img/application/logo.png' },
              { value: 'assets/img/application/logo@2x.png' },
              { value: 'assets/img/application/logo@3x.png' }
          ];
          $scope.touchDevice = appState.getGlobalState('touchDevice');
          $scope.removeNotification = function (index) {
              notificationsService.remove(index);
          };
          $scope.closeDialogs = function (event) {
              //only close dialogs if non-link and non-buttons are clicked
              var el = event.target.nodeName;
              var els = [
                  'INPUT',
                  'A',
                  'BUTTON'
              ];
              if (els.indexOf(el) >= 0) {
                  return;
              }
              var parents = $(event.target).parents('a,button');
              if (parents.length > 0) {
                  return;
              }
              //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
              var nav = $(event.target).parents('#dialog');
              if (nav.length === 1) {
                  return;
              }
              eventsService.emit('app.closeDialogs', event);
          };
          var evts = [];
          //when a user logs out or timesout
          evts.push(eventsService.on('app.notAuthenticated', function () {
              $scope.authenticated = null;
              $scope.user = null;
          }));
          evts.push(eventsService.on('app.userRefresh', function (evt) {
              userService.refreshCurrentUser().then(function (data) {
                  $scope.user = data;
                  //Load locale file
                  if ($scope.user.locale) {
                      tmhDynamicLocale.set($scope.user.locale);
                  }
                  if ($scope.user.avatars) {
                      $scope.avatar = [];
                      if (angular.isArray($scope.user.avatars)) {
                          for (var i = 0; i < $scope.user.avatars.length; i++) {
                              $scope.avatar.push({ value: $scope.user.avatars[i] });
                          }
                      }
                  }
              });
          }));
          //when the app is ready/user is logged in, setup the data
          evts.push(eventsService.on('app.ready', function (evt, data) {
              $scope.authenticated = data.authenticated;
              $scope.user = data.user;
              updateChecker.check().then(function (update) {
                  if (update && update !== 'null') {
                      if (update.type !== 'None') {
                          var notification = {
                              headline: 'Update available',
                              message: 'Click to download',
                              sticky: true,
                              type: 'info',
                              url: update.url
                          };
                          notificationsService.add(notification);
                      }
                  }
              });
              //if the user has changed we need to redirect to the root so they don't try to continue editing the
              //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
              if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
                  $location.path('/').search('');
                  historyService.removeAll();
                  treeService.clearCache();
                  //if the user changed, clearout local storage too - could contain sensitive data
                  localStorageService.clearAll();
              }
              //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data
              if (data.loginType === 'credentials') {
                  localStorageService.clearAll();
              }
              //Load locale file
              if ($scope.user.locale) {
                  tmhDynamicLocale.set($scope.user.locale);
              }
              if ($scope.user.avatars) {
                  $scope.avatar = [];
                  if (angular.isArray($scope.user.avatars)) {
                      for (var i = 0; i < $scope.user.avatars.length; i++) {
                          $scope.avatar.push({ value: $scope.user.avatars[i] });
                      }
                  }
              }
          }));
          evts.push(eventsService.on('app.ysod', function (name, error) {
              $scope.ysodOverlay = {
                  view: 'ysod',
                  error: error,
                  show: true
              };
          }));
          // manage the help dialog by subscribing to the showHelp appState
          $scope.drawer = {};
          evts.push(eventsService.on('appState.drawerState.changed', function (e, args) {
              // set view
              if (args.key === 'view') {
                  $scope.drawer.view = args.value;
              }
              // set custom model
              if (args.key === 'model') {
                  $scope.drawer.model = args.value;
              }
              // show / hide drawer
              if (args.key === 'showDrawer') {
                  $scope.drawer.show = args.value;
              }
          }));
          evts.push(eventsService.on('appState.tour.start', function (name, args) {
              $scope.tour = args;
              $scope.tour.show = true;
          }));
          evts.push(eventsService.on('appState.tour.end', function () {
              $scope.tour = null;
          }));
          evts.push(eventsService.on('appState.tour.complete', function () {
              $scope.tour = null;
          }));
          evts.push(eventsService.on('appState.backdrop', function (name, args) {
              $scope.backdrop = args;
          }));
          //ensure to unregister from all events!
          $scope.$on('$destroy', function () {
              for (var e in evts) {
                  eventsService.unsubscribe(evts[e]);
              }
          });
      }
      //register it
      angular.module('umbraco').controller('Umbraco.MainController', MainController).config(function (tmhDynamicLocaleProvider) {
          //Set url for locale files
          tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js');
      });
      /**
   * @ngdoc controller
   * @name Umbraco.NavigationController
   * @function
   *
   * @description
   * Handles the section area of the app
   *
   * @param {navigationService} navigationService A reference to the navigationService
   */
      function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
          //TODO: Need to think about this and an nicer way to acheive what this is doing.
          //the tree event handler i used to subscribe to the main tree click events
          $scope.treeEventHandler = $({});
          navigationService.setupTreeEvents($scope.treeEventHandler);
          //Put the navigation service on this scope so we can use it's methods/properties in the view.
          // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since
          //   when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one.
          $scope.nav = navigationService;
          // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope,
          // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice.
          $rootScope.nav = navigationService;
          //set up our scope vars
          $scope.showContextMenuDialog = false;
          $scope.showContextMenu = false;
          $scope.showSearchResults = false;
          $scope.menuDialogTitle = null;
          $scope.menuActions = [];
          $scope.menuNode = null;
          $scope.currentSection = appState.getSectionState('currentSection');
          $scope.showNavigation = appState.getGlobalState('showNavigation');
          //trigger search with a hotkey:
          keyboardService.bind('ctrl+shift+s', function () {
              navigationService.showSearch();
          });
          //trigger dialods with a hotkey:
          keyboardService.bind('esc', function () {
              eventsService.emit('app.closeDialogs');
          });
          $scope.selectedId = navigationService.currentId;
          var evts = [];
          //Listen for global state changes
          evts.push(eventsService.on('appState.globalState.changed', function (e, args) {
              if (args.key === 'showNavigation') {
                  $scope.showNavigation = args.value;
              }
          }));
          //Listen for menu state changes
          evts.push(eventsService.on('appState.menuState.changed', function (e, args) {
              if (args.key === 'showMenuDialog') {
                  $scope.showContextMenuDialog = args.value;
              }
              if (args.key === 'showMenu') {
                  $scope.showContextMenu = args.value;
              }
              if (args.key === 'dialogTitle') {
                  $scope.menuDialogTitle = args.value;
              }
              if (args.key === 'menuActions') {
                  $scope.menuActions = args.value;
              }
              if (args.key === 'currentNode') {
                  $scope.menuNode = args.value;
              }
          }));
          //Listen for section state changes
          evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
              var f = args;
              if (args.value.root && args.value.root.metaData.containsTrees === false) {
                  $rootScope.emptySection = true;
              } else {
                  $rootScope.emptySection = false;
              }
          }));
          //Listen for section state changes
          evts.push(eventsService.on('appState.sectionState.changed', function (e, args) {
              //section changed
              if (args.key === 'currentSection') {
                  $scope.currentSection = args.value;
              }
              //show/hide search results
              if (args.key === 'showSearchResults') {
                  $scope.showSearchResults = args.value;
              }
          }));
          //This reacts to clicks passed to the body element which emits a global call to close all dialogs
          evts.push(eventsService.on('app.closeDialogs', function (event) {
              if (appState.getGlobalState('stickyNavigation')) {
                  navigationService.hideNavigation();
                  //TODO: don't know why we need this? - we are inside of an angular event listener.
                  angularHelper.safeApply($scope);
              }
          }));
          //when a user logs out or timesout
          evts.push(eventsService.on('app.notAuthenticated', function () {
              $scope.authenticated = false;
          }));
          //when the application is ready and the user is authorized setup the data
          evts.push(eventsService.on('app.ready', function (evt, data) {
              $scope.authenticated = true;
          }));
          //this reacts to the options item in the tree
          //todo, migrate to nav service
          $scope.searchShowMenu = function (ev, args) {
              //always skip default
              args.skipDefault = true;
              navigationService.showMenu(ev, args);
          };
          //todo, migrate to nav service
          $scope.searchHide = function () {
              navigationService.hideSearch();
          };
          //the below assists with hiding/showing the tree
          var treeActive = false;
          //Sets a service variable as soon as the user hovers the navigation with the mouse
          //used by the leaveTree method to delay hiding
          $scope.enterTree = function (event) {
              treeActive = true;
          };
          // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
          $scope.leaveTree = function (event) {
              //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down
              if (!event) {
                  return;
              }
              if (!appState.getGlobalState('touchDevice')) {
                  treeActive = false;
                  $timeout(function () {
                      if (!treeActive) {
                          navigationService.hideTree();
                      }
                  }, 300);
              }
          };
          //ensure to unregister from all events!
          $scope.$on('$destroy', function () {
              for (var e in evts) {
                  eventsService.unsubscribe(evts[e]);
              }
          });
      }
      //register it
      angular.module('umbraco').controller('Umbraco.NavigationController', NavigationController);
      /**
   * @ngdoc controller
   * @name Umbraco.SearchController
   * @function
   * 
   * @description
   * Controls the search functionality in the site
   *  
   */
      function SearchController($scope, searchService, $log, $location, navigationService, $q) {
          $scope.searchTerm = null;
          $scope.searchResults = [];
          $scope.isSearching = false;
          $scope.selectedResult = -1;
          $scope.navigateResults = function (ev) {
              //38: up 40: down, 13: enter
              switch (ev.keyCode) {
              case 38:
                  iterateResults(true);
                  break;
              case 40:
                  iterateResults(false);
                  break;
              case 13:
                  if ($scope.selectedItem) {
                      $location.path($scope.selectedItem.editorPath);
                      navigationService.hideSearch();
                  }
                  break;
              }
          };
          var group = undefined;
          var groupNames = [];
          var groupIndex = -1;
          var itemIndex = -1;
          $scope.selectedItem = undefined;
          $scope.clearSearch = function () {
              $scope.searchTerm = null;
          };
          function iterateResults(up) {
              //default group
              if (!group) {
                  for (var g in $scope.groups) {
                      if ($scope.groups.hasOwnProperty(g)) {
                          groupNames.push(g);
                      }
                  }
                  //Sorting to match the groups order
                  groupNames.sort();
                  group = $scope.groups[groupNames[0]];
                  groupIndex = 0;
              }
              if (up) {
                  if (itemIndex === 0) {
                      if (groupIndex === 0) {
                          gotoGroup(Object.keys($scope.groups).length - 1, true);
                      } else {
                          gotoGroup(groupIndex - 1, true);
                      }
                  } else {
                      gotoItem(itemIndex - 1);
                  }
              } else {
                  if (itemIndex < group.results.length - 1) {
                      gotoItem(itemIndex + 1);
                  } else {
                      if (groupIndex === Object.keys($scope.groups).length - 1) {
                          gotoGroup(0);
                      } else {
                          gotoGroup(groupIndex + 1);
                      }
                  }
              }
          }
          function gotoGroup(index, up) {
              groupIndex = index;
              group = $scope.groups[groupNames[groupIndex]];
              if (up) {
                  gotoItem(group.results.length - 1);
              } else {
                  gotoItem(0);
              }
          }
          function gotoItem(index) {
              itemIndex = index;
              $scope.selectedItem = group.results[itemIndex];
          }
          //used to cancel any request in progress if another one needs to take it's place
          var canceler = null;
          $scope.$watch('searchTerm', _.debounce(function (newVal, oldVal) {
              $scope.$apply(function () {
                  $scope.hasResults = false;
                  if ($scope.searchTerm) {
                      if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                          //Resetting for brand new search
                          group = undefined;
                          groupNames = [];
                          groupIndex = -1;
                          itemIndex = -1;
                          $scope.isSearching = true;
                          navigationService.showSearch();
                          $scope.selectedItem = undefined;
                          //a canceler exists, so perform the cancelation operation and reset
                          if (canceler) {
                              canceler.resolve();
                              canceler = $q.defer();
                          } else {
                              canceler = $q.defer();
                          }
                          searchService.searchAll({
                              term: $scope.searchTerm,
                              canceler: canceler
                          }).then(function (result) {
                              //result is a dictionary of group Title and it's results
                              var filtered = {};
                              _.each(result, function (value, key) {
                                  if (value.results.length > 0) {
                                      filtered[key] = value;
                                  }
                              });
                              $scope.groups = filtered;
                              // check if search has results
                              $scope.hasResults = Object.keys($scope.groups).length > 0;
                              //set back to null so it can be re-created
                              canceler = null;
                              $scope.isSearching = false;
                          });
                      }
                  } else {
                      $scope.isSearching = false;
                      navigationService.hideSearch();
                      $scope.selectedItem = undefined;
                  }
              });
          }, 200));
      }
      //register it
      angular.module('umbraco').controller('Umbraco.SearchController', SearchController);
      /**
   * @ngdoc controller
   * @name Umbraco.MainController
   * @function
   * 
   * @description
   * The controller for the AuthorizeUpgrade login page
   * 
   */
      function AuthorizeUpgradeController($scope, $window) {
          //Add this method to the scope - this method will be called by the login dialog controller when the login is successful
          // then we'll handle the redirect.
          $scope.submit = function (event) {
              var qry = $window.location.search.trimStart('?').split('&');
              var redir = _.find(qry, function (item) {
                  return item.startsWith('redir=');
              });
              if (redir) {
                  $window.location = decodeURIComponent(redir.split('=')[1]);
              } else {
                  $window.location = '/';
              }
          };
      }
      angular.module('umbraco').controller('Umbraco.AuthorizeUpgradeController', AuthorizeUpgradeController);
      /**
   * @ngdoc controller
   * @name Umbraco.DashboardController
   * @function
   * 
   * @description
   * Controls the dashboards of the application
   * 
   */
      function DashboardController($scope, $routeParams, dashboardResource, localizationService) {
          $scope.page = {};
          $scope.page.nameLocked = true;
          $scope.page.loading = true;
          $scope.dashboard = {};
          localizationService.localize('sections_' + $routeParams.section).then(function (name) {
              $scope.dashboard.name = name;
          });
          dashboardResource.getDashboard($routeParams.section).then(function (tabs) {
              $scope.dashboard.tabs = tabs;
              $scope.page.loading = false;
          });
      }
      //register it
      angular.module('umbraco').controller('Umbraco.DashboardController', DashboardController);
      angular.module('umbraco').controller('Umbraco.Dialogs.ApprovedColorPickerController', function ($scope, $http, umbPropEditorHelper, assetsService) {
          assetsService.loadJs('lib/cssparser/cssparser.js', $scope).then(function () {
              var cssPath = $scope.dialogData.cssPath;
              $scope.cssClass = $scope.dialogData.cssClass;
              $scope.classes = [];
              $scope.change = function (newClass) {
                  $scope.model.value = newClass;
              };
              $http.get(cssPath).success(function (data) {
                  var parser = new CSSParser();
                  $scope.classes = parser.parse(data, false, false).cssRules;
                  $scope.classes.splice(0, 0, 'noclass');
              });
              assetsService.loadCss('/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css', $scope);
              assetsService.loadCss(cssPath, $scope);
          });
      });
      function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
          $scope.defaultButton = null;
          $scope.subButtons = [];
          var dialogOptions = $scope.$parent.dialogOptions;
          // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
          function performSave(args) {
              contentEditingHelper.contentEditorPerformSave({
                  statusMessage: args.statusMessage,
                  saveMethod: args.saveMethod,
                  scope: $scope,
                  content: $scope.content
              }).then(function (content) {
                  //success            
                  if (dialogOptions.closeOnSave) {
                      $scope.submit(content);
                  }
              }, function (err) {
              });
          }
          function filterTabs(entity, blackList) {
              if (blackList) {
                  _.each(entity.tabs, function (tab) {
                      tab.hide = _.contains(blackList, tab.alias);
                  });
              }
              return entity;
          }
          ;
          function init(content) {
              var buttons = contentEditingHelper.configureContentEditorButtons({
                  create: $routeParams.create,
                  content: content,
                  methods: {
                      saveAndPublish: $scope.saveAndPublish,
                      sendToPublish: $scope.sendToPublish,
                      save: $scope.save,
                      unPublish: angular.noop
                  }
              });
              $scope.defaultButton = buttons.defaultButton;
              $scope.subButtons = buttons.subButtons;
              //This is a total hack but we have really no other way of sharing data to the property editors of this
              // content item, so we'll just set the property on the content item directly
              $scope.content.isDialogEditor = true;
              editorState.set($scope.content);
          }
          //check if the entity is being passed in, otherwise load it from the server
          if (angular.isObject(dialogOptions.entity)) {
              $scope.loaded = true;
              $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter);
              init($scope.content);
          } else {
              contentResource.getById(dialogOptions.id).then(function (data) {
                  $scope.loaded = true;
                  $scope.content = filterTabs(data, dialogOptions.tabFilter);
                  init($scope.content);
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
              });
          }
          $scope.sendToPublish = function () {
              performSave({
                  saveMethod: contentResource.sendToPublish,
                  statusMessage: 'Sending...'
              });
          };
          $scope.saveAndPublish = function () {
              performSave({
                  saveMethod: contentResource.publish,
                  statusMessage: 'Publishing...'
              });
          };
          $scope.save = function () {
              performSave({
                  saveMethod: contentResource.save,
                  statusMessage: 'Saving...'
              });
          };
          // this method is called for all action buttons and then we proxy based on the btn definition
          $scope.performAction = function (btn) {
              if (!btn || !angular.isFunction(btn.handler)) {
                  throw 'btn.handler must be a function reference';
              }
              if (!$scope.busy) {
                  btn.handler.apply(this);
              }
          };
      }
      angular.module('umbraco').controller('Umbraco.Dialogs.Content.EditController', ContentEditDialogController);
      angular.module('umbraco').controller('Umbraco.Dialogs.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) {
          $scope.section = $routeParams.section;
          $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
          if (!$scope.section) {
              $scope.section = 'content';
          }
          $scope.sectionName = $scope.section;
          var rq = {};
          rq.section = $scope.section;
          //translate section name
          localizationService.localize('sections_' + rq.section).then(function (value) {
              $scope.sectionName = value;
          });
          userService.getCurrentUser().then(function (user) {
              rq.lang = user.locale;
              if ($routeParams.url) {
                  rq.path = decodeURIComponent($routeParams.url);
                  if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                      rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                  }
                  if (rq.path.indexOf('.aspx') > 0) {
                      rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                  }
              } else {
                  rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
              }
              helpService.findHelp(rq).then(function (topics) {
                  $scope.topics = topics;
              });
              helpService.findVideos(rq).then(function (videos) {
                  $scope.videos = videos;
              });
          });
      });
      //used for the icon picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.IconPickerController', function ($scope, iconHelper) {
          iconHelper.getIcons().then(function (icons) {
              $scope.icons = icons;
          });
          $scope.submitClass = function (icon) {
              if ($scope.color) {
                  $scope.submit(icon + ' ' + $scope.color);
              } else {
                  $scope.submit(icon);
              }
          };
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.InsertMacroController
   * @function
   * 
   * @description
   * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this 
   * is actually loaded into an iframe with full html.
   */
      function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) {
          /** changes the view to edit the params of the selected macro */
          function editParams() {
              //get the macro params if there are any
              macroResource.getMacroParameters($scope.selectedMacro.id).then(function (data) {
                  //go to next page if there are params otherwise we can just exit
                  if (!angular.isArray(data) || data.length === 0) {
                      //we can just exist!
                      submitForm();
                  } else {
                      $scope.wizardStep = 'paramSelect';
                      $scope.macroParams = data;
                      //fill in the data if we are editing this macro
                      if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) {
                          _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) {
                              var prop = _.find($scope.macroParams, function (item) {
                                  return item.alias == key;
                              });
                              if (prop) {
                                  if (_.isString(val)) {
                                      //we need to unescape values as they have most likely been escaped while inserted 
                                      val = _.unescape(val);
                                      //detect if it is a json string
                                      if (val.detectIsJson()) {
                                          try {
                                              //Parse it to json
                                              prop.value = angular.fromJson(val);
                                          } catch (e) {
                                              // not json
                                              prop.value = val;
                                          }
                                      } else {
                                          prop.value = val;
                                      }
                                  } else {
                                      prop.value = val;
                                  }
                              }
                          });
                      }
                  }
              });
          }
          /** submit the filled out macro params */
          function submitForm() {
              //collect the value data, close the dialog and send the data back to the caller
              //create a dictionary for the macro params
              var paramDictionary = {};
              _.each($scope.macroParams, function (item) {
                  var val = item.value;
                  if (item.value != null && item.value != undefined && !_.isString(item.value)) {
                      try {
                          val = angular.toJson(val);
                      } catch (e) {
                      }
                  }
                  //each value needs to be xml escaped!! since the value get's stored as an xml attribute
                  paramDictionary[item.alias] = _.escape(val);
              });
              //need to find the macro alias for the selected id
              var macroAlias = $scope.selectedMacro.alias;
              //get the syntax based on the rendering engine
              var syntax;
              if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'WebForms') {
                  syntax = macroService.generateWebFormsSyntax({
                      macroAlias: macroAlias,
                      macroParamsDictionary: paramDictionary
                  });
              } else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'Mvc') {
                  syntax = macroService.generateMvcSyntax({
                      macroAlias: macroAlias,
                      macroParamsDictionary: paramDictionary
                  });
              } else {
                  syntax = macroService.generateMacroSyntax({
                      macroAlias: macroAlias,
                      macroParamsDictionary: paramDictionary
                  });
              }
              $scope.submit({
                  syntax: syntax,
                  macroAlias: macroAlias,
                  macroParamsDictionary: paramDictionary
              });
          }
          $scope.macros = [];
          $scope.selectedMacro = null;
          $scope.wizardStep = 'macroSelect';
          $scope.macroParams = [];
          $scope.submitForm = function () {
              if (formHelper.submitForm({ scope: $scope })) {
                  formHelper.resetForm({ scope: $scope });
                  if ($scope.wizardStep === 'macroSelect') {
                      editParams();
                  } else {
                      submitForm();
                  }
              }
          };
          //here we check to see if we've been passed a selected macro and if so we'll set the
          //editor to start with parameter editing
          if ($scope.dialogData && $scope.dialogData.macroData) {
              $scope.wizardStep = 'paramSelect';
          }
          //get the macro list - pass in a filter if it is only for rte
          entityResource.getAll('Macro', $scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) {
              //if 'allowedMacros' is specified, we need to filter
              if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) {
                  $scope.macros = _.filter(data, function (d) {
                      return _.contains($scope.dialogData.allowedMacros, d.alias);
                  });
              } else {
                  $scope.macros = data;
              }
              //check if there's a pre-selected macro and if it exists
              if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
                  var found = _.find(data, function (item) {
                      return item.alias === $scope.dialogData.macroData.macroAlias;
                  });
                  if (found) {
                      //select the macro and go to next screen
                      $scope.selectedMacro = found;
                      editParams();
                      return;
                  }
              }
              //we don't have a pre-selected macro so ensure the correct step is set
              $scope.wizardStep = 'macroSelect';
          });
      }
      angular.module('umbraco').controller('Umbraco.Dialogs.InsertMacroController', InsertMacroController);
      /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.LegacyDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function LegacyDeleteController($scope, legacyResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              legacyResource.deleteItem({
                  nodeId: $scope.currentNode.id,
                  nodeType: $scope.currentNode.nodeType,
                  alias: $scope.currentNode.name
              }).then(function () {
                  $scope.currentNode.loading = false;
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Dialogs.LegacyDeleteController', LegacyDeleteController);
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.LinkPickerController', function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
          var dialogOptions = $scope.dialogOptions;
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          $scope.dialogTreeEventHandler = $({});
          $scope.target = {};
          $scope.searchInfo = {
              searchFromId: null,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          if (dialogOptions.currentTarget) {
              $scope.target = dialogOptions.currentTarget;
              //if we have a node ID, we fetch the current node to build the form data
              if ($scope.target.id || $scope.target.udi) {
                  var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
                  if (!$scope.target.path) {
                      entityResource.getPath(id, 'Document').then(function (path) {
                          $scope.target.path = path;
                          //now sync the tree to this path
                          $scope.dialogTreeEventHandler.syncTree({
                              path: $scope.target.path,
                              tree: 'content'
                          });
                      });
                  }
                  contentResource.getNiceUrl(id).then(function (url) {
                      $scope.target.url = url;
                  });
              }
          }
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if (args.node.metaData.listViewNode) {
                  //check if list view 'search' node was selected
                  $scope.searchInfo.showSearch = true;
                  $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
                  $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
              } else {
                  eventsService.emit('dialogs.linkPicker.select', args);
                  if ($scope.currentNode) {
                      //un-select if there's a current one selected
                      $scope.currentNode.selected = false;
                  }
                  $scope.currentNode = args.node;
                  $scope.currentNode.selected = true;
                  $scope.target.id = args.node.id;
                  $scope.target.udi = args.node.udi;
                  $scope.target.name = args.node.name;
                  if (args.node.id < 0) {
                      $scope.target.url = '/';
                  } else {
                      contentResource.getNiceUrl(args.node.id).then(function (url) {
                          $scope.target.url = url;
                      });
                  }
                  if (!angular.isUndefined($scope.target.isMedia)) {
                      delete $scope.target.isMedia;
                  }
              }
          }
          function nodeExpandedHandler(ev, args) {
              if (angular.isArray(args.children)) {
                  //iterate children
                  _.each(args.children, function (child) {
                      //check if any of the items are list views, if so we need to add a custom 
                      // child: A node to activate the search
                      if (child.metaData.isContainer) {
                          child.hasChildren = true;
                          child.children = [{
                                  level: child.level + 1,
                                  hasChildren: false,
                                  name: searchText,
                                  metaData: { listViewNode: child },
                                  cssClass: 'icon umb-tree-icon sprTree icon-search',
                                  cssClasses: ['not-published']
                              }];
                      }
                  });
              }
          }
          $scope.switchToMediaPicker = function () {
              userService.getCurrentUser().then(function (userData) {
                  dialogService.mediaPicker({
                      startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0],
                      callback: function (media) {
                          $scope.target.id = media.id;
                          $scope.target.isMedia = true;
                          $scope.target.name = media.name;
                          $scope.target.url = mediaHelper.resolveFile(media);
                      }
                  });
              });
          };
          $scope.hideSearch = function () {
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = null;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          // method to select a search result 
          $scope.selectResult = function (evt, result) {
              result.selected = result.selected === true ? false : true;
              nodeSelectHandler(evt, {
                  event: evt,
                  node: result
              });
          };
          //callback when there are search results 
          $scope.onSearchResults = function (results) {
              $scope.searchInfo.results = results;
              $scope.searchInfo.showSearch = true;
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
          });
      });
      angular.module('umbraco').controller('Umbraco.Dialogs.LoginController', function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) {
          $scope.invitedUser = null;
          $scope.invitedUserPasswordModel = {
              password: '',
              confirmPassword: '',
              buttonState: '',
              passwordPolicies: null,
              passwordPolicyText: ''
          };
          $scope.loginStates = { submitButton: 'init' };
          $scope.avatarFile = {
              filesHolder: null,
              uploadStatus: null,
              uploadProgress: 0,
              maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB',
              acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes),
              uploaded: false
          };
          $scope.togglePassword = function () {
              var elem = $('form[name=\'loginForm\'] input[name=\'password\']');
              elem.attr('type', elem.attr('type') === 'text' ? 'password' : 'text');
          };
          function init() {
              // Check if it is a new user
              var inviteVal = $location.search().invite;
              if (inviteVal && (inviteVal === '1' || inviteVal === '2')) {
                  $q.all([
                      //get the current invite user
                      authResource.getCurrentInvitedUser().then(function (data) {
                          $scope.invitedUser = data;
                      }, function () {
                          //it failed so we should remove the search
                          $location.search('invite', null);
                      }),
                      //get the membership provider config for password policies
                      authResource.getMembershipProviderConfig().then(function (data) {
                          $scope.invitedUserPasswordModel.passwordPolicies = data;
                          //localize the text
                          localizationService.localize('errorHandling_errorInPasswordFormat', [
                              $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength,
                              $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars
                          ]).then(function (data) {
                              $scope.invitedUserPasswordModel.passwordPolicyText = data;
                          });
                      })
                  ]).then(function () {
                      $scope.inviteStep = Number(inviteVal);
                  });
              }
          }
          $scope.changeAvatar = function (files, event) {
              if (files && files.length > 0) {
                  upload(files[0]);
              }
          };
          $scope.getStarted = function () {
              $location.search('invite', null);
              $scope.submit(true);
          };
          function upload(file) {
              $scope.avatarFile.uploadProgress = 0;
              Upload.upload({
                  url: umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostSetAvatar'),
                  fields: {},
                  file: file
              }).progress(function (evt) {
                  if ($scope.avatarFile.uploadStatus !== 'done' && $scope.avatarFile.uploadStatus !== 'error') {
                      // set uploading status on file
                      $scope.avatarFile.uploadStatus = 'uploading';
                      // calculate progress in percentage
                      var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                      // set percentage property on file
                      $scope.avatarFile.uploadProgress = progressPercentage;
                  }
              }).success(function (data, status, headers, config) {
                  $scope.avatarFile.uploadProgress = 100;
                  // set done status on file
                  $scope.avatarFile.uploadStatus = 'done';
                  $scope.invitedUser.avatars = data;
                  $scope.avatarFile.uploaded = true;
              }).error(function (evt, status, headers, config) {
                  // set status done
                  $scope.avatarFile.uploadStatus = 'error';
                  // If file not found, server will return a 404 and display this message
                  if (status === 404) {
                      $scope.avatarFile.serverErrorMessage = 'File not found';
                  } else if (status == 400) {
                      //it's a validation error
                      $scope.avatarFile.serverErrorMessage = evt.message;
                  } else {
                      //it's an unhandled error
                      //if the service returns a detailed error
                      if (evt.InnerException) {
                          $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                          //Check if its the common "too large file" exception
                          if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                              $scope.avatarFile.serverErrorMessage = 'File too large to upload';
                          }
                      } else if (evt.Message) {
                          $scope.avatarFile.serverErrorMessage = evt.Message;
                      }
                  }
              });
          }
          $scope.inviteSavePassword = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      statusMessage: 'Saving...'
                  })) {
                  $scope.invitedUserPasswordModel.buttonState = 'busy';
                  currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password).then(function (data) {
                      //success
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      $scope.invitedUserPasswordModel.buttonState = 'success';
                      //set the user and set them as logged in
                      $scope.invitedUser = data;
                      userService.setAuthenticationSuccessful(data);
                      $scope.inviteStep = 2;
                  }, function (err) {
                      //error
                      formHelper.handleError(err);
                      $scope.invitedUserPasswordModel.buttonState = 'error';
                  });
              }
          };
          var setFieldFocus = function (form, field) {
              $timeout(function () {
                  $('form[name=\'' + form + '\'] input[name=\'' + field + '\']').focus();
              });
          };
          var twoFactorloginDialog = null;
          function show2FALoginDialog(view, callback) {
              if (!twoFactorloginDialog) {
                  twoFactorloginDialog = dialogService.open({
                      //very special flag which means that global events cannot close this dialog
                      manualClose: true,
                      template: view,
                      modalClass: 'login-overlay',
                      animation: 'slide',
                      show: true,
                      callback: callback
                  });
              }
          }
          function resetInputValidation() {
              $scope.confirmPassword = '';
              $scope.password = '';
              $scope.login = '';
              if ($scope.loginForm) {
                  $scope.loginForm.username.$setValidity('auth', true);
                  $scope.loginForm.password.$setValidity('auth', true);
              }
              if ($scope.requestPasswordResetForm) {
                  $scope.requestPasswordResetForm.email.$setValidity('auth', true);
              }
              if ($scope.setPasswordForm) {
                  $scope.setPasswordForm.password.$setValidity('auth', true);
                  $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
              }
          }
          $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
          $scope.showLogin = function () {
              $scope.errorMsg = '';
              resetInputValidation();
              $scope.view = 'login';
              setFieldFocus('loginForm', 'username');
          };
          $scope.showRequestPasswordReset = function () {
              $scope.errorMsg = '';
              resetInputValidation();
              $scope.view = 'request-password-reset';
              $scope.showEmailResetConfirmation = false;
              setFieldFocus('requestPasswordResetForm', 'email');
          };
          $scope.showSetPassword = function () {
              $scope.errorMsg = '';
              resetInputValidation();
              $scope.view = 'set-password';
              setFieldFocus('setPasswordForm', 'password');
          };
          var d = new Date();
          var konamiGreetings = new Array('Suze Sunday', 'Malibu Monday', 'Tequila Tuesday', 'Whiskey Wednesday', 'Negroni Day', 'Fernet Friday', 'Sancerre Saturday');
          var konamiMode = $cookies.konamiLogin;
          if (konamiMode == '1') {
              $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()];
          } else {
              localizationService.localize('login_greeting' + d.getDay()).then(function (label) {
                  $scope.greeting = label;
              });    // weekday[d.getDay()];
          }
          $scope.errorMsg = '';
          $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
          $scope.externalLoginProviders = externalLoginInfo.providers;
          $scope.externalLoginInfo = externalLoginInfo;
          $scope.resetPasswordCodeInfo = resetPasswordCodeInfo;
          $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
          $scope.activateKonamiMode = function () {
              if ($cookies.konamiLogin == '1') {
                  // somehow I can't update the cookie value using $cookies, so going native
                  document.cookie = 'konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
                  document.location.reload();
              } else {
                  document.cookie = 'konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;';
                  $scope.$apply(function () {
                      $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()];
                  });
              }
          };
          $scope.loginSubmit = function (login, password) {
              //TODO: Do validation properly like in the invite password update
              //if the login and password are not empty we need to automatically
              // validate them - this is because if there are validation errors on the server
              // then the user has to change both username & password to resubmit which isn't ideal,
              // so if they're not empty, we'll just make sure to set them to valid.
              if (login && password && login.length > 0 && password.length > 0) {
                  $scope.loginForm.username.$setValidity('auth', true);
                  $scope.loginForm.password.$setValidity('auth', true);
              }
              if ($scope.loginForm.$invalid) {
                  return;
              }
              $scope.loginStates.submitButton = 'busy';
              userService.authenticate(login, password).then(function (data) {
                  $scope.loginStates.submitButton = 'success';
                  $scope.submit(true);
              }, function (reason) {
                  //is Two Factor required?
                  if (reason.status === 402) {
                      $scope.errorMsg = 'Additional authentication required';
                      show2FALoginDialog(reason.data.twoFactorView, $scope.submit);
                  } else {
                      $scope.loginStates.submitButton = 'error';
                      $scope.errorMsg = reason.errorMsg;
                      //set the form inputs to invalid
                      $scope.loginForm.username.$setValidity('auth', false);
                      $scope.loginForm.password.$setValidity('auth', false);
                  }
              });
              //setup a watch for both of the model values changing, if they change
              // while the form is invalid, then revalidate them so that the form can
              // be submitted again.
              $scope.loginForm.username.$viewChangeListeners.push(function () {
                  if ($scope.loginForm.$invalid) {
                      $scope.loginForm.username.$setValidity('auth', true);
                      $scope.loginForm.password.$setValidity('auth', true);
                  }
              });
              $scope.loginForm.password.$viewChangeListeners.push(function () {
                  if ($scope.loginForm.$invalid) {
                      $scope.loginForm.username.$setValidity('auth', true);
                      $scope.loginForm.password.$setValidity('auth', true);
                  }
              });
          };
          $scope.requestPasswordResetSubmit = function (email) {
              //TODO: Do validation properly like in the invite password update
              if (email && email.length > 0) {
                  $scope.requestPasswordResetForm.email.$setValidity('auth', true);
              }
              $scope.showEmailResetConfirmation = false;
              if ($scope.requestPasswordResetForm.$invalid) {
                  return;
              }
              $scope.errorMsg = '';
              authResource.performRequestPasswordReset(email).then(function () {
                  //remove the email entered
                  $scope.email = '';
                  $scope.showEmailResetConfirmation = true;
              }, function (reason) {
                  $scope.errorMsg = reason.errorMsg;
                  $scope.requestPasswordResetForm.email.$setValidity('auth', false);
              });
              $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () {
                  if ($scope.requestPasswordResetForm.email.$invalid) {
                      $scope.requestPasswordResetForm.email.$setValidity('auth', true);
                  }
              });
          };
          $scope.setPasswordSubmit = function (password, confirmPassword) {
              $scope.showSetPasswordConfirmation = false;
              if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) {
                  $scope.setPasswordForm.password.$setValidity('auth', true);
                  $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
              }
              if ($scope.setPasswordForm.$invalid) {
                  return;
              }
              //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword
              authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode).then(function () {
                  $scope.showSetPasswordConfirmation = true;
                  $scope.resetComplete = true;
                  //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again
                  resetPasswordCodeInfo.resetCodeModel = null;
              }, function (reason) {
                  if (reason.data && reason.data.Message) {
                      $scope.errorMsg = reason.data.Message;
                  } else {
                      $scope.errorMsg = reason.errorMsg;
                  }
                  $scope.setPasswordForm.password.$setValidity('auth', false);
                  $scope.setPasswordForm.confirmPassword.$setValidity('auth', false);
              });
              $scope.setPasswordForm.password.$viewChangeListeners.push(function () {
                  if ($scope.setPasswordForm.password.$invalid) {
                      $scope.setPasswordForm.password.$setValidity('auth', true);
                  }
              });
              $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () {
                  if ($scope.setPasswordForm.confirmPassword.$invalid) {
                      $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
                  }
              });
          };
          //Now, show the correct panel:
          if ($scope.resetPasswordCodeInfo.resetCodeModel) {
              $scope.showSetPassword();
          } else if ($scope.resetPasswordCodeInfo.errors.length > 0) {
              $scope.view = 'password-reset-code-expired';
          } else {
              $scope.showLogin();
          }
          init();
      });
      //used for the macro picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.MacroPickerController', function ($scope, macroFactory, umbPropEditorHelper) {
          $scope.macros = macroFactory.all(true);
          $scope.dialogMode = 'list';
          $scope.configureMacro = function (macro) {
              $scope.dialogMode = 'configure';
              $scope.dialogData.macro = macroFactory.getMacro(macro.alias);
              //set the correct view for each item
              for (var i = 0; i < dialogData.macro.properties.length; i++) {
                  dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view);
              }
          };
      });
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.onlyImages = dialogOptions.onlyImages;
          $scope.showDetails = dialogOptions.showDetails;
          $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false;
          $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
          $scope.cropSize = dialogOptions.cropSize;
          //preload selected item
          $scope.target = undefined;
          if (dialogOptions.currentTarget) {
              $scope.target = dialogOptions.currentTarget;
          }
          $scope.acceptedMediatypes = [];
          mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) {
              $scope.acceptedMediatypes = types;
          });
          $scope.upload = function (v) {
              angular.element('.umb-file-dropzone-directive .file-select').click();
          };
          $scope.dragLeave = function (el, event) {
              $scope.activeDrag = false;
          };
          $scope.dragEnter = function (el, event) {
              $scope.activeDrag = true;
          };
          $scope.submitFolder = function (e) {
              if (e.keyCode === 13) {
                  e.preventDefault();
                  mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) {
                      $scope.showFolderInput = false;
                      $scope.newFolderName = '';
                      //we've added a new folder so lets clear the tree cache for that specific item
                      treeService.clearCache({
                          cacheKey: '__media',
                          //this is the main media tree cache key
                          childrenOf: data.parentId
                      });
                      $scope.gotoFolder(data);
                  });
              }
          };
          $scope.gotoFolder = function (folder) {
              if (!folder) {
                  folder = {
                      id: -1,
                      name: 'Media',
                      icon: 'icon-folder'
                  };
              }
              if (folder.id > 0) {
                  entityResource.getAncestors(folder.id, 'media').then(function (anc) {
                      // anc.splice(0,1);  
                      $scope.path = _.filter(anc, function (f) {
                          return f.path.indexOf($scope.startNodeId) !== -1;
                      });
                  });
                  mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) {
                      $scope.acceptedMediatypes = types;
                  });
              } else {
                  $scope.path = [];
              }
              //mediaResource.rootMedia()
              mediaResource.getChildren(folder.id).then(function (data) {
                  $scope.searchTerm = '';
                  $scope.images = data.items ? data.items : [];
              });
              $scope.currentFolder = folder;
          };
          $scope.clickHandler = function (image, ev, select) {
              ev.preventDefault();
              if (image.isFolder && !select) {
                  $scope.gotoFolder(image);
              } else {
                  eventsService.emit('dialogs.mediaPicker.select', image);
                  //we have 3 options add to collection (if multi) show details, or submit it right back to the callback
                  if ($scope.multiPicker) {
                      $scope.select(image);
                      image.cssclass = $scope.dialogData.selection.indexOf(image) > -1 ? 'selected' : '';
                  } else if ($scope.showDetails) {
                      $scope.target = image;
                      $scope.target.url = mediaHelper.resolveFile(image);
                  } else {
                      $scope.submit(image);
                  }
              }
          };
          $scope.exitDetails = function () {
              if (!$scope.currentFolder) {
                  $scope.gotoFolder();
              }
              $scope.target = undefined;
          };
          $scope.onUploadComplete = function () {
              $scope.gotoFolder($scope.currentFolder);
          };
          $scope.onFilesQueue = function () {
              $scope.activeDrag = false;
          };
          //default root item
          if (!$scope.target) {
              $scope.gotoFolder({
                  id: $scope.startNodeId,
                  name: 'Media',
                  icon: 'icon-folder'
              });
          }
      });
      //used for the member picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          $scope.multiPicker = dialogOptions.multiPicker;
          /** Method used for selecting a node */
          function select(text, id) {
              if (dialogOptions.multiPicker) {
                  $scope.select(id);
              } else {
                  $scope.submit(id);
              }
          }
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              eventsService.emit('dialogs.memberGroupPicker.select', args);
              //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
              //from the server in this method.
              select(args.node.name, args.node.id);
              //toggle checked state
              args.node.selected = args.node.selected === true ? false : true;
          }
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      angular.module('umbraco').controller('Umbraco.Dialogs.RteEmbedController', function ($scope, $http, umbRequestHelper) {
          $scope.form = {};
          $scope.form.url = '';
          $scope.form.width = 360;
          $scope.form.height = 240;
          $scope.form.constrain = true;
          $scope.form.preview = '';
          $scope.form.success = false;
          $scope.form.info = '';
          $scope.form.supportsDimensions = false;
          var origWidth = 500;
          var origHeight = 300;
          $scope.showPreview = function () {
              if ($scope.form.url) {
                  $scope.form.show = true;
                  $scope.form.preview = '<div class="umb-loader" style="height: 10px; margin: 10px 0px;"></div>';
                  $scope.form.info = '';
                  $scope.form.success = false;
                  $http({
                      method: 'GET',
                      url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'),
                      params: {
                          url: $scope.form.url,
                          width: $scope.form.width,
                          height: $scope.form.height
                      }
                  }).success(function (data) {
                      $scope.form.preview = '';
                      switch (data.Status) {
                      case 0:
                          //not supported
                          $scope.form.info = 'Not supported';
                          break;
                      case 1:
                          //error
                          $scope.form.info = 'Could not embed media - please ensure the URL is valid';
                          break;
                      case 2:
                          $scope.form.preview = data.Markup;
                          $scope.form.supportsDimensions = data.SupportsDimensions;
                          $scope.form.success = true;
                          break;
                      }
                  }).error(function () {
                      $scope.form.supportsDimensions = false;
                      $scope.form.preview = '';
                      $scope.form.info = 'Could not embed media - please ensure the URL is valid';
                  });
              } else {
                  $scope.form.supportsDimensions = false;
                  $scope.form.preview = '';
                  $scope.form.info = 'Please enter a URL';
              }
          };
          $scope.changeSize = function (type) {
              var width, height;
              if ($scope.form.constrain) {
                  width = parseInt($scope.form.width, 10);
                  height = parseInt($scope.form.height, 10);
                  if (type == 'width') {
                      origHeight = Math.round(width / origWidth * height);
                      $scope.form.height = origHeight;
                  } else {
                      origWidth = Math.round(height / origHeight * width);
                      $scope.form.width = origWidth;
                  }
              }
              if ($scope.form.url != '') {
                  $scope.showPreview();
              }
          };
          $scope.insert = function () {
              $scope.submit($scope.form.preview);
          };
      });
      angular.module('umbraco').controller('Umbraco.Dialogs.Template.QueryBuilderController', function ($scope, $http, dialogService) {
          $http.get('backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties').then(function (response) {
              $scope.properties = response.data;
          });
          $http.get('backoffice/UmbracoApi/TemplateQuery/GetContentTypes').then(function (response) {
              $scope.contentTypes = response.data;
          });
          $http.get('backoffice/UmbracoApi/TemplateQuery/GetFilterConditions').then(function (response) {
              $scope.conditions = response.data;
          });
          $scope.query = {
              contentType: { name: 'Everything' },
              source: { name: 'My website' },
              filters: [{
                      property: undefined,
                      operator: undefined
                  }],
              sort: {
                  property: {
                      alias: '',
                      name: ''
                  },
                  direction: 'ascending'
              }
          };
          $scope.chooseSource = function (query) {
              dialogService.contentPicker({
                  callback: function (data) {
                      if (data.id > 0) {
                          query.source = {
                              id: data.id,
                              name: data.name
                          };
                      } else {
                          query.source.name = 'My website';
                          delete query.source.id;
                      }
                  }
              });
          };
          var throttledFunc = _.throttle(function () {
              $http.post('backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery', $scope.query).then(function (response) {
                  $scope.result = response.data;
              });
          }, 200);
          $scope.$watch('query', function (value) {
              throttledFunc();
          }, true);
          $scope.getPropertyOperators = function (property) {
              var conditions = _.filter($scope.conditions, function (condition) {
                  var index = condition.appliesTo.indexOf(property.type);
                  return index >= 0;
              });
              return conditions;
          };
          $scope.addFilter = function (query) {
              query.filters.push({});
          };
          $scope.trashFilter = function (query) {
              query.filters.splice(query, 1);
          };
          $scope.changeSortOrder = function (query) {
              if (query.sort.direction === 'ascending') {
                  query.sort.direction = 'descending';
              } else {
                  query.sort.direction = 'ascending';
              }
          };
          $scope.setSortProperty = function (query, property) {
              query.sort.property = property;
              if (property.type === 'datetime') {
                  query.sort.direction = 'descending';
              } else {
                  query.sort.direction = 'ascending';
              }
          };
      });
      angular.module('umbraco').controller('Umbraco.Dialogs.Template.SnippetController', function ($scope) {
          $scope.type = $scope.dialogOptions.type;
          $scope.section = {
              name: '',
              required: false
          };
      });
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Dialogs.TreePickerController', function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
          var tree = null;
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          $scope.section = dialogOptions.section;
          $scope.treeAlias = dialogOptions.treeAlias;
          $scope.multiPicker = dialogOptions.multiPicker;
          $scope.hideHeader = true;
          $scope.searchInfo = {
              searchFromId: dialogOptions.startNodeId,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          //create the custom query string param for this tree
          $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : '';
          $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : '';
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
          var entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document';
          //min / max values
          if (dialogOptions.minNumber) {
              dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
          }
          if (dialogOptions.maxNumber) {
              dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
          }
          if (dialogOptions.section === 'member') {
              entityType = 'Member';
          } else if (dialogOptions.section === 'media') {
              entityType = 'Media';
          }
          //Configures filtering
          if (dialogOptions.filter) {
              dialogOptions.filterExclude = false;
              dialogOptions.filterAdvanced = false;
              //used advanced filtering
              if (angular.isFunction(dialogOptions.filter)) {
                  dialogOptions.filterAdvanced = true;
              } else if (angular.isObject(dialogOptions.filter)) {
                  dialogOptions.filterAdvanced = true;
              } else {
                  if (dialogOptions.filter.startsWith('!')) {
                      dialogOptions.filterExclude = true;
                      dialogOptions.filter = dialogOptions.filter.substring(1);
                  }
                  //used advanced filtering
                  if (dialogOptions.filter.startsWith('{')) {
                      dialogOptions.filterAdvanced = true;
                      //convert to object
                      dialogOptions.filter = angular.fromJson(dialogOptions.filter);
                  }
              }
          }
          function nodeExpandedHandler(ev, args) {
              if (angular.isArray(args.children)) {
                  //iterate children
                  _.each(args.children, function (child) {
                      //check if any of the items are list views, if so we need to add some custom 
                      // children: A node to activate the search, any nodes that have already been 
                      // selected in the search
                      if (child.metaData.isContainer) {
                          child.hasChildren = true;
                          child.children = [{
                                  level: child.level + 1,
                                  hasChildren: false,
                                  parent: function () {
                                      return child;
                                  },
                                  name: searchText,
                                  metaData: { listViewNode: child },
                                  cssClass: 'icon-search',
                                  cssClasses: ['not-published']
                              }];
                          //add base transition classes to this node
                          child.cssClasses.push('tree-node-slide-up');
                          var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                              return i.parentId == child.id;
                          });
                          _.each(listViewResults, function (item) {
                              child.children.unshift({
                                  id: item.id,
                                  name: item.name,
                                  cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                  level: child.level + 1,
                                  metaData: { isSearchResult: true },
                                  hasChildren: false,
                                  parent: function () {
                                      return child;
                                  }
                              });
                          });
                      }
                      //now we need to look in the already selected search results and 
                      // toggle the check boxes for those ones that are listed
                      var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                          return child.id == selected.id;
                      });
                      if (exists) {
                          child.selected = true;
                      }
                  });
                  //check filter
                  performFiltering(args.children);
              }
          }
          //gets the tree object when it loads
          function treeLoadedHandler(ev, args) {
              tree = args.tree;
          }
          //wires up selection
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if (args.node.metaData.listViewNode) {
                  //check if list view 'search' node was selected
                  $scope.searchInfo.showSearch = true;
                  $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
                  $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
                  //add transition classes
                  var listViewNode = args.node.parent();
                  listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
              } else if (args.node.metaData.isSearchResult) {
                  //check if the item selected was a search result from a list view
                  //unselect
                  select(args.node.name, args.node.id);
                  //remove it from the list view children
                  var listView = args.node.parent();
                  listView.children = _.reject(listView.children, function (child) {
                      return child.id == args.node.id;
                  });
                  //remove it from the custom tracked search result list
                  $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                      return i.id == args.node.id;
                  });
              } else {
                  eventsService.emit('dialogs.treePickerController.select', args);
                  if (args.node.filtered) {
                      return;
                  }
                  //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                  //from the server in this method.
                  select(args.node.name, args.node.id);
                  //toggle checked state
                  args.node.selected = args.node.selected === true ? false : true;
              }
          }
          /** Method used for selecting a node */
          function select(text, id, entity) {
              //if we get the root, we just return a constructed entity, no need for server data
              if (id < 0) {
                  if ($scope.multiPicker) {
                      $scope.select(id);
                  } else {
                      var node = {
                          alias: null,
                          icon: 'icon-folder',
                          id: id,
                          name: text
                      };
                      $scope.submit(node);
                  }
              } else {
                  if ($scope.multiPicker) {
                      $scope.select(Number(id));
                  } else {
                      $scope.hideSearch();
                      //if an entity has been passed in, use it
                      if (entity) {
                          $scope.submit(entity);
                      } else {
                          //otherwise we have to get it from the server
                          entityResource.getById(id, entityType).then(function (ent) {
                              $scope.submit(ent);
                          });
                      }
                  }
              }
          }
          function performFiltering(nodes) {
              if (!dialogOptions.filter) {
                  return;
              }
              //remove any list view search nodes from being filtered since these are special nodes that always must
              // be allowed to be clicked on
              nodes = _.filter(nodes, function (n) {
                  return !angular.isObject(n.metaData.listViewNode);
              });
              if (dialogOptions.filterAdvanced) {
                  //filter either based on a method or an object
                  var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter);
                  angular.forEach(filtered, function (value, key) {
                      value.filtered = true;
                      if (dialogOptions.filterCssClass) {
                          if (!value.cssClasses) {
                              value.cssClasses = [];
                          }
                          value.cssClasses.push(dialogOptions.filterCssClass);
                      }
                  });
              } else {
                  var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(',');
                  angular.forEach(nodes, function (value, key) {
                      var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
                      if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
                          value.filtered = true;
                          if (dialogOptions.filterCssClass) {
                              if (!value.cssClasses) {
                                  value.cssClasses = [];
                              }
                              value.cssClasses.push(dialogOptions.filterCssClass);
                          }
                      }
                  });
              }
          }
          $scope.multiSubmit = function (result) {
              entityResource.getByIds(result, entityType).then(function (ents) {
                  $scope.submit(ents);
              });
          };
          /** method to select a search result */
          $scope.selectResult = function (evt, result) {
              if (result.filtered) {
                  return;
              }
              result.selected = result.selected === true ? false : true;
              //since result = an entity, we'll pass it in so we don't have to go back to the server
              select(result.name, result.id, result);
              //add/remove to our custom tracked list of selected search results
              if (result.selected) {
                  $scope.searchInfo.selectedSearchResults.push(result);
              } else {
                  $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                      return i.id == result.id;
                  });
              }
              //ensure the tree node in the tree is checked/unchecked if it already exists there
              if (tree) {
                  var found = treeService.getDescendantNode(tree.root, result.id);
                  if (found) {
                      found.selected = result.selected;
                  }
              }
          };
          $scope.hideSearch = function () {
              //Traverse the entire displayed tree and update each node to sync with the selected search results
              if (tree) {
                  //we need to ensure that any currently displayed nodes that get selected
                  // from the search get updated to have a check box!
                  function checkChildren(children) {
                      _.each(children, function (child) {
                          //check if the id is in the selection, if so ensure it's flagged as selected
                          var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                              return child.id == selected.id;
                          });
                          //if the curr node exists in selected search results, ensure it's checked
                          if (exists) {
                              child.selected = true;
                          }    //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
                          else if (child.metaData.isSearchResult) {
                              //if this tree node is under a list view it means that the node was added
                              // to the tree dynamically under the list view that was searched, so we actually want to remove
                              // it all together from the tree
                              var listView = child.parent();
                              listView.children = _.reject(listView.children, function (c) {
                                  return c.id == child.id;
                              });
                          }
                          //check if the current node is a list view and if so, check if there's any new results
                          // that need to be added as child nodes to it based on search results selected
                          if (child.metaData.isContainer) {
                              child.cssClasses = _.reject(child.cssClasses, function (c) {
                                  return c === 'tree-node-slide-up-hide-active';
                              });
                              var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                                  return i.parentId == child.id;
                              });
                              _.each(listViewResults, function (item) {
                                  var childExists = _.find(child.children, function (c) {
                                      return c.id == item.id;
                                  });
                                  if (!childExists) {
                                      var parent = child;
                                      child.children.unshift({
                                          id: item.id,
                                          name: item.name,
                                          cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                          level: child.level + 1,
                                          metaData: { isSearchResult: true },
                                          hasChildren: false,
                                          parent: function () {
                                              return parent;
                                          }
                                      });
                                  }
                              });
                          }
                          //recurse
                          if (child.children && child.children.length > 0) {
                              checkChildren(child.children);
                          }
                      });
                  }
                  checkChildren(tree.root.children);
              }
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          $scope.onSearchResults = function (results) {
              //filter all items - this will mark an item as filtered
              performFiltering(results);
              //now actually remove all filtered items so they are not even displayed
              results = _.filter(results, function (item) {
                  return !item.filtered;
              });
              $scope.searchInfo.results = results;
              //sync with the curr selected results
              _.each($scope.searchInfo.results, function (result) {
                  var exists = _.find($scope.dialogData.selection, function (selectedId) {
                      return result.id == selectedId;
                  });
                  if (exists) {
                      result.selected = true;
                  }
              });
              $scope.searchInfo.showSearch = true;
          };
          $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      angular.module('umbraco').controller('Umbraco.Dialogs.UserController', function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) {
          $scope.history = historyService.getCurrent();
          $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
          $scope.showPasswordFields = false;
          $scope.changePasswordButtonState = 'init';
          $scope.externalLoginProviders = externalLoginInfo.providers;
          $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
          var evts = [];
          evts.push(eventsService.on('historyService.add', function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on('historyService.remove', function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on('historyService.removeAll', function (e, args) {
              $scope.history = [];
          }));
          $scope.logout = function () {
              //Add event listener for when there are pending changes on an editor which means our route was not successful
              var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) {
                  //one time listener, remove the event
                  pendingChangeEvent();
                  $scope.close();
              });
              //perform the path change, if it is successful then the promise will resolve otherwise it will fail
              $scope.close();
              $location.path('/logout');
          };
          $scope.gotoHistory = function (link) {
              $location.path(link);
              $scope.close();
          };
          //Manually update the remaining timeout seconds
          function updateTimeout() {
              $timeout(function () {
                  if ($scope.remainingAuthSeconds > 0) {
                      $scope.remainingAuthSeconds--;
                      $scope.$digest();
                      //recurse
                      updateTimeout();
                  }
              }, 1000, false);    // 1 second, do NOT execute a global digest
          }
          function updateUserInfo() {
              //get the user
              userService.getCurrentUser().then(function (user) {
                  $scope.user = user;
                  if ($scope.user) {
                      $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                      $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1;
                      //set the timer
                      updateTimeout();
                      authResource.getCurrentUserLinkedLogins().then(function (logins) {
                          //reset all to be un-linked
                          for (var provider in $scope.externalLoginProviders) {
                              $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
                          }
                          //set the linked logins
                          for (var login in logins) {
                              var found = _.find($scope.externalLoginProviders, function (i) {
                                  return i.authType == login;
                              });
                              if (found) {
                                  found.linkedProviderKey = logins[login];
                              }
                          }
                      });
                  }
              });
          }
          $scope.unlink = function (e, loginProvider, providerKey) {
              var result = confirm('Are you sure you want to unlink this account?');
              if (!result) {
                  e.preventDefault();
                  return;
              }
              authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
                  updateUserInfo();
              });
          };
          updateUserInfo();
          //remove all event handlers
          $scope.$on('$destroy', function () {
              for (var e = 0; e < evts.length; e++) {
                  evts[e]();
              }
          });
          /* ---------- UPDATE PASSWORD ---------- */
          //create the initial model for change password property editor
          $scope.changePasswordModel = {
              alias: '_umb_password',
              view: 'changepassword',
              config: {},
              value: {}
          };
          //go get the config for the membership provider and add it to the model
          authResource.getMembershipProviderConfig().then(function (data) {
              $scope.changePasswordModel.config = data;
              //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
              //this will ensure the oldPassword is shown so they can change it
              // disable reset password functionality beacuse it does not make sense for the current user.
              $scope.changePasswordModel.config.hasPassword = true;
              $scope.changePasswordModel.config.disableToggle = true;
              $scope.changePasswordModel.config.enableReset = false;
          });
          $scope.changePassword = function () {
              if (formHelper.submitForm({ scope: $scope })) {
                  $scope.changePasswordButtonState = 'busy';
                  currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) {
                      //if the password has been reset, then update our model
                      if (data.value) {
                          $scope.changePasswordModel.value.generatedPassword = data.value;
                      }
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      $scope.changePasswordButtonState = 'success';
                  }, function (err) {
                      formHelper.handleError(err);
                      $scope.changePasswordButtonState = 'error';
                  });
              }
          };
          $scope.togglePasswordFields = function () {
              clearPasswordFields();
              $scope.showPasswordFields = !$scope.showPasswordFields;
          };
          function clearPasswordFields() {
              $scope.changePasswordModel.value.newPassword = '';
              $scope.changePasswordModel.confirm = '';
          }
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.LegacyDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function YsodController($scope, legacyResource, treeService, navigationService) {
          if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
              //trim whitespace
              $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
          }
          $scope.closeDialog = function () {
              $scope.dismiss();
          };
      }
      angular.module('umbraco').controller('Umbraco.Dialogs.YsodController', YsodController);
      (function () {
          'use strict';
          function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) {
              var vm = this;
              var evts = [];
              vm.title = localizationService.localize('general_help');
              vm.subtitle = 'Umbraco version' + ' ' + Umbraco.Sys.ServerVariables.application.version;
              vm.section = $routeParams.section;
              vm.tree = $routeParams.tree;
              vm.sectionName = '';
              vm.customDashboard = null;
              vm.tours = [];
              vm.closeDrawer = closeDrawer;
              vm.startTour = startTour;
              vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage;
              vm.showTourButton = showTourButton;
              function startTour(tour) {
                  tourService.startTour(tour);
                  closeDrawer();
              }
              function oninit() {
                  tourService.getGroupedTours().then(function (groupedTours) {
                      vm.tours = groupedTours;
                      getTourGroupCompletedPercentage();
                  });
                  // load custom help dashboard
                  dashboardResource.getDashboard('user-help').then(function (dashboard) {
                      vm.customDashboard = dashboard;
                  });
                  if (!vm.section) {
                      vm.section = 'content';
                  }
                  setSectionName();
                  userService.getCurrentUser().then(function (user) {
                      vm.userType = user.userType;
                      vm.userLang = user.locale;
                      evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
                          handleSectionChange();
                      }));
                      findHelp(vm.section, vm.tree, vm.usertype, vm.userLang);
                  });
                  // check if a tour is running - if it is open the matching group
                  var currentTour = tourService.getCurrentTour();
                  if (currentTour) {
                      openTourGroup(currentTour.alias);
                  }
              }
              function closeDrawer() {
                  appState.setDrawerState('showDrawer', false);
              }
              function handleSectionChange() {
                  $timeout(function () {
                      if (vm.section !== $routeParams.section || vm.tree !== $routeParams.tree) {
                          vm.section = $routeParams.section;
                          vm.tree = $routeParams.tree;
                          setSectionName();
                          findHelp(vm.section, vm.tree, vm.usertype, vm.userLang);
                      }
                  });
              }
              function findHelp(section, tree, usertype, userLang) {
                  helpService.getContextHelpForPage(section, tree).then(function (topics) {
                      vm.topics = topics;
                  });
                  var rq = {};
                  rq.section = vm.section;
                  rq.usertype = usertype;
                  rq.lang = userLang;
                  if ($routeParams.url) {
                      rq.path = decodeURIComponent($routeParams.url);
                      if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                          rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                      }
                      if (rq.path.indexOf('.aspx') > 0) {
                          rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                      }
                  } else {
                      rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
                  }
                  helpService.findVideos(rq).then(function (videos) {
                      vm.videos = videos;
                  });
              }
              function setSectionName() {
                  // Get section name
                  var languageKey = 'sections_' + vm.section;
                  localizationService.localize(languageKey).then(function (value) {
                      vm.sectionName = value;
                  });
              }
              function showTourButton(index, tourGroup) {
                  if (index !== 0) {
                      var prevTour = tourGroup.tours[index - 1];
                      if (prevTour.completed) {
                          return true;
                      }
                  } else {
                      return true;
                  }
              }
              function openTourGroup(tourAlias) {
                  angular.forEach(vm.tours, function (group) {
                      angular.forEach(group, function (tour) {
                          if (tour.alias === tourAlias) {
                              group.open = true;
                          }
                      });
                  });
              }
              function getTourGroupCompletedPercentage() {
                  // Finding out, how many tours are completed for the progress circle
                  angular.forEach(vm.tours, function (group) {
                      var completedTours = 0;
                      angular.forEach(group.tours, function (tour) {
                          if (tour.completed) {
                              completedTours++;
                          }
                      });
                      group.completedPercentage = Math.round(completedTours / group.tours.length * 100);
                  });
              }
              evts.push(eventsService.on('appState.tour.complete', function (event, tour) {
                  tourService.getGroupedTours().then(function (groupedTours) {
                      vm.tours = groupedTours;
                      openTourGroup(tour.alias);
                      getTourGroupCompletedPercentage();
                  });
              }));
              $scope.$on('$destroy', function () {
                  for (var e in evts) {
                      eventsService.unsubscribe(evts[e]);
                  }
              });
              oninit();
          }
          angular.module('umbraco').controller('Umbraco.Drawers.Help', HelpDrawerController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.LegacyController
   * @function
   * 
   * @description
   * A controller to control the legacy iframe injection
   * 
  */
      function LegacyController($scope, $routeParams, $element) {
          var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, ''));
          //split into path and query
          var urlParts = url.split('?');
          var extIndex = urlParts[0].lastIndexOf('.');
          var ext = extIndex === -1 ? '' : urlParts[0].substr(extIndex);
          //path cannot be a js file
          if (ext !== '.js' || ext === '') {
              //path cannot contain any of these chars
              var toClean = '*(){}[];:<>\\|\'"';
              for (var i = 0; i < toClean.length; i++) {
                  var reg = new RegExp('\\' + toClean[i], 'g');
                  urlParts[0] = urlParts[0].replace(reg, '');
              }
              //join cleaned path and query back together
              url = urlParts[0] + (urlParts.length === 1 ? '' : '?' + urlParts[1]);
              $scope.legacyPath = url;
          } else {
              throw 'Invalid url';
          }
      }
      angular.module('umbraco').controller('Umbraco.LegacyController', LegacyController);
      /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
      angular.module('umbraco').controller('Umbraco.LoginController', function (eventsService, $scope, userService, $location, $rootScope) {
          userService._showLoginDialog();
          var evtOn = eventsService.on('app.ready', function (evt, data) {
              $scope.avatar = 'assets/img/application/logo.png';
              var path = '/';
              //check if there's a returnPath query string, if so redirect to it
              var locationObj = $location.search();
              if (locationObj.returnPath) {
                  path = decodeURIComponent(locationObj.returnPath);
              }
              $location.url(path);
          });
          $scope.$on('$destroy', function () {
              eventsService.unsubscribe(evtOn);
          });
      });
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Notifications.ConfirmRouteChangeController', function ($scope, $location, $log, notificationsService) {
          $scope.discard = function (not) {
              not.args.listener();
              $location.search('');
              //we need to break the path up into path and query
              var parts = not.args.path.split('?');
              var query = {};
              if (parts.length > 1) {
                  _.each(parts[1].split('&'), function (q) {
                      var keyVal = q.split('=');
                      query[keyVal[0]] = keyVal[1];
                  });
              }
              $location.path(parts[0]).search(query);
              notificationsService.remove(not);
          };
          $scope.stay = function (not) {
              notificationsService.remove(not);
          };
      });
      (function () {
          'use strict';
          function CompositionsOverlay($scope) {
              var vm = this;
              vm.isSelected = isSelected;
              function isSelected(alias) {
                  if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) {
                      return true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Overlays.CompositionsOverlay', CompositionsOverlay);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.PropertyController
   * @function
   *
   * @description
   * The controller for the content type editor property dialog
   */
      (function () {
          'use strict';
          function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService) {
              var vm = this;
              if (!$scope.model.title) {
                  $scope.model.title = localizationService.localize('defaultdialogs_selectEditor');
              }
              vm.searchTerm = '';
              vm.showTabs = false;
              vm.tabsLoaded = 0;
              vm.typesAndEditors = [];
              vm.userConfigured = [];
              vm.loading = false;
              vm.tabs = [
                  {
                      active: true,
                      id: 1,
                      label: localizationService.localize('contentTypeEditor_availableEditors'),
                      alias: 'Default',
                      typesAndEditors: []
                  },
                  {
                      active: false,
                      id: 2,
                      label: localizationService.localize('contentTypeEditor_reuse'),
                      alias: 'Reuse',
                      userConfigured: []
                  }
              ];
              vm.filterItems = filterItems;
              vm.showDetailsOverlay = showDetailsOverlay;
              vm.hideDetailsOverlay = hideDetailsOverlay;
              vm.pickEditor = pickEditor;
              vm.pickDataType = pickDataType;
              function activate() {
                  getGroupedDataTypes();
                  getGroupedPropertyEditors();
              }
              function getGroupedPropertyEditors() {
                  vm.loading = true;
                  dataTypeResource.getGroupedPropertyEditors().then(function (data) {
                      vm.tabs[0].typesAndEditors = data;
                      vm.typesAndEditors = data;
                      vm.tabsLoaded = vm.tabsLoaded + 1;
                      checkIfTabContentIsLoaded();
                  });
              }
              function getGroupedDataTypes() {
                  vm.loading = true;
                  dataTypeResource.getGroupedDataTypes().then(function (data) {
                      vm.tabs[1].userConfigured = data;
                      vm.userConfigured = data;
                      vm.tabsLoaded = vm.tabsLoaded + 1;
                      checkIfTabContentIsLoaded();
                  });
              }
              function checkIfTabContentIsLoaded() {
                  if (vm.tabsLoaded === 2) {
                      vm.loading = false;
                      vm.showTabs = true;
                  }
              }
              function filterItems() {
                  // clear item details
                  $scope.model.itemDetails = null;
                  if (vm.searchTerm) {
                      vm.showFilterResult = true;
                      vm.showTabs = false;
                  } else {
                      vm.showFilterResult = false;
                      vm.showTabs = true;
                  }
              }
              function showDetailsOverlay(property) {
                  var propertyDetails = {};
                  propertyDetails.icon = property.icon;
                  propertyDetails.title = property.name;
                  $scope.model.itemDetails = propertyDetails;
              }
              function hideDetailsOverlay() {
                  $scope.model.itemDetails = null;
              }
              function pickEditor(editor) {
                  var parentId = -1;
                  dataTypeResource.getScaffold(parentId).then(function (dataType) {
                      // set alias
                      dataType.selectedEditor = editor.alias;
                      // set name
                      var nameArray = [];
                      if ($scope.model.contentTypeName) {
                          nameArray.push($scope.model.contentTypeName);
                      }
                      if ($scope.model.property.label) {
                          nameArray.push($scope.model.property.label);
                      }
                      if (editor.name) {
                          nameArray.push(editor.name);
                      }
                      // make name
                      dataType.name = nameArray.join(' - ');
                      // get pre values
                      dataTypeResource.getPreValues(dataType.selectedEditor).then(function (preValues) {
                          dataType.preValues = preValues;
                          openEditorSettingsOverlay(dataType, true);
                      });
                  });
              }
              function pickDataType(selectedDataType) {
                  dataTypeResource.getById(selectedDataType.id).then(function (dataType) {
                      contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function (propertyType) {
                          submitOverlay(dataType, propertyType, false);
                      });
                  });
              }
              function openEditorSettingsOverlay(dataType, isNew) {
                  vm.editorSettingsOverlay = {
                      title: localizationService.localize('contentTypeEditor_editorSettings'),
                      dataType: dataType,
                      view: 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html',
                      show: true,
                      submit: function (model) {
                          var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
                          dataTypeResource.save(model.dataType, preValues, isNew).then(function (newDataType) {
                              contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) {
                                  submitOverlay(newDataType, propertyType, true);
                                  vm.editorSettingsOverlay.show = false;
                                  vm.editorSettingsOverlay = null;
                              });
                          });
                      }
                  };
              }
              function submitOverlay(dataType, propertyType, isNew) {
                  // update property
                  $scope.model.property.config = propertyType.config;
                  $scope.model.property.editor = propertyType.editor;
                  $scope.model.property.view = propertyType.view;
                  $scope.model.property.dataTypeId = dataType.id;
                  $scope.model.property.dataTypeIcon = dataType.icon;
                  $scope.model.property.dataTypeName = dataType.name;
                  $scope.model.updateSameDataTypes = isNew;
                  $scope.model.submit($scope.model);
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.EditorPickerOverlay', EditorPickerOverlay);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.PropertyController
   * @function
   *
   * @description
   * The controller for the content type editor property dialog
   */
      (function () {
          'use strict';
          function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService, userService) {
              var vm = this;
              vm.showValidationPattern = false;
              vm.focusOnPatternField = false;
              vm.focusOnMandatoryField = false;
              vm.selectedValidationType = {};
              vm.validationTypes = [
                  {
                      'name': localizationService.localize('validation_validateAsEmail'),
                      'key': 'email',
                      'pattern': '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+',
                      'enableEditing': true
                  },
                  {
                      'name': localizationService.localize('validation_validateAsNumber'),
                      'key': 'number',
                      'pattern': '^[0-9]*$',
                      'enableEditing': true
                  },
                  {
                      'name': localizationService.localize('validation_validateAsUrl'),
                      'key': 'url',
                      'pattern': 'https?://[a-zA-Z0-9-.]+.[a-zA-Z]{2,}',
                      'enableEditing': true
                  },
                  {
                      'name': localizationService.localize('validation_enterCustomValidation'),
                      'key': 'custom',
                      'pattern': '',
                      'enableEditing': true
                  }
              ];
              vm.changeValidationType = changeValidationType;
              vm.changeValidationPattern = changeValidationPattern;
              vm.openEditorPickerOverlay = openEditorPickerOverlay;
              vm.openEditorSettingsOverlay = openEditorSettingsOverlay;
              userService.getCurrentUser().then(function (user) {
                  vm.showSensitiveData = user.userGroups.indexOf('sensitiveData') != -1;
              });
              function activate() {
                  matchValidationType();
              }
              function changeValidationPattern() {
                  matchValidationType();
              }
              function openEditorPickerOverlay(property) {
                  vm.focusOnMandatoryField = false;
                  vm.editorPickerOverlay = {};
                  vm.editorPickerOverlay.property = $scope.model.property;
                  vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName;
                  vm.editorPickerOverlay.view = 'views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html';
                  vm.editorPickerOverlay.show = true;
                  vm.editorPickerOverlay.submit = function (model) {
                      $scope.model.updateSameDataTypes = model.updateSameDataTypes;
                      vm.focusOnMandatoryField = true;
                      // update property
                      property.config = model.property.config;
                      property.editor = model.property.editor;
                      property.view = model.property.view;
                      property.dataTypeId = model.property.dataTypeId;
                      property.dataTypeIcon = model.property.dataTypeIcon;
                      property.dataTypeName = model.property.dataTypeName;
                      vm.editorPickerOverlay.show = false;
                      vm.editorPickerOverlay = null;
                  };
                  vm.editorPickerOverlay.close = function (model) {
                      vm.editorPickerOverlay.show = false;
                      vm.editorPickerOverlay = null;
                  };
              }
              function openEditorSettingsOverlay(property) {
                  vm.focusOnMandatoryField = false;
                  // get data type
                  dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                      vm.editorSettingsOverlay = {};
                      vm.editorSettingsOverlay.title = 'Editor settings';
                      vm.editorSettingsOverlay.view = 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html';
                      vm.editorSettingsOverlay.dataType = dataType;
                      vm.editorSettingsOverlay.show = true;
                      vm.editorSettingsOverlay.submit = function (model) {
                          var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
                          dataTypeResource.save(model.dataType, preValues, false).then(function (newDataType) {
                              contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) {
                                  // update editor
                                  property.config = propertyType.config;
                                  property.editor = propertyType.editor;
                                  property.view = propertyType.view;
                                  property.dataTypeId = newDataType.id;
                                  property.dataTypeIcon = newDataType.icon;
                                  property.dataTypeName = newDataType.name;
                                  // set flag to update same data types
                                  $scope.model.updateSameDataTypes = true;
                                  vm.focusOnMandatoryField = true;
                                  vm.editorSettingsOverlay.show = false;
                                  vm.editorSettingsOverlay = null;
                              });
                          });
                      };
                      vm.editorSettingsOverlay.close = function (oldModel) {
                          vm.editorSettingsOverlay.show = false;
                          vm.editorSettingsOverlay = null;
                      };
                  });
              }
              function matchValidationType() {
                  if ($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== '' && $scope.model.property.validation.pattern !== undefined) {
                      var match = false;
                      // find and show if a match from the list has been chosen
                      angular.forEach(vm.validationTypes, function (validationType, index) {
                          if ($scope.model.property.validation.pattern === validationType.pattern) {
                              vm.selectedValidationType = vm.validationTypes[index];
                              vm.showValidationPattern = true;
                              match = true;
                          }
                      });
                      // if there is no match - choose the custom validation option.
                      if (!match) {
                          angular.forEach(vm.validationTypes, function (validationType) {
                              if (validationType.key === 'custom') {
                                  vm.selectedValidationType = validationType;
                                  vm.showValidationPattern = true;
                              }
                          });
                      }
                  }
              }
              function changeValidationType(selectedValidationType) {
                  if (selectedValidationType) {
                      $scope.model.property.validation.pattern = selectedValidationType.pattern;
                      vm.showValidationPattern = true;
                      // set focus on textarea
                      if (selectedValidationType.key === 'custom') {
                          vm.focusOnPatternField = true;
                      }
                  } else {
                      $scope.model.property.validation.pattern = '';
                      vm.showValidationPattern = false;
                  }
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.Overlay.PropertySettingsOverlay', PropertySettingsOverlay);
      }());
      (function () {
          'use strict';
          function CopyOverlay($scope, localizationService, eventsService, entityHelper) {
              var vm = this;
              if (!$scope.model.title) {
                  $scope.model.title = localizationService.localize('general_copy');
              }
              vm.hideSearch = hideSearch;
              vm.selectResult = selectResult;
              vm.onSearchResults = onSearchResults;
              var dialogOptions = $scope.model;
              var searchText = 'Search...';
              var node = dialogOptions.currentNode;
              localizationService.localize('general_search').then(function (value) {
                  searchText = value + '...';
              });
              $scope.model.relateToOriginal = true;
              $scope.dialogTreeEventHandler = $({});
              vm.searchInfo = {
                  searchFromId: null,
                  searchFromName: null,
                  showSearch: false,
                  results: [],
                  selectedSearchResults: []
              };
              // get entity type based on the section
              $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
              function nodeSelectHandler(ev, args) {
                  if (args && args.event) {
                      args.event.preventDefault();
                      args.event.stopPropagation();
                  }
                  //eventsService.emit("editors.content.copyController.select", args);
                  if ($scope.model.target) {
                      //un-select if there's a current one selected
                      $scope.model.target.selected = false;
                  }
                  $scope.model.target = args.node;
                  $scope.model.target.selected = true;
              }
              function nodeExpandedHandler(ev, args) {
                  // open mini list view for list views
                  if (args.node.metaData.isContainer) {
                      openMiniListView(args.node);
                  }
              }
              function hideSearch() {
                  vm.searchInfo.showSearch = false;
                  vm.searchInfo.searchFromId = null;
                  vm.searchInfo.searchFromName = null;
                  vm.searchInfo.results = [];
              }
              // method to select a search result
              function selectResult(evt, result) {
                  result.selected = result.selected === true ? false : true;
                  nodeSelectHandler(evt, {
                      event: evt,
                      node: result
                  });
              }
              //callback when there are search results
              function onSearchResults(results) {
                  vm.searchInfo.results = results;
                  vm.searchInfo.showSearch = true;
              }
              $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
              $scope.$on('$destroy', function () {
                  $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
                  $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
              });
              // Mini list view
              $scope.selectListViewNode = function (node) {
                  node.selected = node.selected === true ? false : true;
                  nodeSelectHandler({}, { node: node });
              };
              $scope.closeMiniListView = function () {
                  $scope.miniListView = undefined;
              };
              function openMiniListView(node) {
                  $scope.miniListView = node;
              }
          }
          angular.module('umbraco').controller('Umbraco.Overlays.CopyOverlay', CopyOverlay);
      }());
      (function () {
          'use strict';
          function EmbedOverlay($scope, $http, umbRequestHelper, localizationService) {
              var vm = this;
              var origWidth = 500;
              var origHeight = 300;
              if (!$scope.model.title) {
                  $scope.model.title = localizationService.localize('general_embed');
              }
              $scope.model.embed = {
                  url: '',
                  width: 360,
                  height: 240,
                  constrain: true,
                  preview: '',
                  success: false,
                  info: '',
                  supportsDimensions: ''
              };
              vm.showPreview = showPreview;
              vm.changeSize = changeSize;
              function showPreview() {
                  if ($scope.model.embed.url) {
                      $scope.model.embed.show = true;
                      $scope.model.embed.preview = '<div class="umb-loader" style="height: 10px; margin: 10px 0px;"></div>';
                      $scope.model.embed.info = '';
                      $scope.model.embed.success = false;
                      $http({
                          method: 'GET',
                          url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'),
                          params: {
                              url: $scope.model.embed.url,
                              width: $scope.model.embed.width,
                              height: $scope.model.embed.height
                          }
                      }).success(function (data) {
                          $scope.model.embed.preview = '';
                          switch (data.Status) {
                          case 0:
                              //not supported
                              $scope.model.embed.info = 'Not supported';
                              break;
                          case 1:
                              //error
                              $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                              break;
                          case 2:
                              $scope.model.embed.preview = data.Markup;
                              $scope.model.embed.supportsDimensions = data.SupportsDimensions;
                              $scope.model.embed.success = true;
                              break;
                          }
                      }).error(function () {
                          $scope.model.embed.supportsDimensions = false;
                          $scope.model.embed.preview = '';
                          $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                      });
                  } else {
                      $scope.model.embed.supportsDimensions = false;
                      $scope.model.embed.preview = '';
                      $scope.model.embed.info = 'Please enter a URL';
                  }
              }
              function changeSize(type) {
                  var width, height;
                  if ($scope.model.embed.constrain) {
                      width = parseInt($scope.model.embed.width, 10);
                      height = parseInt($scope.model.embed.height, 10);
                      if (type == 'width') {
                          origHeight = Math.round(width / origWidth * height);
                          $scope.model.embed.height = origHeight;
                      } else {
                          origWidth = Math.round(height / origHeight * width);
                          $scope.model.embed.width = origWidth;
                      }
                  }
                  if ($scope.model.embed.url !== '') {
                      showPreview();
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Overlays.EmbedOverlay', EmbedOverlay);
      }());
      angular.module('umbraco').controller('Umbraco.Overlays.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) {
          $scope.section = $routeParams.section;
          $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
          $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version;
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('general_help');
          }
          if (!$scope.section) {
              $scope.section = 'content';
          }
          $scope.sectionName = $scope.section;
          var rq = {};
          rq.section = $scope.section;
          //translate section name
          localizationService.localize('sections_' + rq.section).then(function (value) {
              $scope.sectionName = value;
          });
          userService.getCurrentUser().then(function (user) {
              rq.lang = user.locale;
              if ($routeParams.url) {
                  rq.path = decodeURIComponent($routeParams.url);
                  if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                      rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                  }
                  if (rq.path.indexOf('.aspx') > 0) {
                      rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                  }
              } else {
                  rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
              }
              helpService.findHelp(rq).then(function (topics) {
                  $scope.topics = topics;
              });
              helpService.findVideos(rq).then(function (videos) {
                  $scope.videos = videos;
              });
          });
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.PropertyController
   * @function
   *
   * @description
   * The controller for the content type editor property dialog
   */
      function IconPickerOverlay($scope, iconHelper, localizationService) {
          $scope.loading = true;
          $scope.model.hideSubmitButton = true;
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectIcon');
          }
          iconHelper.getIcons().then(function (icons) {
              $scope.icons = icons;
              $scope.loading = false;
          });
          $scope.selectIcon = function (icon, color) {
              $scope.model.icon = icon;
              $scope.model.color = color;
              $scope.submitForm($scope.model);
          };
      }
      angular.module('umbraco').controller('Umbraco.Overlays.IconPickerOverlay', IconPickerOverlay);
      (function () {
          'use strict';
          function InsertOverlayController($scope, localizationService) {
              var vm = this;
              if (!$scope.model.title) {
                  $scope.model.title = localizationService.localize('template_insert');
              }
              if (!$scope.model.subtitle) {
                  $scope.model.subtitle = localizationService.localize('template_insertDesc');
              }
              vm.openMacroPicker = openMacroPicker;
              vm.openPageFieldOverlay = openPageFieldOverlay;
              vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
              vm.openPartialOverlay = openPartialOverlay;
              function openMacroPicker() {
                  vm.macroPickerOverlay = {
                      view: 'macropicker',
                      title: localizationService.localize('template_insertMacro'),
                      dialogData: {},
                      show: true,
                      submit: function (model) {
                          $scope.model.insert = {
                              'type': 'macro',
                              'macroParams': model.macroParams,
                              'selectedMacro': model.selectedMacro
                          };
                          $scope.model.submit($scope.model);
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                      }
                  };
              }
              function openPageFieldOverlay() {
                  vm.pageFieldOverlay = {
                      title: localizationService.localize('template_insertPageField'),
                      description: localizationService.localize('template_insertPageFieldDesc'),
                      submitButtonLabel: 'Insert',
                      closeButtonlabel: 'Cancel',
                      view: 'insertfield',
                      show: true,
                      submit: function (model) {
                          $scope.model.insert = {
                              'type': 'umbracoField',
                              'umbracoField': model.umbracoField
                          };
                          $scope.model.submit($scope.model);
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                      },
                      close: function (model) {
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                      }
                  };
              }
              function openDictionaryItemOverlay() {
                  vm.dictionaryItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'dictionary',
                      entityType: 'dictionary',
                      multiPicker: false,
                      title: localizationService.localize('template_insertDictionaryItem'),
                      description: localizationService.localize('template_insertDictionaryItemDesc'),
                      emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                      show: true,
                      select: function (node) {
                          $scope.model.insert = {
                              'type': 'dictionary',
                              'node': node
                          };
                          $scope.model.submit($scope.model);
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                      },
                      close: function (model) {
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                      }
                  };
              }
              function openPartialOverlay() {
                  vm.partialItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'partialViews',
                      entityType: 'partialView',
                      multiPicker: false,
                      filter: function (i) {
                          if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                              return true;
                          }
                      },
                      filterCssClass: 'not-allowed',
                      title: localizationService.localize('template_insertPartialView'),
                      show: true,
                      select: function (node) {
                          $scope.model.insert = {
                              'type': 'partial',
                              'node': node
                          };
                          $scope.model.submit($scope.model);
                          vm.partialItemOverlay.show = false;
                          vm.partialItemOverlay = null;
                      },
                      close: function (model) {
                          vm.partialItemOverlay.show = false;
                          vm.partialItemOverlay = null;
                      }
                  };
              }
          }
          angular.module('umbraco').controller('Umbraco.Overlays.InsertOverlay', InsertOverlayController);
      }());
      (function () {
          'use strict';
          function InsertFieldController($scope, $http, contentTypeResource) {
              var vm = this;
              vm.field;
              vm.altField;
              vm.altText;
              vm.insertBefore;
              vm.insertAfter;
              vm.recursive = false;
              vm.properties = [];
              vm.standardFields = [];
              vm.date = false;
              vm.dateTime = false;
              vm.dateTimeSeparator = '';
              vm.casingUpper = false;
              vm.casingLower = false;
              vm.encodeHtml = false;
              vm.encodeUrl = false;
              vm.convertLinebreaks = false;
              vm.showAltField = false;
              vm.showAltText = false;
              vm.setDateOption = setDateOption;
              vm.setCasingOption = setCasingOption;
              vm.setEncodingOption = setEncodingOption;
              vm.generateOutputSample = generateOutputSample;
              function onInit() {
                  // set default title
                  if (!$scope.model.title) {
                      $scope.model.title = 'Insert value';
                  }
                  // Load all fields
                  contentTypeResource.getAllPropertyTypeAliases().then(function (array) {
                      vm.properties = array;
                  });
                  // Load all standard fields
                  contentTypeResource.getAllStandardFields().then(function (array) {
                      vm.standardFields = array;
                  });
              }
              // date formatting
              function setDateOption(option) {
                  if (option === 'date') {
                      if (vm.date) {
                          vm.date = false;
                      } else {
                          vm.date = true;
                          vm.dateTime = false;
                      }
                  }
                  if (option === 'dateWithTime') {
                      if (vm.dateTime) {
                          vm.dateTime = false;
                      } else {
                          vm.date = false;
                          vm.dateTime = true;
                      }
                  }
              }
              // casing formatting
              function setCasingOption(option) {
                  if (option === 'uppercase') {
                      if (vm.casingUpper) {
                          vm.casingUpper = false;
                      } else {
                          vm.casingUpper = true;
                          vm.casingLower = false;
                      }
                  }
                  if (option === 'lowercase') {
                      if (vm.casingLower) {
                          vm.casingLower = false;
                      } else {
                          vm.casingUpper = false;
                          vm.casingLower = true;
                      }
                  }
              }
              // encoding formatting
              function setEncodingOption(option) {
                  if (option === 'html') {
                      if (vm.encodeHtml) {
                          vm.encodeHtml = false;
                      } else {
                          vm.encodeHtml = true;
                          vm.encodeUrl = false;
                      }
                  }
                  if (option === 'url') {
                      if (vm.encodeUrl) {
                          vm.encodeUrl = false;
                      } else {
                          vm.encodeHtml = false;
                          vm.encodeUrl = true;
                      }
                  }
              }
              function generateOutputSample() {
                  var pageField = (vm.field !== undefined ? '@Umbraco.Field("' + vm.field + '"' : '') + (vm.altField !== undefined ? ', altFieldAlias:"' + vm.altField + '"' : '') + (vm.altText !== undefined ? ', altText:"' + vm.altText + '"' : '') + (vm.insertBefore !== undefined ? ', insertBefore:"' + vm.insertBefore + '"' : '') + (vm.insertAfter !== undefined ? ', insertAfter:"' + vm.insertAfter + '"' : '') + (vm.recursive !== false ? ', recursive: ' + vm.recursive : '') + (vm.date !== false ? ', formatAsDate: ' + vm.date : '') + (vm.dateTime !== false ? ', formatAsDateWithTimeSeparator:"' + vm.dateTimeSeparator + '"' : '') + (vm.casingUpper !== false ? ', casing: ' + 'RenderFieldCaseType.Upper' : '') + (vm.casingLower !== false ? ', casing: ' + 'RenderFieldCaseType.Lower' : '') + (vm.encodeHtml !== false ? ', encoding: ' + 'RenderFieldEncodingType.Html' : '') + (vm.encodeUrl !== false ? ', encoding: ' + 'RenderFieldEncodingType.Url' : '') + (vm.convertLinebreaks !== false ? ', convertLineBreaks: ' + 'true' : '') + (vm.field ? ')' : '');
                  $scope.model.umbracoField = pageField;
                  return pageField;
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.InsertFieldController', InsertFieldController);
      }());
      function ItemPickerOverlay($scope, localizationService) {
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectItem');
          }
          $scope.model.hideSubmitButton = true;
          $scope.selectItem = function (item) {
              $scope.model.selectedItem = item;
              $scope.submitForm($scope.model);
          };
      }
      angular.module('umbraco').controller('Umbraco.Overlays.ItemPickerOverlay', ItemPickerOverlay);
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Overlays.LinkPickerController', function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
          var dialogOptions = $scope.model;
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectLink');
          }
          $scope.dialogTreeEventHandler = $({});
          $scope.model.target = {};
          $scope.searchInfo = {
              searchFromId: null,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          $scope.showTarget = $scope.model.hideTarget !== true;
          if (dialogOptions.currentTarget) {
              $scope.model.target = dialogOptions.currentTarget;
              //if we have a node ID, we fetch the current node to build the form data
              if ($scope.model.target.id || $scope.model.target.udi) {
                  //will be either a udi or an int
                  var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
                  if (!$scope.model.target.path) {
                      entityResource.getPath(id, 'Document').then(function (path) {
                          $scope.model.target.path = path;
                          //now sync the tree to this path
                          $scope.dialogTreeEventHandler.syncTree({
                              path: $scope.model.target.path,
                              tree: 'content'
                          });
                      });
                  }
                  contentResource.getNiceUrl(id).then(function (url) {
                      $scope.model.target.url = url;
                  });
              }
          }
          function nodeSelectHandler(ev, args) {
              if (args && args.event) {
                  args.event.preventDefault();
                  args.event.stopPropagation();
              }
              eventsService.emit('dialogs.linkPicker.select', args);
              if ($scope.currentNode) {
                  //un-select if there's a current one selected
                  $scope.currentNode.selected = false;
              }
              $scope.currentNode = args.node;
              $scope.currentNode.selected = true;
              $scope.model.target.id = args.node.id;
              $scope.model.target.udi = args.node.udi;
              $scope.model.target.name = args.node.name;
              if (args.node.id < 0) {
                  $scope.model.target.url = '/';
              } else {
                  contentResource.getNiceUrl(args.node.id).then(function (url) {
                      $scope.model.target.url = url;
                  });
              }
              if (!angular.isUndefined($scope.model.target.isMedia)) {
                  delete $scope.model.target.isMedia;
              }
          }
          function nodeExpandedHandler(ev, args) {
              // open mini list view for list views
              if (args.node.metaData.isContainer) {
                  openMiniListView(args.node);
              }
          }
          $scope.switchToMediaPicker = function () {
              userService.getCurrentUser().then(function (userData) {
                  $scope.mediaPickerOverlay = {
                      view: 'mediapicker',
                      startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                      startNodeIsVirtual: userData.startMediaIds.length !== 1,
                      show: true,
                      submit: function (model) {
                          var media = model.selectedImages[0];
                          $scope.model.target.id = media.id;
                          $scope.model.target.udi = media.udi;
                          $scope.model.target.isMedia = true;
                          $scope.model.target.name = media.name;
                          $scope.model.target.url = mediaHelper.resolveFile(media);
                          $scope.mediaPickerOverlay.show = false;
                          $scope.mediaPickerOverlay = null;
                      }
                  };
              });
          };
          $scope.hideSearch = function () {
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = null;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          // method to select a search result
          $scope.selectResult = function (evt, result) {
              result.selected = result.selected === true ? false : true;
              nodeSelectHandler(evt, {
                  event: evt,
                  node: result
              });
          };
          //callback when there are search results
          $scope.onSearchResults = function (results) {
              $scope.searchInfo.results = results;
              $scope.searchInfo.showSearch = true;
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
          });
          // Mini list view
          $scope.selectListViewNode = function (node) {
              node.selected = node.selected === true ? false : true;
              nodeSelectHandler({}, { node: node });
          };
          $scope.closeMiniListView = function () {
              $scope.miniListView = undefined;
          };
          function openMiniListView(node) {
              $scope.miniListView = node;
          }
      });
      function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) {
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectMacro');
          }
          $scope.macros = [];
          $scope.model.selectedMacro = null;
          $scope.model.macroParams = [];
          $scope.wizardStep = 'macroSelect';
          $scope.noMacroParams = false;
          $scope.selectMacro = function (macro) {
              $scope.model.selectedMacro = macro;
              if ($scope.wizardStep === 'macroSelect') {
                  editParams();
              } else {
                  $scope.model.submit($scope.model);
              }
          };
          /** changes the view to edit the params of the selected macro */
          function editParams() {
              //get the macro params if there are any
              macroResource.getMacroParameters($scope.model.selectedMacro.id).then(function (data) {
                  //go to next page if there are params otherwise we can just exit
                  if (!angular.isArray(data) || data.length === 0) {
                      $scope.model.submit($scope.model);
                  } else {
                      $scope.wizardStep = 'paramSelect';
                      $scope.model.macroParams = data;
                      //fill in the data if we are editing this macro
                      if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) {
                          _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) {
                              var prop = _.find($scope.model.macroParams, function (item) {
                                  return item.alias == key;
                              });
                              if (prop) {
                                  if (_.isString(val)) {
                                      //we need to unescape values as they have most likely been escaped while inserted
                                      val = _.unescape(val);
                                      //detect if it is a json string
                                      if (val.detectIsJson()) {
                                          try {
                                              //Parse it to json
                                              prop.value = angular.fromJson(val);
                                          } catch (e) {
                                              // not json
                                              prop.value = val;
                                          }
                                      } else {
                                          prop.value = val;
                                      }
                                  } else {
                                      prop.value = val;
                                  }
                              }
                          });
                      }
                  }
              });
          }
          //here we check to see if we've been passed a selected macro and if so we'll set the
          //editor to start with parameter editing
          if ($scope.model.dialogData && $scope.model.dialogData.macroData) {
              $scope.wizardStep = 'paramSelect';
          }
          //get the macro list - pass in a filter if it is only for rte
          entityResource.getAll('Macro', $scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) {
              if (angular.isArray(data) && data.length == 0) {
                  $scope.nomacros = true;
              }
              //if 'allowedMacros' is specified, we need to filter
              if (angular.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) {
                  $scope.macros = _.filter(data, function (d) {
                      return _.contains($scope.model.dialogData.allowedMacros, d.alias);
                  });
              } else {
                  $scope.macros = data;
              }
              //check if there's a pre-selected macro and if it exists
              if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) {
                  var found = _.find(data, function (item) {
                      return item.alias === $scope.model.dialogData.macroData.macroAlias;
                  });
                  if (found) {
                      //select the macro and go to next screen
                      $scope.model.selectedMacro = found;
                      editParams();
                      return;
                  }
              }
              //we don't have a pre-selected macro so ensure the correct step is set
              $scope.wizardStep = 'macroSelect';
          });
      }
      angular.module('umbraco').controller('Umbraco.Overlays.MacroPickerController', MacroPickerController);
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Overlays.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) {
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectMedia');
          }
          var dialogOptions = $scope.model;
          $scope.disableFolderSelect = dialogOptions.disableFolderSelect;
          $scope.onlyImages = dialogOptions.onlyImages;
          $scope.showDetails = dialogOptions.showDetails;
          $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false;
          $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
          $scope.cropSize = dialogOptions.cropSize;
          $scope.lastOpenedNode = localStorageService.get('umbLastOpenedMediaNodeId');
          $scope.lockedFolder = true;
          var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
          var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
          if ($scope.onlyImages) {
              $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes);
          } else {
              // Use whitelist of allowed file types if provided
              if (allowedUploadFiles !== '') {
                  $scope.acceptedFileTypes = allowedUploadFiles;
              } else {
                  // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                  $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
              }
          }
          $scope.maxFileSize = umbracoSettings.maxFileSize + 'KB';
          $scope.model.selectedImages = [];
          $scope.acceptedMediatypes = [];
          mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) {
              $scope.acceptedMediatypes = types;
          });
          $scope.searchOptions = {
              pageNumber: 1,
              pageSize: 100,
              totalItems: 0,
              totalPages: 0,
              filter: ''
          };
          //preload selected item
          $scope.target = undefined;
          if (dialogOptions.currentTarget) {
              $scope.target = dialogOptions.currentTarget;
          }
          function onInit() {
              if ($scope.startNodeId !== -1) {
                  entityResource.getById($scope.startNodeId, 'media').then(function (ent) {
                      $scope.startNodeId = ent.id;
                      run();
                  });
              } else {
                  run();
              }
          }
          function run() {
              //default root item
              if (!$scope.target) {
                  if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
                      entityResource.getById($scope.lastOpenedNode, 'media').then(ensureWithinStartNode, gotoStartNode);
                  } else {
                      gotoStartNode();
                  }
              } else {
                  //if a target is specified, go look it up - generally this target will just contain ids not the actual full
                  //media object so we need to look it up
                  var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
                  var altText = $scope.target.altText;
                  mediaResource.getById(id).then(function (node) {
                      $scope.target = node;
                      if (ensureWithinStartNode(node)) {
                          selectImage(node);
                          $scope.target.url = mediaHelper.resolveFile(node);
                          $scope.target.altText = altText;
                          $scope.openDetailsDialog();
                      }
                  }, gotoStartNode);
              }
          }
          $scope.upload = function (v) {
              angular.element('.umb-file-dropzone-directive .file-select').click();
          };
          $scope.dragLeave = function (el, event) {
              $scope.activeDrag = false;
          };
          $scope.dragEnter = function (el, event) {
              $scope.activeDrag = true;
          };
          $scope.submitFolder = function () {
              if ($scope.newFolderName) {
                  $scope.creatingFolder = true;
                  mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) {
                      //we've added a new folder so lets clear the tree cache for that specific item
                      treeService.clearCache({
                          cacheKey: '__media',
                          //this is the main media tree cache key
                          childrenOf: data.parentId
                      });
                      $scope.creatingFolder = false;
                      $scope.gotoFolder(data);
                      $scope.showFolderInput = false;
                      $scope.newFolderName = '';
                  });
              } else {
                  $scope.showFolderInput = false;
              }
          };
          $scope.enterSubmitFolder = function (event) {
              if (event.keyCode === 13) {
                  $scope.submitFolder();
                  event.stopPropagation();
              }
          };
          $scope.gotoFolder = function (folder) {
              if (!$scope.multiPicker) {
                  deselectAllImages($scope.model.selectedImages);
              }
              if (!folder) {
                  folder = {
                      id: -1,
                      name: 'Media',
                      icon: 'icon-folder'
                  };
              }
              if (folder.id > 0) {
                  entityResource.getAncestors(folder.id, 'media').then(function (anc) {
                      $scope.path = _.filter(anc, function (f) {
                          return f.path.indexOf($scope.startNodeId) !== -1;
                      });
                  });
                  mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) {
                      $scope.acceptedMediatypes = types;
                  });
              } else {
                  $scope.path = [];
              }
              $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual;
              $scope.currentFolder = folder;
              localStorageService.set('umbLastOpenedMediaNodeId', folder.id);
              return getChildren(folder.id);
          };
          $scope.clickHandler = function (image, event, index) {
              if (image.isFolder) {
                  if ($scope.disableFolderSelect) {
                      $scope.gotoFolder(image);
                  } else {
                      eventsService.emit('dialogs.mediaPicker.select', image);
                      selectImage(image);
                  }
              } else {
                  eventsService.emit('dialogs.mediaPicker.select', image);
                  if ($scope.showDetails) {
                      $scope.target = image;
                      // handle both entity and full media object
                      if (image.image) {
                          $scope.target.url = image.image;
                      } else {
                          $scope.target.url = mediaHelper.resolveFile(image);
                      }
                      $scope.openDetailsDialog();
                  } else {
                      selectImage(image);
                  }
              }
          };
          $scope.clickItemName = function (item) {
              if (item.isFolder) {
                  $scope.gotoFolder(item);
              }
          };
          function selectImage(image) {
              if (image.selected) {
                  for (var i = 0; $scope.model.selectedImages.length > i; i++) {
                      var imageInSelection = $scope.model.selectedImages[i];
                      if (image.key === imageInSelection.key) {
                          image.selected = false;
                          $scope.model.selectedImages.splice(i, 1);
                      }
                  }
              } else {
                  if (!$scope.multiPicker) {
                      deselectAllImages($scope.model.selectedImages);
                  }
                  image.selected = true;
                  $scope.model.selectedImages.push(image);
              }
          }
          function deselectAllImages(images) {
              for (var i = 0; i < images.length; i++) {
                  var image = images[i];
                  image.selected = false;
              }
              images.length = 0;
          }
          $scope.onUploadComplete = function (files) {
              $scope.gotoFolder($scope.currentFolder).then(function () {
                  if (files.length === 1 && $scope.model.selectedImages.length === 0) {
                      selectImage($scope.images[$scope.images.length - 1]);
                  }
              });
          };
          $scope.onFilesQueue = function () {
              $scope.activeDrag = false;
          };
          function ensureWithinStartNode(node) {
              // make sure that last opened node is on the same path as start node
              var nodePath = node.path.split(',');
              if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) {
                  $scope.gotoFolder({
                      id: $scope.lastOpenedNode,
                      name: 'Media',
                      icon: 'icon-folder'
                  });
                  return true;
              } else {
                  $scope.gotoFolder({
                      id: $scope.startNodeId,
                      name: 'Media',
                      icon: 'icon-folder'
                  });
                  return false;
              }
          }
          function gotoStartNode(err) {
              $scope.gotoFolder({
                  id: $scope.startNodeId,
                  name: 'Media',
                  icon: 'icon-folder'
              });
          }
          $scope.openDetailsDialog = function () {
              $scope.mediaPickerDetailsOverlay = {};
              $scope.mediaPickerDetailsOverlay.show = true;
              $scope.mediaPickerDetailsOverlay.submit = function (model) {
                  $scope.model.selectedImages.push($scope.target);
                  $scope.model.submit($scope.model);
                  $scope.mediaPickerDetailsOverlay.show = false;
                  $scope.mediaPickerDetailsOverlay = null;
              };
              $scope.mediaPickerDetailsOverlay.close = function (oldModel) {
                  $scope.mediaPickerDetailsOverlay.show = false;
                  $scope.mediaPickerDetailsOverlay = null;
              };
          };
          var debounceSearchMedia = _.debounce(function () {
              $scope.$apply(function () {
                  if ($scope.searchOptions.filter) {
                      searchMedia();
                  } else {
                      // reset pagination
                      $scope.searchOptions = {
                          pageNumber: 1,
                          pageSize: 100,
                          totalItems: 0,
                          totalPages: 0,
                          filter: ''
                      };
                      getChildren($scope.currentFolder.id);
                  }
              });
          }, 500);
          $scope.changeSearch = function () {
              $scope.loading = true;
              debounceSearchMedia();
          };
          $scope.changePagination = function (pageNumber) {
              $scope.loading = true;
              $scope.searchOptions.pageNumber = pageNumber;
              searchMedia();
          };
          function searchMedia() {
              $scope.loading = true;
              entityResource.getPagedDescendants($scope.startNodeId, 'Media', $scope.searchOptions).then(function (data) {
                  // update image data to work with image grid
                  angular.forEach(data.items, function (mediaItem) {
                      // set thumbnail and src
                      mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true);
                      mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false);
                      // set properties to match a media object
                      if (mediaItem.metaData && mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) {
                          mediaItem.properties = [
                              {
                                  alias: 'umbracoWidth',
                                  value: mediaItem.metaData.umbracoWidth.Value
                              },
                              {
                                  alias: 'umbracoHeight',
                                  value: mediaItem.metaData.umbracoHeight.Value
                              }
                          ];
                      }
                  });
                  // update images
                  $scope.images = data.items ? data.items : [];
                  // update pagination
                  if (data.pageNumber > 0)
                      $scope.searchOptions.pageNumber = data.pageNumber;
                  if (data.pageSize > 0)
                      $scope.searchOptions.pageSize = data.pageSize;
                  $scope.searchOptions.totalItems = data.totalItems;
                  $scope.searchOptions.totalPages = data.totalPages;
                  // set already selected images to selected
                  preSelectImages();
                  $scope.loading = false;
              });
          }
          function getChildren(id) {
              $scope.loading = true;
              return mediaResource.getChildren(id).then(function (data) {
                  $scope.searchOptions.filter = '';
                  $scope.images = data.items ? data.items : [];
                  // set already selected images to selected
                  preSelectImages();
                  $scope.loading = false;
              });
          }
          function preSelectImages() {
              for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) {
                  var folderImage = $scope.images[folderImageIndex];
                  var imageIsSelected = false;
                  if ($scope.model && angular.isArray($scope.model.selectedImages)) {
                      for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) {
                          var selectedImage = $scope.model.selectedImages[selectedImageIndex];
                          if (folderImage.key === selectedImage.key) {
                              imageIsSelected = true;
                          }
                      }
                  }
                  if (imageIsSelected) {
                      folderImage.selected = true;
                  }
              }
          }
          onInit();
      });
      angular.module('umbraco').controller('Umbraco.Overlays.MediaTypePickerController', function ($scope) {
          $scope.select = function (mediatype) {
              $scope.model.selectedType = mediatype;
              $scope.model.submit($scope.model);
              $scope.model.show = false;
          };
      });
      //used for the member picker dialog
      angular.module('umbraco').controller('Umbraco.Overlays.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log, localizationService) {
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('defaultdialogs_selectMemberGroup');
          }
          $scope.dialogTreeEventHandler = $({});
          $scope.multiPicker = $scope.model.multiPicker;
          function activate() {
              if ($scope.multiPicker) {
                  $scope.model.selectedMemberGroups = [];
              } else {
                  $scope.model.selectedMemberGroup = '';
              }
          }
          function selectMemberGroup(id) {
              $scope.model.selectedMemberGroup = id;
          }
          function selectMemberGroups(id) {
              $scope.model.selectedMemberGroups.push(id);
          }
          /** Method used for selecting a node */
          function select(text, id) {
              if ($scope.model.multiPicker) {
                  selectMemberGroups(id);
              } else {
                  selectMemberGroup(id);
                  $scope.model.submit($scope.model);
              }
          }
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              eventsService.emit('dialogs.memberGroupPicker.select', args);
              //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
              //from the server in this method.
              select(args.node.name, args.node.id);
              //toggle checked state
              args.node.selected = args.node.selected === true ? false : true;
          }
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
          activate();
      });
      (function () {
          'use strict';
          function MoveOverlay($scope, localizationService, eventsService, entityHelper) {
              var vm = this;
              vm.hideSearch = hideSearch;
              vm.selectResult = selectResult;
              vm.onSearchResults = onSearchResults;
              var dialogOptions = $scope.model;
              var searchText = 'Search...';
              var node = dialogOptions.currentNode;
              localizationService.localize('general_search').then(function (value) {
                  searchText = value + '...';
              });
              if (!$scope.model.title) {
                  $scope.model.title = localizationService.localize('actions_move');
              }
              $scope.model.relateToOriginal = true;
              $scope.dialogTreeEventHandler = $({});
              vm.searchInfo = {
                  searchFromId: null,
                  searchFromName: null,
                  showSearch: false,
                  results: [],
                  selectedSearchResults: []
              };
              // get entity type based on the section
              $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
              function nodeSelectHandler(ev, args) {
                  if (args && args.event) {
                      args.event.preventDefault();
                      args.event.stopPropagation();
                  }
                  //eventsService.emit("editors.content.copyController.select", args);
                  if ($scope.model.target) {
                      //un-select if there's a current one selected
                      $scope.model.target.selected = false;
                  }
                  $scope.model.target = args.node;
                  $scope.model.target.selected = true;
              }
              function nodeExpandedHandler(ev, args) {
                  // open mini list view for list views
                  if (args.node.metaData.isContainer) {
                      openMiniListView(args.node);
                  }
              }
              function hideSearch() {
                  vm.searchInfo.showSearch = false;
                  vm.searchInfo.searchFromId = null;
                  vm.searchInfo.searchFromName = null;
                  vm.searchInfo.results = [];
              }
              // method to select a search result
              function selectResult(evt, result) {
                  result.selected = result.selected === true ? false : true;
                  nodeSelectHandler(evt, {
                      event: evt,
                      node: result
                  });
              }
              //callback when there are search results
              function onSearchResults(results) {
                  vm.searchInfo.results = results;
                  vm.searchInfo.showSearch = true;
              }
              $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
              $scope.$on('$destroy', function () {
                  $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
                  $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
              });
              // Mini list view
              $scope.selectListViewNode = function (node) {
                  node.selected = node.selected === true ? false : true;
                  nodeSelectHandler({}, { node: node });
              };
              $scope.closeMiniListView = function () {
                  $scope.miniListView = undefined;
              };
              function openMiniListView(node) {
                  $scope.miniListView = node;
              }
          }
          angular.module('umbraco').controller('Umbraco.Overlays.MoveOverlay', MoveOverlay);
      }());
      (function () {
          'use strict';
          function NodePermissionsController($scope, localizationService) {
              var vm = this;
              function onInit() {
                  // set default title
                  if (!$scope.model.title) {
                      localizationService.localize('defaultdialogs_permissionsEdit').then(function (value) {
                          $scope.model.title = value + ' ' + $scope.model.node.name;
                      });
                  }
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.NodePermissionsController', NodePermissionsController);
      }());
      (function () {
          'use strict';
          function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) {
              var everything = '';
              var myWebsite = '';
              var ascendingTranslation = '';
              var descendingTranslation = '';
              var vm = this;
              vm.properties = [];
              vm.contentTypes = [];
              vm.conditions = [];
              vm.datePickerConfig = {
                  pickDate: true,
                  pickTime: false,
                  format: 'YYYY-MM-DD'
              };
              vm.chooseSource = chooseSource;
              vm.getPropertyOperators = getPropertyOperators;
              vm.addFilter = addFilter;
              vm.trashFilter = trashFilter;
              vm.changeSortOrder = changeSortOrder;
              vm.setSortProperty = setSortProperty;
              vm.setContentType = setContentType;
              vm.setFilterProperty = setFilterProperty;
              vm.setFilterTerm = setFilterTerm;
              vm.changeConstraintValue = changeConstraintValue;
              vm.datePickerChange = datePickerChange;
              function onInit() {
                  vm.query = {
                      contentType: { name: everything },
                      source: { name: myWebsite },
                      filters: [{
                              property: undefined,
                              operator: undefined
                          }],
                      sort: {
                          property: {
                              alias: '',
                              name: ''
                          },
                          direction: 'ascending',
                          //This is the value for sorting sent to server
                          translation: {
                              currentLabel: ascendingTranslation,
                              //This is the localized UI value in the the dialog
                              ascending: ascendingTranslation,
                              descending: descendingTranslation
                          }
                      }
                  };
                  templateQueryResource.getAllowedProperties().then(function (properties) {
                      vm.properties = properties;
                  });
                  templateQueryResource.getContentTypes().then(function (contentTypes) {
                      vm.contentTypes = contentTypes;
                  });
                  templateQueryResource.getFilterConditions().then(function (conditions) {
                      vm.conditions = conditions;
                  });
                  throttledFunc();
              }
              function chooseSource(query) {
                  vm.contentPickerOverlay = {
                      view: 'contentpicker',
                      show: true,
                      submit: function (model) {
                          var selectedNodeId = model.selection[0].id;
                          var selectedNodeName = model.selection[0].name;
                          if (selectedNodeId > 0) {
                              query.source = {
                                  id: selectedNodeId,
                                  name: selectedNodeName
                              };
                          } else {
                              query.source.name = myWebsite;
                              delete query.source.id;
                          }
                          throttledFunc();
                          vm.contentPickerOverlay.show = false;
                          vm.contentPickerOverlay = null;
                      },
                      close: function (oldModel) {
                          vm.contentPickerOverlay.show = false;
                          vm.contentPickerOverlay = null;
                      }
                  };
              }
              function getPropertyOperators(property) {
                  var conditions = _.filter(vm.conditions, function (condition) {
                      var index = condition.appliesTo.indexOf(property.type);
                      return index >= 0;
                  });
                  return conditions;
              }
              function addFilter(query) {
                  query.filters.push({});
              }
              function trashFilter(query, filter) {
                  for (var i = 0; i < query.filters.length; i++) {
                      if (query.filters[i] == filter) {
                          query.filters.splice(i, 1);
                      }
                  }
                  //if we remove the last one, add a new one to generate ui for it.
                  if (query.filters.length == 0) {
                      query.filters.push({});
                  }
              }
              function changeSortOrder(query) {
                  if (query.sort.direction === 'ascending') {
                      query.sort.direction = 'descending';
                      query.sort.translation.currentLabel = query.sort.translation.descending;
                  } else {
                      query.sort.direction = 'ascending';
                      query.sort.translation.currentLabel = query.sort.translation.ascending;
                  }
                  throttledFunc();
              }
              function setSortProperty(query, property) {
                  query.sort.property = property;
                  if (property.type === 'datetime') {
                      query.sort.direction = 'descending';
                      query.sort.translation.currentLabel = query.sort.translation.descending;
                  } else {
                      query.sort.direction = 'ascending';
                      query.sort.translation.currentLabel = query.sort.translation.ascending;
                  }
                  throttledFunc();
              }
              function setContentType(contentType) {
                  vm.query.contentType = contentType;
                  throttledFunc();
              }
              function setFilterProperty(filter, property) {
                  filter.property = property;
                  filter.term = {};
                  filter.constraintValue = '';
              }
              function setFilterTerm(filter, term) {
                  filter.term = term;
                  if (filter.constraintValue) {
                      throttledFunc();
                  }
              }
              function changeConstraintValue() {
                  throttledFunc();
              }
              function datePickerChange(event, filter) {
                  if (event.date && event.date.isValid()) {
                      filter.constraintValue = event.date.format(vm.datePickerConfig.format);
                      throttledFunc();
                  }
              }
              var throttledFunc = _.throttle(function () {
                  templateQueryResource.postTemplateQuery(vm.query).then(function (response) {
                      $scope.model.result = response;
                  });
              }, 200);
              localizationService.localizeMany([
                  'template_allContent',
                  'template_websiteRoot',
                  'template_ascending',
                  'template_descending'
              ]).then(function (res) {
                  everything = res[0];
                  myWebsite = res[1];
                  ascendingTranslation = res[2];
                  descendingTranslation = res[3];
                  onInit();
              });
          }
          angular.module('umbraco').controller('Umbraco.Overlays.QueryBuilderController', QueryBuilderOverlayController);
      }());
      (function () {
          'use strict';
          function SectionPickerController($scope, sectionResource, localizationService) {
              var vm = this;
              vm.sections = [];
              vm.loading = false;
              vm.selectSection = selectSection;
              //////////
              function onInit() {
                  vm.loading = true;
                  // set default title
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectSections');
                  }
                  // make sure we can push to something
                  if (!$scope.model.selection) {
                      $scope.model.selection = [];
                  }
                  // get sections
                  sectionResource.getAllSections().then(function (sections) {
                      vm.sections = sections;
                      setSectionIcon(vm.sections);
                      if ($scope.model.selection && $scope.model.selection.length > 0) {
                          preSelect($scope.model.selection);
                      }
                      vm.loading = false;
                  });
              }
              function preSelect(selection) {
                  angular.forEach(selection, function (selected) {
                      angular.forEach(vm.sections, function (section) {
                          if (selected.alias === section.alias) {
                              section.selected = true;
                          }
                      });
                  });
              }
              function selectSection(section) {
                  if (!section.selected) {
                      section.selected = true;
                      $scope.model.selection.push(section);
                  } else {
                      angular.forEach($scope.model.selection, function (selectedSection, index) {
                          if (selectedSection.alias === section.alias) {
                              section.selected = false;
                              $scope.model.selection.splice(index, 1);
                          }
                      });
                  }
              }
              function setSectionIcon(sections) {
                  angular.forEach(sections, function (section) {
                      section.icon = 'icon-section ' + section.cssclass;
                  });
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.SectionPickerController', SectionPickerController);
      }());
      (function () {
          'use strict';
          function TemplateSectionsOverlayController($scope) {
              var vm = this;
              $scope.model.mandatoryRenderSection = false;
              if (!$scope.model.title) {
                  $scope.model.title = 'Sections';
              }
              vm.select = select;
              function onInit() {
                  if ($scope.model.hasMaster) {
                      $scope.model.insertType = 'addSection';
                  } else {
                      $scope.model.insertType = 'renderBody';
                  }
              }
              function select(type) {
                  $scope.model.insertType = type;
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.TemplateSectionsOverlay', TemplateSectionsOverlayController);
      }());
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Overlays.TreePickerController', function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
          var tree = null;
          var dialogOptions = $scope.model;
          $scope.treeReady = false;
          $scope.dialogTreeEventHandler = $({});
          $scope.section = dialogOptions.section;
          $scope.treeAlias = dialogOptions.treeAlias;
          $scope.multiPicker = dialogOptions.multiPicker;
          $scope.hideHeader = typeof dialogOptions.hideHeader === 'boolean' ? dialogOptions.hideHeader : true;
          // if you need to load a not initialized tree set this value to false - default is true
          $scope.onlyInitialized = dialogOptions.onlyInitialized;
          $scope.searchInfo = {
              searchFromId: dialogOptions.startNodeId,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          $scope.model.selection = [];
          //Used for toggling an empty-state message
          //Some trees can have no items (dictionary & forms email templates)
          $scope.hasItems = true;
          $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
          //This is called from ng-init
          //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. 
          //this is probably an anti pattern IMO and shouldn't be used
          $scope.init = function (contentType) {
              if (contentType === 'content') {
                  $scope.entityType = 'Document';
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectContent');
                  }
              } else if (contentType === 'member') {
                  $scope.entityType = 'Member';
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectMember');
                  }
              } else if (contentType === 'media') {
                  $scope.entityType = 'Media';
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectMedia');
                  }
              }
          };
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
          $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document';
          //min / max values
          if (dialogOptions.minNumber) {
              dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
          }
          if (dialogOptions.maxNumber) {
              dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
          }
          if (dialogOptions.section === 'member') {
              $scope.entityType = 'Member';
          } else if (dialogOptions.section === 'media') {
              $scope.entityType = 'Media';
          }
          // Search and listviews is only working for content, media and member section
          var searchableSections = [
              'content',
              'media',
              'member'
          ];
          $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
          //if a alternative startnode is used, we need to check if it is a container
          if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== '-1') {
              entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) {
                  if (node.metaData.IsContainer) {
                      openMiniListView(node);
                  }
                  initTree();
              });
          } else {
              initTree();
          }
          //Configures filtering
          if (dialogOptions.filter) {
              dialogOptions.filterExclude = false;
              dialogOptions.filterAdvanced = false;
              //used advanced filtering
              if (angular.isFunction(dialogOptions.filter)) {
                  dialogOptions.filterAdvanced = true;
              } else if (angular.isObject(dialogOptions.filter)) {
                  dialogOptions.filterAdvanced = true;
              } else {
                  if (dialogOptions.filter.startsWith('!')) {
                      dialogOptions.filterExclude = true;
                      dialogOptions.filter = dialogOptions.filter.substring(1);
                  }
                  //used advanced filtering
                  if (dialogOptions.filter.startsWith('{')) {
                      dialogOptions.filterAdvanced = true;
                      //convert to object
                      dialogOptions.filter = angular.fromJson(dialogOptions.filter);
                  }
              }
          }
          function initTree() {
              //create the custom query string param for this tree
              $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : '';
              $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : '';
              $scope.treeReady = true;
          }
          function nodeExpandedHandler(ev, args) {
              // open mini list view for list views
              if (args.node.metaData.isContainer) {
                  openMiniListView(args.node);
              }
              if (angular.isArray(args.children)) {
                  //iterate children
                  _.each(args.children, function (child) {
                      //now we need to look in the already selected search results and
                      // toggle the check boxes for those ones that are listed
                      var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                          return child.id == selected.id;
                      });
                      if (exists) {
                          child.selected = true;
                      }
                  });
                  //check filter
                  performFiltering(args.children);
              }
          }
          //gets the tree object when it loads
          function treeLoadedHandler(ev, args) {
              //args.tree contains children (args.tree.root.children)
              $scope.hasItems = args.tree.root.children.length > 0;
              tree = args.tree;
          }
          //wires up selection
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if (args.node.metaData.isSearchResult) {
                  //check if the item selected was a search result from a list view
                  //unselect
                  select(args.node.name, args.node.id);
                  //remove it from the list view children
                  var listView = args.node.parent();
                  listView.children = _.reject(listView.children, function (child) {
                      return child.id == args.node.id;
                  });
                  //remove it from the custom tracked search result list
                  $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                      return i.id == args.node.id;
                  });
              } else {
                  eventsService.emit('dialogs.treePickerController.select', args);
                  if (args.node.filtered) {
                      return;
                  }
                  //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                  //from the server in this method.
                  if ($scope.model.select) {
                      $scope.model.select(args.node);
                  } else {
                      select(args.node.name, args.node.id);
                      //toggle checked state
                      args.node.selected = args.node.selected === true ? false : true;
                  }
              }
          }
          /** Method used for selecting a node */
          function select(text, id, entity) {
              //if we get the root, we just return a constructed entity, no need for server data
              if (id < 0) {
                  var rootNode = {
                      alias: null,
                      icon: 'icon-folder',
                      id: id,
                      name: text
                  };
                  if ($scope.multiPicker) {
                      if (entity) {
                          multiSelectItem(entity);
                      } else {
                          multiSelectItem(rootNode);
                      }
                  } else {
                      $scope.model.selection.push(rootNode);
                      $scope.model.submit($scope.model);
                  }
              } else {
                  if ($scope.multiPicker) {
                      if (entity) {
                          multiSelectItem(entity);
                      } else {
                          //otherwise we have to get it from the server
                          entityResource.getById(id, $scope.entityType).then(function (ent) {
                              multiSelectItem(ent);
                          });
                      }
                  } else {
                      $scope.hideSearch();
                      //if an entity has been passed in, use it
                      if (entity) {
                          $scope.model.selection.push(entity);
                          $scope.model.submit($scope.model);
                      } else {
                          //otherwise we have to get it from the server
                          entityResource.getById(id, $scope.entityType).then(function (ent) {
                              $scope.model.selection.push(ent);
                              $scope.model.submit($scope.model);
                          });
                      }
                  }
              }
          }
          function multiSelectItem(item) {
              var found = false;
              var foundIndex = 0;
              if ($scope.model.selection.length > 0) {
                  for (i = 0; $scope.model.selection.length > i; i++) {
                      var selectedItem = $scope.model.selection[i];
                      if (selectedItem.id === item.id) {
                          found = true;
                          foundIndex = i;
                      }
                  }
              }
              if (found) {
                  $scope.model.selection.splice(foundIndex, 1);
              } else {
                  $scope.model.selection.push(item);
              }
          }
          function performFiltering(nodes) {
              if (!dialogOptions.filter) {
                  return;
              }
              //remove any list view search nodes from being filtered since these are special nodes that always must
              // be allowed to be clicked on
              nodes = _.filter(nodes, function (n) {
                  return !angular.isObject(n.metaData.listViewNode);
              });
              if (dialogOptions.filterAdvanced) {
                  //filter either based on a method or an object
                  var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter);
                  angular.forEach(filtered, function (value, key) {
                      value.filtered = true;
                      if (dialogOptions.filterCssClass) {
                          if (!value.cssClasses) {
                              value.cssClasses = [];
                          }
                          value.cssClasses.push(dialogOptions.filterCssClass);
                      }
                  });
              } else {
                  var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(',');
                  angular.forEach(nodes, function (value, key) {
                      var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
                      if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
                          value.filtered = true;
                          if (dialogOptions.filterCssClass) {
                              if (!value.cssClasses) {
                                  value.cssClasses = [];
                              }
                              value.cssClasses.push(dialogOptions.filterCssClass);
                          }
                      }
                  });
              }
          }
          $scope.multiSubmit = function (result) {
              entityResource.getByIds(result, $scope.entityType).then(function (ents) {
                  $scope.submit(ents);
              });
          };
          /** method to select a search result */
          $scope.selectResult = function (evt, result) {
              if (result.filtered) {
                  return;
              }
              result.selected = result.selected === true ? false : true;
              //since result = an entity, we'll pass it in so we don't have to go back to the server
              select(result.name, result.id, result);
              //add/remove to our custom tracked list of selected search results
              if (result.selected) {
                  $scope.searchInfo.selectedSearchResults.push(result);
              } else {
                  $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                      return i.id == result.id;
                  });
              }
              //ensure the tree node in the tree is checked/unchecked if it already exists there
              if (tree) {
                  var found = treeService.getDescendantNode(tree.root, result.id);
                  if (found) {
                      found.selected = result.selected;
                  }
              }
          };
          $scope.hideSearch = function () {
              //Traverse the entire displayed tree and update each node to sync with the selected search results
              if (tree) {
                  //we need to ensure that any currently displayed nodes that get selected
                  // from the search get updated to have a check box!
                  function checkChildren(children) {
                      _.each(children, function (child) {
                          //check if the id is in the selection, if so ensure it's flagged as selected
                          var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                              return child.id == selected.id;
                          });
                          //if the curr node exists in selected search results, ensure it's checked
                          if (exists) {
                              child.selected = true;
                          }    //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
                          else if (child.metaData.isSearchResult) {
                              //if this tree node is under a list view it means that the node was added
                              // to the tree dynamically under the list view that was searched, so we actually want to remove
                              // it all together from the tree
                              var listView = child.parent();
                              listView.children = _.reject(listView.children, function (c) {
                                  return c.id == child.id;
                              });
                          }
                          //check if the current node is a list view and if so, check if there's any new results
                          // that need to be added as child nodes to it based on search results selected
                          if (child.metaData.isContainer) {
                              child.cssClasses = _.reject(child.cssClasses, function (c) {
                                  return c === 'tree-node-slide-up-hide-active';
                              });
                              var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                                  return i.parentId == child.id;
                              });
                              _.each(listViewResults, function (item) {
                                  var childExists = _.find(child.children, function (c) {
                                      return c.id == item.id;
                                  });
                                  if (!childExists) {
                                      var parent = child;
                                      child.children.unshift({
                                          id: item.id,
                                          name: item.name,
                                          cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                          level: child.level + 1,
                                          metaData: { isSearchResult: true },
                                          hasChildren: false,
                                          parent: function () {
                                              return parent;
                                          }
                                      });
                                  }
                              });
                          }
                          //recurse
                          if (child.children && child.children.length > 0) {
                              checkChildren(child.children);
                          }
                      });
                  }
                  checkChildren(tree.root.children);
              }
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          $scope.onSearchResults = function (results) {
              //filter all items - this will mark an item as filtered
              performFiltering(results);
              //now actually remove all filtered items so they are not even displayed
              results = _.filter(results, function (item) {
                  return !item.filtered;
              });
              $scope.searchInfo.results = results;
              //sync with the curr selected results
              _.each($scope.searchInfo.results, function (result) {
                  var exists = _.find($scope.model.selection, function (selectedId) {
                      return result.id == selectedId;
                  });
                  if (exists) {
                      result.selected = true;
                  }
              });
              $scope.searchInfo.showSearch = true;
          };
          $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
          $scope.selectListViewNode = function (node) {
              select(node.name, node.id);
              //toggle checked state
              node.selected = node.selected === true ? false : true;
          };
          $scope.closeMiniListView = function () {
              $scope.miniListView = undefined;
          };
          function openMiniListView(node) {
              $scope.miniListView = node;
          }
      });
      angular.module('umbraco').controller('Umbraco.Overlays.UserController', function ($scope, $location, $timeout, dashboardResource, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) {
          $scope.history = historyService.getCurrent();
          $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
          $scope.showPasswordFields = false;
          $scope.changePasswordButtonState = 'init';
          $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version;
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('general_user');
          }
          $scope.externalLoginProviders = externalLoginInfo.providers;
          $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
          var evts = [];
          evts.push(eventsService.on('historyService.add', function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on('historyService.remove', function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on('historyService.removeAll', function (e, args) {
              $scope.history = [];
          }));
          $scope.logout = function () {
              //Add event listener for when there are pending changes on an editor which means our route was not successful
              var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) {
                  //one time listener, remove the event
                  pendingChangeEvent();
                  $scope.model.close();
              });
              //perform the path change, if it is successful then the promise will resolve otherwise it will fail
              $scope.model.close();
              $location.path('/logout');
          };
          $scope.gotoHistory = function (link) {
              $location.path(link);
              $scope.model.close();
          };
          //Manually update the remaining timeout seconds
          function updateTimeout() {
              $timeout(function () {
                  if ($scope.remainingAuthSeconds > 0) {
                      $scope.remainingAuthSeconds--;
                      $scope.$digest();
                      //recurse
                      updateTimeout();
                  }
              }, 1000, false);    // 1 second, do NOT execute a global digest
          }
          function updateUserInfo() {
              //get the user
              userService.getCurrentUser().then(function (user) {
                  $scope.user = user;
                  if ($scope.user) {
                      $scope.model.title = user.name;
                      $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                      $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1;
                      //set the timer
                      updateTimeout();
                      authResource.getCurrentUserLinkedLogins().then(function (logins) {
                          //reset all to be un-linked
                          for (var provider in $scope.externalLoginProviders) {
                              $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
                          }
                          //set the linked logins
                          for (var login in logins) {
                              var found = _.find($scope.externalLoginProviders, function (i) {
                                  return i.authType == login;
                              });
                              if (found) {
                                  found.linkedProviderKey = logins[login];
                              }
                          }
                      });
                  }
              });
          }
          $scope.unlink = function (e, loginProvider, providerKey) {
              var result = confirm('Are you sure you want to unlink this account?');
              if (!result) {
                  e.preventDefault();
                  return;
              }
              authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
                  updateUserInfo();
              });
          };
          updateUserInfo();
          //remove all event handlers
          $scope.$on('$destroy', function () {
              for (var e = 0; e < evts.length; e++) {
                  evts[e]();
              }
          });
          /* ---------- UPDATE PASSWORD ---------- */
          //create the initial model for change password
          $scope.changePasswordModel = {
              config: {},
              value: {}
          };
          //go get the config for the membership provider and add it to the model
          authResource.getMembershipProviderConfig().then(function (data) {
              $scope.changePasswordModel.config = data;
              //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
              //this will ensure the oldPassword is shown so they can change it
              // disable reset password functionality beacuse it does not make sense inside the backoffice
              $scope.changePasswordModel.config.hasPassword = true;
              $scope.changePasswordModel.config.disableToggle = true;
              $scope.changePasswordModel.config.enableReset = false;
          });
          $scope.changePassword = function () {
              if (formHelper.submitForm({ scope: $scope })) {
                  $scope.changePasswordButtonState = 'busy';
                  currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) {
                      //reset old data 
                      clearPasswordFields();
                      //if the password has been reset, then update our model
                      if (data.value) {
                          $scope.changePasswordModel.value.generatedPassword = data.value;
                      }
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      $scope.changePasswordButtonState = 'success';
                      $timeout(function () {
                          $scope.togglePasswordFields();
                      }, 2000);
                  }, function (err) {
                      formHelper.handleError(err);
                      $scope.changePasswordButtonState = 'error';
                  });
              }
          };
          $scope.togglePasswordFields = function () {
              clearPasswordFields();
              $scope.showPasswordFields = !$scope.showPasswordFields;
          };
          function clearPasswordFields() {
              $scope.changePasswordModel.value.oldPassword = '';
              $scope.changePasswordModel.value.newPassword = '';
              $scope.changePasswordModel.value.confirm = '';
          }
          dashboardResource.getDashboard('user-dialog').then(function (dashboard) {
              $scope.dashboard = dashboard;
          });
      });
      (function () {
          'use strict';
          function UserGroupPickerController($scope, userGroupsResource, localizationService) {
              var vm = this;
              vm.userGroups = [];
              vm.loading = false;
              vm.selectUserGroup = selectUserGroup;
              //////////
              function onInit() {
                  vm.loading = true;
                  // set default title
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectUsers');
                  }
                  // make sure we can push to something
                  if (!$scope.model.selection) {
                      $scope.model.selection = [];
                  }
                  // get venues
                  userGroupsResource.getUserGroups().then(function (userGroups) {
                      vm.userGroups = userGroups;
                      if ($scope.model.selection && $scope.model.selection.length > 0) {
                          preSelect($scope.model.selection);
                      }
                      vm.loading = false;
                  });
              }
              function preSelect(selection) {
                  angular.forEach(selection, function (selected) {
                      angular.forEach(vm.userGroups, function (userGroup) {
                          if (selected.id === userGroup.id) {
                              userGroup.selected = true;
                          }
                      });
                  });
              }
              function selectUserGroup(userGroup) {
                  if (!userGroup.selected) {
                      userGroup.selected = true;
                      $scope.model.selection.push(userGroup);
                  } else {
                      angular.forEach($scope.model.selection, function (selectedUserGroup, index) {
                          if (selectedUserGroup.id === userGroup.id) {
                              userGroup.selected = false;
                              $scope.model.selection.splice(index, 1);
                          }
                      });
                  }
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.UserGroupPickerController', UserGroupPickerController);
      }());
      (function () {
          'use strict';
          function UserPickerController($scope, usersResource, localizationService) {
              var vm = this;
              vm.users = [];
              vm.loading = false;
              vm.usersOptions = {};
              vm.selectUser = selectUser;
              vm.searchUsers = searchUsers;
              vm.changePageNumber = changePageNumber;
              //////////
              function onInit() {
                  vm.loading = true;
                  // set default title
                  if (!$scope.model.title) {
                      $scope.model.title = localizationService.localize('defaultdialogs_selectUsers');
                  }
                  // make sure we can push to something
                  if (!$scope.model.selection) {
                      $scope.model.selection = [];
                  }
                  // get users
                  getUsers();
              }
              function preSelect(selection, users) {
                  angular.forEach(selection, function (selected) {
                      angular.forEach(users, function (user) {
                          if (selected.id === user.id) {
                              user.selected = true;
                          }
                      });
                  });
              }
              function selectUser(user) {
                  if (!user.selected) {
                      user.selected = true;
                      $scope.model.selection.push(user);
                  } else {
                      angular.forEach($scope.model.selection, function (selectedUser, index) {
                          if (selectedUser.id === user.id) {
                              user.selected = false;
                              $scope.model.selection.splice(index, 1);
                          }
                      });
                  }
              }
              var search = _.debounce(function () {
                  $scope.$apply(function () {
                      getUsers();
                  });
              }, 500);
              function searchUsers() {
                  search();
              }
              function getUsers() {
                  vm.loading = true;
                  // Get users
                  usersResource.getPagedResults(vm.usersOptions).then(function (users) {
                      vm.users = users.items;
                      vm.usersOptions.pageNumber = users.pageNumber;
                      vm.usersOptions.pageSize = users.pageSize;
                      vm.usersOptions.totalItems = users.totalItems;
                      vm.usersOptions.totalPages = users.totalPages;
                      preSelect($scope.model.selection, vm.users);
                      vm.loading = false;
                  });
              }
              function changePageNumber(pageNumber) {
                  vm.usersOptions.pageNumber = pageNumber;
                  getUsers();
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Overlays.UserPickerController', UserPickerController);
      }());
      angular.module('umbraco').controller('Umbraco.Overlays.YsodController', function ($scope, legacyResource, treeService, navigationService, localizationService) {
          if (!$scope.model.title) {
              $scope.model.title = localizationService.localize('errors_receivedErrorFromServer');
          }
          if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) {
              //trim whitespace
              $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim();
          }
          if ($scope.model.error && $scope.model.error.data) {
              $scope.model.error.data.InnerExceptions = [];
              var ex = $scope.model.error.data.InnerException;
              while (ex) {
                  if (ex.StackTrace) {
                      ex.StackTrace = ex.StackTrace.trim();
                  }
                  $scope.model.error.data.InnerExceptions.push(ex);
                  ex = ex.InnerException;
              }
          }
      });
      (function () {
          'use strict';
          function NodeNameController($scope) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  if (element.val().toLowerCase() === 'home') {
                      $scope.model.nextStep();
                  } else {
                      vm.error = true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateContent.NodeNameController', NodeNameController);
      }());
      (function () {
          'use strict';
          function DocTypeNameController($scope) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  if (element.val().toLowerCase() === 'home page') {
                      $scope.model.nextStep();
                  } else {
                      vm.error = true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.DocTypeNameController', DocTypeNameController);
      }());
      (function () {
          'use strict';
          function PropertyNameController($scope) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  if (element.val().toLowerCase() === 'welcome text') {
                      $scope.model.nextStep();
                  } else {
                      vm.error = true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.PropertyNameController', PropertyNameController);
      }());
      (function () {
          'use strict';
          function TabNameController($scope) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  if (element.val().toLowerCase() === 'home') {
                      $scope.model.nextStep();
                  } else {
                      vm.error = true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.TabNameController', TabNameController);
      }());
      (function () {
          'use strict';
          function FolderNameController($scope) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  if (element.val().toLowerCase() === 'my images') {
                      $scope.model.nextStep();
                  } else {
                      vm.error = true;
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.FolderNameController', FolderNameController);
      }());
      (function () {
          'use strict';
          function UploadImagesController($scope, editorState, mediaResource) {
              var vm = this;
              var element = angular.element($scope.model.currentStep.element);
              vm.error = false;
              vm.initNextStep = initNextStep;
              function initNextStep() {
                  vm.error = false;
                  vm.buttonState = 'busy';
                  var currentNode = editorState.getCurrent();
                  // make sure we have uploaded at least one image
                  mediaResource.getChildren(currentNode.id).then(function (data) {
                      var children = data;
                      if (children.items && children.items.length > 0) {
                          $scope.model.nextStep();
                      } else {
                          vm.error = true;
                      }
                      vm.buttonState = 'init';
                  });
              }
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.UploadImagesController', UploadImagesController);
      }());
      (function () {
          'use strict';
          function TemplatesTreeController($scope) {
              var vm = this;
              var eventElement = angular.element($scope.model.currentStep.eventElement);
              function onInit() {
                  // check if tree is already open - if it is - go to next step
                  if (eventElement.hasClass('icon-navigation-down')) {
                      $scope.model.nextStep();
                  }
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Tours.UmbIntroRenderInTemplate.TemplatesTreeController', TemplatesTreeController);
      }());
      angular.module('umbraco').controller('Umbraco.Editors.Content.CopyController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
          var dialogOptions = $scope.dialogOptions;
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          $scope.relateToOriginal = true;
          $scope.recursive = true;
          $scope.dialogTreeEventHandler = $({});
          $scope.busy = false;
          $scope.searchInfo = {
              searchFromId: null,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          $scope.treeModel = { hideHeader: false };
          userService.getCurrentUser().then(function (userData) {
              $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
          });
          var node = dialogOptions.currentNode;
          function nodeSelectHandler(ev, args) {
              if (args && args.event) {
                  args.event.preventDefault();
                  args.event.stopPropagation();
              }
              eventsService.emit('editors.content.copyController.select', args);
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          function nodeExpandedHandler(ev, args) {
              // open mini list view for list views
              if (args.node.metaData.isContainer) {
                  openMiniListView(args.node);
              }
          }
          $scope.hideSearch = function () {
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = null;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          // method to select a search result
          $scope.selectResult = function (evt, result) {
              result.selected = result.selected === true ? false : true;
              nodeSelectHandler(evt, {
                  event: evt,
                  node: result
              });
          };
          //callback when there are search results
          $scope.onSearchResults = function (results) {
              $scope.searchInfo.results = results;
              $scope.searchInfo.showSearch = true;
          };
          $scope.copy = function () {
              $scope.busy = true;
              $scope.error = false;
              contentResource.copy({
                  parentId: $scope.target.id,
                  id: node.id,
                  relateToOriginal: $scope.relateToOriginal,
                  recursive: $scope.recursive
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                  navigationService.syncTree({
                      tree: 'content',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'content',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
          });
          // Mini list view
          $scope.selectListViewNode = function (node) {
              node.selected = node.selected === true ? false : true;
              nodeSelectHandler({}, { node: node });
          };
          $scope.closeMiniListView = function () {
              $scope.miniListView = undefined;
          };
          function openMiniListView(node) {
              $scope.miniListView = node;
          }
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.CreateController
   * @function
   * 
   * @description
   * The controller for the content creation dialog
   */
      function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper, $location, navigationService, blueprintConfig) {
          function initialize() {
              contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
                  $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
              });
              $scope.selectContentType = true;
              $scope.selectBlueprint = false;
              $scope.allowBlank = blueprintConfig.allowBlank;
          }
          function close() {
              navigationService.hideMenu();
          }
          function createBlank(docType) {
              $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + docType.alias + '&create=true');
              close();
          }
          function createOrSelectBlueprintIfAny(docType) {
              var blueprintIds = _.keys(docType.blueprints || {});
              $scope.docType = docType;
              if (blueprintIds.length) {
                  if (blueprintConfig.skipSelect) {
                      createFromBlueprint(blueprintIds[0]);
                  } else {
                      $scope.selectContentType = false;
                      $scope.selectBlueprint = true;
                  }
              } else {
                  createBlank(docType);
              }
          }
          function createFromBlueprint(blueprintId) {
              $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + $scope.docType.alias + '&create=true&blueprintId=' + blueprintId);
              close();
          }
          $scope.createBlank = createBlank;
          $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
          $scope.createFromBlueprint = createFromBlueprint;
          initialize();
      }
      angular.module('umbraco').controller('Umbraco.Editors.Content.CreateController', contentCreateController);
      angular.module('umbraco').value('blueprintConfig', {
          skipSelect: false,
          allowBlank: true
      });
      (function () {
          function CreateBlueprintController($scope, contentResource, notificationsService, navigationService, localizationService, formHelper, contentEditingHelper) {
              $scope.message = { name: $scope.currentNode.name };
              var successText = {};
              localizationService.localize('blueprints_createBlueprintFrom', ['<em>' + $scope.message.name + '</em>']).then(function (localizedVal) {
                  $scope.title = localizedVal;
              });
              $scope.cancel = function () {
                  navigationService.hideMenu();
              };
              $scope.create = function () {
                  if (formHelper.submitForm({
                          scope: $scope,
                          formCtrl: this.blueprintForm,
                          statusMessage: 'Creating blueprint...'
                      })) {
                      contentResource.createBlueprintFromContent($scope.currentNode.id, $scope.message.name).then(function (data) {
                          formHelper.resetForm({
                              scope: $scope,
                              notifications: data.notifications
                          });
                          navigationService.hideMenu();
                      }, function (err) {
                          contentEditingHelper.handleSaveError({
                              redirectOnFailure: false,
                              err: err
                          });
                      });
                  }
              };
          }
          angular.module('umbraco').controller('Umbraco.Editors.Content.CreateBlueprintController', CreateBlueprintController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
          $scope.performDelete = function () {
              // stop from firing again on double-click
              if ($scope.busy) {
                  return false;
              }
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              $scope.busy = true;
              contentResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  treeService.removeNode($scope.currentNode);
                  if (rootNode) {
                      //ensure the recycle bin has child nodes now            
                      var recycleBin = treeService.getDescendantNode(rootNode, -20);
                      if (recycleBin) {
                          recycleBin.hasChildren = true;
                      }
                  }
                  //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                  if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                      //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                      var location = '/content';
                      if ($scope.currentNode.parentId.toString() !== '-1')
                          location = '/content/content/edit/' + $scope.currentNode.parentId;
                      $location.path(location);
                  }
                  navigationService.hideMenu();
              }, function (err) {
                  $scope.currentNode.loading = false;
                  $scope.busy = false;
                  //check if response is ysod
                  if (err.status && err.status >= 500) {
                      dialogService.ysodDialog(err);
                  }
                  if (err.data && angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Content.DeleteController', ContentDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.EditController
   * @function
   * 
   * @description
   * The controller for the content editor
   */
      function ContentEditController($scope, $routeParams, contentResource) {
          function scaffoldEmpty() {
              return contentResource.getScaffold($routeParams.id, $routeParams.doctype);
          }
          function scaffoldBlueprint() {
              return contentResource.getBlueprintScaffold($routeParams.id, $routeParams.blueprintId);
          }
          $scope.contentId = $routeParams.id;
          $scope.saveMethod = contentResource.save;
          $scope.getMethod = contentResource.getById;
          $scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : scaffoldEmpty;
          $scope.page = $routeParams.page;
          $scope.isNew = $routeParams.create;
      }
      angular.module('umbraco').controller('Umbraco.Editors.Content.EditController', ContentEditController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.EmptyRecycleBinController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) {
          $scope.busy = false;
          $scope.performDelete = function () {
              //(used in the UI)
              $scope.busy = true;
              $scope.currentNode.loading = true;
              contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                  $scope.busy = false;
                  $scope.currentNode.loading = false;
                  //show any notifications
                  if (angular.isArray(result.notifications)) {
                      for (var i = 0; i < result.notifications.length; i++) {
                          notificationsService.showNotification(result.notifications[i]);
                      }
                  }
                  treeService.removeChildNodes($scope.currentNode);
                  navigationService.hideMenu();
                  //reload the current view
                  $route.reload();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Content.EmptyRecycleBinController', ContentEmptyRecycleBinController);
      angular.module('umbraco').controller('Umbraco.Editors.Content.MoveController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
          var dialogOptions = $scope.dialogOptions;
          var searchText = 'Search...';
          localizationService.localize('general_search').then(function (value) {
              searchText = value + '...';
          });
          $scope.dialogTreeEventHandler = $({});
          $scope.busy = false;
          $scope.searchInfo = {
              searchFromId: null,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          };
          $scope.treeModel = { hideHeader: false };
          userService.getCurrentUser().then(function (userData) {
              $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
          });
          var node = dialogOptions.currentNode;
          function nodeSelectHandler(ev, args) {
              if (args && args.event) {
                  args.event.preventDefault();
                  args.event.stopPropagation();
              }
              eventsService.emit('editors.content.moveController.select', args);
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          function nodeExpandedHandler(ev, args) {
              // open mini list view for list views
              if (args.node.metaData.isContainer) {
                  openMiniListView(args.node);
              }
          }
          $scope.hideSearch = function () {
              $scope.searchInfo.showSearch = false;
              $scope.searchInfo.searchFromId = null;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          };
          // method to select a search result 
          $scope.selectResult = function (evt, result) {
              result.selected = result.selected === true ? false : true;
              nodeSelectHandler(evt, {
                  event: evt,
                  node: result
              });
          };
          //callback when there are search results 
          $scope.onSearchResults = function (results) {
              $scope.searchInfo.results = results;
              $scope.searchInfo.showSearch = true;
          };
          $scope.move = function () {
              $scope.busy = true;
              $scope.error = false;
              contentResource.move({
                  parentId: $scope.target.id,
                  id: node.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currently edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'content',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'content',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
          });
          // Mini list view
          $scope.selectListViewNode = function (node) {
              node.selected = node.selected === true ? false : true;
              nodeSelectHandler({}, { node: node });
          };
          $scope.closeMiniListView = function () {
              $scope.miniListView = undefined;
          };
          function openMiniListView(node) {
              $scope.miniListView = node;
          }
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.RecycleBinController
   * @function
   * 
   * @description
   * Controls the recycle bin for content
   * 
   */
      function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) {
          //ensures the list view doesn't actually load until we query for the list view config
          // for the section
          $scope.page = {};
          $scope.page.name = 'Recycle Bin';
          $scope.page.nameLocked = true;
          //ensures the list view doesn't actually load until we query for the list view config
          // for the section
          $scope.listViewPath = null;
          $routeParams.id = '-20';
          contentResource.getRecycleBin().then(function (result) {
              //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a 
              // single property, so we'll extract that property (list view) and use it's data.
              var listproperty = result.tabs[0].properties[0];
              _.each(listproperty.config, function (val, key) {
                  $scope.model.config[key] = val;
              });
              $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
          });
          $scope.model = {
              config: {
                  entityType: $routeParams.section,
                  layouts: []
              }
          };
          // sync tree node
          navigationService.syncTree({
              tree: 'content',
              path: [
                  '-1',
                  $routeParams.id
              ],
              forceReload: false
          });
          localizePageName();
          function localizePageName() {
              var pageName = 'general_recycleBin';
              localizationService.localize(pageName).then(function (value) {
                  $scope.page.name = value;
              });
          }
      }
      angular.module('umbraco').controller('Umbraco.Editors.Content.RecycleBinController', ContentRecycleBinController);
      angular.module('umbraco').controller('Umbraco.Editors.Content.RestoreController', function ($scope, relationResource, contentResource, navigationService, appState, treeService) {
          var dialogOptions = $scope.dialogOptions;
          var node = dialogOptions.currentNode;
          $scope.error = null;
          $scope.success = false;
          relationResource.getByChildId(node.id, 'relateParentDocumentOnDelete').then(function (data) {
              if (data.length == 0) {
                  $scope.success = false;
                  $scope.error = {
                      errorMsg: 'Cannot automatically restore this item',
                      data: { Message: 'There is no \'restore\' relation found for this node. Use the Move menu item to move it manually.' }
                  };
                  return;
              }
              $scope.relation = data[0];
              if ($scope.relation.parentId == -1) {
                  $scope.target = {
                      id: -1,
                      name: 'Root'
                  };
              } else {
                  contentResource.getById($scope.relation.parentId).then(function (data) {
                      $scope.target = data;
                      // make sure the target item isn't in the recycle bin
                      if ($scope.target.path.indexOf('-20') !== -1) {
                          $scope.error = {
                              errorMsg: 'Cannot automatically restore this item',
                              data: { Message: 'The item you want to restore it under (' + $scope.target.name + ') is in the recycle bin. Use the Move menu item to move the item manually.' }
                          };
                          $scope.success = false;
                      }
                  }, function (err) {
                      $scope.success = false;
                      $scope.error = err;
                  });
              }
          }, function (err) {
              $scope.success = false;
              $scope.error = err;
          });
          $scope.restore = function () {
              // this code was copied from `content.move.controller.js`
              contentResource.move({
                  parentId: $scope.target.id,
                  id: node.id
              }).then(function (path) {
                  $scope.success = true;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'content',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'content',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
              });
          };
      });
      (function () {
          'use strict';
          function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper) {
              var vm = this;
              var currentForm;
              vm.availableUserGroups = [];
              vm.selectedUserGroups = [];
              vm.removedUserGroups = [];
              vm.viewState = 'manageGroups';
              vm.labels = {};
              vm.showNotification = false;
              vm.setViewSate = setViewSate;
              vm.editPermissions = editPermissions;
              vm.setPermissions = setPermissions;
              vm.save = save;
              vm.removePermissions = removePermissions;
              vm.cancelManagePermissions = cancelManagePermissions;
              vm.closeDialog = closeDialog;
              vm.stay = stay;
              function onInit() {
                  vm.loading = true;
                  contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) {
                      initData(userGroups);
                      vm.loading = false;
                      currentForm = angularHelper.getCurrentForm($scope);
                  });
              }
              /**
          * This will initialize the data and set the correct selectedUserGroups based on the default permissions and explicit permissions assigned
          * @param {any} userGroups
          */
              function initData(userGroups) {
                  //reset this
                  vm.selectedUserGroups = [];
                  vm.availableUserGroups = userGroups;
                  angular.forEach(vm.availableUserGroups, function (group) {
                      if (group.permissions) {
                          //if there's explicit permissions assigned than it's selected
                          assignGroupPermissions(group);
                      }
                  });
              }
              function setViewSate(state) {
                  vm.viewState = state;
              }
              function editPermissions(group) {
                  vm.selectedUserGroup = group;
                  if (!vm.selectedUserGroup.permissions) {
                      //if no permissions are explicitly set this means we need to show the defaults
                      vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions;
                  }
                  localizationService.localize('defaultdialogs_permissionsSetForGroup', [
                      $scope.currentNode.name,
                      vm.selectedUserGroup.name
                  ]).then(function (value) {
                      vm.labels.permissionsSetForGroup = value;
                  });
                  setViewSate('managePermissions');
              }
              function assignGroupPermissions(group) {
                  // clear allowed permissions before we make the list so we don't have duplicates
                  group.allowedPermissions = [];
                  // get list of checked permissions
                  angular.forEach(group.permissions, function (permissionGroup) {
                      angular.forEach(permissionGroup, function (permission) {
                          if (permission.checked) {
                              //the `allowedPermissions` is what will get sent up to the server for saving
                              group.allowedPermissions.push(permission);
                          }
                      });
                  });
                  if (!group.selected) {
                      // set to selected so we can remove from the dropdown easily
                      group.selected = true;
                      vm.selectedUserGroups.push(group);
                      //remove from the removed groups if it's been re-added
                      vm.removedUserGroups = _.reject(vm.removedUserGroups, function (g) {
                          return g.id == group.id;
                      });
                  }
              }
              function setPermissions(group) {
                  assignGroupPermissions(group);
                  setViewSate('manageGroups');
              }
              /**
           * This essentially resets the permissions for a group for this content item, it will remove it from the selected list
           * @param {any} index
           */
              function removePermissions(index) {
                  // remove as selected so we can select it from the dropdown again
                  var group = vm.selectedUserGroups[index];
                  group.selected = false;
                  //reset assigned permissions - so it will default back to default permissions
                  group.permissions = [];
                  group.allowedPermissions = [];
                  vm.selectedUserGroups.splice(index, 1);
                  //track it in the removed so this gets pushed to the server
                  vm.removedUserGroups.push(group);
              }
              function cancelManagePermissions() {
                  setViewSate('manageGroups');
              }
              function formatSaveModel(permissionsSave, groupCollection) {
                  angular.forEach(groupCollection, function (g) {
                      permissionsSave[g.id] = [];
                      angular.forEach(g.allowedPermissions, function (p) {
                          permissionsSave[g.id].push(p.permissionCode);
                      });
                  });
              }
              function save() {
                  vm.saveState = 'busy';
                  vm.saveError = false;
                  vm.saveSuccces = false;
                  //this is a dictionary that we need to populate
                  var permissionsSave = {};
                  //format the selectedUserGroups, then the removedUserGroups since we want to pass data from both collections up
                  formatSaveModel(permissionsSave, vm.selectedUserGroups);
                  formatSaveModel(permissionsSave, vm.removedUserGroups);
                  var saveModel = {
                      contentId: $scope.currentNode.id,
                      permissions: permissionsSave
                  };
                  contentResource.savePermissions(saveModel).then(function (userGroups) {
                      //re-assign model from server since it could have changed
                      initData(userGroups);
                      // clear dirty state on the form so we don't see the discard changes notification
                      // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed
                      $timeout(function () {
                          if (currentForm) {
                              currentForm.$dirty = false;
                          }
                      });
                      vm.saveState = 'success';
                      vm.saveSuccces = true;
                  }, function (error) {
                      vm.saveState = 'error';
                      vm.saveError = error;
                  });
              }
              function stay() {
                  vm.showNotification = false;
              }
              function closeDialog() {
                  // check if form has been changed. If it has show discard changes notification
                  if (currentForm && currentForm.$dirty) {
                      vm.showNotification = true;
                  } else {
                      $scope.nav.hideDialog();
                  }
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Content.RightsController', ContentRightsController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentBlueprint.CreateController
   * @function
   *
   * @description
   * The controller for creating content blueprints
   */
      function ContentBlueprintCreateController($scope, $location, contentTypeResource, navigationService) {
          var vm = this;
          var node = $scope.dialogOptions.currentNode;
          vm.createBlueprint = createBlueprint;
          function onInit() {
              vm.loading = true;
              contentTypeResource.getAll().then(function (documentTypes) {
                  vm.documentTypes = documentTypes;
                  vm.loading = false;
              });
          }
          function createBlueprint(documentType) {
              $location.path('/settings/contentBlueprints/edit/' + node.id).search('create', 'true').search('doctype', documentType.alias);
              navigationService.hideMenu();
          }
          onInit();
      }
      angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.CreateController', ContentBlueprintCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentBlueprint.DeleteController
   * @function
   *
   * @description
   * The controller for deleting content blueprints
   */
      function ContentBlueprintDeleteController($scope, contentResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              contentResource.deleteBlueprint($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.DeleteController', ContentBlueprintDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.EditController
   * @function
   * 
   * @description
   * The controller for the content editor
   */
      function ContentBlueprintEditController($scope, $routeParams, contentResource) {
          var excludedProps = [
              '_umb_urls',
              '_umb_releasedate',
              '_umb_expiredate',
              '_umb_template'
          ];
          function getScaffold() {
              return contentResource.getScaffold(-1, $routeParams.doctype).then(function (scaffold) {
                  var lastTab = scaffold.tabs[scaffold.tabs.length - 1];
                  lastTab.properties = _.filter(lastTab.properties, function (p) {
                      return excludedProps.indexOf(p.alias) === -1;
                  });
                  scaffold.allowPreview = false;
                  scaffold.allowedActions = [
                      'A',
                      'S',
                      'C'
                  ];
                  return scaffold;
              });
          }
          $scope.contentId = $routeParams.id;
          $scope.isNew = $routeParams.id === '-1';
          $scope.saveMethod = contentResource.saveBlueprint;
          $scope.getMethod = contentResource.getBlueprintById;
          $scope.getScaffoldMethod = getScaffold;
      }
      angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.EditController', ContentBlueprintEditController);
      function startUpVideosDashboardController($scope, xmlhelper, $log, $http) {
          $scope.videos = [];
          $scope.init = function (url) {
              var proxyUrl = 'dashboard/feedproxy.aspx?url=' + url;
              $http.get(proxyUrl).then(function (data) {
                  var feed = $(data.data);
                  $('item', feed).each(function (i, item) {
                      var video = {};
                      video.thumbnail = $(item).find('thumbnail').attr('url');
                      video.title = $('title', item).text();
                      video.link = $('guid', item).text();
                      $scope.videos.push(video);
                  });
              });
          };
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.StartupVideosController', startUpVideosDashboardController);
      function startUpDynamicContentController($timeout, $scope, dashboardResource, assetsService, tourService, eventsService) {
          var vm = this;
          var evts = [];
          vm.loading = true;
          vm.showDefault = false;
          vm.startTour = startTour;
          function onInit() {
              // load tours
              tourService.getGroupedTours().then(function (groupedTours) {
                  vm.tours = groupedTours;
              });
          }
          function startTour(tour) {
              tourService.startTour(tour);
          }
          // default dashboard content
          vm.defaultDashboard = {
              infoBoxes: [
                  {
                      title: 'Documentation',
                      description: 'Find the answers to your Umbraco questions',
                      url: 'https://our.umbraco.org/documentation/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=documentation/'
                  },
                  {
                      title: 'Community',
                      description: 'Find the answers or ask your Umbraco questions',
                      url: 'https://our.umbraco.org/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=our_forum'
                  },
                  {
                      title: 'Umbraco.tv',
                      description: 'Tutorial videos (some are free, some are on subscription)',
                      url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=tutorial_videos'
                  },
                  {
                      title: 'Training',
                      description: 'Real-life training and official Umbraco certifications',
                      url: 'https://umbraco.com/training/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=training'
                  }
              ],
              articles: [
                  {
                      title: 'Umbraco.TV - Learn from the source!',
                      description: 'Umbraco.TV will help you go from zero to Umbraco hero at a pace that suits you. Our easy to follow online training videos will give you the fundamental knowledge to start building awesome Umbraco websites.',
                      img: 'views/dashboard/default/umbracotv.jpg',
                      url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=tv',
                      altText: 'Umbraco.TV - Hours of Umbraco Video Tutorials',
                      buttonText: 'Visit Umbraco.TV'
                  },
                  {
                      title: 'Our Umbraco - The Friendliest Community',
                      description: 'Our Umbraco - the official community site is your one stop for everything Umbraco. Whether you need a question answered or looking for cool plugins, the world\'s best and friendliest community is just a click away.',
                      img: 'views/dashboard/default/ourumbraco.jpg',
                      url: 'https://our.umbraco.org/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=our',
                      altText: 'Our Umbraco',
                      buttonText: 'Visit Our Umbraco'
                  }
              ]
          };
          evts.push(eventsService.on('appState.tour.complete', function (name, completedTour) {
              $timeout(function () {
                  angular.forEach(vm.tours, function (tourGroup) {
                      angular.forEach(tourGroup, function (tour) {
                          if (tour.alias === completedTour.alias) {
                              tour.completed = true;
                          }
                      });
                  });
              });
          }));
          //proxy remote css through the local server
          assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl('content'), $scope);
          dashboardResource.getRemoteDashboardContent('content').then(function (data) {
              vm.loading = false;
              //test if we have received valid data
              //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code
              if (data && data.sections) {
                  vm.dashboard = data;
              } else {
                  vm.showDefault = true;
              }
          }, function (exception) {
              console.error(exception);
              vm.loading = false;
              vm.showDefault = true;
          });
          onInit();
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.StartUpDynamicContentController', startUpDynamicContentController);
      function FormsController($scope, $route, $cookieStore, packageResource, localizationService) {
          $scope.installForms = function () {
              $scope.state = localizationService.localize('packager_installStateDownloading');
              packageResource.fetch('CD44CF39-3D71-4C19-B6EE-948E1FAF0525').then(function (pack) {
                  $scope.state = localizationService.localize('packager_installStateImporting');
                  return packageResource.import(pack);
              }, $scope.error).then(function (pack) {
                  $scope.state = localizationService.localize('packager_installStateInstalling');
                  return packageResource.installFiles(pack);
              }, $scope.error).then(function (pack) {
                  $scope.state = localizationService.localize('packager_installStateRestarting');
                  return packageResource.installData(pack);
              }, $scope.error).then(function (pack) {
                  $scope.state = localizationService.localize('packager_installStateComplete');
                  return packageResource.cleanUp(pack);
              }, $scope.error).then($scope.complete, $scope.error);
          };
          $scope.complete = function (result) {
              var url = window.location.href + '?init=true';
              $cookieStore.put('umbPackageInstallId', result.packageGuid);
              window.location.reload(true);
          };
          $scope.error = function (err) {
              $scope.state = undefined;
              $scope.error = err;
              //This will return a rejection meaning that the promise change above will stop
              return $q.reject();
          };
          function Video_player(videoId) {
              // Get dom elements
              this.container = document.getElementById(videoId);
              this.video = this.container.getElementsByTagName('video')[0];
              //Create controls
              this.controls = document.createElement('div');
              this.controls.className = 'video-controls';
              this.seek_bar = document.createElement('input');
              this.seek_bar.className = 'seek-bar';
              this.seek_bar.type = 'range';
              this.seek_bar.setAttribute('value', '0');
              this.loader = document.createElement('div');
              this.loader.className = 'loader';
              this.progress_bar = document.createElement('span');
              this.progress_bar.className = 'progress-bar';
              // Insert controls
              this.controls.appendChild(this.seek_bar);
              this.container.appendChild(this.controls);
              this.controls.appendChild(this.loader);
              this.loader.appendChild(this.progress_bar);
          }
          Video_player.prototype.seeking = function () {
              // get the value of the seekbar (hidden input[type="range"])
              var time = this.video.duration * (this.seek_bar.value / 100);
              // Update video to seekbar value
              this.video.currentTime = time;
          };
          // Stop video when user initiates seeking
          Video_player.prototype.start_seek = function () {
              this.video.pause();
          };
          // Start video when user stops seeking
          Video_player.prototype.stop_seek = function () {
              this.video.play();
          };
          // Update the progressbar (span.loader) according to video.currentTime
          Video_player.prototype.update_progress_bar = function () {
              // Get video progress in %
              var value = 100 / this.video.duration * this.video.currentTime;
              // Update progressbar
              this.progress_bar.style.width = value + '%';
          };
          // Bind progressbar to mouse when seeking
          Video_player.prototype.handle_mouse_move = function (event) {
              // Get position of progressbar relative to browser window
              var pos = this.progress_bar.getBoundingClientRect().left;
              // Make sure event is reckonized cross-browser
              event = event || window.event;
              // Update progressbar
              this.progress_bar.style.width = event.clientX - pos + 'px';
          };
          // Eventlisteners for seeking
          Video_player.prototype.video_event_handler = function (videoPlayer, interval) {
              // Update the progress bar
              var animate_progress_bar = setInterval(function () {
                  videoPlayer.update_progress_bar();
              }, interval);
              // Fire when input value changes (user seeking)
              videoPlayer.seek_bar.addEventListener('change', function () {
                  videoPlayer.seeking();
              });
              // Fire when user clicks on seekbar
              videoPlayer.seek_bar.addEventListener('mousedown', function (clickEvent) {
                  // Pause video playback
                  videoPlayer.start_seek();
                  // Stop updating progressbar according to video progress
                  clearInterval(animate_progress_bar);
                  // Update progressbar to where user clicks
                  videoPlayer.handle_mouse_move(clickEvent);
                  // Bind progressbar to cursor
                  window.onmousemove = function (moveEvent) {
                      videoPlayer.handle_mouse_move(moveEvent);
                  };
              });
              // Fire when user releases seekbar
              videoPlayer.seek_bar.addEventListener('mouseup', function () {
                  // Unbind progressbar from cursor
                  window.onmousemove = null;
                  // Start video playback
                  videoPlayer.stop_seek();
                  // Animate the progressbar
                  animate_progress_bar = setInterval(function () {
                      videoPlayer.update_progress_bar();
                  }, interval);
              });
          };
          var videoPlayer = new Video_player('video_1');
          videoPlayer.video_event_handler(videoPlayer, 17);
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.FormsDashboardController', FormsController);
      function startupLatestEditsController($scope) {
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.StartupLatestEditsController', startupLatestEditsController);
      function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) {
          var currentUser = {};
          userService.getCurrentUser().then(function (user) {
              currentUser = user;
              // check if the user has access to the root which they will require to see this dashboard
              if (currentUser.startMediaIds.indexOf(-1) >= 0) {
                  //get the system media listview
                  contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
                      $scope.fakeProperty = {
                          alias: 'contents',
                          config: dt.config,
                          description: '',
                          editor: dt.editor,
                          hideLabel: true,
                          id: 1,
                          label: 'Contents:',
                          validation: {
                              mandatory: false,
                              pattern: null
                          },
                          value: '',
                          view: dt.view
                      };
                  });
              } else if (currentUser.startMediaIds.length > 0) {
                  // redirect to start node
                  $location.path('/media/media/edit/' + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0]));
              }
          });
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.MediaFolderBrowserDashboardController', MediaFolderBrowserDashboardController);
      function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
          $scope.indexerDetails = [];
          $scope.searcherDetails = [];
          $scope.loading = true;
          function checkProcessing(indexer, checkActionName) {
              umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', checkActionName, { indexerName: indexer.name })), 'Failed to check index processing').then(function (data) {
                  if (data !== null && data !== 'null') {
                      //copy all resulting properties
                      for (var k in data) {
                          indexer[k] = data[k];
                      }
                      indexer.isProcessing = false;
                  } else {
                      $timeout(function () {
                          //don't continue if we've tried 100 times
                          if (indexer.processingAttempts < 100) {
                              checkProcessing(indexer, checkActionName);
                              //add an attempt
                              indexer.processingAttempts++;
                          } else {
                              //we've exceeded 100 attempts, stop processing
                              indexer.isProcessing = false;
                          }
                      }, 1000);
                  }
              });
          }
          $scope.search = function (searcher, e) {
              if (e && e.keyCode !== 13) {
                  return;
              }
              umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearchResults', {
                  searcherName: searcher.name,
                  query: encodeURIComponent(searcher.searchText),
                  queryType: searcher.searchType
              })), 'Failed to search').then(function (searchResults) {
                  searcher.isSearching = true;
                  searcher.searchResults = searchResults;
              });
          };
          $scope.toggle = function (provider, propName) {
              if (provider[propName] !== undefined) {
                  provider[propName] = !provider[propName];
              } else {
                  provider[propName] = true;
              }
          };
          $scope.rebuildIndex = function (indexer) {
              if (confirm('This will cause the index to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild an index during times of high website traffic ' + 'or when editors are editing content.')) {
                  indexer.isProcessing = true;
                  indexer.processingAttempts = 0;
                  umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostRebuildIndex', { indexerName: indexer.name })), 'Failed to rebuild index').then(function () {
                      //rebuilding has started, nothing is returned accept a 200 status code.
                      //lets poll to see if it is done.
                      $timeout(function () {
                          checkProcessing(indexer, 'PostCheckRebuildIndex');
                      }, 1000);
                  });
              }
          };
          $scope.optimizeIndex = function (indexer) {
              if (confirm('This will cause the index to be optimized which will improve its performance. ' + 'It is not recommended to optimize an index during times of high website traffic ' + 'or when editors are editing content.')) {
                  indexer.isProcessing = true;
                  umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostOptimizeIndex', { indexerName: indexer.name })), 'Failed to optimize index').then(function () {
                      //optimizing has started, nothing is returned accept a 200 status code.
                      //lets poll to see if it is done.
                      $timeout(function () {
                          checkProcessing(indexer, 'PostCheckOptimizeIndex');
                      }, 1000);
                  });
              }
          };
          $scope.closeSearch = function (searcher) {
              searcher.isSearching = true;
          };
          //go get the data
          //combine two promises and execute when they are both done
          $q.all([
              //get the indexer details
              umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetIndexerDetails')), 'Failed to retrieve indexer details').then(function (data) {
                  $scope.indexerDetails = data;
              }),
              //get the searcher details
              umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearcherDetails')), 'Failed to retrieve searcher details').then(function (data) {
                  $scope.searcherDetails = data;
                  for (var s in $scope.searcherDetails) {
                      $scope.searcherDetails[s].searchType = 'text';
                  }
              })
          ]).then(function () {
              //all init loading is complete
              $scope.loading = false;
          });
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.ExamineMgmtController', ExamineMgmtController);
      (function () {
          'use strict';
          function HealthCheckController($scope, healthCheckResource) {
              var SUCCESS = 0;
              var WARNING = 1;
              var ERROR = 2;
              var INFO = 3;
              var vm = this;
              vm.viewState = 'list';
              vm.groups = [];
              vm.selectedGroup = {};
              vm.getStatus = getStatus;
              vm.executeAction = executeAction;
              vm.checkAllGroups = checkAllGroups;
              vm.checkAllInGroup = checkAllInGroup;
              vm.openGroup = openGroup;
              vm.setViewState = setViewState;
              // Get a (grouped) list of all health checks
              healthCheckResource.getAllChecks().then(function (response) {
                  vm.groups = response;
              });
              function setGroupGlobalResultType(group) {
                  var totalSuccess = 0;
                  var totalError = 0;
                  var totalWarning = 0;
                  var totalInfo = 0;
                  // count total number of statusses
                  angular.forEach(group.checks, function (check) {
                      angular.forEach(check.status, function (status) {
                          switch (status.resultType) {
                          case SUCCESS:
                              totalSuccess = totalSuccess + 1;
                              break;
                          case WARNING:
                              totalWarning = totalWarning + 1;
                              break;
                          case ERROR:
                              totalError = totalError + 1;
                              break;
                          case INFO:
                              totalInfo = totalInfo + 1;
                              break;
                          }
                      });
                  });
                  group.totalSuccess = totalSuccess;
                  group.totalError = totalError;
                  group.totalWarning = totalWarning;
                  group.totalInfo = totalInfo;
              }
              // Get the status of an individual check
              function getStatus(check) {
                  check.loading = true;
                  check.status = null;
                  healthCheckResource.getStatus(check.id).then(function (response) {
                      check.loading = false;
                      check.status = response;
                  });
              }
              function executeAction(check, index, action) {
                  check.loading = true;
                  healthCheckResource.executeAction(action).then(function (response) {
                      check.status[index] = response;
                      check.loading = false;
                  });
              }
              function checkAllGroups(groups) {
                  // set number of checks which has been executed
                  for (var i = 0; i < groups.length; i++) {
                      var group = groups[i];
                      checkAllInGroup(group, group.checks);
                  }
                  vm.groups = groups;
              }
              function checkAllInGroup(group, checks) {
                  group.checkCounter = 0;
                  group.loading = true;
                  angular.forEach(checks, function (check) {
                      check.loading = true;
                      healthCheckResource.getStatus(check.id).then(function (response) {
                          check.status = response;
                          group.checkCounter = group.checkCounter + 1;
                          check.loading = false;
                          // when all checks are done, set global group result
                          if (group.checkCounter === checks.length) {
                              setGroupGlobalResultType(group);
                              group.loading = false;
                          }
                      });
                  });
              }
              function openGroup(group) {
                  vm.selectedGroup = group;
                  vm.viewState = 'details';
              }
              function setViewState(state) {
                  vm.viewState = state;
                  if (state === 'list') {
                      for (var i = 0; i < vm.groups.length; i++) {
                          var group = vm.groups[i];
                          setGroupGlobalResultType(group);
                      }
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Dashboard.HealthCheckController', HealthCheckController);
      }());
      (function () {
          'use strict';
          function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) {
              //...todo
              //search by url or url part
              //search by domain
              //display domain in dashboard results?
              //used to cancel any request in progress if another one needs to take it's place
              var vm = this;
              var canceler = null;
              vm.dashboard = {
                  searchTerm: '',
                  loading: false,
                  urlTrackerDisabled: false,
                  userIsAdmin: false
              };
              vm.pagination = {
                  pageIndex: 0,
                  pageNumber: 1,
                  totalPages: 1,
                  pageSize: 20
              };
              vm.goToPage = goToPage;
              vm.search = search;
              vm.removeRedirect = removeRedirect;
              vm.disableUrlTracker = disableUrlTracker;
              vm.enableUrlTracker = enableUrlTracker;
              vm.filter = filter;
              vm.checkEnabled = checkEnabled;
              function activate() {
                  vm.checkEnabled().then(function () {
                      vm.search();
                  });
              }
              function checkEnabled() {
                  vm.dashboard.loading = true;
                  return redirectUrlsResource.getEnableState().then(function (response) {
                      vm.dashboard.urlTrackerDisabled = response.enabled !== true;
                      vm.dashboard.userIsAdmin = response.userIsAdmin;
                      vm.dashboard.loading = false;
                  });
              }
              function goToPage(pageNumber) {
                  vm.pagination.pageIndex = pageNumber - 1;
                  vm.pagination.pageNumber = pageNumber;
                  vm.search();
              }
              function search() {
                  vm.dashboard.loading = true;
                  var searchTerm = vm.dashboard.searchTerm;
                  if (searchTerm === undefined) {
                      searchTerm = '';
                  }
                  redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function (response) {
                      vm.redirectUrls = response.searchResults;
                      // update pagination
                      vm.pagination.pageIndex = response.currentPage;
                      vm.pagination.pageNumber = response.currentPage + 1;
                      vm.pagination.totalPages = response.pageCount;
                      vm.dashboard.loading = false;
                  });
              }
              function removeRedirect(redirectToDelete) {
                  localizationService.localize('redirectUrls_confirmRemove', [
                      redirectToDelete.originalUrl,
                      redirectToDelete.destinationUrl
                  ]).then(function (value) {
                      var toggleConfirm = confirm(value);
                      if (toggleConfirm) {
                          redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () {
                              var index = vm.redirectUrls.indexOf(redirectToDelete);
                              vm.redirectUrls.splice(index, 1);
                              notificationsService.success(localizationService.localize('redirectUrls_redirectRemoved'));
                              // check if new redirects needs to be loaded
                              if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) {
                                  // if we are not on the first page - get records from the previous
                                  if (vm.pagination.pageIndex > 0) {
                                      vm.pagination.pageIndex = vm.pagination.pageIndex - 1;
                                      vm.pagination.pageNumber = vm.pagination.pageNumber - 1;
                                  }
                                  search();
                              }
                          }, function (error) {
                              notificationsService.error(localizationService.localize('redirectUrls_redirectRemoveError'));
                          });
                      }
                  });
              }
              function disableUrlTracker() {
                  localizationService.localize('redirectUrls_confirmDisable').then(function (value) {
                      var toggleConfirm = confirm(value);
                      if (toggleConfirm) {
                          redirectUrlsResource.toggleUrlTracker(true).then(function () {
                              activate();
                              notificationsService.success(localizationService.localize('redirectUrls_disabledConfirm'));
                          }, function (error) {
                              notificationsService.warning(localizationService.localize('redirectUrls_disableError'));
                          });
                      }
                  });
              }
              function enableUrlTracker() {
                  redirectUrlsResource.toggleUrlTracker(false).then(function () {
                      activate();
                      notificationsService.success(localizationService.localize('redirectUrls_enabledConfirm'));
                  }, function (error) {
                      notificationsService.warning(localizationService.localize('redirectUrls_enableError'));
                  });
              }
              var filterDebounced = _.debounce(function (e) {
                  $scope.$apply(function () {
                      //a canceler exists, so perform the cancelation operation and reset
                      if (canceler) {
                          canceler.resolve();
                          canceler = $q.defer();
                      } else {
                          canceler = $q.defer();
                      }
                      vm.search();
                  });
              }, 200);
              function filter() {
                  vm.dashboard.loading = true;
                  filterDebounced();
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.Dashboard.RedirectUrlsController', RedirectUrlsController);
      }());
      function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) {
          function check(item) {
              var action = item.check;
              umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) {
                  item.checking = false;
                  item.invalid = result === 'false';
              });
          }
          $scope.fix = function (item) {
              var action = item.fix;
              if (item.fix) {
                  if (confirm('This will cause all xml structures for this type to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic ' + 'or when editors are editing content.')) {
                      item.fixing = true;
                      umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) {
                          item.fixing = false;
                          item.invalid = result === 'false';
                      });
                  }
              }
          };
          $scope.items = {
              'contentXml': {
                  label: 'Content in the cmsContentXml table',
                  checking: true,
                  fixing: false,
                  fix: 'FixContentXmlTable',
                  check: 'CheckContentXmlTable'
              },
              'mediaXml': {
                  label: 'Media in the cmsContentXml table',
                  checking: true,
                  fixing: false,
                  fix: 'FixMediaXmlTable',
                  check: 'CheckMediaXmlTable'
              },
              'memberXml': {
                  label: 'Members in the cmsContentXml table',
                  checking: true,
                  fixing: false,
                  fix: 'FixMembersXmlTable',
                  check: 'CheckMembersXmlTable'
              }
          };
          for (var i in $scope.items) {
              check($scope.items[i]);
          }
      }
      angular.module('umbraco').controller('Umbraco.Dashboard.XmlDataIntegrityReportController', XmlDataIntegrityReportController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DataType.CreateController
   * @function
   *
   * @description
   * The controller for the data type creation dialog
   */
      function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) {
          $scope.model = {
              folderName: '',
              creatingFolder: false
          };
          var node = $scope.dialogOptions.currentNode;
          $scope.showCreateFolder = function () {
              $scope.model.creatingFolder = true;
          };
          $scope.createContainer = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      formCtrl: this.createFolderForm,
                      statusMessage: 'Creating folder...'
                  })) {
                  dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                      navigationService.hideMenu();
                      var currPath = node.path ? node.path : '-1';
                      navigationService.syncTree({
                          tree: 'datatypes',
                          path: currPath + ',' + folderId,
                          forceReload: true,
                          activate: true
                      });
                      formHelper.resetForm({ scope: $scope });
                      var section = appState.getSectionState('currentSection');
                  }, function (err) {
                  });
              }
              ;
          };
          $scope.createDataType = function () {
              $location.search('create', null);
              $location.path('/developer/datatypes/edit/' + node.id).search('create', 'true');
              navigationService.hideMenu();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.DataType.CreateController', DataTypeCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              dataTypeResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.performContainerDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.DataType.DeleteController', DataTypeDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DataType.EditController
   * @function
   *
   * @description
   * The controller for the content editor
   */
      function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) {
          //setup scope vars
          $scope.page = {};
          $scope.page.loading = false;
          $scope.page.nameLocked = false;
          $scope.page.menu = {};
          $scope.page.menu.currentSection = appState.getSectionState('currentSection');
          $scope.page.menu.currentNode = null;
          var evts = [];
          //method used to configure the pre-values when we retrieve them from the server
          function createPreValueProps(preVals) {
              $scope.preValues = [];
              for (var i = 0; i < preVals.length; i++) {
                  $scope.preValues.push({
                      hideLabel: preVals[i].hideLabel,
                      alias: preVals[i].key,
                      description: preVals[i].description,
                      label: preVals[i].label,
                      view: preVals[i].view,
                      value: preVals[i].value,
                      config: preVals[i].config
                  });
              }
          }
          //set up the standard data type props
          $scope.properties = {
              selectedEditor: {
                  alias: 'selectedEditor',
                  description: 'Select a property editor',
                  label: 'Property editor'
              },
              selectedEditorId: {
                  alias: 'selectedEditorId',
                  label: 'Property editor alias'
              }
          };
          //setup the pre-values as props
          $scope.preValues = [];
          if ($routeParams.create) {
              $scope.page.loading = true;
              //we are creating so get an empty data type item
              dataTypeResource.getScaffold($routeParams.id).then(function (data) {
                  $scope.preValuesLoaded = true;
                  $scope.content = data;
                  setHeaderNameState($scope.content);
                  //set a shared state
                  editorState.set($scope.content);
                  $scope.page.loading = false;
              });
          } else {
              loadDataType();
          }
          function loadDataType() {
              $scope.page.loading = true;
              //we are editing so get the content item from the server
              dataTypeResource.getById($routeParams.id).then(function (data) {
                  $scope.preValuesLoaded = true;
                  $scope.content = data;
                  createPreValueProps($scope.content.preValues);
                  setHeaderNameState($scope.content);
                  //share state
                  editorState.set($scope.content);
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
                  navigationService.syncTree({
                      tree: 'datatypes',
                      path: data.path
                  }).then(function (syncArgs) {
                      $scope.page.menu.currentNode = syncArgs.node;
                  });
                  $scope.page.loading = false;
              });
          }
          $scope.$watch('content.selectedEditor', function (newVal, oldVal) {
              //when the value changes, we need to dynamically load in the new editor
              if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
                  //we are editing so get the content item from the server
                  var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
                  dataTypeResource.getPreValues(newVal, currDataTypeId).then(function (data) {
                      $scope.preValuesLoaded = true;
                      $scope.content.preValues = data;
                      createPreValueProps($scope.content.preValues);
                      setHeaderNameState($scope.content);
                      //share state
                      editorState.set($scope.content);
                  });
              }
          });
          function setHeaderNameState(content) {
              if (content.isSystem == 1) {
                  $scope.page.nameLocked = true;
              }
          }
          $scope.save = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      statusMessage: 'Saving...'
                  })) {
                  $scope.page.saveButtonState = 'busy';
                  dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create).then(function (data) {
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          rebindCallback: function () {
                              createPreValueProps(data.preValues);
                          }
                      });
                      setHeaderNameState($scope.content);
                      //share state
                      editorState.set($scope.content);
                      navigationService.syncTree({
                          tree: 'datatypes',
                          path: data.path,
                          forceReload: true
                      }).then(function (syncArgs) {
                          $scope.page.menu.currentNode = syncArgs.node;
                      });
                      $scope.page.saveButtonState = 'success';
                      dataTypeHelper.rebindChangedProperties($scope.content, data);
                  }, function (err) {
                      //NOTE: in the case of data type values we are setting the orig/new props
                      // to be the same thing since that only really matters for content/media.
                      contentEditingHelper.handleSaveError({
                          redirectOnFailure: false,
                          err: err
                      });
                      $scope.page.saveButtonState = 'error';
                      //share state
                      editorState.set($scope.content);
                  });
              }
          };
          evts.push(eventsService.on('app.refreshEditor', function (name, error) {
              loadDataType();
          }));
          //ensure to unregister from all events!
          $scope.$on('$destroy', function () {
              for (var e in evts) {
                  eventsService.unsubscribe(evts[e]);
              }
          });
      }
      angular.module('umbraco').controller('Umbraco.Editors.DataType.EditController', DataTypeEditController);
      angular.module('umbraco').controller('Umbraco.Editors.DataType.MoveController', function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          $scope.move = function () {
              $scope.busy = true;
              $scope.error = false;
              dataTypeResource.move({
                  parentId: $scope.target.id,
                  id: dialogOptions.currentNode.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'dataTypes',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'dataTypes',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
                  eventsService.emit('app.refreshEditor');
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CopyController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          $scope.copy = function () {
              $scope.busy = true;
              $scope.error = false;
              contentTypeResource.copy({
                  parentId: $scope.target.id,
                  id: dialogOptions.currentNode.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                  navigationService.syncTree({
                      tree: 'documentTypes',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'documentTypes',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.CreateController
   * @function
   *
   * @description
   * The controller for the doc type creation dialog
   */
      function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService) {
          $scope.model = {
              allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === 'container',
              folderName: '',
              creatingFolder: false
          };
          var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
          $scope.model.disableTemplates = disableTemplates;
          var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
          $scope.showCreateFolder = function () {
              $scope.model.creatingFolder = true;
          };
          $scope.createContainer = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      formCtrl: this.createFolderForm,
                      statusMessage: localizeCreateFolder
                  })) {
                  contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                      navigationService.hideMenu();
                      var currPath = node.path ? node.path : '-1';
                      navigationService.syncTree({
                          tree: 'documenttypes',
                          path: currPath + ',' + folderId,
                          forceReload: true,
                          activate: true
                      });
                      formHelper.resetForm({ scope: $scope });
                      var section = appState.getSectionState('currentSection');
                  }, function (err) {
                      $scope.error = err;
                      //show any notifications
                      if (angular.isArray(err.data.notifications)) {
                          for (var i = 0; i < err.data.notifications.length; i++) {
                              notificationsService.showNotification(err.data.notifications[i]);
                          }
                      }
                  });
              }
          };
          // Disabling logic for creating document type with template if disableTemplates is set to true
          if (!disableTemplates) {
              $scope.createDocType = function () {
                  $location.search('create', null);
                  $location.search('notemplate', null);
                  $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true');
                  navigationService.hideMenu();
              };
          }
          $scope.createComponent = function () {
              $location.search('create', null);
              $location.search('notemplate', null);
              $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true').search('notemplate', 'true');
              navigationService.hideMenu();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CreateController', DocumentTypesCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.DeleteController
   * @function
   *
   * @description
   * The controller for deleting content
   */
      function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              contentTypeResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.performContainerDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.DeleteController', DocumentTypesDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.EditController
   * @function
   *
   * @description
   * The controller for the content type editor
   */
      (function () {
          'use strict';
          function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              var evts = [];
              var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
              var buttons = [
                  {
                      'name': localizationService.localize('general_design'),
                      'alias': 'design',
                      'icon': 'icon-document-dashed-line',
                      'view': 'views/documenttypes/views/design/design.html',
                      'active': true
                  },
                  {
                      'name': localizationService.localize('general_listView'),
                      'alias': 'listView',
                      'icon': 'icon-list',
                      'view': 'views/documenttypes/views/listview/listview.html'
                  },
                  {
                      'name': localizationService.localize('general_rights'),
                      'alias': 'permissions',
                      'icon': 'icon-keychain',
                      'view': 'views/documenttypes/views/permissions/permissions.html'
                  },
                  {
                      'name': localizationService.localize('treeHeaders_templates'),
                      'alias': 'templates',
                      'icon': 'icon-layout',
                      'view': 'views/documenttypes/views/templates/templates.html'
                  }
              ];
              vm.save = save;
              vm.currentNode = null;
              vm.contentType = {};
              vm.page = {};
              vm.page.loading = false;
              vm.page.saveButtonState = 'init';
              vm.page.navigation = [];
              loadButtons();
              vm.page.keyboardShortcutsOverview = [
                  {
                      'name': localizationService.localize('main_sections'),
                      'shortcuts': [{
                              'description': localizationService.localize('shortcuts_navigateSections'),
                              'keys': [
                                  { 'key': '1' },
                                  { 'key': '4' }
                              ],
                              'keyRange': true
                          }]
                  },
                  {
                      'name': localizationService.localize('general_design'),
                      'shortcuts': [
                          {
                              'description': localizationService.localize('shortcuts_addTab'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 't' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addProperty'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'p' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addEditor'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'e' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_editDataType'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'd' }
                              ]
                          }
                      ]
                  },
                  {
                      'name': localizationService.localize('general_listView'),
                      'shortcuts': [{
                              'description': localizationService.localize('shortcuts_toggleListView'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'l' }
                              ]
                          }]
                  },
                  {
                      'name': localizationService.localize('general_rights'),
                      'shortcuts': [
                          {
                              'description': localizationService.localize('shortcuts_toggleAllowAsRoot'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'r' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addChildNode'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'c' }
                              ]
                          }
                      ]
                  },
                  {
                      'name': localizationService.localize('treeHeaders_templates'),
                      'shortcuts': [{
                              'description': localizationService.localize('shortcuts_addTemplate'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 't' }
                              ]
                          }]
                  }
              ];
              contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                  vm.page.modelsBuilder = result;
                  if (result) {
                      //Models builder mode:
                      vm.page.defaultButton = {
                          alias: 'save',
                          hotKey: 'ctrl+s',
                          hotKeyWhenHidden: true,
                          labelKey: 'buttons_save',
                          letter: 'S',
                          type: 'submit',
                          handler: function () {
                              vm.save();
                          }
                      };
                      vm.page.subButtons = [{
                              alias: 'saveAndGenerateModels',
                              hotKey: 'ctrl+g',
                              hotKeyWhenHidden: true,
                              labelKey: 'buttons_saveAndGenerateModels',
                              letter: 'G',
                              handler: function () {
                                  vm.page.saveButtonState = 'busy';
                                  vm.save().then(function (result) {
                                      vm.page.saveButtonState = 'busy';
                                      localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                          localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                              notificationsService.info(headerValue, msgValue);
                                          });
                                      });
                                      contentTypeHelper.generateModels().then(function (result) {
                                          // generateModels() returns the dashboard content
                                          if (!result.lastError) {
                                              //re-check model status
                                              contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                  vm.page.modelsBuilder = statusResult;
                                              });
                                              //clear and add success
                                              vm.page.saveButtonState = 'init';
                                              localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                  notificationsService.success(value);
                                              });
                                          } else {
                                              vm.page.saveButtonState = 'error';
                                              localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                  notificationsService.error(value);
                                              });
                                          }
                                      }, function () {
                                          vm.page.saveButtonState = 'error';
                                          localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                              notificationsService.error(value);
                                          });
                                      });
                                  });
                              }
                          }];
                  }
              });
              if ($routeParams.create) {
                  vm.page.loading = true;
                  //we are creating so get an empty data type item
                  contentTypeResource.getScaffold($routeParams.id).then(function (dt) {
                      init(dt);
                      vm.page.loading = false;
                  });
              } else {
                  loadDocumentType();
              }
              function loadDocumentType() {
                  vm.page.loading = true;
                  contentTypeResource.getById($routeParams.id).then(function (dt) {
                      init(dt);
                      syncTreeNode(vm.contentType, dt.path, true);
                      vm.page.loading = false;
                  });
              }
              function loadButtons() {
                  angular.forEach(buttons, function (val, index) {
                      if (disableTemplates === true && val.alias === 'templates') {
                          buttons.splice(index, 1);
                      }
                  });
                  vm.page.navigation = buttons;
              }
              /* ---------- SAVE ---------- */
              function save() {
                  // only save if there is no overlays open
                  if (overlayHelper.getNumberOfOverlays() === 0) {
                      var deferred = $q.defer();
                      vm.page.saveButtonState = 'busy';
                      // reformat allowed content types to array if id's
                      vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                      contentEditingHelper.contentEditorPerformSave({
                          statusMessage: localizeSaving,
                          saveMethod: contentTypeResource.save,
                          scope: $scope,
                          content: vm.contentType,
                          //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                          // type when server side validation fails - as opposed to content where we are capable of saving the content
                          // item if server side validation fails
                          redirectOnFailure: false,
                          // we need to rebind... the IDs that have been created!
                          rebindCallback: function (origContentType, savedContentType) {
                              vm.contentType.id = savedContentType.id;
                              vm.contentType.groups.forEach(function (group) {
                                  if (!group.name)
                                      return;
                                  var k = 0;
                                  while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                      k++;
                                  if (k == savedContentType.groups.length) {
                                      group.id = 0;
                                      return;
                                  }
                                  var savedGroup = savedContentType.groups[k];
                                  if (!group.id)
                                      group.id = savedGroup.id;
                                  group.properties.forEach(function (property) {
                                      if (property.id || !property.alias)
                                          return;
                                      k = 0;
                                      while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                          k++;
                                      if (k == savedGroup.properties.length) {
                                          property.id = 0;
                                          return;
                                      }
                                      var savedProperty = savedGroup.properties[k];
                                      property.id = savedProperty.id;
                                  });
                              });
                          }
                      }).then(function (data) {
                          //success
                          syncTreeNode(vm.contentType, data.path);
                          vm.page.saveButtonState = 'success';
                          deferred.resolve(data);
                      }, function (err) {
                          //error
                          if (err) {
                              editorState.set($scope.content);
                          } else {
                              localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                  localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                      notificationsService.error(headerValue, msgValue);
                                  });
                              });
                          }
                          vm.page.saveButtonState = 'error';
                          deferred.reject(err);
                      });
                      return deferred.promise;
                  }
              }
              function init(contentType) {
                  // set all tab to inactive
                  if (contentType.groups.length !== 0) {
                      angular.forEach(contentType.groups, function (group) {
                          angular.forEach(group.properties, function (property) {
                              // get data type details for each property
                              getDataTypeDetails(property);
                          });
                      });
                  }
                  // insert template on new doc types
                  if (!$routeParams.notemplate && contentType.id === 0) {
                      contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate);
                      contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
                  }
                  // convert icons for content type
                  convertLegacyIcons(contentType);
                  //set a shared state
                  editorState.set(contentType);
                  vm.contentType = contentType;
              }
              function convertLegacyIcons(contentType) {
                  // make array to store contentType icon
                  var contentTypeArray = [];
                  // push icon to array
                  contentTypeArray.push({ 'icon': contentType.icon });
                  // run through icon method
                  iconHelper.formatContentTypeIcons(contentTypeArray);
                  // set icon back on contentType
                  contentType.icon = contentTypeArray[0].icon;
              }
              function getDataTypeDetails(property) {
                  if (property.propertyState !== 'init') {
                      dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                          property.dataTypeIcon = dataType.icon;
                          property.dataTypeName = dataType.name;
                      });
                  }
              }
              /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
              function syncTreeNode(dt, path, initialLoad) {
                  navigationService.syncTree({
                      tree: 'documenttypes',
                      path: path.split(','),
                      forceReload: initialLoad !== true
                  }).then(function (syncArgs) {
                      vm.currentNode = syncArgs.node;
                  });
              }
              evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                  loadDocumentType();
              }));
              //ensure to unregister from all events!
              $scope.$on('$destroy', function () {
                  for (var e in evts) {
                      eventsService.unsubscribe(evts[e]);
                  }
              });
          }
          angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.EditController', DocumentTypesEditController);
      }());
      angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.MoveController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          $scope.move = function () {
              $scope.busy = true;
              $scope.error = false;
              contentTypeResource.move({
                  parentId: $scope.target.id,
                  id: dialogOptions.currentNode.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'documentTypes',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'documentTypes',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
                  eventsService.emit('app.refreshEditor');
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      angular.module('umbraco').controller('Umbraco.Editors.ContentTypeContainers.RenameController', [
          '$scope',
          '$injector',
          'navigationService',
          'notificationsService',
          'localizationService',
          function (scope, injector, navigationService, notificationsService, localizationService) {
              var notificationHeader;
              function reportSuccessAndClose(treeName) {
                  var lastComma = scope.currentNode.path.lastIndexOf(','), path = lastComma === -1 ? scope.currentNode.path : scope.currentNode.path.substring(0, lastComma - 1);
                  navigationService.syncTree({
                      tree: treeName,
                      path: path,
                      forceReload: true,
                      activate: true
                  });
                  localizationService.localize('renamecontainer_folderWasRenamed', [
                      scope.currentNode.name,
                      scope.model.folderName
                  ]).then(function (msg) {
                      notificationsService.showNotification({
                          type: 0,
                          header: notificationHeader,
                          message: msg
                      });
                  });
                  navigationService.hideMenu();
              }
              localizationService.localize('renamecontainer_renamed').then(function (s) {
                  notificationHeader = s;
              });
              scope.model = { folderName: scope.currentNode.name };
              scope.renameContainer = function (resourceKey, treeName) {
                  var resource = injector.get(resourceKey);
                  resource.renameContainer(scope.currentNode.id, scope.model.folderName).then(function () {
                      reportSuccessAndClose(treeName);
                  }, function (err) {
                      scope.error = err;
                      if (angular.isArray(err.data.notifications)) {
                          for (var i = 0; i < err.data.notifications.length; i++) {
                              notificationsService.showNotification(err.data.notifications[i]);
                          }
                      }
                  });
              };
          }
      ]);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.PropertyController
   * @function
   *
   * @description
   * The controller for the content type editor property dialog
   */
      (function () {
          'use strict';
          function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService) {
              /* ----------- SCOPE VARIABLES ----------- */
              var vm = this;
              var childNodeSelectorOverlayTitle = '';
              vm.contentTypes = [];
              vm.selectedChildren = [];
              vm.overlayTitle = '';
              vm.addChild = addChild;
              vm.removeChild = removeChild;
              /* ---------- INIT ---------- */
              init();
              function init() {
                  childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode');
                  contentTypeResource.getAll().then(function (contentTypes) {
                      vm.contentTypes = contentTypes;
                      // convert legacy icons
                      iconHelper.formatContentTypeIcons(vm.contentTypes);
                      vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes);
                      if ($scope.model.id === 0) {
                          contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                      }
                  });
              }
              function addChild($event) {
                  vm.childNodeSelectorOverlay = {
                      view: 'itempicker',
                      title: childNodeSelectorOverlayTitle,
                      availableItems: vm.contentTypes,
                      selectedItems: vm.selectedChildren,
                      event: $event,
                      show: true,
                      submit: function (model) {
                          vm.selectedChildren.push(model.selectedItem);
                          $scope.model.allowedContentTypes.push(model.selectedItem.id);
                          vm.childNodeSelectorOverlay.show = false;
                          vm.childNodeSelectorOverlay = null;
                      }
                  };
              }
              function removeChild(selectedChild, index) {
                  // remove from vm
                  vm.selectedChildren.splice(index, 1);
                  // remove from content type model
                  var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                  $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.DocumentType.PermissionsController', PermissionsController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.TemplatesController
   * @function
   *
   * @description
   * The controller for the content type editor templates sub view
   */
      (function () {
          'use strict';
          function TemplatesController($scope, entityResource, contentTypeHelper, $routeParams) {
              /* ----------- SCOPE VARIABLES ----------- */
              var vm = this;
              vm.availableTemplates = [];
              vm.updateTemplatePlaceholder = false;
              /* ---------- INIT ---------- */
              init();
              function init() {
                  entityResource.getAll('Template').then(function (templates) {
                      vm.availableTemplates = templates;
                      // update placeholder template information on new doc types
                      if (!$routeParams.notemplate && $scope.model.id === 0) {
                          vm.updateTemplatePlaceholder = true;
                          vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates);
                      }
                  });
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.DocumentType.TemplatesController', TemplatesController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.CreateController
   * @function
   * 
   * @description
   * The controller for the media creation dialog
   */
      function mediaCreateController($scope, $routeParams, $location, mediaTypeResource, iconHelper, navigationService) {
          mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
              $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
          });
          $scope.createMediaItem = function (docType) {
              $location.path('/media/media/edit/' + $scope.currentNode.id).search('doctype', docType.alias).search('create', 'true');
              navigationService.hideMenu();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Media.CreateController', mediaCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
      function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
          $scope.performDelete = function () {
              // stop from firing again on double-click
              if ($scope.busy) {
                  return false;
              }
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              $scope.busy = true;
              mediaResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  treeService.removeNode($scope.currentNode);
                  if (rootNode) {
                      //ensure the recycle bin has child nodes now            
                      var recycleBin = treeService.getDescendantNode(rootNode, -21);
                      if (recycleBin) {
                          recycleBin.hasChildren = true;
                      }
                  }
                  //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                  if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                      //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                      var location = '/media';
                      if ($scope.currentNode.parentId.toString() !== '-1')
                          location = '/media/media/edit/' + $scope.currentNode.parentId;
                      $location.path(location);
                  }
                  navigationService.hideMenu();
              }, function (err) {
                  $scope.currentNode.loading = false;
                  $scope.busy = false;
                  //check if response is ysod
                  if (err.status && err.status >= 500) {
                      dialogService.ysodDialog(err);
                  }
                  if (err.data && angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Media.DeleteController', MediaDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.EditController
   * @function
   * 
   * @description
   * The controller for the media editor
   */
      function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
          //setup scope vars
          $scope.currentSection = appState.getSectionState('currentSection');
          $scope.currentNode = null;
          //the editors affiliated node
          $scope.page = {};
          $scope.page.loading = false;
          $scope.page.menu = {};
          $scope.page.menu.currentSection = appState.getSectionState('currentSection');
          $scope.page.menu.currentNode = null;
          //the editors affiliated node
          $scope.page.listViewPath = null;
          $scope.page.saveButtonState = 'init';
          /** Syncs the content item to it's tree node - this occurs on first load and after saving */
          function syncTreeNode(content, path, initialLoad) {
              if (!$scope.content.isChildOfListView) {
                  navigationService.syncTree({
                      tree: 'media',
                      path: path.split(','),
                      forceReload: initialLoad !== true
                  }).then(function (syncArgs) {
                      $scope.page.menu.currentNode = syncArgs.node;
                  });
              } else if (initialLoad === true) {
                  //it's a child item, just sync the ui node to the parent
                  navigationService.syncTree({
                      tree: 'media',
                      path: path.substring(0, path.lastIndexOf(',')).split(','),
                      forceReload: initialLoad !== true
                  });
                  //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node 
                  // from the server so that we can load in the actions menu.
                  umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) {
                      $scope.page.menu.currentNode = node;
                  });
              }
          }
          if ($routeParams.create) {
              $scope.page.loading = true;
              mediaResource.getScaffold($routeParams.id, $routeParams.doctype).then(function (data) {
                  $scope.content = data;
                  editorState.set($scope.content);
                  // We don't get the info tab from the server from version 7.8 so we need to manually add it
                  contentEditingHelper.addInfoTab($scope.content.tabs);
                  $scope.page.loading = false;
              });
          } else {
              $scope.page.loading = true;
              mediaResource.getById($routeParams.id).then(function (data) {
                  $scope.content = data;
                  if (data.isChildOfListView && data.trashed === false) {
                      $scope.page.listViewPath = $routeParams.page ? '/media/media/edit/' + data.parentId + '?page=' + $routeParams.page : '/media/media/edit/' + data.parentId;
                  }
                  editorState.set($scope.content);
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
                  syncTreeNode($scope.content, data.path, true);
                  if ($scope.content.parentId && $scope.content.parentId != -1) {
                      //We fetch all ancestors of the node to generate the footer breadcrump navigation
                      entityResource.getAncestors($routeParams.id, 'media').then(function (anc) {
                          $scope.ancestors = anc;
                      });
                  }
                  // We don't get the info tab from the server from version 7.8 so we need to manually add it
                  contentEditingHelper.addInfoTab($scope.content.tabs);
                  $scope.page.loading = false;
              });
          }
          $scope.save = function () {
              if (!$scope.busy && formHelper.submitForm({
                      scope: $scope,
                      statusMessage: 'Saving...'
                  })) {
                  $scope.busy = true;
                  $scope.page.saveButtonState = 'busy';
                  mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) {
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                      });
                      editorState.set($scope.content);
                      $scope.busy = false;
                      syncTreeNode($scope.content, data.path);
                      $scope.page.saveButtonState = 'success';
                  }, function (err) {
                      contentEditingHelper.handleSaveError({
                          err: err,
                          redirectOnFailure: true,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                      });
                      //show any notifications
                      if (angular.isArray(err.data.notifications)) {
                          for (var i = 0; i < err.data.notifications.length; i++) {
                              notificationsService.showNotification(err.data.notifications[i]);
                          }
                      }
                      editorState.set($scope.content);
                      $scope.busy = false;
                      $scope.page.saveButtonState = 'error';
                  });
              } else {
                  $scope.busy = false;
              }
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Media.EditController', mediaEditController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.EmptyRecycleBinController
   * @function
   * 
   * @description
   * The controller for deleting media
   */
      function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) {
          $scope.busy = false;
          $scope.performDelete = function () {
              //(used in the UI)
              $scope.busy = true;
              $scope.currentNode.loading = true;
              mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                  $scope.busy = false;
                  $scope.currentNode.loading = false;
                  //show any notifications
                  if (angular.isArray(result.notifications)) {
                      for (var i = 0; i < result.notifications.length; i++) {
                          notificationsService.showNotification(result.notifications[i]);
                      }
                  }
                  treeService.removeChildNodes($scope.currentNode);
                  navigationService.hideMenu();
                  //reload the current view
                  $route.reload();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Media.EmptyRecycleBinController', MediaEmptyRecycleBinController);
      //used for the media picker dialog
      angular.module('umbraco').controller('Umbraco.Editors.Media.MoveController', function ($scope, userService, eventsService, mediaResource, appState, treeService, navigationService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          var node = dialogOptions.currentNode;
          $scope.treeModel = { hideHeader: false };
          userService.getCurrentUser().then(function (userData) {
              $scope.treeModel.hideHeader = userData.startMediaIds.length > 0 && userData.startMediaIds.indexOf(-1) == -1;
          });
          function nodeSelectHandler(ev, args) {
              if (args && args.event) {
                  args.event.preventDefault();
                  args.event.stopPropagation();
              }
              eventsService.emit('editors.media.moveController.select', args);
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          function nodeExpandedHandler(ev, args) {
              // open mini list view for list views
              if (args.node.metaData.isContainer) {
                  openMiniListView(args.node);
              }
          }
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
          $scope.move = function () {
              mediaResource.move({
                  parentId: $scope.target.id,
                  id: node.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'media',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'media',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
              });
          };
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
              $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
          });
          // Mini list view
          $scope.selectListViewNode = function (node) {
              node.selected = node.selected === true ? false : true;
              nodeSelectHandler({}, { node: node });
          };
          $scope.closeMiniListView = function () {
              $scope.miniListView = undefined;
          };
          function openMiniListView(node) {
              $scope.miniListView = node;
          }
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.MediaRecycleBinController
   * @function
   * 
   * @description
   * Controls the recycle bin for media
   * 
   */
      function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) {
          //ensures the list view doesn't actually load until we query for the list view config
          // for the section
          $scope.page = {};
          $scope.page.name = 'Recycle Bin';
          $scope.page.nameLocked = true;
          //ensures the list view doesn't actually load until we query for the list view config
          // for the section
          $scope.listViewPath = null;
          $routeParams.id = '-21';
          mediaResource.getRecycleBin().then(function (result) {
              //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a 
              // single property, so we'll extract that property (list view) and use it's data.
              var listproperty = result.tabs[0].properties[0];
              _.each(listproperty.config, function (val, key) {
                  $scope.model.config[key] = val;
              });
              $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
          });
          $scope.model = {
              config: {
                  entityType: $routeParams.section,
                  layouts: []
              }
          };
          // sync tree node
          navigationService.syncTree({
              tree: 'media',
              path: [
                  '-1',
                  $routeParams.id
              ],
              forceReload: false
          });
          localizePageName();
          function localizePageName() {
              var pageName = 'general_recycleBin';
              localizationService.localize(pageName).then(function (value) {
                  $scope.page.name = value;
              });
          }
      }
      angular.module('umbraco').controller('Umbraco.Editors.Media.RecycleBinController', MediaRecycleBinController);
      angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CopyController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          $scope.copy = function () {
              $scope.busy = true;
              $scope.error = false;
              mediaTypeResource.copy({
                  parentId: $scope.target.id,
                  id: dialogOptions.currentNode.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                  navigationService.syncTree({
                      tree: 'mediaTypes',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'mediaTypes',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MediaType.CreateController
   * @function
   *
   * @description
   * The controller for the media type creation dialog
   */
      function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) {
          $scope.model = {
              folderName: '',
              creatingFolder: false
          };
          var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
          $scope.showCreateFolder = function () {
              $scope.model.creatingFolder = true;
          };
          $scope.createContainer = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      formCtrl: this.createFolderForm,
                      statusMessage: localizeCreateFolder
                  })) {
                  mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                      navigationService.hideMenu();
                      var currPath = node.path ? node.path : '-1';
                      navigationService.syncTree({
                          tree: 'mediatypes',
                          path: currPath + ',' + folderId,
                          forceReload: true,
                          activate: true
                      });
                      formHelper.resetForm({ scope: $scope });
                      var section = appState.getSectionState('currentSection');
                  }, function (err) {
                  });
              }
              ;
          };
          $scope.createMediaType = function () {
              $location.search('create', null);
              $location.path('/settings/mediatypes/edit/' + node.id).search('create', 'true');
              navigationService.hideMenu();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CreateController', MediaTypesCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MediaType.DeleteController
   * @function
   *
   * @description
   * The controller for the media type delete dialog
   */
      function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              mediaTypeResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.performContainerDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.DeleteController', MediaTypesDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MediaType.EditController
   * @function
   *
   * @description
   * The controller for the media type editor
   */
      (function () {
          'use strict';
          function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              var evts = [];
              vm.save = save;
              vm.currentNode = null;
              vm.contentType = {};
              vm.page = {};
              vm.page.loading = false;
              vm.page.saveButtonState = 'init';
              vm.page.navigation = [
                  {
                      'name': localizationService.localize('general_design'),
                      'icon': 'icon-document-dashed-line',
                      'view': 'views/mediatypes/views/design/design.html',
                      'active': true
                  },
                  {
                      'name': localizationService.localize('general_listView'),
                      'icon': 'icon-list',
                      'view': 'views/mediatypes/views/listview/listview.html'
                  },
                  {
                      'name': localizationService.localize('general_rights'),
                      'icon': 'icon-keychain',
                      'view': 'views/mediatypes/views/permissions/permissions.html'
                  }
              ];
              vm.page.keyboardShortcutsOverview = [
                  {
                      'name': localizationService.localize('main_sections'),
                      'shortcuts': [{
                              'description': localizationService.localize('shortcuts_navigateSections'),
                              'keys': [
                                  { 'key': '1' },
                                  { 'key': '3' }
                              ],
                              'keyRange': true
                          }]
                  },
                  {
                      'name': localizationService.localize('general_design'),
                      'shortcuts': [
                          {
                              'description': localizationService.localize('shortcuts_addTab'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 't' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addProperty'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'p' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addEditor'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'e' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_editDataType'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'd' }
                              ]
                          }
                      ]
                  },
                  {
                      'name': localizationService.localize('general_listView'),
                      'shortcuts': [{
                              'description': localizationService.localize('shortcuts_toggleListView'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'l' }
                              ]
                          }]
                  },
                  {
                      'name': localizationService.localize('general_rights'),
                      'shortcuts': [
                          {
                              'description': localizationService.localize('shortcuts_toggleAllowAsRoot'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'r' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addChildNode'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'c' }
                              ]
                          }
                      ]
                  }
              ];
              contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                  vm.page.modelsBuilder = result;
                  if (result) {
                      //Models builder mode:
                      vm.page.defaultButton = {
                          hotKey: 'ctrl+s',
                          hotKeyWhenHidden: true,
                          labelKey: 'buttons_save',
                          letter: 'S',
                          type: 'submit',
                          handler: function () {
                              vm.save();
                          }
                      };
                      vm.page.subButtons = [{
                              hotKey: 'ctrl+g',
                              hotKeyWhenHidden: true,
                              labelKey: 'buttons_saveAndGenerateModels',
                              letter: 'G',
                              handler: function () {
                                  vm.page.saveButtonState = 'busy';
                                  vm.save().then(function (result) {
                                      vm.page.saveButtonState = 'busy';
                                      localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                          localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                              notificationsService.info(headerValue, msgValue);
                                          });
                                      });
                                      contentTypeHelper.generateModels().then(function (result) {
                                          if (result.success) {
                                              //re-check model status
                                              contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                  vm.page.modelsBuilder = statusResult;
                                              });
                                              //clear and add success
                                              vm.page.saveButtonState = 'init';
                                              localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                  notificationsService.success(value);
                                              });
                                          } else {
                                              vm.page.saveButtonState = 'error';
                                              localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                  notificationsService.error(value);
                                              });
                                          }
                                      }, function () {
                                          vm.page.saveButtonState = 'error';
                                          localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                              notificationsService.error(value);
                                          });
                                      });
                                  });
                              }
                          }];
                  }
              });
              if ($routeParams.create) {
                  vm.page.loading = true;
                  //we are creating so get an empty data type item
                  mediaTypeResource.getScaffold($routeParams.id).then(function (dt) {
                      init(dt);
                      vm.page.loading = false;
                  });
              } else {
                  loadMediaType();
              }
              function loadMediaType() {
                  vm.page.loading = true;
                  mediaTypeResource.getById($routeParams.id).then(function (dt) {
                      init(dt);
                      syncTreeNode(vm.contentType, dt.path, true);
                      vm.page.loading = false;
                  });
              }
              /* ---------- SAVE ---------- */
              function save() {
                  // only save if there is no overlays open
                  if (overlayHelper.getNumberOfOverlays() === 0) {
                      var deferred = $q.defer();
                      vm.page.saveButtonState = 'busy';
                      // reformat allowed content types to array if id's
                      vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                      contentEditingHelper.contentEditorPerformSave({
                          statusMessage: localizeSaving,
                          saveMethod: mediaTypeResource.save,
                          scope: $scope,
                          content: vm.contentType,
                          //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                          // type when server side validation fails - as opposed to content where we are capable of saving the content
                          // item if server side validation fails
                          redirectOnFailure: false,
                          // we need to rebind... the IDs that have been created!
                          rebindCallback: function (origContentType, savedContentType) {
                              vm.contentType.id = savedContentType.id;
                              vm.contentType.groups.forEach(function (group) {
                                  if (!group.name)
                                      return;
                                  var k = 0;
                                  while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                      k++;
                                  if (k == savedContentType.groups.length) {
                                      group.id = 0;
                                      return;
                                  }
                                  var savedGroup = savedContentType.groups[k];
                                  if (!group.id)
                                      group.id = savedGroup.id;
                                  group.properties.forEach(function (property) {
                                      if (property.id || !property.alias)
                                          return;
                                      k = 0;
                                      while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                          k++;
                                      if (k == savedGroup.properties.length) {
                                          property.id = 0;
                                          return;
                                      }
                                      var savedProperty = savedGroup.properties[k];
                                      property.id = savedProperty.id;
                                  });
                              });
                          }
                      }).then(function (data) {
                          //success
                          syncTreeNode(vm.contentType, data.path);
                          vm.page.saveButtonState = 'success';
                          deferred.resolve(data);
                      }, function (err) {
                          //error
                          if (err) {
                              editorState.set($scope.content);
                          } else {
                              localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                  localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                      notificationsService.error(headerValue, msgValue);
                                  });
                              });
                          }
                          vm.page.saveButtonState = 'error';
                          deferred.reject(err);
                      });
                      return deferred.promise;
                  }
              }
              function init(contentType) {
                  // set all tab to inactive
                  if (contentType.groups.length !== 0) {
                      angular.forEach(contentType.groups, function (group) {
                          angular.forEach(group.properties, function (property) {
                              // get data type details for each property
                              getDataTypeDetails(property);
                          });
                      });
                  }
                  // convert icons for content type
                  convertLegacyIcons(contentType);
                  //set a shared state
                  editorState.set(contentType);
                  vm.contentType = contentType;
              }
              function convertLegacyIcons(contentType) {
                  // make array to store contentType icon
                  var contentTypeArray = [];
                  // push icon to array
                  contentTypeArray.push({ 'icon': contentType.icon });
                  // run through icon method
                  iconHelper.formatContentTypeIcons(contentTypeArray);
                  // set icon back on contentType
                  contentType.icon = contentTypeArray[0].icon;
              }
              function getDataTypeDetails(property) {
                  if (property.propertyState !== 'init') {
                      dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                          property.dataTypeIcon = dataType.icon;
                          property.dataTypeName = dataType.name;
                      });
                  }
              }
              /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
              function syncTreeNode(dt, path, initialLoad) {
                  navigationService.syncTree({
                      tree: 'mediatypes',
                      path: path.split(','),
                      forceReload: initialLoad !== true
                  }).then(function (syncArgs) {
                      vm.currentNode = syncArgs.node;
                  });
              }
              evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                  loadMediaType();
              }));
              //ensure to unregister from all events!
              $scope.$on('$destroy', function () {
                  for (var e in evts) {
                      eventsService.unsubscribe(evts[e]);
                  }
              });
          }
          angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.EditController', MediaTypesEditController);
      }());
      angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.MoveController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              if ($scope.target) {
                  //un-select if there's a current one selected
                  $scope.target.selected = false;
              }
              $scope.target = args.node;
              $scope.target.selected = true;
          }
          $scope.move = function () {
              $scope.busy = true;
              $scope.error = false;
              mediaTypeResource.move({
                  parentId: $scope.target.id,
                  id: dialogOptions.currentNode.id
              }).then(function (path) {
                  $scope.error = false;
                  $scope.success = true;
                  $scope.busy = false;
                  //first we need to remove the node that launched the dialog
                  treeService.removeNode($scope.currentNode);
                  //get the currently edited node (if any)
                  var activeNode = appState.getTreeState('selectedNode');
                  //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                  //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                  navigationService.syncTree({
                      tree: 'mediaTypes',
                      path: path,
                      forceReload: true,
                      activate: false
                  }).then(function (args) {
                      if (activeNode) {
                          var activeNodePath = treeService.getPath(activeNode).join();
                          //sync to this node now - depending on what was copied this might already be synced but might not be
                          navigationService.syncTree({
                              tree: 'mediaTypes',
                              path: activeNodePath,
                              forceReload: false,
                              activate: true
                          });
                      }
                  });
                  eventsService.emit('app.refreshEditor');
              }, function (err) {
                  $scope.success = false;
                  $scope.error = err;
                  $scope.busy = false;
                  //show any notifications
                  if (angular.isArray(err.data.notifications)) {
                      for (var i = 0; i < err.data.notifications.length; i++) {
                          notificationsService.showNotification(err.data.notifications[i]);
                      }
                  }
              });
          };
          $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
          });
      });
      (function () {
          'use strict';
          function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService) {
              /* ----------- SCOPE VARIABLES ----------- */
              var vm = this;
              var childNodeSelectorOverlayTitle = '';
              vm.mediaTypes = [];
              vm.selectedChildren = [];
              vm.addChild = addChild;
              vm.removeChild = removeChild;
              /* ---------- INIT ---------- */
              init();
              function init() {
                  childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode');
                  mediaTypeResource.getAll().then(function (mediaTypes) {
                      vm.mediaTypes = mediaTypes;
                      // convert legacy icons
                      iconHelper.formatContentTypeIcons(vm.mediaTypes);
                      vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes);
                      if ($scope.model.id === 0) {
                          contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                      }
                  });
              }
              function addChild($event) {
                  vm.childNodeSelectorOverlay = {
                      view: 'itempicker',
                      title: childNodeSelectorOverlayTitle,
                      availableItems: vm.mediaTypes,
                      selectedItems: vm.selectedChildren,
                      event: $event,
                      show: true,
                      submit: function (model) {
                          vm.selectedChildren.push(model.selectedItem);
                          $scope.model.allowedContentTypes.push(model.selectedItem.id);
                          vm.childNodeSelectorOverlay.show = false;
                          vm.childNodeSelectorOverlay = null;
                      }
                  };
              }
              function removeChild(selectedChild, index) {
                  // remove from vm
                  vm.selectedChildren.splice(index, 1);
                  // remove from content type model
                  var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                  $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.MediaType.PermissionsController', PermissionsController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.CreateController
   * @function
   * 
   * @description
   * The controller for the member creation dialog
   */
      function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) {
          memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
              $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
          });
      }
      angular.module('umbraco').controller('Umbraco.Editors.Member.CreateController', memberCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.DeleteController
   * @function
   *
   * @description
   * The controller for deleting content
   */
      function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              memberResource.deleteByKey($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  treeService.removeNode($scope.currentNode);
                  //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                  if (editorState.current && editorState.current.key == $scope.currentNode.id) {
                      $location.path('/member/member/list/' + ($routeParams.listName ? $routeParams.listName : 'all-members'));
                  }
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Member.DeleteController', MemberDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.EditController
   * @function
   *
   * @description
   * The controller for the member editor
   */
      function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
          //setup scope vars
          $scope.page = {};
          $scope.page.loading = true;
          $scope.page.menu = {};
          $scope.page.menu.currentSection = appState.getSectionState('currentSection');
          $scope.page.menu.currentNode = null;
          //the editors affiliated node
          $scope.page.nameLocked = false;
          $scope.page.listViewPath = null;
          $scope.page.saveButtonState = 'init';
          $scope.page.exportButton = 'init';
          $scope.busy = false;
          $scope.page.listViewPath = $routeParams.page && $routeParams.listName ? '/member/member/list/' + $routeParams.listName + '?page=' + $routeParams.page : null;
          //build a path to sync the tree with
          function buildTreePath(data) {
              return $routeParams.listName ? '-1,' + $routeParams.listName : '-1';
          }
          if ($routeParams.create) {
              //if there is no doc type specified then we are going to assume that
              // we are not using the umbraco membership provider
              if ($routeParams.doctype) {
                  //we are creating so get an empty member item
                  memberResource.getScaffold($routeParams.doctype).then(function (data) {
                      $scope.content = data;
                      setHeaderNameState($scope.content);
                      editorState.set($scope.content);
                      $scope.page.loading = false;
                  });
              } else {
                  memberResource.getScaffold().then(function (data) {
                      $scope.content = data;
                      setHeaderNameState($scope.content);
                      editorState.set($scope.content);
                      $scope.page.loading = false;
                  });
              }
          } else {
              //so, we usually refernce all editors with the Int ID, but with members we have
              //a different pattern, adding a route-redirect here to handle this:
              //isNumber doesnt work here since its seen as a string
              //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers.
              if ($routeParams.id && $routeParams.id.length < 9) {
                  entityResource.getById($routeParams.id, 'Member').then(function (entity) {
                      $location.path('/member/member/edit/' + entity.key);
                  });
              } else {
                  //we are editing so get the content item from the server
                  memberResource.getByKey($routeParams.id).then(function (data) {
                      $scope.content = data;
                      setHeaderNameState($scope.content);
                      editorState.set($scope.content);
                      var path = buildTreePath(data);
                      //sync the tree (only for ui purposes)
                      navigationService.syncTree({
                          tree: 'member',
                          path: path.split(',')
                      });
                      //it's the initial load of the editor, we need to get the tree node
                      // from the server so that we can load in the actions menu.
                      umbRequestHelper.resourcePromise($http.get(data.treeNodeUrl), 'Failed to retrieve data for child node ' + data.key).then(function (node) {
                          $scope.page.menu.currentNode = node;
                      });
                      //in one particular special case, after we've created a new item we redirect back to the edit
                      // route but there might be server validation errors in the collection which we need to display
                      // after the redirect, so we will bind all subscriptions which will show the server validation errors
                      // if there are any and then clear them so the collection no longer persists them.
                      serverValidationManager.executeAndClearAllSubscriptions();
                      $scope.page.loading = false;
                  });
              }
          }
          function setHeaderNameState(content) {
              if (content.membershipScenario === 0) {
                  $scope.page.nameLocked = true;
              }
          }
          $scope.save = function () {
              if (!$scope.busy && formHelper.submitForm({
                      scope: $scope,
                      statusMessage: 'Saving...'
                  })) {
                  $scope.busy = true;
                  $scope.page.saveButtonState = 'busy';
                  memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) {
                      formHelper.resetForm({
                          scope: $scope,
                          notifications: data.notifications
                      });
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          //specify a custom id to redirect to since we want to use the GUID
                          redirectId: data.key,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                      });
                      editorState.set($scope.content);
                      $scope.busy = false;
                      $scope.page.saveButtonState = 'success';
                      var path = buildTreePath(data);
                      //sync the tree (only for ui purposes)
                      navigationService.syncTree({
                          tree: 'member',
                          path: path.split(','),
                          forceReload: true
                      });
                  }, function (err) {
                      contentEditingHelper.handleSaveError({
                          redirectOnFailure: false,
                          err: err,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                      });
                      editorState.set($scope.content);
                      $scope.busy = false;
                      $scope.page.saveButtonState = 'error';
                  });
              } else {
                  $scope.busy = false;
              }
          };
          $scope.export = function () {
              var memberKey = $scope.content.key;
              memberResource.exportMemberData(memberKey);
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Member.EditController', MemberEditController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.ListController
   * @function
   *
   * @description
   * The controller for the member list view
   */
      function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) {
          //setup scope vars
          $scope.currentSection = appState.getSectionState('currentSection');
          $scope.currentNode = null;
          //the editors affiliated node
          $scope.page = {};
          $scope.page.lockedName = true;
          $scope.page.loading = true;
          //we are editing so get the content item from the server
          memberResource.getListNode($routeParams.id).then(function (data) {
              $scope.content = data;
              //translate "All Members"
              if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(' ', '').toLowerCase() == 'allmembers') {
                  localizationService.localize('member_allMembers').then(function (value) {
                      $scope.content.name = value;
                  });
              }
              editorState.set($scope.content);
              navigationService.syncTree({
                  tree: 'member',
                  path: data.path.split(',')
              }).then(function (syncArgs) {
                  $scope.currentNode = syncArgs.node;
              });
              //in one particular special case, after we've created a new item we redirect back to the edit
              // route but there might be server validation errors in the collection which we need to display
              // after the redirect, so we will bind all subscriptions which will show the server validation errors
              // if there are any and then clear them so the collection no longer persists them.
              serverValidationManager.executeAndClearAllSubscriptions();
              $scope.page.loading = false;
          });
      }
      angular.module('umbraco').controller('Umbraco.Editors.Member.ListController', MemberListController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MemberType.CreateController
   * @function
   *
   * @description
   * The controller for the member type creation dialog
   */
      function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) {
          $scope.model = {
              folderName: '',
              creatingFolder: false
          };
          var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
          $scope.showCreateFolder = function () {
              $scope.model.creatingFolder = true;
          };
          $scope.createContainer = function () {
              if (formHelper.submitForm({
                      scope: $scope,
                      formCtrl: this.createFolderForm,
                      statusMessage: localizeCreateFolder
                  })) {
                  memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                      navigationService.hideMenu();
                      var currPath = node.path ? node.path : '-1';
                      navigationService.syncTree({
                          tree: 'membertypes',
                          path: currPath + ',' + folderId,
                          forceReload: true,
                          activate: true
                      });
                      formHelper.resetForm({ scope: $scope });
                      var section = appState.getSectionState('currentSection');
                  }, function (err) {
                  });
              }
              ;
          };
          $scope.createMemberType = function () {
              $location.search('create', null);
              $location.path('/settings/membertypes/edit/' + node.id).search('create', 'true');
              navigationService.hideMenu();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.CreateController', MemberTypesCreateController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MemberTypes.DeleteController
   * @function
   *
   * @description
   * The controller for deleting member types
   */
      function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              memberTypeResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.DeleteController', MemberTypesDeleteController);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.MemberType.EditController
   * @function
   *
   * @description
   * The controller for the member type editor
   */
      (function () {
          'use strict';
          function MemberTypesEditController($scope, $rootScope, $routeParams, $log, $filter, memberTypeResource, dataTypeResource, editorState, iconHelper, formHelper, navigationService, contentEditingHelper, notificationsService, $q, localizationService, overlayHelper, contentTypeHelper) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              vm.save = save;
              vm.currentNode = null;
              vm.contentType = {};
              vm.page = {};
              vm.page.loading = false;
              vm.page.saveButtonState = 'init';
              vm.page.navigation = [{
                      'name': localizationService.localize('general_design'),
                      'icon': 'icon-document-dashed-line',
                      'view': 'views/membertypes/views/design/design.html',
                      'active': true
                  }];
              vm.page.keyboardShortcutsOverview = [{
                      'name': localizationService.localize('shortcuts_shortcut'),
                      'shortcuts': [
                          {
                              'description': localizationService.localize('shortcuts_addTab'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 't' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addProperty'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'p' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_addEditor'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'e' }
                              ]
                          },
                          {
                              'description': localizationService.localize('shortcuts_editDataType'),
                              'keys': [
                                  { 'key': 'alt' },
                                  { 'key': 'shift' },
                                  { 'key': 'd' }
                              ]
                          }
                      ]
                  }];
              contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                  vm.page.modelsBuilder = result;
                  if (result) {
                      //Models builder mode:
                      vm.page.defaultButton = {
                          hotKey: 'ctrl+s',
                          hotKeyWhenHidden: true,
                          labelKey: 'buttons_save',
                          letter: 'S',
                          type: 'submit',
                          handler: function () {
                              vm.save();
                          }
                      };
                      vm.page.subButtons = [{
                              hotKey: 'ctrl+g',
                              hotKeyWhenHidden: true,
                              labelKey: 'buttons_saveAndGenerateModels',
                              letter: 'G',
                              handler: function () {
                                  vm.page.saveButtonState = 'busy';
                                  vm.save().then(function (result) {
                                      vm.page.saveButtonState = 'busy';
                                      localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                          localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                              notificationsService.info(headerValue, msgValue);
                                          });
                                      });
                                      contentTypeHelper.generateModels().then(function (result) {
                                          if (result.success) {
                                              //re-check model status
                                              contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                  vm.page.modelsBuilder = statusResult;
                                              });
                                              //clear and add success
                                              vm.page.saveButtonState = 'init';
                                              localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                  notificationsService.success(value);
                                              });
                                          } else {
                                              vm.page.saveButtonState = 'error';
                                              localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                  notificationsService.error(value);
                                              });
                                          }
                                      }, function () {
                                          vm.page.saveButtonState = 'error';
                                          localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                              notificationsService.error(value);
                                          });
                                      });
                                  });
                              }
                          }];
                  }
              });
              if ($routeParams.create) {
                  vm.page.loading = true;
                  //we are creating so get an empty data type item
                  memberTypeResource.getScaffold($routeParams.id).then(function (dt) {
                      init(dt);
                      vm.page.loading = false;
                  });
              } else {
                  vm.page.loading = true;
                  memberTypeResource.getById($routeParams.id).then(function (dt) {
                      init(dt);
                      syncTreeNode(vm.contentType, dt.path, true);
                      vm.page.loading = false;
                  });
              }
              function save() {
                  // only save if there is no overlays open
                  if (overlayHelper.getNumberOfOverlays() === 0) {
                      var deferred = $q.defer();
                      vm.page.saveButtonState = 'busy';
                      contentEditingHelper.contentEditorPerformSave({
                          statusMessage: localizeSaving,
                          saveMethod: memberTypeResource.save,
                          scope: $scope,
                          content: vm.contentType,
                          //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                          // type when server side validation fails - as opposed to content where we are capable of saving the content
                          // item if server side validation fails
                          redirectOnFailure: false,
                          // we need to rebind... the IDs that have been created!
                          rebindCallback: function (origContentType, savedContentType) {
                              vm.contentType.id = savedContentType.id;
                              vm.contentType.groups.forEach(function (group) {
                                  if (!group.name)
                                      return;
                                  var k = 0;
                                  while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                      k++;
                                  if (k == savedContentType.groups.length) {
                                      group.id = 0;
                                      return;
                                  }
                                  var savedGroup = savedContentType.groups[k];
                                  if (!group.id)
                                      group.id = savedGroup.id;
                                  group.properties.forEach(function (property) {
                                      if (property.id || !property.alias)
                                          return;
                                      k = 0;
                                      while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                          k++;
                                      if (k == savedGroup.properties.length) {
                                          property.id = 0;
                                          return;
                                      }
                                      var savedProperty = savedGroup.properties[k];
                                      property.id = savedProperty.id;
                                  });
                              });
                          }
                      }).then(function (data) {
                          //success
                          syncTreeNode(vm.contentType, data.path);
                          vm.page.saveButtonState = 'success';
                          deferred.resolve(data);
                      }, function (err) {
                          //error
                          if (err) {
                              editorState.set($scope.content);
                          } else {
                              localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                  localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                      notificationsService.error(headerValue, msgValue);
                                  });
                              });
                          }
                          vm.page.saveButtonState = 'error';
                          deferred.reject(err);
                      });
                      return deferred.promise;
                  }
              }
              function init(contentType) {
                  // set all tab to inactive
                  if (contentType.groups.length !== 0) {
                      angular.forEach(contentType.groups, function (group) {
                          angular.forEach(group.properties, function (property) {
                              // get data type details for each property
                              getDataTypeDetails(property);
                          });
                      });
                  }
                  // convert legacy icons
                  convertLegacyIcons(contentType);
                  //set a shared state
                  editorState.set(contentType);
                  vm.contentType = contentType;
              }
              function convertLegacyIcons(contentType) {
                  // make array to store contentType icon
                  var contentTypeArray = [];
                  // push icon to array
                  contentTypeArray.push({ 'icon': contentType.icon });
                  // run through icon method
                  iconHelper.formatContentTypeIcons(contentTypeArray);
                  // set icon back on contentType
                  contentType.icon = contentTypeArray[0].icon;
              }
              function getDataTypeDetails(property) {
                  if (property.propertyState !== 'init') {
                      dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                          property.dataTypeIcon = dataType.icon;
                          property.dataTypeName = dataType.name;
                      });
                  }
              }
              /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
              function syncTreeNode(dt, path, initialLoad) {
                  navigationService.syncTree({
                      tree: 'membertypes',
                      path: path.split(','),
                      forceReload: initialLoad !== true
                  }).then(function (syncArgs) {
                      vm.currentNode = syncArgs.node;
                  });
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.EditController', MemberTypesEditController);
      }());
      angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.MoveController', function ($scope) {
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Packages.DeleteController
   * @function
   *
   * @description
   * The controller for deleting content
   */
      function PackageDeleteController($scope, packageResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Packages.DeleteController', PackageDeleteController);
      (function () {
          'use strict';
          function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) {
              //Hack!
              // if there is a cookie value for packageInstallUri then we need to redirect there,
              // the issue is that we still have webforms and we cannot go to a hash location and then window.reload
              // because it will double load it.
              // we will refresh and then navigate there.
              var installPackageUri = localStorageService.get('packageInstallUri');
              if (installPackageUri) {
                  localStorageService.remove('packageInstallUri');
              }
              if (installPackageUri && installPackageUri !== 'installed') {
                  //navigate to the custom installer screen, if it is just "installed", then we'll 
                  //show the installed view
                  $location.path(installPackageUri).search('');
              } else {
                  var vm = this;
                  vm.page = {};
                  vm.page.name = 'Packages';
                  vm.page.navigation = [
                      {
                          'name': 'Packages',
                          'icon': 'icon-cloud',
                          'view': 'views/packager/views/repo.html',
                          'active': !installPackageUri || installPackageUri === 'navigation'
                      },
                      {
                          'name': 'Installed',
                          'icon': 'icon-box',
                          'view': 'views/packager/views/installed.html',
                          'active': installPackageUri === 'installed'
                      },
                      {
                          'name': 'Install local',
                          'icon': 'icon-add',
                          'view': 'views/packager/views/install-local.html',
                          'active': installPackageUri === 'local'
                      }
                  ];
                  $timeout(function () {
                      navigationService.syncTree({
                          tree: 'packager',
                          path: '-1'
                      });
                  });
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.Packages.OverviewController', PackagesOverviewController);
      }());
      (function () {
          'use strict';
          function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService, $q) {
              var vm = this;
              vm.state = 'upload';
              vm.localPackage = {};
              vm.installPackage = installPackage;
              vm.installState = {
                  status: '',
                  progress: 0
              };
              vm.installCompleted = false;
              vm.zipFile = {
                  uploadStatus: 'idle',
                  uploadProgress: 0,
                  serverErrorMessage: null
              };
              $scope.handleFiles = function (files, event) {
                  if (files) {
                      for (var i = 0; i < files.length; i++) {
                          upload(files[i]);
                      }
                  }
              };
              function upload(file) {
                  Upload.upload({
                      url: umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'UploadLocalPackage'),
                      fields: {},
                      file: file
                  }).progress(function (evt) {
                      // hack: in some browsers the progress event is called after success
                      // this prevents the UI from going back to a uploading state
                      if (vm.zipFile.uploadStatus !== 'done' && vm.zipFile.uploadStatus !== 'error') {
                          // set view state to uploading
                          vm.state = 'uploading';
                          // calculate progress in percentage
                          var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                          // set percentage property on file
                          vm.zipFile.uploadProgress = progressPercentage;
                          // set uploading status on file
                          vm.zipFile.uploadStatus = 'uploading';
                      }
                  }).success(function (data, status, headers, config) {
                      if (data.notifications && data.notifications.length > 0) {
                          // set error status on file
                          vm.zipFile.uploadStatus = 'error';
                          // Throw message back to user with the cause of the error
                          vm.zipFile.serverErrorMessage = data.notifications[0].message;
                      } else {
                          // set done status on file
                          vm.zipFile.uploadStatus = 'done';
                          loadPackage();
                          vm.zipFile.uploadProgress = 100;
                          vm.localPackage = data;
                      }
                  }).error(function (evt, status, headers, config) {
                      // set status done
                      vm.zipFile.uploadStatus = 'error';
                      // If file not found, server will return a 404 and display this message
                      if (status === 404) {
                          vm.zipFile.serverErrorMessage = 'File not found';
                      } else if (status == 400) {
                          //it's a validation error
                          vm.zipFile.serverErrorMessage = evt.message;
                      } else {
                          //it's an unhandled error
                          //if the service returns a detailed error
                          if (evt.InnerException) {
                              vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                              //Check if its the common "too large file" exception
                              if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                  vm.zipFile.serverErrorMessage = 'File too large to upload';
                              }
                          } else if (evt.Message) {
                              vm.zipFile.serverErrorMessage = evt.Message;
                          }
                      }
                  });
              }
              function loadPackage() {
                  if (vm.zipFile.uploadStatus === 'done') {
                      vm.state = 'packageDetails';
                  }
              }
              function installPackage() {
                  vm.installState.status = localizationService.localize('packager_installStateImporting');
                  vm.installState.progress = '0';
                  packageResource.import(vm.localPackage).then(function (pack) {
                      vm.installState.progress = '25';
                      vm.installState.status = localizationService.localize('packager_installStateInstalling');
                      return packageResource.installFiles(pack);
                  }, installError).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateRestarting');
                      vm.installState.progress = '50';
                      var deferred = $q.defer();
                      //check if the app domain is restarted ever 2 seconds
                      var count = 0;
                      function checkRestart() {
                          $timeout(function () {
                              packageResource.checkRestart(pack).then(function (d) {
                                  count++;
                                  //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                  if (d.isRestarting && count < 10) {
                                      checkRestart();
                                  } else {
                                      //it's restarted!
                                      deferred.resolve(d);
                                  }
                              }, installError);
                          }, 2000);
                      }
                      checkRestart();
                      return deferred.promise;
                  }, installError).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateRestarting');
                      vm.installState.progress = '75';
                      return packageResource.installData(pack);
                  }, installError).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateComplete');
                      vm.installState.progress = '100';
                      return packageResource.cleanUp(pack);
                  }, installError).then(function (result) {
                      if (result.postInstallationPath) {
                          //Put the redirect Uri in a cookie so we can use after reloading
                          localStorageService.set('packageInstallUri', result.postInstallationPath);
                      } else {
                          //set to a constant value so it knows to just go to the installed view
                          localStorageService.set('packageInstallUri', 'installed');
                      }
                      vm.installState.status = localizationService.localize('packager_installStateCompleted');
                      vm.installCompleted = true;
                  }, installError);
              }
              function installError() {
                  //This will return a rejection meaning that the promise change above will stop
                  return $q.reject();
              }
              vm.reloadPage = function () {
                  //reload on next digest (after cookie)
                  $timeout(function () {
                      $window.location.reload(true);
                  });
              };
          }
          angular.module('umbraco').controller('Umbraco.Editors.Packages.InstallLocalController', PackagesInstallLocalController);
      }());
      (function () {
          'use strict';
          function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) {
              var vm = this;
              vm.confirmUninstall = confirmUninstall;
              vm.uninstallPackage = uninstallPackage;
              vm.state = 'list';
              vm.installState = { status: '' };
              vm.package = {};
              function init() {
                  packageResource.getInstalled().then(function (packs) {
                      vm.installedPackages = packs;
                  });
                  vm.installState.status = '';
                  vm.state = 'list';
              }
              function confirmUninstall(pck) {
                  vm.state = 'packageDetails';
                  vm.package = pck;
              }
              function uninstallPackage(installedPackage) {
                  vm.installState.status = localizationService.localize('packager_installStateUninstalling');
                  vm.installState.progress = '0';
                  packageResource.uninstall(installedPackage.id).then(function () {
                      if (installedPackage.files.length > 0) {
                          vm.installState.status = localizationService.localize('packager_installStateComplete');
                          vm.installState.progress = '100';
                          //set this flag so that on refresh it shows the installed packages list
                          localStorageService.set('packageInstallUri', 'installed');
                          //reload on next digest (after cookie)
                          $timeout(function () {
                              $window.location.reload(true);
                          });
                      } else {
                          init();
                      }
                  });
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Packages.InstalledController', PackagesInstalledController);
      }());
      (function () {
          'use strict';
          function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) {
              var vm = this;
              vm.packageViewState = 'packageList';
              vm.categories = [];
              vm.loading = true;
              vm.pagination = {
                  pageNumber: 1,
                  totalPages: 10,
                  pageSize: 24
              };
              vm.searchQuery = '';
              vm.installState = {
                  status: '',
                  progress: 0,
                  type: 'ok'
              };
              vm.selectCategory = selectCategory;
              vm.showPackageDetails = showPackageDetails;
              vm.setPackageViewState = setPackageViewState;
              vm.nextPage = nextPage;
              vm.prevPage = prevPage;
              vm.goToPage = goToPage;
              vm.installPackage = installPackage;
              vm.downloadPackage = downloadPackage;
              vm.openLightbox = openLightbox;
              vm.closeLightbox = closeLightbox;
              vm.search = search;
              vm.installCompleted = false;
              var currSort = 'Latest';
              //used to cancel any request in progress if another one needs to take it's place
              var canceler = null;
              function getActiveCategory() {
                  if (vm.searchQuery !== '') {
                      return '';
                  }
                  for (var i = 0; i < vm.categories.length; i++) {
                      if (vm.categories[i].active === true) {
                          return vm.categories[i].name;
                      }
                  }
                  return '';
              }
              function init() {
                  vm.loading = true;
                  $q.all([
                      ourPackageRepositoryResource.getCategories().then(function (cats) {
                          vm.categories = cats;
                      }),
                      ourPackageRepositoryResource.getPopular(8).then(function (pack) {
                          vm.popular = pack.packages;
                      }),
                      ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort).then(function (pack) {
                          vm.packages = pack.packages;
                          vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                      })
                  ]).then(function () {
                      vm.loading = false;
                  });
              }
              function selectCategory(selectedCategory, categories) {
                  var reset = false;
                  for (var i = 0; i < categories.length; i++) {
                      var category = categories[i];
                      if (category.name === selectedCategory.name && category.active === true) {
                          //it's already selected, let's unselect to show all again
                          reset = true;
                      }
                      category.active = false;
                  }
                  vm.loading = true;
                  vm.searchQuery = '';
                  var searchCategory = selectedCategory.name;
                  if (reset === true) {
                      searchCategory = '';
                  }
                  currSort = 'Latest';
                  $q.all([
                      ourPackageRepositoryResource.getPopular(8, searchCategory).then(function (pack) {
                          vm.popular = pack.packages;
                      }),
                      ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery).then(function (pack) {
                          vm.packages = pack.packages;
                          vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                          vm.pagination.pageNumber = 1;
                      })
                  ]).then(function () {
                      vm.loading = false;
                      selectedCategory.active = reset === false;
                  });
              }
              function showPackageDetails(selectedPackage) {
                  ourPackageRepositoryResource.getDetails(selectedPackage.id).then(function (pack) {
                      packageResource.validateInstalled(pack.name, pack.latestVersion).then(function () {
                          //ok, can install
                          vm.package = pack;
                          vm.package.isValid = true;
                          vm.packageViewState = 'packageDetails';
                      }, function () {
                          //nope, cannot install
                          vm.package = pack;
                          vm.package.isValid = false;
                          vm.packageViewState = 'packageDetails';
                      });
                  });
              }
              function setPackageViewState(state) {
                  if (state) {
                      vm.packageViewState = state;
                  }
              }
              function nextPage(pageNumber) {
                  ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                      vm.packages = pack.packages;
                      vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                  });
              }
              function prevPage(pageNumber) {
                  ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                      vm.packages = pack.packages;
                      vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                  });
              }
              function goToPage(pageNumber) {
                  ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                      vm.packages = pack.packages;
                      vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                  });
              }
              function downloadPackage(selectedPackage) {
                  vm.loading = true;
                  packageResource.fetch(selectedPackage.id).then(function (pack) {
                      vm.packageViewState = 'packageInstall';
                      vm.loading = false;
                      vm.localPackage = pack;
                      vm.localPackage.allowed = true;
                  }, function (evt, status, headers, config) {
                      if (status == 400) {
                          //it's a validation error
                          vm.installState.type = 'error';
                          vm.zipFile.serverErrorMessage = evt.message;
                      }
                  });
              }
              function error(e, args) {
                  //This will return a rejection meaning that the promise change above will stop
                  return $q.reject();
              }
              function installPackage(selectedPackage) {
                  vm.installState.status = localizationService.localize('packager_installStateImporting');
                  vm.installState.progress = '0';
                  packageResource.import(selectedPackage).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateInstalling');
                      vm.installState.progress = '25';
                      return packageResource.installFiles(pack);
                  }, error).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateRestarting');
                      vm.installState.progress = '50';
                      var deferred = $q.defer();
                      //check if the app domain is restarted ever 2 seconds
                      var count = 0;
                      function checkRestart() {
                          $timeout(function () {
                              packageResource.checkRestart(pack).then(function (d) {
                                  count++;
                                  //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                  if (d.isRestarting && count < 10) {
                                      checkRestart();
                                  } else {
                                      //it's restarted!
                                      deferred.resolve(d);
                                  }
                              }, error);
                          }, 2000);
                      }
                      checkRestart();
                      return deferred.promise;
                  }, error).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateRestarting');
                      vm.installState.progress = '75';
                      return packageResource.installData(pack);
                  }, error).then(function (pack) {
                      vm.installState.status = localizationService.localize('packager_installStateComplete');
                      vm.installState.progress = '100';
                      return packageResource.cleanUp(pack);
                  }, error).then(function (result) {
                      if (result.postInstallationPath) {
                          //Put the redirect Uri in a cookie so we can use after reloading
                          localStorageService.set('packageInstallUri', result.postInstallationPath);
                      }
                      vm.installState.status = localizationService.localize('packager_installStateCompleted');
                      vm.installCompleted = true;
                  }, error);
              }
              function openLightbox(itemIndex, items) {
                  vm.lightbox = {
                      show: true,
                      items: items,
                      activeIndex: itemIndex
                  };
              }
              function closeLightbox() {
                  vm.lightbox.show = false;
                  vm.lightbox = null;
              }
              var searchDebounced = _.debounce(function (e) {
                  $scope.$apply(function () {
                      //a canceler exists, so perform the cancelation operation and reset
                      if (canceler) {
                          canceler.resolve();
                          canceler = $q.defer();
                      } else {
                          canceler = $q.defer();
                      }
                      currSort = vm.searchQuery ? 'Default' : 'Latest';
                      ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, '', vm.searchQuery, canceler).then(function (pack) {
                          vm.packages = pack.packages;
                          vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                          vm.pagination.pageNumber = 1;
                          vm.loading = false;
                          //set back to null so it can be re-created
                          canceler = null;
                      });
                  });
              }, 200);
              function search(searchQuery) {
                  vm.loading = true;
                  searchDebounced();
              }
              vm.reloadPage = function () {
                  //reload on next digest (after cookie)
                  $timeout(function () {
                      window.location.reload(true);
                  });
              };
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Packages.RepoController', PackagesRepoController);
      }());
      (function () {
          'use strict';
          function PartialViewMacrosCreateController($scope, codefileResource, macroResource, $location, navigationService, formHelper, localizationService, appState) {
              var vm = this;
              var node = $scope.dialogOptions.currentNode;
              var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
              vm.snippets = [];
              vm.createFolderError = '';
              vm.folderName = '';
              vm.fileName = '';
              vm.showSnippets = false;
              vm.creatingFolder = false;
              vm.showCreateFolder = showCreateFolder;
              vm.createFolder = createFolder;
              vm.createFile = createFile;
              vm.createFileWithoutMacro = createFileWithoutMacro;
              vm.showCreateFromSnippet = showCreateFromSnippet;
              vm.createFileFromSnippet = createFileFromSnippet;
              function onInit() {
                  codefileResource.getSnippets('partialViewMacros').then(function (snippets) {
                      vm.snippets = snippets;
                  });
              }
              function showCreateFolder() {
                  vm.creatingFolder = true;
              }
              function createFolder(form) {
                  if (formHelper.submitForm({
                          scope: $scope,
                          formCtrl: form,
                          statusMessage: localizeCreateFolder
                      })) {
                      codefileResource.createContainer('partialViewMacros', node.id, vm.folderName).then(function (saved) {
                          navigationService.hideMenu();
                          navigationService.syncTree({
                              tree: 'partialViewMacros',
                              path: saved.path,
                              forceReload: true,
                              activate: true
                          });
                          formHelper.resetForm({ scope: $scope });
                          var section = appState.getSectionState('currentSection');
                      }, function (err) {
                          vm.createFolderError = err;
                          //show any notifications
                          formHelper.showNotifications(err.data);
                      });
                  }
              }
              function createFile() {
                  $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true');
                  navigationService.hideMenu();
              }
              function createFileWithoutMacro() {
                  $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('nomacro', 'true');
                  navigationService.hideMenu();
              }
              function createFileFromSnippet(snippet) {
                  $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('snippet', snippet.fileName);
                  navigationService.hideMenu();
              }
              function showCreateFromSnippet() {
                  vm.showSnippets = true;
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.CreateController', PartialViewMacrosCreateController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.PartialViewMacros.DeleteController
   * @function
   *
   * @description
   * The controller for deleting partial view macros
   */
      function PartialViewMacrosDeleteController($scope, codefileResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              codefileResource.deleteByPath('partialViewMacros', $scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.DeleteController', PartialViewMacrosDeleteController);
      (function () {
          'use strict';
          function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.page.loading = true;
              vm.partialViewMacroFile = {};
              //menu
              vm.page.menu = {};
              vm.page.menu.currentSection = appState.getSectionState('currentSection');
              vm.page.menu.currentNode = null;
              // bind functions to view model
              vm.save = save;
              vm.openPageFieldOverlay = openPageFieldOverlay;
              vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
              vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
              vm.openMacroOverlay = openMacroOverlay;
              vm.openInsertOverlay = openInsertOverlay;
              /* Functions bound to view model */
              function save() {
                  vm.page.saveButtonState = 'busy';
                  vm.partialViewMacro.content = vm.editor.getValue();
                  contentEditingHelper.contentEditorPerformSave({
                      statusMessage: localizeSaving,
                      saveMethod: codefileResource.save,
                      scope: $scope,
                      content: vm.partialViewMacro,
                      // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view
                      // when server side validation fails - as opposed to content where we are capable of saving the content
                      // item if server side validation fails
                      redirectOnFailure: false,
                      rebindCallback: function (orignal, saved) {
                      }
                  }).then(function (saved) {
                      // create macro if needed
                      if ($routeParams.create && $routeParams.nomacro !== 'true') {
                          macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
                              completeSave(saved);
                          }, function (err) {
                              //show any notifications
                              formHelper.showNotifications(err.data);
                          });
                      } else {
                          completeSave(saved);
                      }
                  }, function (err) {
                      vm.page.saveButtonState = 'error';
                      localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                          localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                              notificationsService.error(headerValue, msgValue);
                          });
                      });
                  });
              }
              function completeSave(saved) {
                  localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                      localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                          notificationsService.success(headerValue, msgValue);
                      });
                  });
                  //check if the name changed, if so we need to redirect
                  if (vm.partialViewMacro.id !== saved.id) {
                      contentEditingHelper.redirectToRenamedContent(saved.id);
                  } else {
                      vm.page.saveButtonState = 'success';
                      vm.partialViewMacro = saved;
                      //sync state
                      editorState.set(vm.partialViewMacro);
                      // normal tree sync
                      navigationService.syncTree({
                          tree: 'partialViewMacros',
                          path: vm.partialViewMacro.path,
                          forceReload: true
                      }).then(function (syncArgs) {
                          vm.page.menu.currentNode = syncArgs.node;
                      });
                      // clear $dirty state on form
                      setFormState('pristine');
                  }
              }
              function openInsertOverlay() {
                  vm.insertOverlay = {
                      view: 'insert',
                      allowedTypes: {
                          macro: true,
                          dictionary: true,
                          umbracoField: true
                      },
                      hideSubmitButton: true,
                      show: true,
                      submit: function (model) {
                          switch (model.insert.type) {
                          case 'macro':
                              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                              insert(macroObject.syntax);
                              break;
                          case 'dictionary':
                              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                              insert(code);
                              break;
                          case 'umbracoField':
                              insert(model.insert.umbracoField);
                              break;
                          }
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openMacroOverlay() {
                  vm.macroPickerOverlay = {
                      view: 'macropicker',
                      dialogData: {},
                      show: true,
                      title: 'Insert macro',
                      submit: function (model) {
                          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                          insert(macroObject.syntax);
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openPageFieldOverlay() {
                  vm.pageFieldOverlay = {
                      submitButtonLabel: 'Insert',
                      closeButtonlabel: 'Cancel',
                      view: 'insertfield',
                      show: true,
                      submit: function (model) {
                          insert(model.umbracoField);
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                      },
                      close: function (model) {
                          // close the dialog
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openDictionaryItemOverlay() {
                  vm.dictionaryItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'dictionary',
                      entityType: 'dictionary',
                      multiPicker: false,
                      show: true,
                      title: 'Insert dictionary item',
                      emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                      select: function (node) {
                          var code = templateHelper.getInsertDictionarySnippet(node.name);
                          insert(code);
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openQueryBuilderOverlay() {
                  vm.queryBuilderOverlay = {
                      view: 'querybuilder',
                      show: true,
                      title: 'Query for content',
                      submit: function (model) {
                          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                          insert(code);
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              /* Local functions */
              function init() {
                  //we need to load this somewhere, for now its here.
                  assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                  if ($routeParams.create) {
                      var snippet = 'Empty';
                      if ($routeParams.snippet) {
                          snippet = $routeParams.snippet;
                      }
                      codefileResource.getScaffold('partialViewMacros', $routeParams.id, snippet).then(function (partialViewMacro) {
                          if ($routeParams.name) {
                              partialViewMacro.name = $routeParams.name;
                          }
                          ready(partialViewMacro, false);
                      });
                  } else {
                      codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
                          ready(partialViewMacro, true);
                      });
                  }
              }
              function ready(partialViewMacro, syncTree) {
                  vm.page.loading = false;
                  vm.partialViewMacro = partialViewMacro;
                  //sync state
                  editorState.set(vm.partialViewMacro);
                  if (syncTree) {
                      navigationService.syncTree({
                          tree: 'partialViewMacros',
                          path: vm.partialViewMacro.path,
                          forceReload: true
                      }).then(function (syncArgs) {
                          vm.page.menu.currentNode = syncArgs.node;
                      });
                  }
                  // ace configuration
                  vm.aceOption = {
                      mode: 'razor',
                      theme: 'chrome',
                      showPrintMargin: false,
                      advanced: { fontSize: '14px' },
                      onLoad: function (_editor) {
                          vm.editor = _editor;
                          // initial cursor placement
                          // Keep cursor in name field if we are create a new template
                          // else set the cursor at the bottom of the code editor
                          if (!$routeParams.create) {
                              $timeout(function () {
                                  vm.editor.navigateFileEnd();
                                  vm.editor.focus();
                                  persistCurrentLocation();
                              });
                          }
                          //change on blur, focus
                          vm.editor.on('blur', persistCurrentLocation);
                          vm.editor.on('focus', persistCurrentLocation);
                          vm.editor.on('change', changeAceEditor);
                      }
                  };
              }
              function insert(str) {
                  vm.editor.focus();
                  vm.editor.moveCursorToPosition(vm.currentPosition);
                  vm.editor.insert(str);
                  // set form state to $dirty
                  setFormState('dirty');
              }
              function persistCurrentLocation() {
                  vm.currentPosition = vm.editor.getCursorPosition();
              }
              function changeAceEditor() {
                  setFormState('dirty');
              }
              function setFormState(state) {
                  // get the current form
                  var currentForm = angularHelper.getCurrentForm($scope);
                  // set state
                  if (state === 'dirty') {
                      currentForm.$setDirty();
                  } else if (state === 'pristine') {
                      currentForm.$setPristine();
                  }
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.EditController', partialViewMacrosEditController);
      }());
      (function () {
          'use strict';
          function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) {
              var vm = this;
              var node = $scope.dialogOptions.currentNode;
              var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
              vm.snippets = [];
              vm.showSnippets = false;
              vm.creatingFolder = false;
              vm.createFolderError = '';
              vm.folderName = '';
              vm.createPartialView = createPartialView;
              vm.showCreateFolder = showCreateFolder;
              vm.createFolder = createFolder;
              vm.showCreateFromSnippet = showCreateFromSnippet;
              function onInit() {
                  codefileResource.getSnippets('partialViews').then(function (snippets) {
                      vm.snippets = snippets;
                  });
              }
              function createPartialView(selectedSnippet) {
                  var snippet = null;
                  if (selectedSnippet && selectedSnippet.fileName) {
                      snippet = selectedSnippet.fileName;
                  }
                  $location.path('/settings/partialviews/edit/' + node.id).search('create', 'true').search('snippet', snippet);
                  navigationService.hideMenu();
              }
              function showCreateFolder() {
                  vm.creatingFolder = true;
              }
              function createFolder(form) {
                  if (formHelper.submitForm({
                          scope: $scope,
                          formCtrl: form,
                          statusMessage: localizeCreateFolder
                      })) {
                      codefileResource.createContainer('partialViews', node.id, vm.folderName).then(function (saved) {
                          navigationService.hideMenu();
                          navigationService.syncTree({
                              tree: 'partialViews',
                              path: saved.path,
                              forceReload: true,
                              activate: true
                          });
                          formHelper.resetForm({ scope: $scope });
                          var section = appState.getSectionState('currentSection');
                      }, function (err) {
                          vm.createFolderError = err;
                          formHelper.showNotifications(err.data);
                      });
                  }
              }
              function showCreateFromSnippet() {
                  vm.showSnippets = true;
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Editors.PartialViews.CreateController', PartialViewsCreateController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.PartialViews.DeleteController
   * @function
   *
   * @description
   * The controller for deleting partial views
   */
      function PartialViewsDeleteController($scope, codefileResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              codefileResource.deleteByPath('partialViews', $scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.PartialViews.DeleteController', PartialViewsDeleteController);
      (function () {
          'use strict';
          function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.page.loading = true;
              vm.partialView = {};
              //menu
              vm.page.menu = {};
              vm.page.menu.currentSection = appState.getSectionState('currentSection');
              vm.page.menu.currentNode = null;
              //Used to toggle the keyboard shortcut modal
              //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
              vm.showKeyboardShortcut = false;
              //Keyboard shortcuts for help dialog
              vm.page.keyboardShortcutsOverview = [];
              vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
              vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
              vm.page.keyboardShortcutsOverview.push(templateHelper.getPartialViewEditorShortcuts());
              // bind functions to view model
              vm.save = save;
              vm.openPageFieldOverlay = openPageFieldOverlay;
              vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
              vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
              vm.openMacroOverlay = openMacroOverlay;
              vm.openInsertOverlay = openInsertOverlay;
              /* Functions bound to view model */
              function save() {
                  vm.page.saveButtonState = 'busy';
                  vm.partialView.content = vm.editor.getValue();
                  contentEditingHelper.contentEditorPerformSave({
                      statusMessage: localizeSaving,
                      saveMethod: codefileResource.save,
                      scope: $scope,
                      content: vm.partialView,
                      //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews
                      // type when server side validation fails - as opposed to content where we are capable of saving the content
                      // item if server side validation fails
                      redirectOnFailure: false,
                      rebindCallback: function (orignal, saved) {
                      }
                  }).then(function (saved) {
                      localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                          localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                              notificationsService.success(headerValue, msgValue);
                          });
                      });
                      //check if the name changed, if so we need to redirect
                      if (vm.partialView.id !== saved.id) {
                          contentEditingHelper.redirectToRenamedContent(saved.id);
                      } else {
                          vm.page.saveButtonState = 'success';
                          vm.partialView = saved;
                          //sync state
                          editorState.set(vm.partialView);
                          // normal tree sync
                          navigationService.syncTree({
                              tree: 'partialViews',
                              path: vm.partialView.path,
                              forceReload: true
                          }).then(function (syncArgs) {
                              vm.page.menu.currentNode = syncArgs.node;
                          });
                          // clear $dirty state on form
                          setFormState('pristine');
                      }
                  }, function (err) {
                      vm.page.saveButtonState = 'error';
                      localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                          localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                              notificationsService.error(headerValue, msgValue);
                          });
                      });
                  });
              }
              function openInsertOverlay() {
                  vm.insertOverlay = {
                      view: 'insert',
                      allowedTypes: {
                          macro: true,
                          dictionary: true,
                          umbracoField: true
                      },
                      hideSubmitButton: true,
                      show: true,
                      submit: function (model) {
                          switch (model.insert.type) {
                          case 'macro':
                              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                              insert(macroObject.syntax);
                              break;
                          case 'dictionary':
                              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                              insert(code);
                              break;
                          case 'umbracoField':
                              insert(model.insert.umbracoField);
                              break;
                          }
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openMacroOverlay() {
                  vm.macroPickerOverlay = {
                      view: 'macropicker',
                      dialogData: {},
                      show: true,
                      title: 'Insert macro',
                      submit: function (model) {
                          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                          insert(macroObject.syntax);
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openPageFieldOverlay() {
                  vm.pageFieldOverlay = {
                      submitButtonLabel: 'Insert',
                      closeButtonlabel: 'Cancel',
                      view: 'insertfield',
                      show: true,
                      submit: function (model) {
                          insert(model.umbracoField);
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                      },
                      close: function (model) {
                          // close the dialog
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openDictionaryItemOverlay() {
                  vm.dictionaryItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'dictionary',
                      entityType: 'dictionary',
                      multiPicker: false,
                      show: true,
                      title: 'Insert dictionary item',
                      emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                      select: function (node) {
                          var code = templateHelper.getInsertDictionarySnippet(node.name);
                          insert(code);
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openQueryBuilderOverlay() {
                  vm.queryBuilderOverlay = {
                      view: 'querybuilder',
                      show: true,
                      title: 'Query for content',
                      submit: function (model) {
                          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                          insert(code);
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              /* Local functions */
              function init() {
                  //we need to load this somewhere, for now its here.
                  assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                  if ($routeParams.create) {
                      var snippet = 'Empty';
                      if ($routeParams.snippet) {
                          snippet = $routeParams.snippet;
                      }
                      codefileResource.getScaffold('partialViews', $routeParams.id, snippet).then(function (partialView) {
                          ready(partialView, false);
                      });
                  } else {
                      codefileResource.getByPath('partialViews', $routeParams.id).then(function (partialView) {
                          ready(partialView, true);
                      });
                  }
              }
              function ready(partialView, syncTree) {
                  vm.page.loading = false;
                  vm.partialView = partialView;
                  //sync state
                  editorState.set(vm.partialView);
                  if (syncTree) {
                      navigationService.syncTree({
                          tree: 'partialViews',
                          path: vm.partialView.path,
                          forceReload: true
                      }).then(function (syncArgs) {
                          vm.page.menu.currentNode = syncArgs.node;
                      });
                  }
                  // ace configuration
                  vm.aceOption = {
                      mode: 'razor',
                      theme: 'chrome',
                      showPrintMargin: false,
                      advanced: { fontSize: '14px' },
                      onLoad: function (_editor) {
                          vm.editor = _editor;
                          //Update the auto-complete method to use ctrl+alt+space
                          _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                          //Unassigns the keybinding (That was previously auto-complete)
                          //As conflicts with our own tree search shortcut
                          _editor.commands.bindKey('ctrl-space', null);
                          // Assign new keybinding
                          _editor.commands.addCommands([
                              //Disable (alt+shift+K)
                              //Conflicts with our own show shortcuts dialog - this overrides it
                              {
                                  name: 'unSelectOrFindPrevious',
                                  bindKey: 'Alt-Shift-K',
                                  exec: function () {
                                      //Toggle the show keyboard shortcuts overlay
                                      $scope.$apply(function () {
                                          vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertUmbracoValue',
                                  bindKey: 'Alt-Shift-V',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openPageFieldOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertDictionary',
                                  bindKey: 'Alt-Shift-D',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openDictionaryItemOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertUmbracoMacro',
                                  bindKey: 'Alt-Shift-M',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openMacroOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertQuery',
                                  bindKey: 'Alt-Shift-Q',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openQueryBuilderOverlay();
                                      });
                                  },
                                  readOnly: true
                              }
                          ]);
                          // initial cursor placement
                          // Keep cursor in name field if we are create a new template
                          // else set the cursor at the bottom of the code editor
                          if (!$routeParams.create) {
                              $timeout(function () {
                                  vm.editor.navigateFileEnd();
                                  vm.editor.focus();
                                  persistCurrentLocation();
                              });
                          }
                          //change on blur, focus
                          vm.editor.on('blur', persistCurrentLocation);
                          vm.editor.on('focus', persistCurrentLocation);
                          vm.editor.on('change', changeAceEditor);
                      }
                  };
              }
              function insert(str) {
                  vm.editor.focus();
                  vm.editor.moveCursorToPosition(vm.currentPosition);
                  vm.editor.insert(str);
                  // set form state to $dirty
                  setFormState('dirty');
              }
              function persistCurrentLocation() {
                  vm.currentPosition = vm.editor.getCursorPosition();
              }
              function changeAceEditor() {
                  setFormState('dirty');
              }
              function setFormState(state) {
                  // get the current form
                  var currentForm = angularHelper.getCurrentForm($scope);
                  // set state
                  if (state === 'dirty') {
                      currentForm.$setDirty();
                  } else if (state === 'pristine') {
                      currentForm.$setPristine();
                  }
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.PartialViews.EditController', PartialViewsEditController);
      }());
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.BooleanController', function ($scope) {
          $scope.htmlId = 'bool-' + String.CreateGuid();
      });
      function imageFilePickerController($scope) {
          $scope.add = function () {
              $scope.mediaPickerOverlay = {
                  view: 'mediapicker',
                  disableFolderSelect: true,
                  onlyImages: true,
                  show: true,
                  submit: function (model) {
                      $scope.model.value = model.selectedImages[0].image;
                      $scope.mediaPickerOverlay.show = false;
                      $scope.mediaPickerOverlay = null;
                  },
                  close: function () {
                      $scope.mediaPickerOverlay.show = false;
                      $scope.mediaPickerOverlay = null;
                  }
              };
          };
          $scope.remove = function () {
              $scope.model.value = null;
          };
      }
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.ImageFilePickerController', imageFilePickerController);
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) {
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          $scope.renderModel = [];
          $scope.allowRemove = true;
          $scope.allowEdit = true;
          $scope.sortable = false;
          var dialogOptions = {
              multiPicker: false,
              entityType: 'Media',
              section: 'media',
              treeAlias: 'media',
              idType: 'int'
          };
          //combine the dialogOptions with any values returned from the server
          if ($scope.model.config) {
              angular.extend(dialogOptions, $scope.model.config);
          }
          $scope.openContentPicker = function () {
              $scope.contentPickerOverlay = dialogOptions;
              $scope.contentPickerOverlay.view = 'treePicker';
              $scope.contentPickerOverlay.show = true;
              $scope.contentPickerOverlay.submit = function (model) {
                  if ($scope.contentPickerOverlay.multiPicker) {
                      _.each(model.selection, function (item, i) {
                          $scope.add(item);
                      });
                  } else {
                      $scope.clear();
                      $scope.add(model.selection[0]);
                  }
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $scope.contentPickerOverlay.close = function (oldModel) {
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
          };
          $scope.clear = function () {
              $scope.renderModel = [];
          };
          $scope.add = function (item) {
              var itemId = dialogOptions.idType === 'udi' ? item.udi : item.id;
              var currIds = _.map($scope.renderModel, function (i) {
                  return dialogOptions.idType === 'udi' ? i.udi : i.id;
              });
              if (currIds.indexOf(itemId) < 0) {
                  item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                  $scope.renderModel.push({
                      name: item.name,
                      id: item.id,
                      icon: item.icon,
                      udi: item.udi
                  });
                  // store the index of the new item in the renderModel collection so we can find it again
                  var itemRenderIndex = $scope.renderModel.length - 1;
                  // get and update the path for the picked node
                  entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                      $scope.renderModel[itemRenderIndex].path = data;
                  });
              }
          };
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var currIds = _.map($scope.renderModel, function (i) {
                  return dialogOptions.idType === 'udi' ? i.udi : i.id;
              });
              $scope.model.value = trim(currIds.join(), ',');
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          //load media data
          var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
          if (modelIds.length > 0) {
              entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
                  _.each(data, function (item, i) {
                      item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                      $scope.renderModel.push({
                          name: item.name,
                          id: item.id,
                          icon: item.icon,
                          udi: item.udi
                      });
                      // store the index of the new item in the renderModel collection so we can find it again
                      var itemRenderIndex = $scope.renderModel.length - 1;
                      // get and update the path for the picked node
                      entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                          $scope.renderModel[itemRenderIndex].path = data;
                      });
                  });
              });
          }
      }
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.MediaPickerController', mediaPickerController);
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiValuesController', function ($scope, $timeout) {
          //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
          $scope.newItem = '';
          $scope.hasError = false;
          $scope.focusOnNew = false;
          if (!angular.isArray($scope.model.value)) {
              //make an array from the dictionary
              var items = [];
              for (var i in $scope.model.value) {
                  items.push({
                      value: $scope.model.value[i].value,
                      sortOrder: $scope.model.value[i].sortOrder,
                      id: i
                  });
              }
              //ensure the items are sorted by the provided sort order
              items.sort(function (a, b) {
                  return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
              });
              //now make the editor model the array
              $scope.model.value = items;
          }
          $scope.remove = function (item, evt) {
              evt.preventDefault();
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.value === item.value;
              });
          };
          $scope.add = function (evt) {
              evt.preventDefault();
              if ($scope.newItem) {
                  if (!_.contains($scope.model.value, $scope.newItem)) {
                      $scope.model.value.push({ value: $scope.newItem });
                      $scope.newItem = '';
                      $scope.hasError = false;
                      $scope.focusOnNew = true;
                      return;
                  }
              }
              //there was an error, do the highlight (will be set back by the directive)
              $scope.hasError = true;
          };
          $scope.sortableOptions = {
              axis: 'y',
              containment: 'parent',
              cursor: 'move',
              items: '> div.control-group',
              tolerance: 'pointer',
              update: function (e, ui) {
                  // Get the new and old index for the moved element (using the text as the identifier, so 
                  // we'd have a problem if two prevalues were the same, but that would be unlikely)
                  var newIndex = ui.item.index();
                  var movedPrevalueText = $('input[type="text"]', ui.item).val();
                  var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
                  // Move the element in the model
                  if (originalIndex > -1) {
                      var movedElement = $scope.model.value[originalIndex];
                      $scope.model.value.splice(originalIndex, 1);
                      $scope.model.value.splice(newIndex, 0, movedElement);
                  }
              }
          };
          $scope.createNew = function (event) {
              if (event.keyCode == 13) {
                  $scope.add(event);
              }
          };
          function getElementIndexByPrevalueText(value) {
              for (var i = 0; i < $scope.model.value.length; i++) {
                  if ($scope.model.value[i].value === value) {
                      return i;
                  }
              }
              return -1;
          }
      });
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreePickerController', function ($scope, dialogService, entityResource, $log, iconHelper) {
          $scope.renderModel = [];
          $scope.ids = [];
          $scope.allowRemove = true;
          $scope.allowEdit = true;
          $scope.sortable = false;
          var config = {
              multiPicker: false,
              entityType: 'Document',
              type: 'content',
              treeAlias: 'content',
              idType: 'int'
          };
          //combine the config with any values returned from the server
          if ($scope.model.config) {
              angular.extend(config, $scope.model.config);
          }
          if ($scope.model.value) {
              $scope.ids = $scope.model.value.split(',');
              entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
                  _.each(data, function (item, i) {
                      item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                      $scope.renderModel.push({
                          name: item.name,
                          id: item.id,
                          icon: item.icon,
                          udi: item.udi
                      });
                      // store the index of the new item in the renderModel collection so we can find it again
                      var itemRenderIndex = $scope.renderModel.length - 1;
                      // get and update the path for the picked node
                      entityResource.getUrl(item.id, config.entityType).then(function (data) {
                          $scope.renderModel[itemRenderIndex].path = data;
                      });
                  });
              });
          }
          $scope.openContentPicker = function () {
              $scope.treePickerOverlay = config;
              $scope.treePickerOverlay.section = config.type;
              $scope.treePickerOverlay.view = 'treePicker';
              $scope.treePickerOverlay.show = true;
              $scope.treePickerOverlay.submit = function (model) {
                  if (config.multiPicker) {
                      populate(model.selection);
                  } else {
                      populate(model.selection[0]);
                  }
                  $scope.treePickerOverlay.show = false;
                  $scope.treePickerOverlay = null;
              };
              $scope.treePickerOverlay.close = function (oldModel) {
                  $scope.treePickerOverlay.show = false;
                  $scope.treePickerOverlay = null;
              };
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
              $scope.ids.splice(index, 1);
              $scope.model.value = trim($scope.ids.join(), ',');
          };
          $scope.clear = function () {
              $scope.model.value = '';
              $scope.renderModel = [];
              $scope.ids = [];
          };
          $scope.add = function (item) {
              var itemId = config.idType === 'udi' ? item.udi : item.id;
              if ($scope.ids.indexOf(itemId) < 0) {
                  item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                  $scope.ids.push(itemId);
                  $scope.renderModel.push({
                      name: item.name,
                      id: item.id,
                      icon: item.icon,
                      udi: item.udi
                  });
                  $scope.model.value = trim($scope.ids.join(), ',');
                  // store the index of the new item in the renderModel collection so we can find it again
                  var itemRenderIndex = $scope.renderModel.length - 1;
                  // get and update the path for the picked node
                  entityResource.getUrl(item.id, config.entityType).then(function (data) {
                      $scope.renderModel[itemRenderIndex].path = data;
                  });
              }
          };
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              $scope.model.value = trim($scope.ids.join(), ',');
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          function populate(data) {
              if (angular.isArray(data)) {
                  _.each(data, function (item, i) {
                      $scope.add(item);
                  });
              } else {
                  $scope.clear();
                  $scope.add(data);
              }
          }
      });
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreeSourceController', function ($scope, dialogService, entityResource, $log, iconHelper) {
          if (!$scope.model) {
              $scope.model = {};
          }
          if (!$scope.model.value) {
              $scope.model.value = { type: 'content' };
          }
          if (!$scope.model.config) {
              $scope.model.config = { idType: 'int' };
          }
          if ($scope.model.value.id && $scope.model.value.type !== 'member') {
              var ent = 'Document';
              if ($scope.model.value.type === 'media') {
                  ent = 'Media';
              }
              entityResource.getById($scope.model.value.id, ent).then(function (item) {
                  item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                  $scope.node = item;
              });
          }
          $scope.openContentPicker = function () {
              $scope.treePickerOverlay = {
                  view: 'treepicker',
                  idType: $scope.model.config.idType,
                  section: $scope.model.value.type,
                  treeAlias: $scope.model.value.type,
                  multiPicker: false,
                  show: true,
                  submit: function (model) {
                      var item = model.selection[0];
                      populate(item);
                      $scope.treePickerOverlay.show = false;
                      $scope.treePickerOverlay = null;
                  }
              };
          };
          $scope.clear = function () {
              $scope.model.value.id = undefined;
              $scope.node = undefined;
              $scope.model.value.query = undefined;
          };
          //we always need to ensure we dont submit anything broken
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              if ($scope.model.value.type === 'member') {
                  $scope.model.value.id = -1;
                  $scope.model.value.query = '';
              }
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          function populate(item) {
              $scope.clear();
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.node = item;
              $scope.model.value.id = $scope.model.config.idType === 'udi' ? item.udi : item.id;
          }
      });
      function booleanEditorController($scope, $rootScope, assetsService) {
          function setupViewModel() {
              $scope.renderModel = { value: false };
              if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === '1' && $scope.model && !$scope.model.value) {
                  $scope.renderModel.value = true;
              }
              if ($scope.model && $scope.model.value && ($scope.model.value.toString() === '1' || angular.lowercase($scope.model.value) === 'true')) {
                  $scope.renderModel.value = true;
              }
          }
          setupViewModel();
          $scope.$watch('renderModel.value', function (newVal) {
              $scope.model.value = newVal === true ? '1' : '0';
          });
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              setupViewModel();
          };
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.BooleanController', booleanEditorController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ChangePasswordController', function ($scope, $routeParams) {
          $scope.isNew = $routeParams.create;
          function resetModel() {
              //the model config will contain an object, if it does not we'll create defaults
              //NOTE: We will not support doing the password regex on the client side because the regex on the server side
              //based on the membership provider cannot always be ported to js from .net directly.        
              /*
        {
            hasPassword: true/false,
            requiresQuestionAnswer: true/false,
            enableReset: true/false,
            enablePasswordRetrieval: true/false,
            minPasswordLength: 10
        }
        */
              //set defaults if they are not available
              if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
                  $scope.model.config.disableToggle = false;
              }
              if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
                  $scope.model.config.hasPassword = false;
              }
              if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
                  $scope.model.config.enablePasswordRetrieval = true;
              }
              if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
                  $scope.model.config.requiresQuestionAnswer = false;
              }
              if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
                  $scope.model.config.enableReset = true;
              }
              if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
                  $scope.model.config.minPasswordLength = 0;
              }
              //set the model defaults
              if (!angular.isObject($scope.model.value)) {
                  //if it's not an object then just create a new one
                  $scope.model.value = {
                      newPassword: null,
                      oldPassword: null,
                      reset: null,
                      answer: null
                  };
              } else {
                  //just reset the values
                  if (!$scope.isNew) {
                      //if it is new, then leave the generated pass displayed
                      $scope.model.value.newPassword = null;
                      $scope.model.value.oldPassword = null;
                  }
                  $scope.model.value.reset = null;
                  $scope.model.value.answer = null;
              }
          }
          resetModel();
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.CheckboxListController', function ($scope) {
          if (angular.isObject($scope.model.config.items)) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  newItems.push({
                      id: keys[i],
                      sortOrder: vals[i].sortOrder,
                      value: vals[i].value
                  });
              }
              //ensure the items are sorted by the provided sort order
              newItems.sort(function (a, b) {
                  return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
              });
              //re-assign
              $scope.model.config.items = newItems;
          }
          function setupViewModel() {
              $scope.selectedItems = [];
              //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
              // to "" gets selected by default
              if ($scope.model.value === null || $scope.model.value === undefined) {
                  $scope.model.value = [];
              }
              for (var i = 0; i < $scope.model.config.items.length; i++) {
                  var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id);
                  $scope.selectedItems.push({
                      checked: isChecked,
                      key: $scope.model.config.items[i].id,
                      val: $scope.model.config.items[i].value
                  });
              }
          }
          setupViewModel();
          //update the model when the items checked changes
          $scope.$watch('selectedItems', function (newVal, oldVal) {
              $scope.model.value = [];
              for (var x = 0; x < $scope.selectedItems.length; x++) {
                  if ($scope.selectedItems[x].checked) {
                      $scope.model.value.push($scope.selectedItems[x].key);
                  }
              }
          }, true);
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              setupViewModel();
          };
      });
      function ColorPickerController($scope) {
          $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
          if ($scope.isConfigured) {
              for (var key in $scope.model.config.items) {
                  if (!$scope.model.config.items[key].hasOwnProperty('value'))
                      $scope.model.config.items[key] = {
                          value: $scope.model.config.items[key],
                          label: $scope.model.config.items[key]
                      };
              }
              $scope.model.useLabel = isTrue($scope.model.config.useLabel);
              initActiveColor();
          }
          $scope.toggleItem = function (color) {
              var currentColor = $scope.model.value && $scope.model.value.hasOwnProperty('value') ? $scope.model.value.value : $scope.model.value;
              var newColor;
              if (currentColor === color.value) {
                  // deselect
                  $scope.model.value = $scope.model.useLabel ? {
                      value: '',
                      label: ''
                  } : '';
                  newColor = '';
              } else {
                  // select
                  $scope.model.value = $scope.model.useLabel ? {
                      value: color.value,
                      label: color.label
                  } : color.value;
                  newColor = color.value;
              }
              // this is required to re-validate
              $scope.propertyForm.modelValue.$setViewValue(newColor);
          };
          // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
          $scope.validateMandatory = function () {
              var isValid = !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value != '' && (!$scope.model.value.hasOwnProperty('value') || $scope.model.value.value !== '');
              return {
                  isValid: isValid,
                  errorMsg: 'Value cannot be empty',
                  errorKey: 'required'
              };
          };
          $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
          // A color is active if it matches the value and label of the model.
          // If the model doesn't store the label, ignore the label during the comparison.
          $scope.isActiveColor = function (color) {
              // no value
              if (!$scope.model.value)
                  return false;
              // Complex color (value and label)?
              if (!$scope.model.value.hasOwnProperty('value'))
                  return $scope.model.value === color.value;
              return $scope.model.value.value === color.value && $scope.model.value.label === color.label;
          };
          // Finds the color best matching the model's color,
          // and sets the model color to that one. This is useful when
          // either the value or label was changed on the data type.
          function initActiveColor() {
              // no value
              if (!$scope.model.value)
                  return;
              // Complex color (value and label)?
              if (!$scope.model.value.hasOwnProperty('value'))
                  return;
              var modelColor = $scope.model.value.value;
              var modelLabel = $scope.model.value.label;
              // Check for a full match or partial match.
              var foundItem = null;
              // Look for a fully matching color.
              for (var key in $scope.model.config.items) {
                  var item = $scope.model.config.items[key];
                  if (item.value == modelColor && item.label == modelLabel) {
                      foundItem = item;
                      break;
                  }
              }
              // Look for a color with a matching value.
              if (!foundItem) {
                  for (var key in $scope.model.config.items) {
                      var item = $scope.model.config.items[key];
                      if (item.value == modelColor) {
                          foundItem = item;
                          break;
                      }
                  }
              }
              // Look for a color with a matching label.
              if (!foundItem) {
                  for (var key in $scope.model.config.items) {
                      var item = $scope.model.config.items[key];
                      if (item.label == modelLabel) {
                          foundItem = item;
                          break;
                      }
                  }
              }
              // If a match was found, set it as the active color.
              if (foundItem) {
                  $scope.model.value.value = foundItem.value;
                  $scope.model.value.label = foundItem.label;
              }
          }
          // figures out if a value is trueish enough
          function isTrue(bool) {
              return !!bool && bool !== '0' && angular.lowercase(bool) !== 'false';
          }
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ColorPickerController', ColorPickerController);
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiColorPickerController', function ($scope, $timeout, assetsService, angularHelper, $element) {
          //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
          var defaultColor = '000000';
          var defaultLabel = null;
          $scope.newColor = defaultColor;
          $scope.newLavel = defaultLabel;
          $scope.hasError = false;
          assetsService.load([//"lib/spectrum/tinycolor.js",
              'lib/spectrum/spectrum.js'], $scope).then(function () {
              var elem = $element.find('input[name=\'newColor\']');
              elem.spectrum({
                  color: null,
                  showInitial: false,
                  chooseText: 'choose',
                  // TODO: These can be localised
                  cancelText: 'cancel',
                  // TODO: These can be localised
                  preferredFormat: 'hex',
                  showInput: true,
                  clickoutFiresChange: true,
                  hide: function (color) {
                      //show the add butotn
                      $element.find('.btn.add').show();
                  },
                  change: function (color) {
                      angularHelper.safeApply($scope, function () {
                          $scope.newColor = color.toHexString().trimStart('#');    // #ff0000
                      });
                  },
                  show: function () {
                      //hide the add butotn
                      $element.find('.btn.add').hide();
                  }
              });
          });
          if (!angular.isArray($scope.model.value)) {
              //make an array from the dictionary
              var items = [];
              for (var i in $scope.model.value) {
                  var oldValue = $scope.model.value[i];
                  if (oldValue.hasOwnProperty('value')) {
                      items.push({
                          value: oldValue.value,
                          label: oldValue.label,
                          id: i
                      });
                  } else {
                      items.push({
                          value: oldValue,
                          label: oldValue,
                          id: i
                      });
                  }
              }
              //now make the editor model the array
              $scope.model.value = items;
          }
          // ensure labels
          for (var i = 0; i < $scope.model.value.length; i++) {
              var item = $scope.model.value[i];
              item.label = item.hasOwnProperty('label') ? item.label : item.value;
          }
          function validLabel(label) {
              return label !== null && typeof label !== 'undefined' && label !== '' && label.length && label.length > 0;
          }
          $scope.remove = function (item, evt) {
              evt.preventDefault();
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.value === item.value && x.label === item.label;
              });
          };
          $scope.add = function (evt) {
              evt.preventDefault();
              if ($scope.newColor) {
                  var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor;
                  var exists = _.find($scope.model.value, function (item) {
                      return item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase();
                  });
                  if (!exists) {
                      $scope.model.value.push({
                          value: $scope.newColor,
                          label: newLabel
                      });
                      $scope.hasError = false;
                      return;
                  }
                  //there was an error, do the highlight (will be set back by the directive)
                  $scope.hasError = true;
              }
          };
          //load the separate css for the editor to avoid it blocking our js loading
          assetsService.loadCss('lib/spectrum/spectrum.css', $scope);
      });
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) {
          var unsubscribe;
          function subscribe() {
              unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                  var currIds = _.map($scope.renderModel, function (i) {
                      return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                  });
                  $scope.model.value = trim(currIds.join(), ',');
              });
          }
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          function startWatch() {
              //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
              // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
              // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
              // In their source code there is no event so we need to just subscribe to our model changes here.
              //This also makes it easier to manage models, we update one and the rest will just work.
              $scope.$watch(function () {
                  //return the joined Ids as a string to watch
                  return _.map($scope.renderModel, function (i) {
                      return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                  }).join();
              }, function (newVal) {
                  var currIds = _.map($scope.renderModel, function (i) {
                      return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                  });
                  $scope.model.value = trim(currIds.join(), ',');
                  //Validate!
                  if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
                      $scope.contentPickerForm.minCount.$setValidity('minCount', false);
                  } else {
                      $scope.contentPickerForm.minCount.$setValidity('minCount', true);
                  }
                  if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
                      $scope.contentPickerForm.maxCount.$setValidity('maxCount', false);
                  } else {
                      $scope.contentPickerForm.maxCount.$setValidity('maxCount', true);
                  }
                  setSortingState($scope.renderModel);
              });
          }
          $scope.renderModel = [];
          $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
          //the default pre-values
          var defaultConfig = {
              multiPicker: false,
              showOpenButton: false,
              showEditButton: false,
              showPathOnHover: false,
              maxNumber: 1,
              minNumber: 0,
              startNode: {
                  query: '',
                  type: 'content',
                  id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1    // get start node for simple Content Picker
              }
          };
          // sortable options
          $scope.sortableOptions = {
              distance: 10,
              tolerance: 'pointer',
              scroll: true,
              zIndex: 6000
          };
          if ($scope.model.config) {
              //merge the server config on top of the default config, then set the server config to use the result
              $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
          }
          //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
          $scope.model.config.multiPicker = $scope.model.config.multiPicker === '1' ? true : false;
          $scope.model.config.showOpenButton = $scope.model.config.showOpenButton === '1' ? true : false;
          $scope.model.config.showEditButton = $scope.model.config.showEditButton === '1' ? true : false;
          $scope.model.config.showPathOnHover = $scope.model.config.showPathOnHover === '1' ? true : false;
          var entityType = $scope.model.config.startNode.type === 'member' ? 'Member' : $scope.model.config.startNode.type === 'media' ? 'Media' : 'Document';
          $scope.allowOpenButton = entityType === 'Document';
          $scope.allowEditButton = entityType === 'Document';
          $scope.allowRemoveButton = true;
          //the dialog options for the picker
          var dialogOptions = {
              multiPicker: $scope.model.config.multiPicker,
              entityType: entityType,
              filterCssClass: 'not-allowed not-published',
              startNodeId: null,
              callback: function (data) {
                  if (angular.isArray(data)) {
                      _.each(data, function (item, i) {
                          $scope.add(item);
                      });
                  } else {
                      $scope.clear();
                      $scope.add(data);
                  }
                  angularHelper.getCurrentForm($scope).$setDirty();
              },
              treeAlias: $scope.model.config.startNode.type,
              section: $scope.model.config.startNode.type,
              idType: 'int'
          };
          //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
          // pre-value config on to the dialog options
          angular.extend(dialogOptions, $scope.model.config);
          //We need to manually handle the filter for members here since the tree displayed is different and only contains
          // searchable list views
          if (entityType === 'Member') {
              //first change the not allowed filter css class
              dialogOptions.filterCssClass = 'not-allowed';
              var currFilter = dialogOptions.filter;
              //now change the filter to be a method
              dialogOptions.filter = function (i) {
                  //filter out the list view nodes
                  if (i.metaData.isContainer) {
                      return true;
                  }
                  if (!currFilter) {
                      return false;
                  }
                  //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, 
                  // but not much we can do about that since members require special filtering.
                  var filterItem = currFilter.toLowerCase().split(',');
                  var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
                  if (!currFilter.startsWith('!') && !found || currFilter.startsWith('!') && found) {
                      return true;
                  }
                  return false;
              };
          }
          if ($routeParams.section === 'settings' && $routeParams.tree === 'documentTypes') {
              //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query
              dialogOptions.startNodeId = -1;
          } else if ($scope.model.config.startNode.query) {
              //if we have a query for the startnode, we will use that.
              var rootId = $routeParams.id;
              entityResource.getByQuery($scope.model.config.startNode.query, rootId, 'Document').then(function (ent) {
                  dialogOptions.startNodeId = $scope.model.config.idType === 'udi' ? ent.udi : ent.id;
              });
          } else {
              dialogOptions.startNodeId = $scope.model.config.startNode.id;
          }
          //dialog
          $scope.openContentPicker = function () {
              $scope.contentPickerOverlay = dialogOptions;
              $scope.contentPickerOverlay.view = 'treepicker';
              $scope.contentPickerOverlay.show = true;
              $scope.contentPickerOverlay.submit = function (model) {
                  if (angular.isArray(model.selection)) {
                      _.each(model.selection, function (item, i) {
                          $scope.add(item);
                      });
                  }
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $scope.contentPickerOverlay.close = function (oldModel) {
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
              angularHelper.getCurrentForm($scope).$setDirty();
          };
          $scope.showNode = function (index) {
              var item = $scope.renderModel[index];
              var id = item.id;
              var section = $scope.model.config.startNode.type.toLowerCase();
              entityResource.getPath(id, entityType).then(function (path) {
                  navigationService.changeSection(section);
                  navigationService.showTree(section, {
                      tree: section,
                      path: path,
                      forceReload: false,
                      activate: true
                  });
                  var routePath = section + '/' + section + '/edit/' + id.toString();
                  $location.path(routePath).search('');
              });
          };
          $scope.add = function (item) {
              var currIds = _.map($scope.renderModel, function (i) {
                  return $scope.model.config.idType === 'udi' ? i.udi : i.id;
              });
              var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
              if (currIds.indexOf(itemId) < 0) {
                  setEntityUrl(item);
              }
          };
          $scope.clear = function () {
              $scope.renderModel = [];
          };
          $scope.openMiniEditor = function (node) {
              miniEditorHelper.launchMiniEditor(node).then(function (updatedNode) {
                  // update the node
                  node.name = updatedNode.name;
                  node.published = updatedNode.hasPublishedVersion;
                  if (entityType !== 'Member') {
                      entityResource.getUrl(updatedNode.id, entityType).then(function (data) {
                          node.url = data;
                      });
                  }
              });
          };
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              if (unsubscribe) {
                  unsubscribe();
              }
          });
          var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
          //load current data if anything selected
          if (modelIds.length > 0) {
              entityResource.getByIds(modelIds, entityType).then(function (data) {
                  _.each(modelIds, function (id, i) {
                      var entity = _.find(data, function (d) {
                          return $scope.model.config.idType === 'udi' ? d.udi == id : d.id == id;
                      });
                      if (entity) {
                          setEntityUrl(entity);
                      }
                  });
                  //everything is loaded, start the watch on the model
                  startWatch();
                  subscribe();
              });
          } else {
              //everything is loaded, start the watch on the model
              startWatch();
              subscribe();
          }
          function setEntityUrl(entity) {
              // get url for content and media items
              if (entityType !== 'Member') {
                  entityResource.getUrl(entity.id, entityType).then(function (data) {
                      // update url                
                      angular.forEach($scope.renderModel, function (item) {
                          if (item.id === entity.id) {
                              if (entity.trashed) {
                                  item.url = localizationService.dictionary.general_recycleBin;
                              } else {
                                  item.url = data;
                              }
                          }
                      });
                  });
              }
              // add the selected item to the renderModel
              // if it needs to show a url the item will get 
              // updated when the url comes back from server
              addSelectedItem(entity);
          }
          function addSelectedItem(item) {
              // set icon
              if (item.icon) {
                  item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              }
              // set default icon
              if (!item.icon) {
                  switch (entityType) {
                  case 'Document':
                      item.icon = 'icon-document';
                      break;
                  case 'Media':
                      item.icon = 'icon-picture';
                      break;
                  case 'Member':
                      item.icon = 'icon-user';
                      break;
                  }
              }
              $scope.renderModel.push({
                  'name': item.name,
                  'id': item.id,
                  'udi': item.udi,
                  'icon': item.icon,
                  'path': item.path,
                  'url': item.url,
                  'trashed': item.trashed,
                  'published': item.metaData && item.metaData.IsPublished === false && entityType === 'Document' ? false : true    // only content supports published/unpublished content so we set everything else to published so the UI looks correct 
              });
          }
          function setSortingState(items) {
              // disable sorting if the list only consist of one item
              if (items.length > 1) {
                  $scope.sortableOptions.disabled = false;
              } else {
                  $scope.sortableOptions.disabled = true;
              }
          }
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ContentPickerController', contentPickerController);
      function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
          //setup the default config
          var config = {
              pickDate: true,
              pickTime: true,
              useSeconds: true,
              format: 'YYYY-MM-DD HH:mm:ss',
              icons: {
                  time: 'icon-time',
                  date: 'icon-calendar',
                  up: 'icon-chevron-up',
                  down: 'icon-chevron-down'
              }
          };
          //map the user config
          $scope.model.config = angular.extend(config, $scope.model.config);
          //ensure the format doesn't get overwritten with an empty string
          if ($scope.model.config.format === '' || $scope.model.config.format === undefined || $scope.model.config.format === null) {
              $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;
          //hide picker if clicking on the document 
          $scope.hidePicker = function () {
              //$element.find("div:first").datetimepicker("hide");
              // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
              var dtp = $element.find('div:first');
              if (dtp && dtp.datetimepicker) {
                  dtp.datetimepicker('hide');
              }
          };
          $(document).bind('click', $scope.hidePicker);
          //handles the date changing via the api
          function applyDate(e) {
              angularHelper.safeApply($scope, function () {
                  // when a date is changed, update the model
                  if (e.date && e.date.isValid()) {
                      $scope.datePickerForm.datepicker.$setValidity('pickerError', true);
                      $scope.hasDatetimePickerValue = true;
                      $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 () {
              $scope.hasDatetimePickerValue = false;
              $scope.datetimePickerValue = null;
              $scope.model.value = null;
              $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) {
              assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css', $scope).then(function () {
                  var filesToLoad = ['lib/datetimepicker/bootstrap-datetimepicker.js'];
                  $scope.model.config.language = user.locale;
                  assetsService.load(filesToLoad, $scope).then(function () {
                      //The Datepicker js and css files are available and all components are ready to use.
                      // Get the id of the datepicker button that was clicked
                      var pickerId = $scope.model.alias;
                      var element = $element.find('div:first');
                      // Open the datepicker and add a changeDate eventlistener
                      element.datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config)).on('dp.change', applyDate).on('dp.error', function (a, b, c) {
                          $scope.hasDatetimePickerValue = false;
                          $scope.datePickerForm.datepicker.$setValidity('pickerError', false);
                      });
                      if ($scope.hasDatetimePickerValue) {
                          var dateVal;
                          //check if we are supposed to offset the time
                          if ($scope.model.value && $scope.model.config.offsetTime === '1' && $scope.serverTimeNeedsOffsetting) {
                              //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);
                      }
                      element.find('input').bind('blur', function () {
                          //we need to force an apply here
                          $scope.$apply();
                      });
                      //Ensure to remove the event handler when this instance is destroyted
                      $scope.$on('$destroy', function () {
                          element.find('input').unbind('blur');
                          element.datetimepicker('destroy');
                      });
                      var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                          setModelValue();
                      });
                      //unbind doc click event!
                      $scope.$on('$destroy', function () {
                          unsubscribe();
                      });
                  });
              });
          });
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              setModelValue();
          });
          //unbind doc click event!
          $scope.$on('$destroy', function () {
              $(document).unbind('click', $scope.hidePicker);
              unsubscribe();
          });
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.DatepickerController', dateTimePickerController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownController', function ($scope) {
          //setup the default config
          var config = {
              items: [],
              multiple: false
          };
          //map the user config
          angular.extend(config, $scope.model.config);
          //map back to the model
          $scope.model.config = config;
          function convertArrayToDictionaryArray(model) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              for (var i = 0; i < model.length; i++) {
                  newItems.push({
                      id: model[i],
                      sortOrder: 0,
                      value: model[i]
                  });
              }
              return newItems;
          }
          function convertObjectToDictionaryArray(model) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  var label = vals[i].value ? vals[i].value : vals[i];
                  newItems.push({
                      id: keys[i],
                      sortOrder: vals[i].sortOrder,
                      value: label
                  });
              }
              return newItems;
          }
          if (angular.isArray($scope.model.config.items)) {
              //PP: I dont think this will happen, but we have tests that expect it to happen..
              //if array is simple values, convert to array of objects
              if (!angular.isObject($scope.model.config.items[0])) {
                  $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
              }
          } else if (angular.isObject($scope.model.config.items)) {
              $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
          } else {
              throw 'The items property must be either an array or a dictionary';
          }
          //sort the values
          $scope.model.config.items.sort(function (a, b) {
              return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
          });
          //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
          // to "" gets selected by default
          if ($scope.model.value === null || $scope.model.value === undefined) {
              if ($scope.model.config.multiple) {
                  $scope.model.value = [];
              } else {
                  $scope.model.value = '';
              }
          }
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownFlexibleController', function ($scope) {
          //setup the default config
          var config = {
              items: [],
              multiple: false
          };
          //map the user config
          angular.extend(config, $scope.model.config);
          //map back to the model
          $scope.model.config = config;
          function convertArrayToDictionaryArray(model) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              for (var i = 0; i < model.length; i++) {
                  newItems.push({
                      id: model[i],
                      sortOrder: 0,
                      value: model[i]
                  });
              }
              return newItems;
          }
          function convertObjectToDictionaryArray(model) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  var label = vals[i].value ? vals[i].value : vals[i];
                  newItems.push({
                      id: keys[i],
                      sortOrder: vals[i].sortOrder,
                      value: label
                  });
              }
              return newItems;
          }
          $scope.updateSingleDropdownValue = function () {
              $scope.model.value = [$scope.model.singleDropdownValue];
          };
          if (angular.isArray($scope.model.config.items)) {
              //PP: I dont think this will happen, but we have tests that expect it to happen..
              //if array is simple values, convert to array of objects
              if (!angular.isObject($scope.model.config.items[0])) {
                  $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
              }
          } else if (angular.isObject($scope.model.config.items)) {
              $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
          } else {
              throw 'The items property must be either an array or a dictionary';
          }
          //sort the values
          $scope.model.config.items.sort(function (a, b) {
              return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
          });
          //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
          // to "" gets selected by default
          if ($scope.model.value === null || $scope.model.value === undefined) {
              if ($scope.model.config.multiple) {
                  $scope.model.value = [];
              } else {
                  $scope.model.value = '';
              }
          }
          // if we run in single mode we'll store the value in a local variable
          // so we can pass an array as the model as our PropertyValueEditor expects that
          $scope.model.singleDropdownValue = '';
          if ($scope.model.config.multiple === '0') {
              $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value;
          }
      });
      /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
      function entityPicker($scope, entityResource) {
          //set the default to DocumentType
          if (!$scope.model.config.entityType) {
              $scope.model.config.entityType = 'DocumentType';
          }
          //Determine the select list options and which value to publish
          if (!$scope.model.config.publishBy) {
              $scope.selectOptions = 'entity.id as entity.name for entity in entities';
          } else {
              $scope.selectOptions = 'entity.' + $scope.model.config.publishBy + ' as entity.name for entity in entities';
          }
          entityResource.getAll($scope.model.config.entityType).then(function (data) {
              //convert the ids to strings so the drop downs work properly when comparing
              _.each(data, function (d) {
                  d.id = d.id.toString();
              });
              $scope.entities = data;
          });
          if ($scope.model.value === null || $scope.model.value === undefined) {
              if ($scope.model.config.multiple) {
                  $scope.model.value = [];
              } else {
                  $scope.model.value = '';
              }
          } else {
              //if it's multiple, change the value to an array
              if ($scope.model.config.multiple === '1') {
                  if (_.isString($scope.model.value)) {
                      $scope.model.value = $scope.model.value.split(',');
                  }
              }
          }
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.EntityPickerController', entityPicker);
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.FileUploadController
   * @function
   *
   * @description
   * The controller for the file upload property editor. It is important to note that the $scope.model.value
   *  doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we
   *  are submitting files because in that case, we are adding files to the fileManager which is what gets peristed
   *  on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}"
   *  to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to
   *  be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows
   *  for the editors to check if the value has changed and to re-bind the property if that is true.
   *
  */
      function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) {
          /** Clears the file collections when content is saving (if we need to clear) or after saved */
          function clearFiles() {
              //clear the files collection (we don't want to upload any!)
              fileManager.setFiles($scope.model.alias, []);
              //clear the current files
              $scope.files = [];
              if ($scope.propertyForm) {
                  if ($scope.propertyForm.fileCount) {
                      //this is required to re-validate
                      $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                  }
              }
          }
          /** this method is used to initialize the data and to re-initialize it if the server value is changed */
          function initialize(index) {
              clearFiles();
              if (!index) {
                  index = 1;
              }
              //this is used in order to tell the umb-single-file-upload directive to
              //rebuild the html input control (and thus clearing the selected file) since
              //that is the only way to manipulate the html for the file input control.
              $scope.rebuildInput = { index: index };
              //clear the current files
              $scope.files = [];
              //store the original value so we can restore it if the user clears and then cancels clearing.
              $scope.originalValue = $scope.model.value;
              //create the property to show the list of files currently saved
              if ($scope.model.value != '' && $scope.model.value != undefined) {
                  var images = $scope.model.value.split(',');
                  $scope.persistedFiles = _.map(images, function (item) {
                      return {
                          file: item,
                          isImage: imageHelper.detectIfImageByExtension(item)
                      };
                  });
              } else {
                  $scope.persistedFiles = [];
              }
              _.each($scope.persistedFiles, function (file) {
                  var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: file.file }]);
                  var extension = file.file.substring(file.file.lastIndexOf('.') + 1, file.file.length);
                  file.thumbnail = thumbnailUrl;
                  file.extension = extension.toLowerCase();
              });
              $scope.clearFiles = false;
          }
          initialize();
          // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected)
          $scope.validateMandatory = function () {
              return {
                  isValid: !$scope.model.validation.mandatory || ($scope.persistedFiles != null && $scope.persistedFiles.length > 0 || $scope.files != null && $scope.files.length > 0) && !$scope.clearFiles,
                  errorMsg: 'Value cannot be empty',
                  errorKey: 'required'
              };
          };
          //listen for clear files changes to set our model to be sent up to the server
          $scope.$watch('clearFiles', function (isCleared) {
              if (isCleared == true) {
                  $scope.model.value = { clearFiles: true };
                  clearFiles();
              } else {
                  //reset to original value
                  $scope.model.value = $scope.originalValue;
                  //this is required to re-validate
                  if ($scope.propertyForm) {
                      $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                  }
              }
          });
          //listen for when a file is selected
          $scope.$on('filesSelected', function (event, args) {
              $scope.$apply(function () {
                  //set the files collection
                  fileManager.setFiles($scope.model.alias, args.files);
                  //clear the current files
                  $scope.files = [];
                  var newVal = '';
                  for (var i = 0; i < args.files.length; i++) {
                      //save the file object to the scope's files collection
                      $scope.files.push({
                          alias: $scope.model.alias,
                          file: args.files[i]
                      });
                      newVal += args.files[i].name + ',';
                  }
                  //this is required to re-validate
                  $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                  //set clear files to false, this will reset the model too
                  $scope.clearFiles = false;
                  //set the model value to be the concatenation of files selected. Please see the notes
                  // 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();
              });
          });
          //listen for when the model value has changed
          $scope.$watch('model.value', function (newVal, oldVal) {
              //cannot just check for !newVal because it might be an empty string which we
              //want to look for.
              if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                  // here we need to check if the value change needs to trigger an update in the UI.
                  // if the value is only changed in the controller and not in the server values, we do not
                  // want to trigger an update yet.
                  // we can however no longer rely on checking values in the controller vs. values from the server
                  // to determine whether to update or not, since you could potentially be uploading a file with
                  // the exact same name - in that case we need to reinitialize to show the newly uploaded file.
                  if (newVal.clearFiles !== true && !newVal.selectedFiles) {
                      initialize($scope.rebuildInput.index + 1);
                  }
              }
          });
      }
      ;
      angular.module('umbraco').controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController).run(function (mediaHelper, umbRequestHelper, assetsService) {
          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.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;
                  }
              });
          }
      });
      angular.module('umbraco')    //this controller is obsolete and should not be used anymore
                                   //it proxies everything to the system media list view which has overtaken
                                   //all the work this property editor used to perform
  .controller('Umbraco.PropertyEditors.FolderBrowserController', function ($rootScope, $scope, contentTypeResource) {
          //get the system media listview
          contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
              $scope.fakeProperty = {
                  alias: 'contents',
                  config: dt.config,
                  description: '',
                  editor: dt.editor,
                  hideLabel: true,
                  id: 1,
                  label: 'Contents:',
                  validation: {
                      mandatory: false,
                      pattern: null
                  },
                  value: '',
                  view: dt.view
              };
          });
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.GoogleMapsController', function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) {
          assetsService.loadJs('https://www.google.com/jsapi', $scope).then(function () {
              google.load('maps', '3', {
                  callback: initMap,
                  other_params: 'sensor=false'
              });
          });
          function initMap() {
              //Google maps is available and all components are ready to use.
              var valueArray = $scope.model.value.split(',');
              var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]);
              var mapDiv = document.getElementById($scope.model.alias + '_map');
              var mapOptions = {
                  zoom: $scope.model.config.zoom,
                  center: latLng,
                  mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType]
              };
              var geocoder = new google.maps.Geocoder();
              var map = new google.maps.Map(mapDiv, mapOptions);
              var marker = new google.maps.Marker({
                  map: map,
                  position: latLng,
                  draggable: true
              });
              google.maps.event.addListener(map, 'click', function (event) {
                  dialogService.mediaPicker({
                      callback: function (data) {
                          var image = data.selection[0].src;
                          var latLng = event.latLng;
                          var marker = new google.maps.Marker({
                              map: map,
                              icon: image,
                              position: latLng,
                              draggable: true
                          });
                          google.maps.event.addListener(marker, 'dragend', function (e) {
                              var newLat = marker.getPosition().lat();
                              var newLng = marker.getPosition().lng();
                              codeLatLng(marker.getPosition(), geocoder);
                              //set the model value
                              $scope.model.vvalue = newLat + ',' + newLng;
                          });
                      }
                  });
              });
              var tabShown = function (e) {
                  google.maps.event.trigger(map, 'resize');
              };
              //listen for tab changes
              if (tabsCtrl != null) {
                  tabsCtrl.onTabShown(function (args) {
                      tabShown();
                  });
              }
              $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown);
              $scope.$on('$destroy', function () {
                  $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown);
              });
          }
          function codeLatLng(latLng, geocoder) {
              geocoder.geocode({ 'latLng': latLng }, function (results, status) {
                  if (status == google.maps.GeocoderStatus.OK) {
                      var location = results[0].formatted_address;
                      $rootScope.$apply(function () {
                          notificationsService.success('Peter just went to: ', location);
                      });
                  }
              });
          }
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              initMap();
          };
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController', function ($scope) {
          $scope.currentLayout = $scope.model.currentLayout;
          $scope.columns = $scope.model.columns;
          $scope.rows = $scope.model.rows;
          $scope.scaleUp = function (section, max, overflow) {
              var add = 1;
              if (overflow !== true) {
                  add = max > 1 ? 1 : max;
              }
              //var add = (max > 1) ? 1 : max;
              section.grid = section.grid + add;
          };
          $scope.scaleDown = function (section) {
              var remove = section.grid > 1 ? 1 : 0;
              section.grid = section.grid - remove;
          };
          $scope.percentage = function (spans) {
              return (spans / $scope.columns * 100).toFixed(8);
          };
          $scope.toggleCollection = function (collection, toggle) {
              if (toggle) {
                  collection = [];
              } else {
                  delete collection;
              }
          };
          /****************
      		    Section
      		*****************/
          $scope.configureSection = function (section, template) {
              if (section === undefined) {
                  var space = $scope.availableLayoutSpace > 4 ? 4 : $scope.availableLayoutSpace;
                  section = { grid: space };
                  template.sections.push(section);
              }
              $scope.currentSection = section;
          };
          $scope.deleteSection = function (section, template) {
              if ($scope.currentSection === section) {
                  $scope.currentSection = undefined;
              }
              var index = template.sections.indexOf(section);
              template.sections.splice(index, 1);
          };
          $scope.closeSection = function () {
              $scope.currentSection = undefined;
          };
          $scope.$watch('currentLayout', function (layout) {
              if (layout) {
                  var total = 0;
                  _.forEach(layout.sections, function (section) {
                      total = total + section.grid;
                  });
                  $scope.availableLayoutSpace = $scope.columns - total;
              }
          }, true);
      });
      function RowConfigController($scope) {
          $scope.currentRow = $scope.model.currentRow;
          $scope.editors = $scope.model.editors;
          $scope.columns = $scope.model.columns;
          $scope.scaleUp = function (section, max, overflow) {
              var add = 1;
              if (overflow !== true) {
                  add = max > 1 ? 1 : max;
              }
              //var add = (max > 1) ? 1 : max;
              section.grid = section.grid + add;
          };
          $scope.scaleDown = function (section) {
              var remove = section.grid > 1 ? 1 : 0;
              section.grid = section.grid - remove;
          };
          $scope.percentage = function (spans) {
              return (spans / $scope.columns * 100).toFixed(8);
          };
          $scope.toggleCollection = function (collection, toggle) {
              if (toggle) {
                  collection = [];
              } else {
                  delete collection;
              }
          };
          /****************
          area
      *****************/
          $scope.configureCell = function (cell, row) {
              if ($scope.currentCell && $scope.currentCell === cell) {
                  delete $scope.currentCell;
              } else {
                  if (cell === undefined) {
                      var available = $scope.availableRowSpace;
                      var space = 4;
                      if (available < 4 && available > 0) {
                          space = available;
                      }
                      cell = { grid: space };
                      row.areas.push(cell);
                  }
                  $scope.currentCell = cell;
              }
          };
          $scope.deleteArea = function (cell, row) {
              if ($scope.currentCell === cell) {
                  $scope.currentCell = undefined;
              }
              var index = row.areas.indexOf(cell);
              row.areas.splice(index, 1);
          };
          $scope.closeArea = function () {
              $scope.currentCell = undefined;
          };
          $scope.nameChanged = false;
          var originalName = $scope.currentRow.name;
          $scope.$watch('currentRow', function (row) {
              if (row) {
                  var total = 0;
                  _.forEach(row.areas, function (area) {
                      total = total + area.grid;
                  });
                  $scope.availableRowSpace = $scope.columns - total;
                  if (originalName) {
                      if (originalName != row.name) {
                          $scope.nameChanged = true;
                      } else {
                          $scope.nameChanged = false;
                      }
                  }
              }
          }, true);
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController', RowConfigController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.EmbedController', function ($scope, $rootScope, $timeout) {
          $scope.setEmbed = function () {
              $scope.embedDialog = {};
              $scope.embedDialog.view = 'embed';
              $scope.embedDialog.show = true;
              $scope.embedDialog.submit = function (model) {
                  $scope.control.value = model.embed.preview;
                  $scope.embedDialog.show = false;
                  $scope.embedDialog = null;
              };
              $scope.embedDialog.close = function (oldModel) {
                  $scope.embedDialog.show = false;
                  $scope.embedDialog = null;
              };
          };
          $timeout(function () {
              if ($scope.control.$initializing) {
                  $scope.setEmbed();
              }
          }, 200);
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MacroController', function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService, $routeParams) {
          $scope.title = 'Click to insert macro';
          $scope.setMacro = function () {
              var dialogData = {
                  richTextEditor: true,
                  macroData: $scope.control.value || { macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias ? $scope.control.editor.config.macroAlias : '' }
              };
              $scope.macroPickerOverlay = {};
              $scope.macroPickerOverlay.view = 'macropicker';
              $scope.macroPickerOverlay.dialogData = dialogData;
              $scope.macroPickerOverlay.show = true;
              $scope.macroPickerOverlay.submit = function (model) {
                  var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                  $scope.control.value = {
                      macroAlias: macroObject.macroAlias,
                      macroParamsDictionary: macroObject.macroParamsDictionary
                  };
                  $scope.setPreview($scope.control.value);
                  $scope.macroPickerOverlay.show = false;
                  $scope.macroPickerOverlay = null;
              };
              $scope.macroPickerOverlay.close = function (oldModel) {
                  $scope.macroPickerOverlay.show = false;
                  $scope.macroPickerOverlay = null;
              };
          };
          $scope.setPreview = function (macro) {
              var contentId = $routeParams.id;
              macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary).then(function (htmlResult) {
                  $scope.title = macro.macroAlias;
                  if (htmlResult.trim().length > 0 && htmlResult.indexOf('Macro:') < 0) {
                      $scope.preview = htmlResult;
                  }
              });
          };
          $timeout(function () {
              if ($scope.control.$initializing) {
                  $scope.setMacro();
              } else if ($scope.control.value) {
                  $scope.setPreview($scope.control.value);
              }
          }, 200);
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MediaController', function ($scope, $rootScope, $timeout, userService) {
          if (!$scope.model.config.startNodeId) {
              userService.getCurrentUser().then(function (userData) {
                  $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                  $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
              });
          }
          $scope.setImage = function () {
              $scope.mediaPickerOverlay = {};
              $scope.mediaPickerOverlay.view = 'mediapicker';
              $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
              $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
              $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined;
              $scope.mediaPickerOverlay.showDetails = true;
              $scope.mediaPickerOverlay.disableFolderSelect = true;
              $scope.mediaPickerOverlay.onlyImages = true;
              $scope.mediaPickerOverlay.show = true;
              $scope.mediaPickerOverlay.submit = function (model) {
                  var selectedImage = model.selectedImages[0];
                  $scope.control.value = {
                      focalPoint: selectedImage.focalPoint,
                      id: selectedImage.id,
                      udi: selectedImage.udi,
                      image: selectedImage.image,
                      altText: selectedImage.altText
                  };
                  $scope.setUrl();
                  $scope.mediaPickerOverlay.show = false;
                  $scope.mediaPickerOverlay = null;
              };
              $scope.mediaPickerOverlay.close = function (oldModel) {
                  $scope.mediaPickerOverlay.show = false;
                  $scope.mediaPickerOverlay = null;
              };
          };
          $scope.setUrl = function () {
              if ($scope.control.value.image) {
                  var url = $scope.control.value.image;
                  if ($scope.control.editor.config && $scope.control.editor.config.size) {
                      url += '?width=' + $scope.control.editor.config.size.width;
                      url += '&height=' + $scope.control.editor.config.size.height;
                      url += '&animationprocessmode=first';
                      if ($scope.control.value.focalPoint) {
                          url += '&center=' + $scope.control.value.focalPoint.top + ',' + $scope.control.value.focalPoint.left;
                          url += '&mode=crop';
                      }
                  }
                  // set default size if no crop present (moved from the view)
                  if (url.indexOf('?') == -1) {
                      url += '?width=800&upscale=false&animationprocessmode=false';
                  }
                  $scope.url = url;
              }
          };
          $timeout(function () {
              if ($scope.control.$initializing) {
                  $scope.setImage();
              } else if ($scope.control.value) {
                  $scope.setUrl();
              }
          }, 200);
      });
      (function () {
          'use strict';
          function GridRichTextEditorController($scope, tinyMceService, macroService) {
              var vm = this;
              vm.openLinkPicker = openLinkPicker;
              vm.openMediaPicker = openMediaPicker;
              vm.openMacroPicker = openMacroPicker;
              vm.openEmbed = openEmbed;
              function openLinkPicker(editor, currentTarget, anchorElement) {
                  vm.linkPickerOverlay = {
                      view: 'linkpicker',
                      currentTarget: currentTarget,
                      show: true,
                      submit: function (model) {
                          tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
                          vm.linkPickerOverlay.show = false;
                          vm.linkPickerOverlay = null;
                      }
                  };
              }
              function openMediaPicker(editor, currentTarget, userData) {
                  vm.mediaPickerOverlay = {
                      currentTarget: currentTarget,
                      onlyImages: true,
                      showDetails: true,
                      startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                      view: 'mediapicker',
                      show: true,
                      submit: function (model) {
                          tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
                          vm.mediaPickerOverlay.show = false;
                          vm.mediaPickerOverlay = null;
                      }
                  };
              }
              function openEmbed(editor) {
                  vm.embedOverlay = {
                      view: 'embed',
                      show: true,
                      submit: function (model) {
                          tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
                          vm.embedOverlay.show = false;
                          vm.embedOverlay = null;
                      }
                  };
              }
              function openMacroPicker(editor, dialogData) {
                  vm.macroPickerOverlay = {
                      view: 'macropicker',
                      dialogData: dialogData,
                      show: true,
                      submit: function (model) {
                          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                          tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                      }
                  };
              }
          }
          angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.RichTextEditorController', GridRichTextEditorController);
      }());
      angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.TextStringController', function ($scope, $rootScope, $timeout, dialogService) {
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.GridController', function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper, angularHelper) {
          // Grid status variables
          var placeHolder = '';
          var currentForm = angularHelper.getCurrentForm($scope);
          $scope.currentRow = null;
          $scope.currentCell = null;
          $scope.currentToolsControl = null;
          $scope.currentControl = null;
          $scope.openRTEToolbarId = null;
          $scope.hasSettings = false;
          $scope.showRowConfigurations = true;
          $scope.sortMode = false;
          $scope.reorderKey = 'general_reorder';
          // *********************************************
          // Sortable options
          // *********************************************
          var draggedRteSettings;
          $scope.sortableOptionsRow = {
              distance: 10,
              cursor: 'move',
              placeholder: 'ui-sortable-placeholder',
              handle: '.umb-row-title-bar',
              helper: 'clone',
              forcePlaceholderSize: true,
              tolerance: 'pointer',
              zIndex: 1000000000000000000,
              scrollSensitivity: 100,
              cursorAt: {
                  top: 40,
                  left: 60
              },
              sort: function (event, ui) {
                  /* prevent vertical scroll out of the screen */
                  var max = $('.umb-grid').width() - 150;
                  if (parseInt(ui.helper.css('left')) > max) {
                      ui.helper.css({ 'left': max + 'px' });
                  }
                  if (parseInt(ui.helper.css('left')) < 20) {
                      ui.helper.css({ 'left': 20 });
                  }
              },
              start: function (e, ui) {
                  // Fade out row when sorting
                  ui.item.context.style.display = 'block';
                  ui.item.context.style.opacity = '0.5';
                  draggedRteSettings = {};
                  ui.item.find('.mceNoEditor').each(function () {
                      // remove all RTEs in the dragged row and save their settings
                      var id = $(this).attr('id');
                      draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings;    // tinyMCE.execCommand("mceRemoveEditor", false, id);
                  });
              },
              stop: function (e, ui) {
                  // Fade in row when sorting stops
                  ui.item.context.style.opacity = '1';
                  // reset all RTEs affected by the dragging
                  ui.item.parents('.umb-column').find('.mceNoEditor').each(function () {
                      var id = $(this).attr('id');
                      draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings;
                      tinyMCE.execCommand('mceRemoveEditor', false, id);
                      tinyMCE.init(draggedRteSettings[id]);
                  });
                  currentForm.$setDirty();
              }
          };
          var notIncludedRte = [];
          var cancelMove = false;
          var startingArea;
          $scope.sortableOptionsCell = {
              distance: 10,
              cursor: 'move',
              placeholder: 'ui-sortable-placeholder',
              handle: '.umb-control-handle',
              helper: 'clone',
              connectWith: '.umb-cell-inner',
              forcePlaceholderSize: true,
              tolerance: 'pointer',
              zIndex: 1000000000000000000,
              scrollSensitivity: 100,
              cursorAt: {
                  top: 45,
                  left: 90
              },
              sort: function (event, ui) {
                  /* prevent vertical scroll out of the screen */
                  var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($('.umb-grid').offset().left);
                  var max = $('.umb-grid').width() - 220;
                  if (position > max) {
                      ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                  }
                  if (position < 0) {
                      ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                  }
              },
              over: function (event, ui) {
                  var area = $(event.target).scope().area;
                  var allowedEditors = area.allowed;
                  if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors || startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1) {
                      $scope.$apply(function () {
                          $(event.target).scope().area.dropNotAllowed = true;
                      });
                      ui.placeholder.hide();
                      cancelMove = true;
                  } else {
                      if ($(event.target).scope().area.controls.length == 0) {
                          $scope.$apply(function () {
                              $(event.target).scope().area.dropOnEmpty = true;
                          });
                          ui.placeholder.hide();
                      } else {
                          ui.placeholder.show();
                      }
                      cancelMove = false;
                  }
              },
              out: function (event, ui) {
                  $scope.$apply(function () {
                      $(event.target).scope().area.dropNotAllowed = false;
                      $(event.target).scope().area.dropOnEmpty = false;
                  });
              },
              update: function (event, ui) {
                  /* add all RTEs which are affected by the dragging */
                  if (!ui.sender) {
                      if (cancelMove) {
                          ui.item.sortable.cancel();
                      }
                      ui.item.parents('.umb-cell.content').find('.mceNoEditor').each(function () {
                          if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                              notIncludedRte.splice(0, 0, $(this).attr('id'));
                          }
                      });
                  } else {
                      $(event.target).find('.mceNoEditor').each(function () {
                          if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                              notIncludedRte.splice(0, 0, $(this).attr('id'));
                          }
                      });
                  }
                  currentForm.$setDirty();
              },
              start: function (e, ui) {
                  //Get the starting area for reference
                  var area = $(e.target).scope().area;
                  startingArea = area;
                  // fade out control when sorting
                  ui.item.context.style.display = 'block';
                  ui.item.context.style.opacity = '0.5';
                  // reset dragged RTE settings in case a RTE isn't dragged
                  draggedRteSettings = undefined;
                  ui.item.context.style.display = 'block';
                  ui.item.find('.mceNoEditor').each(function () {
                      notIncludedRte = [];
                      var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') });
                      // save the dragged RTE settings
                      if (editors) {
                          draggedRteSettings = editors.settings;
                          // remove the dragged RTE
                          tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                      }
                  });
              },
              stop: function (e, ui) {
                  // Fade in control when sorting stops
                  ui.item.context.style.opacity = '1';
                  ui.item.parents('.umb-cell-content').find('.mceNoEditor').each(function () {
                      if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                          // add all dragged's neighbouring RTEs in the new cell
                          notIncludedRte.splice(0, 0, $(this).attr('id'));
                      }
                  });
                  $timeout(function () {
                      // reconstruct the dragged RTE (could be undefined when dragging something else than RTE)
                      if (draggedRteSettings !== undefined) {
                          tinyMCE.init(draggedRteSettings);
                      }
                      _.forEach(notIncludedRte, function (id) {
                          // reset all the other RTEs
                          if (draggedRteSettings === undefined || id !== draggedRteSettings.id) {
                              var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
                              tinyMCE.execCommand('mceRemoveEditor', false, id);
                              tinyMCE.init(rteSettings);
                          }
                      });
                  }, 500, false);
                  $scope.$apply(function () {
                      var cell = $(e.target).scope().area;
                      cell.hasActiveChild = hasActiveChild(cell, cell.controls);
                      cell.active = false;
                  });
              }
          };
          $scope.toggleSortMode = function () {
              $scope.sortMode = !$scope.sortMode;
              if ($scope.sortMode) {
                  $scope.reorderKey = 'general_reorderDone';
              } else {
                  $scope.reorderKey = 'general_reorder';
              }
          };
          $scope.showReorderButton = function () {
              if ($scope.model.value && $scope.model.value.sections) {
                  for (var i = 0; $scope.model.value.sections.length > i; i++) {
                      var section = $scope.model.value.sections[i];
                      if (section.rows && section.rows.length > 0) {
                          return true;
                      }
                  }
              }
          };
          // *********************************************
          // Add items overlay menu
          // *********************************************
          $scope.openEditorOverlay = function (event, area, index, key) {
              $scope.editorOverlay = {
                  view: 'itempicker',
                  filter: area.$allowedEditors.length > 15,
                  title: localizationService.localize('grid_insertControl'),
                  availableItems: area.$allowedEditors,
                  event: event,
                  show: true,
                  submit: function (model) {
                      $scope.addControl(model.selectedItem, area, index);
                      $scope.editorOverlay.show = false;
                      $scope.editorOverlay = null;
                  }
              };
          };
          // *********************************************
          // Template management functions
          // *********************************************
          $scope.addTemplate = function (template) {
              $scope.model.value = angular.copy(template);
              //default row data
              _.forEach($scope.model.value.sections, function (section) {
                  $scope.initSection(section);
              });
          };
          // *********************************************
          // Row management function
          // *********************************************
          $scope.clickRow = function (index, rows) {
              rows[index].active = true;
          };
          $scope.clickOutsideRow = function (index, rows) {
              rows[index].active = false;
          };
          function getAllowedLayouts(section) {
              var layouts = $scope.model.config.items.layouts;
              //This will occur if it is a new section which has been
              // created from a 'template'
              if (section.allowed && section.allowed.length > 0) {
                  return _.filter(layouts, function (layout) {
                      return _.indexOf(section.allowed, layout.name) >= 0;
                  });
              } else {
                  return layouts;
              }
          }
          $scope.addRow = function (section, layout) {
              //copy the selected layout into the rows collection
              var row = angular.copy(layout);
              // Init row value
              row = $scope.initRow(row);
              // Push the new row
              if (row) {
                  section.rows.push(row);
              }
              currentForm.$setDirty();
              $scope.showRowConfigurations = false;
          };
          $scope.removeRow = function (section, $index) {
              if (section.rows.length > 0) {
                  section.rows.splice($index, 1);
                  $scope.currentRow = null;
                  $scope.openRTEToolbarId = null;
                  currentForm.$setDirty();
              }
              if (section.rows.length === 0) {
                  $scope.showRowConfigurations = true;
              }
          };
          var shouldApply = function (item, itemType, gridItem) {
              if (item.applyTo === undefined || item.applyTo === null || item.applyTo === '') {
                  return true;
              }
              if (typeof item.applyTo === 'string') {
                  return item.applyTo === itemType;
              }
              if (itemType === 'row') {
                  if (item.applyTo.row === undefined) {
                      return false;
                  }
                  if (item.applyTo.row === null || item.applyTo.row === '') {
                      return true;
                  }
                  var rows = item.applyTo.row.split(',');
                  return _.indexOf(rows, gridItem.name) !== -1;
              } else if (itemType === 'cell') {
                  if (item.applyTo.cell === undefined) {
                      return false;
                  }
                  if (item.applyTo.cell === null || item.applyTo.cell === '') {
                      return true;
                  }
                  var cells = item.applyTo.cell.split(',');
                  var cellSize = gridItem.grid.toString();
                  return _.indexOf(cells, cellSize) !== -1;
              }
          };
          $scope.editGridItemSettings = function (gridItem, itemType) {
              placeHolder = '{0}';
              var styles, config;
              if (itemType === 'control') {
                  styles = null;
                  config = angular.copy(gridItem.editor.config.settings);
              } else {
                  styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) {
                      return shouldApply(item, itemType, gridItem);
                  });
                  config = _.filter(angular.copy($scope.model.config.items.config), function (item) {
                      return shouldApply(item, itemType, gridItem);
                  });
              }
              if (angular.isObject(gridItem.config)) {
                  _.each(config, function (cfg) {
                      var val = gridItem.config[cfg.key];
                      if (val) {
                          cfg.value = stripModifier(val, cfg.modifier);
                      }
                  });
              }
              if (angular.isObject(gridItem.styles)) {
                  _.each(styles, function (style) {
                      var val = gridItem.styles[style.key];
                      if (val) {
                          style.value = stripModifier(val, style.modifier);
                      }
                  });
              }
              $scope.gridItemSettingsDialog = {};
              $scope.gridItemSettingsDialog.view = 'views/propertyeditors/grid/dialogs/config.html';
              $scope.gridItemSettingsDialog.title = 'Settings';
              $scope.gridItemSettingsDialog.styles = styles;
              $scope.gridItemSettingsDialog.config = config;
              $scope.gridItemSettingsDialog.show = true;
              $scope.gridItemSettingsDialog.submit = function (model) {
                  var styleObject = {};
                  var configObject = {};
                  _.each(model.styles, function (style) {
                      if (style.value) {
                          styleObject[style.key] = addModifier(style.value, style.modifier);
                      }
                  });
                  _.each(model.config, function (cfg) {
                      if (cfg.value) {
                          configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
                      }
                  });
                  gridItem.styles = styleObject;
                  gridItem.config = configObject;
                  gridItem.hasConfig = gridItemHasConfig(styleObject, configObject);
                  currentForm.$setDirty();
                  $scope.gridItemSettingsDialog.show = false;
                  $scope.gridItemSettingsDialog = null;
              };
              $scope.gridItemSettingsDialog.close = function (oldModel) {
                  $scope.gridItemSettingsDialog.show = false;
                  $scope.gridItemSettingsDialog = null;
              };
          };
          function stripModifier(val, modifier) {
              if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
                  return val;
              } else {
                  var paddArray = modifier.split(placeHolder);
                  if (paddArray.length == 1) {
                      if (modifier.indexOf(placeHolder) === 0) {
                          return val.slice(0, -paddArray[0].length);
                      } else {
                          return val.slice(paddArray[0].length, 0);
                      }
                  } else {
                      if (paddArray[1].length === 0) {
                          return val.slice(paddArray[0].length);
                      }
                      return val.slice(paddArray[0].length, -paddArray[1].length);
                  }
              }
          }
          var addModifier = function (val, modifier) {
              if (!modifier || modifier.indexOf(placeHolder) < 0) {
                  return val;
              } else {
                  return modifier.replace(placeHolder, val);
              }
          };
          function gridItemHasConfig(styles, config) {
              if (_.isEmpty(styles) && _.isEmpty(config)) {
                  return false;
              } else {
                  return true;
              }
          }
          // *********************************************
          // Area management functions
          // *********************************************
          $scope.clickCell = function (index, cells, row) {
              cells[index].active = true;
              row.hasActiveChild = true;
          };
          $scope.clickOutsideCell = function (index, cells, row) {
              cells[index].active = false;
              row.hasActiveChild = hasActiveChild(row, cells);
          };
          $scope.cellPreview = function (cell) {
              if (cell && cell.$allowedEditors) {
                  var editor = cell.$allowedEditors[0];
                  return editor.icon;
              } else {
                  return 'icon-layout';
              }
          };
          // *********************************************
          // Control management functions
          // *********************************************
          $scope.clickControl = function (index, controls, cell) {
              controls[index].active = true;
              cell.hasActiveChild = true;
          };
          $scope.clickOutsideControl = function (index, controls, cell) {
              controls[index].active = false;
              cell.hasActiveChild = hasActiveChild(cell, controls);
          };
          function hasActiveChild(item, children) {
              var activeChild = false;
              for (var i = 0; children.length > i; i++) {
                  var child = children[i];
                  if (child.active) {
                      activeChild = true;
                  }
              }
              if (activeChild) {
                  return true;
              }
          }
          var guid = function () {
              function s4() {
                  return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
              }
              return function () {
                  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
              };
          }();
          $scope.setUniqueId = function (cell, index) {
              return guid();
          };
          $scope.addControl = function (editor, cell, index, initialize) {
              initialize = initialize !== false;
              var newControl = {
                  value: null,
                  editor: editor,
                  $initializing: initialize
              };
              if (index === undefined) {
                  index = cell.controls.length;
              }
              newControl.active = true;
              //populate control
              $scope.initControl(newControl, index + 1);
              cell.controls.push(newControl);
          };
          $scope.addTinyMce = function (cell) {
              var rte = $scope.getEditor('rte');
              $scope.addControl(rte, cell);
          };
          $scope.getEditor = function (alias) {
              return _.find($scope.availableEditors, function (editor) {
                  return editor.alias === alias;
              });
          };
          $scope.removeControl = function (cell, $index) {
              $scope.currentControl = null;
              cell.controls.splice($index, 1);
          };
          $scope.percentage = function (spans) {
              return (spans / $scope.model.config.items.columns * 100).toFixed(8);
          };
          $scope.clearPrompt = function (scopedObject, e) {
              scopedObject.deletePrompt = false;
              e.preventDefault();
              e.stopPropagation();
          };
          $scope.togglePrompt = function (scopedObject) {
              scopedObject.deletePrompt = !scopedObject.deletePrompt;
          };
          $scope.hidePrompt = function (scopedObject) {
              scopedObject.deletePrompt = false;
          };
          $scope.toggleAddRow = function () {
              $scope.showRowConfigurations = !$scope.showRowConfigurations;
          };
          // *********************************************
          // Initialization
          // these methods are called from ng-init on the template
          // so we can controll their first load data
          //
          // intialization sets non-saved data like percentage sizing, allowed editors and
          // other data that should all be pre-fixed with $ to strip it out on save
          // *********************************************
          // *********************************************
          // Init template + sections
          // *********************************************
          $scope.initContent = function () {
              var clear = true;
              //settings indicator shortcut
              if ($scope.model.config.items.config && $scope.model.config.items.config.length > 0 || $scope.model.config.items.styles && $scope.model.config.items.styles.length > 0) {
                  $scope.hasSettings = true;
              }
              //ensure the grid has a column value set,
              //if nothing is found, set it to 12
              if (!$scope.model.config.items.columns) {
                  $scope.model.config.items.columns = 12;
              } else if (angular.isString($scope.model.config.items.columns)) {
                  $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
              }
              if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) {
                  if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) {
                      //This will occur if it is an existing value, in which case
                      // we need to determine which layout was applied by looking up
                      // the name
                      // TODO: We need to change this to an immutable ID!!
                      var found = _.find($scope.model.config.items.templates, function (t) {
                          return t.name === $scope.model.value.name;
                      });
                      if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) {
                          //Cool, we've found the template associated with our current value with matching sections counts, now we need to
                          // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't
                          // allowed for this template based on the current config.
                          _.each(found.sections, function (templateSection, index) {
                              angular.extend($scope.model.value.sections[index], angular.copy(templateSection));
                          });
                      }
                  }
                  _.forEach($scope.model.value.sections, function (section, index) {
                      if (section.grid > 0) {
                          $scope.initSection(section);
                          //we do this to ensure that the grid can be reset by deleting the last row
                          if (section.rows.length > 0) {
                              clear = false;
                          }
                      } else {
                          $scope.model.value.sections.splice(index, 1);
                      }
                  });
              } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
                  $scope.addTemplate($scope.model.config.items.templates[0]);
                  clear = false;
              }
              if (clear) {
                  $scope.model.value = undefined;
              }
          };
          $scope.initSection = function (section) {
              section.$percentage = $scope.percentage(section.grid);
              section.$allowedLayouts = getAllowedLayouts(section);
              if (!section.rows || section.rows.length === 0) {
                  section.rows = [];
                  if (section.$allowedLayouts.length === 1) {
                      $scope.addRow(section, section.$allowedLayouts[0]);
                  }
              } else {
                  _.forEach(section.rows, function (row, index) {
                      if (!row.$initialized) {
                          var initd = $scope.initRow(row);
                          //if init fails, remove
                          if (!initd) {
                              section.rows.splice(index, 1);
                          } else {
                              section.rows[index] = initd;
                          }
                      }
                  });
                  // if there is more than one row added - hide row add tools
                  $scope.showRowConfigurations = false;
              }
          };
          // *********************************************
          // Init layout / row
          // *********************************************
          $scope.initRow = function (row) {
              //merge the layout data with the original config data
              //if there are no config info on this, splice it out
              var original = _.find($scope.model.config.items.layouts, function (o) {
                  return o.name === row.name;
              });
              if (!original) {
                  return null;
              } else {
                  //make a copy to not touch the original config
                  original = angular.copy(original);
                  original.styles = row.styles;
                  original.config = row.config;
                  original.hasConfig = gridItemHasConfig(row.styles, row.config);
                  //sync area configuration
                  _.each(original.areas, function (area, areaIndex) {
                      if (area.grid > 0) {
                          var currentArea = row.areas[areaIndex];
                          if (currentArea) {
                              area.config = currentArea.config;
                              area.styles = currentArea.styles;
                              area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config);
                          }
                          //set editor permissions
                          if (!area.allowed || area.allowAll === true) {
                              area.$allowedEditors = $scope.availableEditors;
                              area.$allowsRTE = true;
                          } else {
                              area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
                                  return _.indexOf(area.allowed, editor.alias) >= 0;
                              });
                              if (_.indexOf(area.allowed, 'rte') >= 0) {
                                  area.$allowsRTE = true;
                              }
                          }
                          //copy over existing controls into the new areas
                          if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
                              area.controls = currentArea.controls;
                              _.forEach(area.controls, function (control, controlIndex) {
                                  $scope.initControl(control, controlIndex);
                              });
                          } else {
                              //if empty
                              area.controls = [];
                              //if only one allowed editor
                              if (area.$allowedEditors.length === 1) {
                                  $scope.addControl(area.$allowedEditors[0], area, 0, false);
                              }
                          }
                          //set width
                          area.$percentage = $scope.percentage(area.grid);
                          area.$uniqueId = $scope.setUniqueId();
                      } else {
                          original.areas.splice(areaIndex, 1);
                      }
                  });
                  //replace the old row
                  original.$initialized = true;
                  //set a disposable unique ID
                  original.$uniqueId = $scope.setUniqueId();
                  //set a no disposable unique ID (util for row styling)
                  original.id = !row.id ? $scope.setUniqueId() : row.id;
                  return original;
              }
          };
          // *********************************************
          // Init control
          // *********************************************
          $scope.initControl = function (control, index) {
              control.$index = index;
              control.$uniqueId = $scope.setUniqueId();
              //error handling in case of missing editor..
              //should only happen if stripped earlier
              if (!control.editor) {
                  control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
              }
              if (!control.$editorPath) {
                  var editorConfig = $scope.getEditor(control.editor.alias);
                  if (editorConfig) {
                      control.editor = editorConfig;
                      //if its an absolute path
                      if (control.editor.view.startsWith('/') || control.editor.view.startsWith('~/')) {
                          control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
                      } else {
                          //use convention
                          control.$editorPath = 'views/propertyeditors/grid/editors/' + control.editor.view + '.html';
                      }
                  } else {
                      control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
                  }
              }
          };
          gridService.getGridEditors().then(function (response) {
              $scope.availableEditors = response.data;
              //Localize the grid editor names
              angular.forEach($scope.availableEditors, function (value, key) {
                  //If no translation is provided, keep using the editor name from the manifest
                  if (localizationService.dictionary.hasOwnProperty('grid_' + value.alias)) {
                      value.name = localizationService.localize('grid_' + value.alias);
                  }
              });
              $scope.contentReady = true;
              // *********************************************
              // Init grid
              // *********************************************
              $scope.initContent();
          });
          //Clean the grid value before submitting to the server, we don't need
          // all of that grid configuration in the value to be stored!! All of that
          // needs to be merged in at runtime to ensure that the real config values are used
          // if they are ever updated.
          var unsubscribe = $scope.$on('formSubmitting', function () {
              if ($scope.model.value && $scope.model.value.sections) {
                  _.each($scope.model.value.sections, function (section) {
                      if (section.rows) {
                          _.each(section.rows, function (row) {
                              if (row.areas) {
                                  _.each(row.areas, function (area) {
                                      //Remove the 'editors' - these are the allowed editors, these will
                                      // be injected at runtime to this editor, it should not be persisted
                                      if (area.editors) {
                                          delete area.editors;
                                      }
                                      if (area.controls) {
                                          _.each(area.controls, function (control) {
                                              if (control.editor) {
                                                  //replace
                                                  var alias = control.editor.alias;
                                                  control.editor = { alias: alias };
                                              }
                                          });
                                      }
                                  });
                              }
                          });
                      }
                  });
              }
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditorController', function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) {
          var emptyModel = {
              styles: [{
                      label: 'Set a background image',
                      description: 'Set a row background',
                      key: 'background-image',
                      view: 'imagepicker',
                      modifier: 'url({0})'
                  }],
              config: [{
                      label: 'Class',
                      description: 'Set a css class',
                      key: 'class',
                      view: 'textstring'
                  }],
              columns: 12,
              templates: [
                  {
                      name: '1 column layout',
                      sections: [{ grid: 12 }]
                  },
                  {
                      name: '2 column layout',
                      sections: [
                          { grid: 4 },
                          { grid: 8 }
                      ]
                  }
              ],
              layouts: [
                  {
                      label: 'Headline',
                      name: 'Headline',
                      areas: [{
                              grid: 12,
                              editors: ['headline']
                          }]
                  },
                  {
                      label: 'Article',
                      name: 'Article',
                      areas: [
                          { grid: 4 },
                          { grid: 8 }
                      ]
                  }
              ]
          };
          /****************
              template
          *****************/
          $scope.configureTemplate = function (template) {
              var templatesCopy = angular.copy($scope.model.value.templates);
              if (template === undefined) {
                  template = {
                      name: '',
                      sections: []
                  };
                  $scope.model.value.templates.push(template);
              }
              $scope.layoutConfigOverlay = {};
              $scope.layoutConfigOverlay.view = 'views/propertyEditors/grid/dialogs/layoutconfig.html';
              $scope.layoutConfigOverlay.currentLayout = template;
              $scope.layoutConfigOverlay.rows = $scope.model.value.layouts;
              $scope.layoutConfigOverlay.columns = $scope.model.value.columns;
              $scope.layoutConfigOverlay.show = true;
              $scope.layoutConfigOverlay.submit = function (model) {
                  $scope.layoutConfigOverlay.show = false;
                  $scope.layoutConfigOverlay = null;
              };
              $scope.layoutConfigOverlay.close = function (oldModel) {
                  //reset templates
                  $scope.model.value.templates = templatesCopy;
                  $scope.layoutConfigOverlay.show = false;
                  $scope.layoutConfigOverlay = null;
              };
          };
          $scope.deleteTemplate = function (index) {
              $scope.model.value.templates.splice(index, 1);
          };
          /****************
              Row
          *****************/
          $scope.configureLayout = function (layout) {
              var layoutsCopy = angular.copy($scope.model.value.layouts);
              if (layout === undefined) {
                  layout = {
                      name: '',
                      areas: []
                  };
                  $scope.model.value.layouts.push(layout);
              }
              $scope.rowConfigOverlay = {};
              $scope.rowConfigOverlay.view = 'views/propertyEditors/grid/dialogs/rowconfig.html';
              $scope.rowConfigOverlay.currentRow = layout;
              $scope.rowConfigOverlay.editors = $scope.editors;
              $scope.rowConfigOverlay.columns = $scope.model.value.columns;
              $scope.rowConfigOverlay.show = true;
              $scope.rowConfigOverlay.submit = function (model) {
                  $scope.rowConfigOverlay.show = false;
                  $scope.rowConfigOverlay = null;
              };
              $scope.rowConfigOverlay.close = function (oldModel) {
                  $scope.model.value.layouts = layoutsCopy;
                  $scope.rowConfigOverlay.show = false;
                  $scope.rowConfigOverlay = null;
              };
          };
          //var rowDeletesPending = false;
          $scope.deleteLayout = function (index) {
              $scope.rowDeleteOverlay = {};
              $scope.rowDeleteOverlay.view = 'views/propertyEditors/grid/dialogs/rowdeleteconfirm.html';
              $scope.rowDeleteOverlay.dialogData = { rowName: $scope.model.value.layouts[index].name };
              $scope.rowDeleteOverlay.show = true;
              $scope.rowDeleteOverlay.submit = function (model) {
                  $scope.model.value.layouts.splice(index, 1);
                  $scope.rowDeleteOverlay.show = false;
                  $scope.rowDeleteOverlay = null;
              };
              $scope.rowDeleteOverlay.close = function (oldModel) {
                  $scope.rowDeleteOverlay.show = false;
                  $scope.rowDeleteOverlay = null;
              };
          };
          /****************
              utillities
          *****************/
          $scope.toggleCollection = function (collection, toggle) {
              if (toggle) {
                  collection = [];
              } else {
                  delete collection;
              }
          };
          $scope.percentage = function (spans) {
              return (spans / $scope.model.value.columns * 100).toFixed(8);
          };
          $scope.zeroWidthFilter = function (cell) {
              return cell.grid > 0;
          };
          /****************
              Config
          *****************/
          $scope.removeConfigValue = function (collection, index) {
              collection.splice(index, 1);
          };
          var editConfigCollection = function (configValues, title, callback) {
              $scope.editConfigCollectionOverlay = {};
              $scope.editConfigCollectionOverlay.view = 'views/propertyeditors/grid/dialogs/editconfig.html';
              $scope.editConfigCollectionOverlay.config = configValues;
              $scope.editConfigCollectionOverlay.title = title;
              $scope.editConfigCollectionOverlay.show = true;
              $scope.editConfigCollectionOverlay.submit = function (model) {
                  callback(model.config);
                  $scope.editConfigCollectionOverlay.show = false;
                  $scope.editConfigCollectionOverlay = null;
              };
              $scope.editConfigCollectionOverlay.close = function (oldModel) {
                  $scope.editConfigCollectionOverlay.show = false;
                  $scope.editConfigCollectionOverlay = null;
              };
          };
          $scope.editConfig = function () {
              editConfigCollection($scope.model.value.config, 'Settings', function (data) {
                  $scope.model.value.config = data;
              });
          };
          $scope.editStyles = function () {
              editConfigCollection($scope.model.value.styles, 'Styling', function (data) {
                  $scope.model.value.styles = data;
              });
          };
          /****************
              editors
          *****************/
          gridService.getGridEditors().then(function (response) {
              $scope.editors = response.data;
          });
          /* init grid data */
          if (!$scope.model.value || $scope.model.value === '' || !$scope.model.value.templates) {
              $scope.model.value = emptyModel;
          } else {
              if (!$scope.model.value.columns) {
                  $scope.model.value.columns = emptyModel.columns;
              }
              if (!$scope.model.value.config) {
                  $scope.model.value.config = [];
              }
              if (!$scope.model.value.styles) {
                  $scope.model.value.styles = [];
              }
          }
          /****************
              Clean up
          *****************/
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var ts = $scope.model.value.templates;
              var ls = $scope.model.value.layouts;
              _.each(ts, function (t) {
                  _.each(t.sections, function (section, index) {
                      if (section.grid === 0) {
                          t.sections.splice(index, 1);
                      }
                  });
              });
              _.each(ls, function (l) {
                  _.each(l.areas, function (area, index) {
                      if (area.grid === 0) {
                          l.areas.splice(index, 1);
                      }
                  });
              });
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
      });
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ImageCropperController', function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) {
          var config = angular.copy($scope.model.config);
          $scope.imageIsLoaded = false;
          //move previously saved value to the editor
          if ($scope.model.value) {
              //backwards compat with the old file upload (incase some-one swaps them..)
              if (angular.isString($scope.model.value)) {
                  config.src = $scope.model.value;
                  $scope.model.value = config;
              } else if ($scope.model.value.crops) {
                  //sync any config changes with the editor and drop outdated crops
                  _.each($scope.model.value.crops, function (saved) {
                      var configured = _.find(config.crops, function (item) {
                          return item.alias === saved.alias;
                      });
                      if (configured && configured.height === saved.height && configured.width === saved.width) {
                          configured.coordinates = saved.coordinates;
                      }
                  });
                  $scope.model.value.crops = config.crops;
                  //restore focalpoint if missing
                  if (!$scope.model.value.focalPoint) {
                      $scope.model.value.focalPoint = {
                          left: 0.5,
                          top: 0.5
                      };
                  }
              }
              $scope.imageSrc = $scope.model.value.src;
          }
          //crop a specific crop
          $scope.crop = function (crop) {
              $scope.currentCrop = crop;
              $scope.currentPoint = undefined;
          };
          //done cropping
          $scope.done = function () {
              $scope.currentCrop = undefined;
              $scope.currentPoint = undefined;
          };
          //crop a specific crop
          $scope.clear = function (crop) {
              //clear current uploaded files
              fileManager.setFiles($scope.model.alias, []);
              //clear the ui
              $scope.imageSrc = undefined;
              if ($scope.model.value) {
                  delete $scope.model.value;
              }
              // set form to dirty to tricker discard changes dialog
              var currForm = angularHelper.getCurrentForm($scope);
              currForm.$setDirty();
          };
          //show previews
          $scope.togglePreviews = function () {
              if ($scope.showPreviews) {
                  $scope.showPreviews = false;
                  $scope.tempShowPreviews = false;
              } else {
                  $scope.showPreviews = true;
              }
          };
          $scope.imageLoaded = function () {
              $scope.imageIsLoaded = true;
          };
          //on image selected, update the cropper
          $scope.$on('filesSelected', function (ev, args) {
              $scope.model.value = config;
              if (args.files && args.files[0]) {
                  fileManager.setFiles($scope.model.alias, args.files);
                  var reader = new FileReader();
                  reader.onload = function (e) {
                      $scope.$apply(function () {
                          $scope.imageSrc = e.target.result;
                      });
                  };
                  reader.readAsDataURL(args.files[0]);
              }
          });
          //here we declare a special method which will be called whenever the value has changed from the server
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //clear current uploaded files
              fileManager.setFiles($scope.model.alias, []);
          };
          var unsubscribe = $scope.$on('formSubmitting', function () {
              $scope.done();
          });
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
      }).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) {
                  if (property.value && property.value.src) {
                      if (thumbnail === true) {
                          return property.value.src + '?width=500&mode=max&animationprocessmode=first';
                      } else {
                          return property.value.src;
                      }    //this is a fallback in case the cropper has been asssigned a upload field
                  } else if (angular.isString(property.value)) {
                      if (thumbnail) {
                          if (mediaHelper.detectIfImageByExtension(property.value)) {
                              var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: property.value }]);
                              return thumbnailUrl;
                          } else {
                              return null;
                          }
                      } else {
                          return property.value;
                      }
                  }
                  return null;
              });
          }
      });
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.CropSizesController', function ($scope, $timeout) {
          if (!$scope.model.value) {
              $scope.model.value = [];
          }
          $scope.remove = function (item, evt) {
              evt.preventDefault();
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.alias === item.alias;
              });
          };
          $scope.edit = function (item, evt) {
              evt.preventDefault();
              $scope.newItem = item;
          };
          $scope.cancel = function (evt) {
              evt.preventDefault();
              $scope.newItem = null;
          };
          $scope.add = function (evt) {
              evt.preventDefault();
              if ($scope.newItem && $scope.newItem.alias && angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) && $scope.newItem.width > 0 && $scope.newItem.height > 0) {
                  var exists = _.find($scope.model.value, function (item) {
                      return $scope.newItem.alias === item.alias;
                  });
                  if (!exists) {
                      $scope.model.value.push($scope.newItem);
                      $scope.newItem = {};
                      $scope.hasError = false;
                      return;
                  }
              }
              //there was an error, do the highlight (will be set back by the directive)
              $scope.hasError = true;
          };
      });
      function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
          if (!$scope.model.value) {
              $scope.model.value = [];
          }
          $scope.propertyAliases = [];
          $scope.selectedField = null;
          $scope.systemFields = [
              { value: 'sortOrder' },
              { value: 'updateDate' },
              { value: 'updater' },
              { value: 'createDate' },
              { value: 'owner' },
              { value: 'published' },
              { value: 'contentTypeAlias' },
              { value: 'email' },
              { value: 'username' }
          ];
          $scope.getLocalizedKey = function (alias) {
              switch (alias) {
              case 'name':
                  return 'general_name';
              case 'sortOrder':
                  return 'general_sort';
              case 'updateDate':
                  return 'content_updateDate';
              case 'updater':
                  return 'content_updatedBy';
              case 'createDate':
                  return 'content_createDate';
              case 'owner':
                  return 'content_createBy';
              case 'published':
                  return 'content_isPublished';
              case 'contentTypeAlias':
                  //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias'
                  return 'content_documentType';
              case 'email':
                  return 'general_email';
              case 'username':
                  return 'general_username';
              }
              return alias;
          };
          $scope.removeField = function (e) {
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.alias === e.alias;
              });
          };
          //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
          _.each($scope.systemFields, function (e, i) {
              var key = $scope.getLocalizedKey(e.value);
              localizationService.localize(key).then(function (v) {
                  e.name = v;
                  switch (e.value) {
                  case 'updater':
                      e.name += ' (Content only)';
                      break;
                  case 'published':
                      e.name += ' (Content only)';
                      break;
                  case 'email':
                      e.name += ' (Members only)';
                      break;
                  case 'username':
                      e.name += ' (Members only)';
                      break;
                  }
              });
          });
          // Return a helper with preserved width of cells
          var fixHelper = function (e, ui) {
              ui.children().each(function () {
                  $(this).width($(this).width());
              });
              var row = ui.clone();
              row.css('background-color', 'lightgray');
              return row;
          };
          $scope.sortableOptions = {
              helper: fixHelper,
              handle: '.handle',
              opacity: 0.5,
              axis: 'y',
              containment: 'parent',
              cursor: 'move',
              items: '> tr',
              tolerance: 'pointer',
              forcePlaceholderSize: true,
              start: function (e, ui) {
                  ui.placeholder.height(ui.item.height());
              },
              update: function (e, ui) {
                  // Get the new and old index for the moved element (using the text as the identifier)
                  var newIndex = ui.item.index();
                  var movedAlias = $('.alias-value', ui.item).text().trim();
                  var originalIndex = getAliasIndexByText(movedAlias);
                  // Move the element in the model
                  if (originalIndex > -1) {
                      var movedElement = $scope.model.value[originalIndex];
                      $scope.model.value.splice(originalIndex, 1);
                      $scope.model.value.splice(newIndex, 0, movedElement);
                  }
              }
          };
          contentTypeResource.getAllPropertyTypeAliases().then(function (data) {
              $scope.propertyAliases = data;
          });
          $scope.addField = function () {
              var val = $scope.selectedField;
              var isSystem = val.startsWith('_system_');
              if (isSystem) {
                  val = val.trimStart('_system_');
              }
              var exists = _.find($scope.model.value, function (i) {
                  return i.alias === val;
              });
              if (!exists) {
                  $scope.model.value.push({
                      alias: val,
                      isSystem: isSystem ? 1 : 0
                  });
              }
          };
          function getAliasIndexByText(value) {
              for (var i = 0; i < $scope.model.value.length; i++) {
                  if ($scope.model.value[i].alias === value) {
                      return i;
                  }
              }
              return -1;
          }
      }
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.IncludePropertiesListViewController', includePropsPreValsController);
      /**
   * @ngdoc controller
   * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController
   * @function
   *
   * @description
   * The controller for configuring layouts for list views
   */
      (function () {
          'use strict';
          function ListViewLayoutsPreValsController($scope) {
              var vm = this;
              vm.focusLayoutName = false;
              vm.layoutsSortableOptions = {
                  distance: 10,
                  tolerance: 'pointer',
                  opacity: 0.7,
                  scroll: true,
                  cursor: 'move',
                  handle: '.list-view-layout__sort-handle'
              };
              vm.addLayout = addLayout;
              vm.showPrompt = showPrompt;
              vm.hidePrompt = hidePrompt;
              vm.removeLayout = removeLayout;
              vm.openIconPicker = openIconPicker;
              function activate() {
              }
              function addLayout() {
                  vm.focusLayoutName = false;
                  var layout = {
                      'name': '',
                      'path': '',
                      'icon': 'icon-stop',
                      'selected': true
                  };
                  $scope.model.value.push(layout);
              }
              function showPrompt(layout) {
                  layout.deletePrompt = true;
              }
              function hidePrompt(layout) {
                  layout.deletePrompt = false;
              }
              function removeLayout($index, layout) {
                  $scope.model.value.splice($index, 1);
              }
              function openIconPicker(layout) {
                  vm.iconPickerDialog = {
                      view: 'iconpicker',
                      show: true,
                      submit: function (model) {
                          if (model.color) {
                              layout.icon = model.icon + ' ' + model.color;
                          } else {
                              layout.icon = model.icon;
                          }
                          vm.focusLayoutName = true;
                          vm.iconPickerDialog.show = false;
                          vm.iconPickerDialog = null;
                      }
                  };
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.PrevalueEditors.ListViewLayoutsPreValsController', ListViewLayoutsPreValsController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.DocumentType.EditController
   * @function
   *
   * @description
   * The controller for the content type editor
   */
      (function () {
          'use strict';
          function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) {
              var vm = this;
              var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
              vm.nodeId = $scope.contentId;
              // Use whitelist of allowed file types if provided
              vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
              if (vm.acceptedFileTypes === '') {
                  // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                  vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
              }
              vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
              vm.activeDrag = false;
              vm.mediaDetailsTooltip = {};
              vm.itemsWithoutFolders = [];
              vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
              vm.acceptedMediatypes = [];
              vm.dragEnter = dragEnter;
              vm.dragLeave = dragLeave;
              vm.onFilesQueue = onFilesQueue;
              vm.onUploadComplete = onUploadComplete;
              vm.hoverMediaItemDetails = hoverMediaItemDetails;
              vm.selectContentItem = selectContentItem;
              vm.selectItem = selectItem;
              vm.selectFolder = selectFolder;
              vm.goToItem = goToItem;
              function activate() {
                  vm.itemsWithoutFolders = filterOutFolders($scope.items);
                  //no need to make another REST/DB call if this data is not used when we are browsing the bin
                  if ($scope.entityType === 'media' && !vm.isRecycleBin) {
                      mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                          vm.acceptedMediatypes = types;
                      });
                  }
              }
              function filterOutFolders(items) {
                  var newArray = [];
                  if (items && items.length) {
                      for (var i = 0; items.length > i; i++) {
                          var item = items[i];
                          var isFolder = !mediaHelper.hasFilePropertyType(item);
                          if (!isFolder) {
                              newArray.push(item);
                          }
                      }
                  }
                  return newArray;
              }
              function dragEnter(el, event) {
                  vm.activeDrag = true;
              }
              function dragLeave(el, event) {
                  vm.activeDrag = false;
              }
              function onFilesQueue() {
                  vm.activeDrag = false;
              }
              function onUploadComplete() {
                  $scope.getContent($scope.contentId);
              }
              function hoverMediaItemDetails(item, event, hover) {
                  if (hover && !vm.mediaDetailsTooltip.show) {
                      vm.mediaDetailsTooltip.event = event;
                      vm.mediaDetailsTooltip.item = item;
                      vm.mediaDetailsTooltip.show = true;
                  } else if (!hover && vm.mediaDetailsTooltip.show) {
                      vm.mediaDetailsTooltip.show = false;
                  }
              }
              function selectContentItem(item, $event, $index) {
                  listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event);
              }
              function selectItem(item, $event, $index) {
                  listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event);
              }
              function selectFolder(folder, $event, $index) {
                  listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event);
              }
              function goToItem(item, $event, $index) {
                  $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id);
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.GridLayoutController', ListViewGridLayoutController);
      }());
      (function () {
          'use strict';
          function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) {
              var vm = this;
              var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
              vm.nodeId = $scope.contentId;
              // Use whitelist of allowed file types if provided
              vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
              if (vm.acceptedFileTypes === '') {
                  // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                  vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
              }
              vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
              vm.activeDrag = false;
              vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
              vm.acceptedMediatypes = [];
              vm.selectItem = selectItem;
              vm.clickItem = clickItem;
              vm.selectAll = selectAll;
              vm.isSelectedAll = isSelectedAll;
              vm.isSortDirection = isSortDirection;
              vm.sort = sort;
              vm.dragEnter = dragEnter;
              vm.dragLeave = dragLeave;
              vm.onFilesQueue = onFilesQueue;
              vm.onUploadComplete = onUploadComplete;
              markAsSensitive();
              function activate() {
                  if ($scope.entityType === 'media') {
                      mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                          vm.acceptedMediatypes = types;
                      });
                  }
              }
              function selectAll($event) {
                  listViewHelper.selectAllItems($scope.items, $scope.selection, $event);
              }
              function isSelectedAll() {
                  return listViewHelper.isSelectedAll($scope.items, $scope.selection);
              }
              function selectItem(selectedItem, $index, $event) {
                  listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event);
              }
              function clickItem(item) {
                  // if item.id is 2147483647 (int.MaxValue) use item.key
                  $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id));
              }
              function isSortDirection(col, direction) {
                  return listViewHelper.setSortingDirection(col, direction, $scope.options);
              }
              function sort(field, allow, isSystem) {
                  if (allow) {
                      $scope.options.orderBySystemField = isSystem;
                      listViewHelper.setSorting(field, allow, $scope.options);
                      $scope.getContent($scope.contentId);
                  }
              }
              // Dropzone upload functions
              function dragEnter(el, event) {
                  vm.activeDrag = true;
              }
              function dragLeave(el, event) {
                  vm.activeDrag = false;
              }
              function onFilesQueue() {
                  vm.activeDrag = false;
              }
              function onUploadComplete() {
                  $scope.getContent($scope.contentId);
              }
              function markAsSensitive() {
                  angular.forEach($scope.options.includeProperties, function (option) {
                      option.isSensitive = false;
                      angular.forEach($scope.items, function (item) {
                          angular.forEach(item.properties, function (property) {
                              if (option.alias === property.alias) {
                                  option.isSensitive = property.isSensitive;
                              }
                          });
                      });
                  });
              }
              activate();
          }
          angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.ListLayoutController', ListViewListLayoutController);
      }());
      function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService) {
          //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
          // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
          // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
          // the list view tab entirely when it's new.
          if ($routeParams.create) {
              $scope.isNew = true;
              return;
          }
          //Now we need to check if this is for media, members or content because that will depend on the resources we use
          var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
          //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
          if ($scope.model.config.entityType && $scope.model.config.entityType === 'member' || appState.getSectionState('currentSection') === 'member') {
              $scope.entityType = 'member';
              contentResource = $injector.get('memberResource');
              getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
              getListResultsCallback = contentResource.getPagedResults;
              deleteItemCallback = contentResource.deleteByKey;
              getIdCallback = function (selected) {
                  return selected.key;
              };
              createEditUrlCallback = function (item) {
                  return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.key + '?page=' + $scope.options.pageNumber + '&listName=' + $scope.contentId;
              };
          } else {
              //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
              if ($scope.model.config.entityType && $scope.model.config.entityType === 'media' || appState.getSectionState('currentSection') === 'media') {
                  $scope.entityType = 'media';
                  contentResource = $injector.get('mediaResource');
                  getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;
              } else {
                  $scope.entityType = 'content';
                  contentResource = $injector.get('contentResource');
                  getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;
              }
              getListResultsCallback = contentResource.getChildren;
              deleteItemCallback = contentResource.deleteById;
              getIdCallback = function (selected) {
                  return selected.id;
              };
              createEditUrlCallback = function (item) {
                  return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.id + '?page=' + $scope.options.pageNumber;
              };
          }
          $scope.pagination = [];
          $scope.isNew = false;
          $scope.actionInProgress = false;
          $scope.selection = [];
          $scope.folders = [];
          $scope.listViewResultSet = {
              totalPages: 0,
              items: []
          };
          $scope.currentNodePermissions = {};
          //Just ensure we do have an editorState
          if (editorState.current) {
              //Fetch current node allowed actions for the current user
              //This is the current node & not each individual child node in the list
              var currentUserPermissions = editorState.current.allowedActions;
              //Create a nicer model rather than the funky & hard to remember permissions strings
              $scope.currentNodePermissions = {
                  'canCopy': _.contains(currentUserPermissions, 'O'),
                  //Magic Char = O
                  'canCreate': _.contains(currentUserPermissions, 'C'),
                  //Magic Char = C
                  'canDelete': _.contains(currentUserPermissions, 'D'),
                  //Magic Char = D
                  'canMove': _.contains(currentUserPermissions, 'M'),
                  //Magic Char = M                
                  'canPublish': _.contains(currentUserPermissions, 'U'),
                  //Magic Char = U
                  'canUnpublish': _.contains(currentUserPermissions, 'U')
              };
          }
          //when this is null, we don't check permissions
          $scope.buttonPermissions = null;
          //When we are dealing with 'content', we need to deal with permissions on child nodes.
          // Currently there is no real good way to 
          if ($scope.entityType === 'content') {
              var idsWithPermissions = null;
              $scope.buttonPermissions = {
                  canCopy: true,
                  canCreate: true,
                  canDelete: true,
                  canMove: true,
                  canPublish: true,
                  canUnpublish: true
              };
              $scope.$watch(function () {
                  return $scope.selection.length;
              }, function (newVal, oldVal) {
                  if (idsWithPermissions == null && newVal > 0 || idsWithPermissions != null) {
                      //get all of the selected ids
                      var ids = _.map($scope.selection, function (i) {
                          return i.id.toString();
                      });
                      //remove the dictionary items that don't have matching ids
                      var filtered = {};
                      _.each(idsWithPermissions, function (value, key, list) {
                          if (_.contains(ids, key)) {
                              filtered[key] = value;
                          }
                      });
                      idsWithPermissions = filtered;
                      //find all ids that we haven't looked up permissions for
                      var existingIds = _.keys(idsWithPermissions);
                      var missingLookup = _.map(_.difference(ids, existingIds), function (i) {
                          return Number(i);
                      });
                      if (missingLookup.length > 0) {
                          contentResource.getPermissions(missingLookup).then(function (p) {
                              $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions);
                          });
                      } else {
                          $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions);
                      }
                  }
              });
          }
          $scope.options = {
              displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1,
              pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
              pageNumber: $routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0 ? $routeParams.page : 1,
              filter: '',
              orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
              orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : 'desc',
              orderBySystemField: true,
              includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
                  {
                      alias: 'updateDate',
                      header: 'Last edited',
                      isSystem: 1
                  },
                  {
                      alias: 'updater',
                      header: 'Last edited by',
                      isSystem: 1
                  }
              ],
              layout: {
                  layouts: $scope.model.config.layouts,
                  activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts)
              },
              allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish,
              allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish,
              allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy,
              allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove,
              allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete
          };
          // Check if selected order by field is actually custom field
          for (var j = 0; j < $scope.options.includeProperties.length; j++) {
              var includedProperty = $scope.options.includeProperties[j];
              if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) {
                  $scope.options.orderBySystemField = includedProperty.isSystem === 1;
                  break;
              }
          }
          //update all of the system includeProperties to enable sorting
          _.each($scope.options.includeProperties, function (e, i) {
              //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted
              // to do that, we'd need to update the base query for content to include the content type alias column
              // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff?
              if (e.alias != 'contentTypeAlias') {
                  e.allowSorting = true;
              }
              // Another special case for members, only fields on the base table (cmsMember) can be used for sorting
              if (e.isSystem && $scope.entityType == 'member') {
                  e.allowSorting = e.alias == 'username' || e.alias == 'email';
              }
              if (e.isSystem) {
                  //localize the header
                  var key = getLocalizedKey(e.alias);
                  localizationService.localize(key).then(function (v) {
                      e.header = v;
                  });
              }
          });
          $scope.selectLayout = function (selectedLayout) {
              $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts);
          };
          function showNotificationsAndReset(err, reload, successMsg) {
              //check if response is ysod
              if (err.status && err.status >= 500) {
                  // Open ysod overlay
                  $scope.ysodOverlay = {
                      view: 'ysod',
                      error: err,
                      show: true
                  };
              }
              $timeout(function () {
                  $scope.bulkStatus = '';
                  $scope.actionInProgress = false;
              }, 500);
              if (reload === true) {
                  $scope.reloadView($scope.contentId, true);
              }
              if (err.data && angular.isArray(err.data.notifications)) {
                  for (var i = 0; i < err.data.notifications.length; i++) {
                      notificationsService.showNotification(err.data.notifications[i]);
                  }
              } else if (successMsg) {
                  localizationService.localize('bulk_done').then(function (v) {
                      notificationsService.success(v, successMsg);
                  });
              }
          }
          $scope.next = function (pageNumber) {
              $scope.options.pageNumber = pageNumber;
              $scope.reloadView($scope.contentId);
          };
          $scope.goToPage = function (pageNumber) {
              $scope.options.pageNumber = pageNumber;
              $scope.reloadView($scope.contentId);
          };
          $scope.prev = function (pageNumber) {
              $scope.options.pageNumber = pageNumber;
              $scope.reloadView($scope.contentId);
          };
          /*Loads the search results, based on parameters set in prev,next,sort and so on*/
          /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
     with simple values */
          $scope.getContent = function () {
              $scope.reloadView($scope.contentId, true);
          };
          $scope.reloadView = function (id, reloadFolders) {
              $scope.viewLoaded = false;
              listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
              getListResultsCallback(id, $scope.options).then(function (data) {
                  $scope.actionInProgress = false;
                  $scope.listViewResultSet = data;
                  //update all values for display
                  if ($scope.listViewResultSet.items) {
                      _.each($scope.listViewResultSet.items, function (e, index) {
                          setPropertyValues(e);
                      });
                  }
                  if (reloadFolders && $scope.entityType === 'media') {
                      //The folders aren't loaded - we only need to do this once since we're never changing node ids
                      mediaResource.getChildFolders($scope.contentId).then(function (folders) {
                          $scope.folders = folders;
                          $scope.viewLoaded = true;
                      });
                  } else {
                      $scope.viewLoaded = true;
                  }
                  //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
                  // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
                  // available page and then re-load again
                  if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
                      $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
                      //reload!
                      $scope.reloadView(id);
                  }
              });
          };
          var searchListView = _.debounce(function () {
              $scope.$apply(function () {
                  makeSearch();
              });
          }, 500);
          $scope.forceSearch = function (ev) {
              //13: enter
              switch (ev.keyCode) {
              case 13:
                  makeSearch();
                  break;
              }
          };
          $scope.enterSearch = function () {
              $scope.viewLoaded = false;
              searchListView();
          };
          function makeSearch() {
              if ($scope.options.filter !== null && $scope.options.filter !== undefined) {
                  $scope.options.pageNumber = 1;
                  $scope.reloadView($scope.contentId);
              }
          }
          $scope.isAnythingSelected = function () {
              if ($scope.selection.length === 0) {
                  return false;
              } else {
                  return true;
              }
          };
          $scope.selectedItemsCount = function () {
              return $scope.selection.length;
          };
          $scope.clearSelection = function () {
              listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
          };
          $scope.getIcon = function (entry) {
              return iconHelper.convertFromLegacyIcon(entry.icon);
          };
          function serial(selected, fn, getStatusMsg, index) {
              return fn(selected, index).then(function (content) {
                  index++;
                  $scope.bulkStatus = getStatusMsg(index, selected.length);
                  return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content;
              }, function (err) {
                  var reload = index > 0;
                  showNotificationsAndReset(err, reload);
                  return err;
              });
          }
          function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) {
              var selected = $scope.selection;
              if (selected.length === 0)
                  return;
              if (confirmMsg && !confirm(confirmMsg))
                  return;
              $scope.actionInProgress = true;
              $scope.bulkStatus = getStatusMsg(0, selected.length);
              return serial(selected, fn, getStatusMsg, 0).then(function (result) {
                  // executes once the whole selection has been processed
                  // in case of an error (caught by serial), result will be the error
                  if (!(result.data && angular.isArray(result.data.notifications)))
                      showNotificationsAndReset(result, true, getSuccessMsg(selected.length));
              });
          }
          $scope.delete = function () {
              var confirmDeleteText = '';
              localizationService.localize('defaultdialogs_confirmdelete').then(function (value) {
                  confirmDeleteText = value;
                  var attempt = applySelected(function (selected, index) {
                      return deleteItemCallback(getIdCallback(selected[index]));
                  }, function (count, total) {
                      var key = total === 1 ? 'bulk_deletedItemOfItem' : 'bulk_deletedItemOfItems';
                      return localizationService.localize(key, [
                          count,
                          total
                      ]);
                  }, function (total) {
                      var key = total === 1 ? 'bulk_deletedItem' : 'bulk_deletedItems';
                      return localizationService.localize(key, [total]);
                  }, confirmDeleteText + '?');
                  if (attempt) {
                      attempt.then(function () {
                          //executes if all is successful, let's sync the tree
                          var activeNode = appState.getTreeState('selectedNode');
                          if (activeNode) {
                              navigationService.reloadNode(activeNode);
                          }
                      });
                  }
              });
          };
          $scope.publish = function () {
              applySelected(function (selected, index) {
                  return contentResource.publishById(getIdCallback(selected[index]));
              }, function (count, total) {
                  var key = total === 1 ? 'bulk_publishedItemOfItem' : 'bulk_publishedItemOfItems';
                  return localizationService.localize(key, [
                      count,
                      total
                  ]);
              }, function (total) {
                  var key = total === 1 ? 'bulk_publishedItem' : 'bulk_publishedItems';
                  return localizationService.localize(key, [total]);
              });
          };
          $scope.unpublish = function () {
              applySelected(function (selected, index) {
                  return contentResource.unPublish(getIdCallback(selected[index]));
              }, function (count, total) {
                  var key = total === 1 ? 'bulk_unpublishedItemOfItem' : 'bulk_unpublishedItemOfItems';
                  return localizationService.localize(key, [
                      count,
                      total
                  ]);
              }, function (total) {
                  var key = total === 1 ? 'bulk_unpublishedItem' : 'bulk_unpublishedItems';
                  return localizationService.localize(key, [total]);
              });
          };
          $scope.move = function () {
              $scope.moveDialog = {};
              $scope.moveDialog.title = localizationService.localize('general_move');
              $scope.moveDialog.section = $scope.entityType;
              $scope.moveDialog.currentNode = $scope.contentId;
              $scope.moveDialog.view = 'move';
              $scope.moveDialog.show = true;
              $scope.moveDialog.submit = function (model) {
                  if (model.target) {
                      performMove(model.target);
                  }
                  $scope.moveDialog.show = false;
                  $scope.moveDialog = null;
              };
              $scope.moveDialog.close = function (oldModel) {
                  $scope.moveDialog.show = false;
                  $scope.moveDialog = null;
              };
          };
          function performMove(target) {
              //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return 
              // a specific value from one of the methods, so we'll have to try this way. Even though the first method
              // will fire once per every node moved, the destination path will be the same and we need to use that to sync.
              var newPath = null;
              applySelected(function (selected, index) {
                  return contentResource.move({
                      parentId: target.id,
                      id: getIdCallback(selected[index])
                  }).then(function (path) {
                      newPath = path;
                      return path;
                  });
              }, function (count, total) {
                  var key = total === 1 ? 'bulk_movedItemOfItem' : 'bulk_movedItemOfItems';
                  return localizationService.localize(key, [
                      count,
                      total
                  ]);
              }, function (total) {
                  var key = total === 1 ? 'bulk_movedItem' : 'bulk_movedItems';
                  return localizationService.localize(key, [total]);
              }).then(function () {
                  //executes if all is successful, let's sync the tree
                  if (newPath) {
                      //we need to do a double sync here: first refresh the node where the content was moved,
                      // then refresh the node where the content was moved from
                      navigationService.syncTree({
                          tree: target.nodeType ? target.nodeType : target.metaData.treeAlias,
                          path: newPath,
                          forceReload: true,
                          activate: false
                      }).then(function (args) {
                          //get the currently edited node (if any)
                          var activeNode = appState.getTreeState('selectedNode');
                          if (activeNode) {
                              navigationService.reloadNode(activeNode);
                          }
                      });
                  }
              });
          }
          $scope.copy = function () {
              $scope.copyDialog = {};
              $scope.copyDialog.title = localizationService.localize('general_copy');
              $scope.copyDialog.section = $scope.entityType;
              $scope.copyDialog.currentNode = $scope.contentId;
              $scope.copyDialog.view = 'copy';
              $scope.copyDialog.show = true;
              $scope.copyDialog.submit = function (model) {
                  if (model.target) {
                      performCopy(model.target, model.relateToOriginal);
                  }
                  $scope.copyDialog.show = false;
                  $scope.copyDialog = null;
              };
              $scope.copyDialog.close = function (oldModel) {
                  $scope.copyDialog.show = false;
                  $scope.copyDialog = null;
              };
          };
          function performCopy(target, relateToOriginal) {
              applySelected(function (selected, index) {
                  return contentResource.copy({
                      parentId: target.id,
                      id: getIdCallback(selected[index]),
                      relateToOriginal: relateToOriginal
                  });
              }, function (count, total) {
                  var key = total === 1 ? 'bulk_copiedItemOfItem' : 'bulk_copiedItemOfItems';
                  return localizationService.localize(key, [
                      count,
                      total
                  ]);
              }, function (total) {
                  var key = total === 1 ? 'bulk_copiedItem' : 'bulk_copiedItems';
                  return localizationService.localize(key, [total]);
              });
          }
          function getCustomPropertyValue(alias, properties) {
              var value = '';
              var index = 0;
              var foundAlias = false;
              for (var i = 0; i < properties.length; i++) {
                  if (properties[i].alias == alias) {
                      foundAlias = true;
                      break;
                  }
                  index++;
              }
              if (foundAlias) {
                  value = properties[index].value;
              }
              return value;
          }
          /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */
          function setPropertyValues(result) {
              //set the edit url
              result.editPath = createEditUrlCallback(result);
              _.each($scope.options.includeProperties, function (e, i) {
                  var alias = e.alias;
                  // First try to pull the value directly from the alias (e.g. updatedBy)
                  var value = result[alias];
                  // If this returns an object, look for the name property of that (e.g. owner.name)
                  if (value === Object(value)) {
                      value = value['name'];
                  }
                  // If we've got nothing yet, look at a user defined property
                  if (typeof value === 'undefined') {
                      value = getCustomPropertyValue(alias, result.properties);
                  }
                  // If we have a date, format it
                  if (isDate(value)) {
                      value = value.substring(0, value.length - 3);
                  }
                  // set what we've got on the result
                  result[alias] = value;
              });
          }
          function isDate(val) {
              if (angular.isString(val)) {
                  return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
              }
              return false;
          }
          function initView() {
              //default to root id if the id is undefined
              var id = $routeParams.id;
              if (id === undefined) {
                  id = -1;
              }
              $scope.listViewAllowedTypes = getContentTypesCallback(id);
              $scope.contentId = id;
              $scope.isTrashed = id === '-20' || id === '-21';
              $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
              $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed;
              $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || $scope.options.allowBulkUnpublish || $scope.options.allowBulkCopy || $scope.options.allowBulkMove || $scope.options.allowBulkDelete;
              $scope.reloadView($scope.contentId, true);
          }
          function getLocalizedKey(alias) {
              switch (alias) {
              case 'sortOrder':
                  return 'general_sort';
              case 'updateDate':
                  return 'content_updateDate';
              case 'updater':
                  return 'content_updatedBy';
              case 'createDate':
                  return 'content_createDate';
              case 'owner':
                  return 'content_createBy';
              case 'published':
                  return 'content_isPublished';
              case 'contentTypeAlias':
                  //TODO: Check for members
                  return $scope.entityType === 'content' ? 'content_documentType' : 'content_mediatype';
              case 'email':
                  return 'general_email';
              case 'username':
                  return 'general_username';
              }
              return alias;
          }
          function getItemKey(itemId) {
              for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
                  var item = $scope.listViewResultSet.items[i];
                  if (item.id === itemId) {
                      return item.key;
                  }
              }
          }
          //GO!
          initView();
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ListViewController', listViewController);
      function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) {
          //Get the prevalue from the correct place
          function getPrevalues() {
              if (editorState.current.preValues) {
                  return editorState.current.preValues;
              } else {
                  return listViewPrevalueHelper.getPrevalues();
              }
          }
          //Watch the prevalues
          $scope.$watch(function () {
              return _.findWhere(getPrevalues(), { key: 'includeProperties' }).value;
          }, function () {
              populateFields();
          }, true);
          //Use deep watching, otherwise we won't pick up header changes
          function populateFields() {
              // Helper to find a particular value from the list of sort by options
              function findFromSortByFields(value) {
                  return _.find($scope.sortByFields, function (e) {
                      return e.value.toLowerCase() === value.toLowerCase();
                  });
              }
              // Get list of properties assigned as columns of the list view
              var propsPreValue = _.findWhere(getPrevalues(), { key: 'includeProperties' });
              // Populate list of options for the default sort (all the columns plus then node name)
              $scope.sortByFields = [];
              $scope.sortByFields.push({
                  value: 'name',
                  name: 'Name',
                  isSystem: 1
              });
              if (propsPreValue != undefined) {
                  for (var i = 0; i < propsPreValue.value.length; i++) {
                      var value = propsPreValue.value[i];
                      $scope.sortByFields.push({
                          value: value.alias,
                          name: value.header,
                          isSystem: value.isSystem
                      });
                  }
              }
              // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared
              var systemFields = [
                  {
                      value: 'SortOrder',
                      key: 'general_sort'
                  },
                  {
                      value: 'Name',
                      key: 'general_name'
                  },
                  {
                      value: 'VersionDate',
                      key: 'content_updateDate'
                  },
                  {
                      value: 'Updater',
                      key: 'content_updatedBy'
                  },
                  {
                      value: 'CreateDate',
                      key: 'content_createDate'
                  },
                  {
                      value: 'Owner',
                      key: 'content_createBy'
                  },
                  {
                      value: 'ContentTypeAlias',
                      key: 'content_documentType'
                  },
                  {
                      value: 'Published',
                      key: 'content_isPublished'
                  },
                  {
                      value: 'Email',
                      key: 'general_email'
                  },
                  {
                      value: 'Username',
                      key: 'general_username'
                  }
              ];
              _.each(systemFields, function (e) {
                  localizationService.localize(e.key).then(function (v) {
                      var sortByListValue = findFromSortByFields(e.value);
                      if (sortByListValue) {
                          sortByListValue.name = v;
                          switch (e.value) {
                          case 'Updater':
                              e.name += ' (Content only)';
                              break;
                          case 'Published':
                              e.name += ' (Content only)';
                              break;
                          case 'Email':
                              e.name += ' (Members only)';
                              break;
                          case 'Username':
                              e.name += ' (Members only)';
                              break;
                          }
                      }
                  });
              });
              // Check existing model value is available in list and ensure a value is set
              var existingValue = findFromSortByFields($scope.model.value);
              if (existingValue) {
                  // Set the existing value
                  // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set
                  $scope.model.value = existingValue.value;
              } else {
                  // Existing value not found, set to first value
                  $scope.model.value = $scope.sortByFields[0].value;
              }
          }
      }
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.SortByListViewController', sortByPreValsController);
      //DO NOT DELETE THIS, this is in use... 
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MacroContainerController', function ($scope, dialogService, entityResource, macroService) {
          $scope.renderModel = [];
          $scope.allowOpenButton = true;
          $scope.allowRemoveButton = true;
          $scope.sortableOptions = {};
          if ($scope.model.value) {
              var macros = $scope.model.value.split('>');
              angular.forEach(macros, function (syntax, key) {
                  if (syntax && syntax.length > 10) {
                      //re-add the char we split on
                      syntax = syntax + '>';
                      var parsed = macroService.parseMacroSyntax(syntax);
                      if (!parsed) {
                          parsed = {};
                      }
                      parsed.syntax = syntax;
                      collectDetails(parsed);
                      $scope.renderModel.push(parsed);
                      setSortingState($scope.renderModel);
                  }
              });
          }
          function collectDetails(macro) {
              macro.details = '';
              macro.icon = 'icon-settings-alt';
              if (macro.macroParamsDictionary) {
                  angular.forEach(macro.macroParamsDictionary, function (value, key) {
                      macro.details += key + ': ' + value + ' ';
                  });
              }
          }
          function openDialog(index) {
              var dialogData = { allowedMacros: $scope.model.config.allowed };
              if (index !== null && $scope.renderModel[index]) {
                  var macro = $scope.renderModel[index];
                  dialogData['macroData'] = macro;
              }
              $scope.macroPickerOverlay = {};
              $scope.macroPickerOverlay.view = 'macropicker';
              $scope.macroPickerOverlay.dialogData = dialogData;
              $scope.macroPickerOverlay.show = true;
              $scope.macroPickerOverlay.submit = function (model) {
                  var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                  collectDetails(macroObject);
                  //update the raw syntax and the list...
                  if (index !== null && $scope.renderModel[index]) {
                      $scope.renderModel[index] = macroObject;
                  } else {
                      $scope.renderModel.push(macroObject);
                  }
                  setSortingState($scope.renderModel);
                  $scope.macroPickerOverlay.show = false;
                  $scope.macroPickerOverlay = null;
              };
              $scope.macroPickerOverlay.close = function (oldModel) {
                  $scope.macroPickerOverlay.show = false;
                  $scope.macroPickerOverlay = null;
              };
          }
          $scope.edit = function (index) {
              openDialog(index);
          };
          $scope.add = function () {
              if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) {
                  //cannot add more than the max
                  return;
              }
              openDialog();
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
              setSortingState($scope.renderModel);
          };
          $scope.clear = function () {
              $scope.model.value = '';
              $scope.renderModel = [];
          };
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var syntax = [];
              angular.forEach($scope.renderModel, function (value, key) {
                  syntax.push(value.syntax);
              });
              $scope.model.value = syntax.join('');
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          function setSortingState(items) {
              // disable sorting if the list only consist of one item
              if (items.length > 1) {
                  $scope.sortableOptions.disabled = false;
              } else {
                  $scope.sortableOptions.disabled = true;
              }
          }
      });
      function MacroListController($scope, entityResource) {
          $scope.items = [];
          entityResource.getAll('Macro').then(function (items) {
              _.each(items, function (i) {
                  $scope.items.push({
                      name: i.name,
                      alias: i.alias
                  });
              });
          });
      }
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.MacroList', MacroListController);
      //inject umbracos assetsServce and dialog service
      function MarkdownEditorController($scope, $element, assetsService, dialogService, angularHelper, $timeout) {
          //tell the assets service to load the markdown.editor libs from the markdown editors
          //plugin folder
          if ($scope.model.value === null || $scope.model.value === '') {
              $scope.model.value = $scope.model.config.defaultValue;
          }
          function openMediaPicker(callback) {
              $scope.mediaPickerOverlay = {};
              $scope.mediaPickerOverlay.view = 'mediaPicker';
              $scope.mediaPickerOverlay.show = true;
              $scope.mediaPickerOverlay.disableFolderSelect = true;
              $scope.mediaPickerOverlay.submit = function (model) {
                  var selectedImagePath = model.selectedImages[0].image;
                  callback(selectedImagePath);
                  $scope.mediaPickerOverlay.show = false;
                  $scope.mediaPickerOverlay = null;
              };
              $scope.mediaPickerOverlay.close = function (model) {
                  $scope.mediaPickerOverlay.show = false;
                  $scope.mediaPickerOverlay = null;
              };
          }
          assetsService.load([
              'lib/markdown/markdown.converter.js',
              'lib/markdown/markdown.sanitizer.js',
              'lib/markdown/markdown.editor.js'
          ]).then(function () {
              // we need a short delay to wait for the textbox to appear.
              setTimeout(function () {
                  //this function will execute when all dependencies have loaded
                  // but in the case that they've been previously loaded, we can only
                  // init the md editor after this digest because the DOM needs to be ready first
                  // so run the init on a timeout
                  $timeout(function () {
                      $scope.markdownEditorInitComplete = false;
                      var converter2 = new Markdown.Converter();
                      var editor2 = new Markdown.Editor(converter2, '-' + $scope.model.alias);
                      editor2.run();
                      //subscribe to the image dialog clicks
                      editor2.hooks.set('insertImageDialog', function (callback) {
                          openMediaPicker(callback);
                          return true;    // tell the editor that we'll take care of getting the image url
                      });
                      editor2.hooks.set('onPreviewRefresh', function () {
                          // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library.
                          if ($scope.model.value !== $('textarea', $element).val()) {
                              if ($scope.markdownEditorInitComplete) {
                                  //only set dirty after init load to avoid "unsaved" dialogue when we don't want it
                                  angularHelper.getCurrentForm($scope).$setDirty();
                              } else {
                                  $scope.markdownEditorInitComplete = true;
                              }
                              $scope.model.value = $('textarea', $element).val();
                          }
                      });
                  }, 200);
              });
              //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK
              assetsService.loadCss('lib/markdown/markdown.css', $scope);
          });
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MarkdownEditorController', MarkdownEditorController);
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPickerController', function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) {
          //check the pre-values for multi-picker
          var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
          var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
          var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
          if (!$scope.model.config.startNodeId) {
              userService.getCurrentUser().then(function (userData) {
                  $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                  $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
              });
          }
          function setupViewModel() {
              $scope.images = [];
              $scope.ids = [];
              $scope.isMultiPicker = multiPicker;
              if ($scope.model.value) {
                  var ids = $scope.model.value.split(',');
                  //NOTE: We need to use the entityResource NOT the mediaResource here because
                  // the mediaResource has server side auth configured for which the user must have
                  // access to the media section, if they don't they'll get auth errors. The entityResource
                  // acts differently in that it allows access if the user has access to any of the apps that
                  // might require it's use. Therefore we need to use the metaData property to get at the thumbnail
                  // value.
                  entityResource.getByIds(ids, 'Media').then(function (medias) {
                      // The service only returns item results for ids that exist (deleted items are silently ignored).
                      // This results in the picked items value to be set to contain only ids of picked items that could actually be found.
                      // Since a referenced item could potentially be restored later on, instead of changing the selected values here based
                      // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently
                      // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending
                      // on whether it is simply resaved or not.
                      // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders
                      // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before.
                      medias = _.map(ids, function (id) {
                          var found = _.find(medias, function (m) {
                              // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
                              // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
                              // compares and be completely sure it works.
                              return m.udi.toString() === id.toString() || m.id.toString() === id.toString();
                          });
                          if (found) {
                              return found;
                          } else {
                              return {
                                  name: localizationService.dictionary.mediaPicker_deletedItem,
                                  id: $scope.model.config.idType !== 'udi' ? id : null,
                                  udi: $scope.model.config.idType === 'udi' ? id : null,
                                  icon: 'icon-picture',
                                  thumbnail: null,
                                  trashed: true
                              };
                          }
                      });
                      _.each(medias, function (media, i) {
                          // if there is no thumbnail, try getting one if the media is not a placeholder item
                          if (!media.thumbnail && media.id && media.metaData) {
                              media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                          }
                          $scope.images.push(media);
                          if ($scope.model.config.idType === 'udi') {
                              $scope.ids.push(media.udi);
                          } else {
                              $scope.ids.push(media.id);
                          }
                      });
                      $scope.sync();
                  });
              }
          }
          setupViewModel();
          $scope.remove = function (index) {
              $scope.images.splice(index, 1);
              $scope.ids.splice(index, 1);
              $scope.sync();
          };
          $scope.goToItem = function (item) {
              $location.path('media/media/edit/' + item.id);
          };
          $scope.add = function () {
              $scope.mediaPickerOverlay = {
                  view: 'mediapicker',
                  title: 'Select media',
                  startNodeId: $scope.model.config.startNodeId,
                  startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
                  multiPicker: multiPicker,
                  onlyImages: onlyImages,
                  disableFolderSelect: disableFolderSelect,
                  show: true,
                  submit: function (model) {
                      _.each(model.selectedImages, function (media, i) {
                          // if there is no thumbnail, try getting one if the media is not a placeholder item
                          if (!media.thumbnail && media.id && media.metaData) {
                              media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                          }
                          $scope.images.push(media);
                          if ($scope.model.config.idType === 'udi') {
                              $scope.ids.push(media.udi);
                          } else {
                              $scope.ids.push(media.id);
                          }
                      });
                      $scope.sync();
                      $scope.mediaPickerOverlay.show = false;
                      $scope.mediaPickerOverlay = null;
                  }
              };
          };
          $scope.sortableOptions = {
              disabled: !$scope.isMultiPicker,
              items: 'li:not(.add-wrapper)',
              cancel: '.unsortable',
              update: function (e, ui) {
                  var r = [];
                  // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the
                  // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the
                  // watch do all the rest.
                  $timeout(function () {
                      angular.forEach($scope.images, function (value, key) {
                          r.push($scope.model.config.idType === 'udi' ? value.udi : value.id);
                      });
                      $scope.ids = r;
                      $scope.sync();
                  }, 500, false);
              }
          };
          $scope.sync = function () {
              $scope.model.value = $scope.ids.join();
          };
          $scope.showAdd = function () {
              if (!multiPicker) {
                  if ($scope.model.value && $scope.model.value !== '') {
                      return false;
                  }
              }
              return true;
          };
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              setupViewModel();
          };
      });
      //this controller simply tells the dialogs service to open a memberPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      function memberGroupPicker($scope, dialogService) {
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          $scope.renderModel = [];
          $scope.allowRemove = true;
          if ($scope.model.value) {
              var modelIds = $scope.model.value.split(',');
              _.each(modelIds, function (item, i) {
                  $scope.renderModel.push({
                      name: item,
                      id: item,
                      icon: 'icon-users'
                  });
              });
          }
          $scope.openMemberGroupPicker = function () {
              $scope.memberGroupPicker = {};
              $scope.memberGroupPicker.multiPicker = true;
              $scope.memberGroupPicker.view = 'memberGroupPicker';
              $scope.memberGroupPicker.show = true;
              $scope.memberGroupPicker.submit = function (model) {
                  if (model.selectedMemberGroups) {
                      _.each(model.selectedMemberGroups, function (item, i) {
                          $scope.add(item);
                      });
                  }
                  if (model.selectedMemberGroup) {
                      $scope.clear();
                      $scope.add(model.selectedMemberGroup);
                  }
                  $scope.memberGroupPicker.show = false;
                  $scope.memberGroupPicker = null;
              };
              $scope.memberGroupPicker.close = function (oldModel) {
                  $scope.memberGroupPicker.show = false;
                  $scope.memberGroupPicker = null;
              };
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
          };
          $scope.add = function (item) {
              var currIds = _.map($scope.renderModel, function (i) {
                  return i.id;
              });
              if (currIds.indexOf(item) < 0) {
                  $scope.renderModel.push({
                      name: item,
                      id: item,
                      icon: 'icon-users'
                  });
              }
          };
          $scope.clear = function () {
              $scope.renderModel = [];
          };
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var currIds = _.map($scope.renderModel, function (i) {
                  return i.id;
              });
              $scope.model.value = trim(currIds.join(), ',');
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupPickerController', memberGroupPicker);
      function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) {
          //set the available to the keys of the dictionary who's value is true
          $scope.getAvailable = function () {
              var available = [];
              for (var n in $scope.model.value) {
                  if ($scope.model.value[n] === false) {
                      available.push(n);
                  }
              }
              return available;
          };
          //set the selected to the keys of the dictionary who's value is true
          $scope.getSelected = function () {
              var selected = [];
              for (var n in $scope.model.value) {
                  if ($scope.model.value[n] === true) {
                      selected.push(n);
                  }
              }
              return selected;
          };
          $scope.addItem = function (item) {
              //keep the model up to date
              $scope.model.value[item] = true;
          };
          $scope.removeItem = function (item) {
              //keep the model up to date
              $scope.model.value[item] = false;
          };
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupController', memberGroupController);
      //this controller simply tells the dialogs service to open a memberPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper) {
          function trim(str, chr) {
              var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
              return str.replace(rgxtrim, '');
          }
          $scope.renderModel = [];
          $scope.allowRemove = true;
          var dialogOptions = {
              multiPicker: false,
              entityType: 'Member',
              section: 'member',
              treeAlias: 'member',
              filter: function (i) {
                  return i.metaData.isContainer == true;
              },
              filterCssClass: 'not-allowed',
              callback: function (data) {
                  if (angular.isArray(data)) {
                      _.each(data, function (item, i) {
                          $scope.add(item);
                      });
                  } else {
                      $scope.clear();
                      $scope.add(data);
                  }
                  angularHelper.getCurrentForm($scope).$setDirty();
              }
          };
          //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
          // pre-value config on to the dialog options
          if ($scope.model.config) {
              angular.extend(dialogOptions, $scope.model.config);
          }
          $scope.openMemberPicker = function () {
              $scope.memberPickerOverlay = dialogOptions;
              $scope.memberPickerOverlay.view = 'memberPicker';
              $scope.memberPickerOverlay.show = true;
              $scope.memberPickerOverlay.submit = function (model) {
                  if (model.selection) {
                      _.each(model.selection, function (item, i) {
                          $scope.add(item);
                      });
                  }
                  $scope.memberPickerOverlay.show = false;
                  $scope.memberPickerOverlay = null;
              };
              $scope.memberPickerOverlay.close = function (oldModel) {
                  $scope.memberPickerOverlay.show = false;
                  $scope.memberPickerOverlay = null;
              };
          };
          $scope.remove = function (index) {
              $scope.renderModel.splice(index, 1);
          };
          $scope.add = function (item) {
              var currIds = _.map($scope.renderModel, function (i) {
                  if ($scope.model.config.idType === 'udi') {
                      return i.udi;
                  } else {
                      return i.id;
                  }
              });
              var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
              if (currIds.indexOf(itemId) < 0) {
                  item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                  $scope.renderModel.push({
                      name: item.name,
                      id: item.id,
                      udi: item.udi,
                      icon: item.icon
                  });
              }
          };
          $scope.clear = function () {
              $scope.renderModel = [];
          };
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var currIds = _.map($scope.renderModel, function (i) {
                  if ($scope.model.config.idType === 'udi') {
                      return i.udi;
                  } else {
                      return i.id;
                  }
              });
              $scope.model.value = trim(currIds.join(), ',');
          });
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          //load member data
          var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
          entityResource.getByIds(modelIds, 'Member').then(function (data) {
              _.each(data, function (item, i) {
                  // set default icon if it's missing
                  item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user';
                  $scope.renderModel.push({
                      name: item.name,
                      id: item.id,
                      udi: item.udi,
                      icon: item.icon
                  });
              });
          });
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberPickerController', memberPickerController);
      function MultipleTextBoxController($scope, $timeout) {
          var backspaceHits = 0;
          $scope.sortableOptions = {
              axis: 'y',
              containment: 'parent',
              cursor: 'move',
              items: '> div.control-group',
              tolerance: 'pointer'
          };
          if (!$scope.model.value) {
              $scope.model.value = [];
          }
          //add any fields that there isn't values for
          if ($scope.model.config.min > 0) {
              for (var i = 0; i < $scope.model.config.min; i++) {
                  if (i + 1 > $scope.model.value.length) {
                      $scope.model.value.push({ value: '' });
                  }
              }
          }
          $scope.addRemoveOnKeyDown = function (event, index) {
              var txtBoxValue = $scope.model.value[index];
              event.preventDefault();
              switch (event.keyCode) {
              case 13:
                  if ($scope.model.config.max <= 0 && txtBoxValue.value || $scope.model.value.length < $scope.model.config.max && txtBoxValue.value) {
                      var newItemIndex = index + 1;
                      $scope.model.value.splice(newItemIndex, 0, { value: '' });
                      //Focus on the newly added value
                      $scope.model.value[newItemIndex].hasFocus = true;
                  }
                  break;
              case 8:
                  if ($scope.model.value.length > $scope.model.config.min) {
                      var remainder = [];
                      // Used to require an extra hit on backspace for the field to be removed
                      if (txtBoxValue.value === '') {
                          backspaceHits++;
                      } else {
                          backspaceHits = 0;
                      }
                      if (txtBoxValue.value === '' && backspaceHits === 2) {
                          for (var x = 0; x < $scope.model.value.length; x++) {
                              if (x !== index) {
                                  remainder.push($scope.model.value[x]);
                              }
                          }
                          $scope.model.value = remainder;
                          var prevItemIndex = index - 1;
                          //Set focus back on false as the directive only watches for true
                          if (prevItemIndex >= 0) {
                              $scope.model.value[prevItemIndex].hasFocus = false;
                              $timeout(function () {
                                  //Focus on the previous value
                                  $scope.model.value[prevItemIndex].hasFocus = true;
                              });
                          }
                          backspaceHits = 0;
                      }
                  }
                  break;
              default:
              }
          };
          $scope.add = function () {
              if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
                  $scope.model.value.push({ value: '' });
                  // focus new value
                  var newItemIndex = $scope.model.value.length - 1;
                  $scope.model.value[newItemIndex].hasFocus = true;
              }
          };
          $scope.remove = function (index) {
              var remainder = [];
              for (var x = 0; x < $scope.model.value.length; x++) {
                  if (x !== index) {
                      remainder.push($scope.model.value[x]);
                  }
              }
              $scope.model.value = remainder;
          };
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.MultipleTextBoxController', MultipleTextBoxController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.DocTypePickerController', [
          '$scope',
          'Umbraco.PropertyEditors.NestedContent.Resources',
          function ($scope, ncResources) {
              $scope.add = function () {
                  $scope.model.value.push({
                      // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                      // For good measure we'll also prefix the tab alias "nc"
                      ncAlias: '',
                      ncTabAlias: '',
                      nameTemplate: ''
                  });
              };
              $scope.remove = function (index) {
                  $scope.model.value.splice(index, 1);
              };
              $scope.sortableOptions = {
                  axis: 'y',
                  cursor: 'move',
                  handle: '.icon-navigation'
              };
              $scope.docTypeTabs = {};
              ncResources.getContentTypes().then(function (docTypes) {
                  $scope.model.docTypes = docTypes;
                  // Populate document type tab dictionary
                  docTypes.forEach(function (value) {
                      $scope.docTypeTabs[value.alias] = value.tabs;
                  });
              });
              if (!$scope.model.value) {
                  $scope.model.value = [];
                  $scope.add();
              }
          }
      ]);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.PropertyEditorController', [
          '$scope',
          '$interpolate',
          '$filter',
          '$timeout',
          'contentResource',
          'localizationService',
          'iconHelper',
          function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {
              //$scope.model.config.contentTypes;
              //$scope.model.config.minItems;
              //$scope.model.config.maxItems;
              //console.log($scope);
              var inited = false;
              _.each($scope.model.config.contentTypes, function (contentType) {
                  contentType.nameExp = !!contentType.nameTemplate ? $interpolate(contentType.nameTemplate) : undefined;
              });
              $scope.editIconTitle = '';
              $scope.moveIconTitle = '';
              $scope.deleteIconTitle = '';
              // localize the edit icon title
              localizationService.localize('general_edit').then(function (value) {
                  $scope.editIconTitle = value;
              });
              // localize the delete icon title
              localizationService.localize('general_delete').then(function (value) {
                  $scope.deleteIconTitle = value;
              });
              // localize the move icon title
              localizationService.localize('actions_move').then(function (value) {
                  $scope.moveIconTitle = value;
              });
              $scope.nodes = [];
              $scope.currentNode = undefined;
              $scope.realCurrentNode = undefined;
              $scope.scaffolds = undefined;
              $scope.sorting = false;
              $scope.minItems = $scope.model.config.minItems || 0;
              $scope.maxItems = $scope.model.config.maxItems || 0;
              if ($scope.maxItems == 0)
                  $scope.maxItems = 1000;
              $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
              $scope.showIcons = $scope.model.config.showIcons || true;
              $scope.wideMode = $scope.model.config.hideLabel == '1';
              $scope.overlayMenu = {
                  show: false,
                  style: {}
              };
              // helper to force the current form into the dirty state
              $scope.setDirty = function () {
                  if ($scope.propertyForm) {
                      $scope.propertyForm.$setDirty();
                  }
              };
              $scope.addNode = function (alias) {
                  var scaffold = $scope.getScaffold(alias);
                  var newNode = initNode(scaffold, null);
                  $scope.currentNode = newNode;
                  $scope.setDirty();
                  $scope.closeNodeTypePicker();
              };
              $scope.openNodeTypePicker = function (event) {
                  if ($scope.nodes.length >= $scope.maxItems) {
                      return;
                  }
                  // this could be used for future limiting on node types
                  $scope.overlayMenu.scaffolds = [];
                  _.each($scope.scaffolds, function (scaffold) {
                      $scope.overlayMenu.scaffolds.push({
                          alias: scaffold.contentTypeAlias,
                          name: scaffold.contentTypeName,
                          icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                      });
                  });
                  if ($scope.overlayMenu.scaffolds.length == 0) {
                      return;
                  }
                  if ($scope.overlayMenu.scaffolds.length == 1) {
                      // only one scaffold type - no need to display the picker
                      $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                      return;
                  }
                  $scope.overlayMenu.show = true;
              };
              $scope.closeNodeTypePicker = function () {
                  $scope.overlayMenu.show = false;
              };
              $scope.editNode = function (idx) {
                  if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
                      $scope.currentNode = undefined;
                  } else {
                      $scope.currentNode = $scope.nodes[idx];
                  }
              };
              $scope.deleteNode = function (idx) {
                  if ($scope.nodes.length > $scope.model.config.minItems) {
                      if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                          localizationService.localize('content_nestedContentDeleteItem').then(function (value) {
                              if (confirm(value)) {
                                  $scope.nodes.splice(idx, 1);
                                  $scope.setDirty();
                                  updateModel();
                              }
                          });
                      } else {
                          $scope.nodes.splice(idx, 1);
                          $scope.setDirty();
                          updateModel();
                      }
                  }
              };
              $scope.getName = function (idx) {
                  var name = 'Item ' + (idx + 1);
                  if ($scope.model.value[idx]) {
                      var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
                      if (contentType != null && contentType.nameExp) {
                          // Run the expression against the stored dictionary value, NOT the node object
                          var item = $scope.model.value[idx];
                          // Add a temporary index property
                          item['$index'] = idx + 1;
                          var newName = contentType.nameExp(item);
                          if (newName && (newName = $.trim(newName))) {
                              name = newName;
                          }
                          // Delete the index property as we don't want to persist it
                          delete item['$index'];
                      }
                  }
                  // Update the nodes actual name value
                  if ($scope.nodes[idx].name !== name) {
                      $scope.nodes[idx].name = name;
                  }
                  return name;
              };
              $scope.getIcon = function (idx) {
                  var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
                  return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : 'icon-folder';
              };
              $scope.sortableOptions = {
                  axis: 'y',
                  cursor: 'move',
                  handle: '.nested-content__icon--move',
                  start: function (ev, ui) {
                      // Yea, yea, we shouldn't modify the dom, sue me
                      $('#nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () {
                          tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                          $(this).css('visibility', 'hidden');
                      });
                      $scope.$apply(function () {
                          $scope.sorting = true;
                      });
                  },
                  update: function (ev, ui) {
                      $scope.setDirty();
                  },
                  stop: function (ev, ui) {
                      $('#nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () {
                          tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                          $(this).css('visibility', 'visible');
                      });
                      $scope.$apply(function () {
                          $scope.sorting = false;
                          updateModel();
                      });
                  }
              };
              $scope.getScaffold = function (alias) {
                  return _.find($scope.scaffolds, function (scaffold) {
                      return scaffold.contentTypeAlias == alias;
                  });
              };
              $scope.getContentTypeConfig = function (alias) {
                  return _.find($scope.model.config.contentTypes, function (contentType) {
                      return contentType.ncAlias == alias;
                  });
              };
              var notSupported = [
                  'Umbraco.CheckBoxList',
                  'Umbraco.DropDownMultiple',
                  'Umbraco.MacroContainer',
                  'Umbraco.RadioButtonList',
                  'Umbraco.MultipleTextstring',
                  'Umbraco.Tags',
                  'Umbraco.UploadField',
                  'Umbraco.ImageCropper'
              ];
              // Initialize
              var scaffoldsLoaded = 0;
              $scope.scaffolds = [];
              _.each($scope.model.config.contentTypes, function (contentType) {
                  contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                      // remove all tabs except the specified tab
                      var tab = _.find(scaffold.tabs, function (tab) {
                          return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == '');
                      });
                      scaffold.tabs = [];
                      if (tab) {
                          scaffold.tabs.push(tab);
                          angular.forEach(tab.properties, function (property) {
                              if (_.find(notSupported, function (x) {
                                      return x === property.editor;
                                  })) {
                                  property.notSupported = true;
                                  //TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                                  property.notSupportedMessage = 'Property ' + property.label + ' uses editor ' + property.editor + ' which is not supported by Nested Content.';
                              }
                          });
                      }
                      // Store the scaffold object
                      $scope.scaffolds.push(scaffold);
                      scaffoldsLoaded++;
                      initIfAllScaffoldsHaveLoaded();
                  }, function (error) {
                      scaffoldsLoaded++;
                      initIfAllScaffoldsHaveLoaded();
                  });
              });
              var initIfAllScaffoldsHaveLoaded = function () {
                  // Initialize when all scaffolds have loaded
                  if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
                      // Because we're loading the scaffolds async one at a time, we need to
                      // sort them explicitly according to the sort order defined by the data type.
                      var contentTypeAliases = [];
                      _.each($scope.model.config.contentTypes, function (contentType) {
                          contentTypeAliases.push(contentType.ncAlias);
                      });
                      $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
                          return contentTypeAliases.indexOf(s.contentTypeAlias);
                      });
                      // Convert stored nodes
                      if ($scope.model.value) {
                          for (var i = 0; i < $scope.model.value.length; i++) {
                              var item = $scope.model.value[i];
                              var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                              if (scaffold == null) {
                                  // No such scaffold - the content type might have been deleted. We need to skip it.
                                  continue;
                              }
                              initNode(scaffold, item);
                          }
                      }
                      // Enforce min items
                      if ($scope.nodes.length < $scope.model.config.minItems) {
                          for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                              $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                          }
                      }
                      // If there is only one item, set it as current node
                      if ($scope.singleMode || $scope.nodes.length == 1 && $scope.maxItems == 1) {
                          $scope.currentNode = $scope.nodes[0];
                      }
                      inited = true;
                  }
              };
              var initNode = function (scaffold, item) {
                  var node = angular.copy(scaffold);
                  node.key = item && item.key ? item.key : UUID.generate();
                  node.ncContentTypeAlias = scaffold.contentTypeAlias;
                  for (var t = 0; t < node.tabs.length; t++) {
                      var tab = node.tabs[t];
                      for (var p = 0; p < tab.properties.length; p++) {
                          var prop = tab.properties[p];
                          prop.propertyAlias = prop.alias;
                          prop.alias = $scope.model.alias + '___' + prop.alias;
                          // Force validation to occur server side as this is the
                          // only way we can have consistancy between mandatory and
                          // regex validation messages. Not ideal, but it works.
                          prop.validation = {
                              mandatory: false,
                              pattern: ''
                          };
                          if (item) {
                              if (item[prop.propertyAlias]) {
                                  prop.value = item[prop.propertyAlias];
                              }
                          }
                      }
                  }
                  $scope.nodes.push(node);
                  return node;
              };
              var updateModel = function () {
                  if ($scope.realCurrentNode) {
                      $scope.$broadcast('ncSyncVal', { key: $scope.realCurrentNode.key });
                  }
                  if (inited) {
                      var newValues = [];
                      for (var i = 0; i < $scope.nodes.length; i++) {
                          var node = $scope.nodes[i];
                          var newValue = {
                              key: node.key,
                              name: node.name,
                              ncContentTypeAlias: node.ncContentTypeAlias
                          };
                          for (var t = 0; t < node.tabs.length; t++) {
                              var tab = node.tabs[t];
                              for (var p = 0; p < tab.properties.length; p++) {
                                  var prop = tab.properties[p];
                                  if (typeof prop.value !== 'function') {
                                      newValue[prop.propertyAlias] = prop.value;
                                  }
                              }
                          }
                          newValues.push(newValue);
                      }
                      $scope.model.value = newValues;
                  }
              };
              $scope.$watch('currentNode', function (newVal) {
                  updateModel();
                  $scope.realCurrentNode = newVal;
              });
              var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                  updateModel();
              });
              $scope.$on('$destroy', function () {
                  unsubscribe();
              });
              //TODO: Move this into a shared location?
              var UUID = function () {
                  var self = {};
                  var lut = [];
                  for (var i = 0; i < 256; i++) {
                      lut[i] = (i < 16 ? '0' : '') + i.toString(16);
                  }
                  self.generate = function () {
                      var d0 = Math.random() * 4294967295 | 0;
                      var d1 = Math.random() * 4294967295 | 0;
                      var d2 = Math.random() * 4294967295 | 0;
                      var d3 = Math.random() * 4294967295 | 0;
                      return lut[d0 & 255] + lut[d0 >> 8 & 255] + lut[d0 >> 16 & 255] + lut[d0 >> 24 & 255] + '-' + lut[d1 & 255] + lut[d1 >> 8 & 255] + '-' + lut[d1 >> 16 & 15 | 64] + lut[d1 >> 24 & 255] + '-' + lut[d2 & 63 | 128] + lut[d2 >> 8 & 255] + '-' + lut[d2 >> 16 & 255] + lut[d2 >> 24 & 255] + lut[d3 & 255] + lut[d3 >> 8 & 255] + lut[d3 >> 16 & 255] + lut[d3 >> 24 & 255];
                  };
                  return self;
              }();
          }
      ]);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.RadioButtonsController', function ($scope) {
          if (angular.isObject($scope.model.config.items)) {
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  newItems.push({
                      id: keys[i],
                      sortOrder: vals[i].sortOrder,
                      value: vals[i].value
                  });
              }
              //ensure the items are sorted by the provided sort order
              newItems.sort(function (a, b) {
                  return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
              });
              //re-assign
              $scope.model.config.items = newItems;
          }
      });
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.ReadOnlyValueController
   * @function
   * 
   * @description
   * The controller for the readonlyvalue property editor. 
   *  This controller offer more functionality than just a simple label as it will be able to apply formatting to the 
   *  value to be displayed. This means that we also have to apply more complex logic of watching the model value when 
   *  it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
   *  In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of 
   *  readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
  */
      function ReadOnlyValueController($rootScope, $scope, $filter) {
          function formatDisplayValue() {
              if ($scope.model.config && angular.isArray($scope.model.config) && $scope.model.config.length > 0 && $scope.model.config[0] && $scope.model.config.filter) {
                  if ($scope.model.config.format) {
                      $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
                  } else {
                      $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
                  }
              } else {
                  $scope.displayvalue = $scope.model.value;
              }
          }
          //format the display value on init:
          formatDisplayValue();
          $scope.$watch('model.value', function (newVal, oldVal) {
              //cannot just check for !newVal because it might be an empty string which we 
              //want to look for.
              if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                  //update the display val again
                  formatDisplayValue();
              }
          });
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.ReadOnlyValueController', ReadOnlyValueController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.RelatedLinksController', function ($rootScope, $scope, dialogService, iconHelper) {
          if (!$scope.model.value) {
              $scope.model.value = [];
          }
          $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE;
          $scope.newCaption = '';
          $scope.newLink = 'http://';
          $scope.newNewWindow = false;
          $scope.newInternal = null;
          $scope.newInternalName = '';
          $scope.newInternalIcon = null;
          $scope.addExternal = true;
          $scope.currentEditLink = null;
          $scope.hasError = false;
          $scope.internal = function ($event) {
              $scope.currentEditLink = null;
              $scope.contentPickerOverlay = {};
              $scope.contentPickerOverlay.view = 'contentpicker';
              $scope.contentPickerOverlay.multiPicker = false;
              $scope.contentPickerOverlay.show = true;
              $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int';
              $scope.contentPickerOverlay.submit = function (model) {
                  select(model.selection[0]);
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $scope.contentPickerOverlay.close = function (oldModel) {
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $event.preventDefault();
          };
          $scope.selectInternal = function ($event, link) {
              $scope.currentEditLink = link;
              $scope.contentPickerOverlay = {};
              $scope.contentPickerOverlay.view = 'contentpicker';
              $scope.contentPickerOverlay.multiPicker = false;
              $scope.contentPickerOverlay.show = true;
              $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int';
              $scope.contentPickerOverlay.submit = function (model) {
                  select(model.selection[0]);
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $scope.contentPickerOverlay.close = function (oldModel) {
                  $scope.contentPickerOverlay.show = false;
                  $scope.contentPickerOverlay = null;
              };
              $event.preventDefault();
          };
          $scope.edit = function (idx) {
              for (var i = 0; i < $scope.model.value.length; i++) {
                  $scope.model.value[i].edit = false;
              }
              $scope.model.value[idx].edit = true;
          };
          $scope.saveEdit = function (idx) {
              $scope.model.value[idx].title = $scope.model.value[idx].caption;
              $scope.model.value[idx].edit = false;
          };
          $scope.delete = function (idx) {
              $scope.model.value.splice(idx, 1);
          };
          $scope.add = function ($event) {
              if (!angular.isArray($scope.model.value)) {
                  $scope.model.value = [];
              }
              if ($scope.newCaption == '') {
                  $scope.hasError = true;
              } else {
                  if ($scope.addExternal) {
                      var newExtLink = new function () {
                          this.caption = $scope.newCaption;
                          this.link = $scope.newLink;
                          this.newWindow = $scope.newNewWindow;
                          this.edit = false;
                          this.isInternal = false;
                          this.type = 'external';
                          this.title = $scope.newCaption;
                      }();
                      $scope.model.value.push(newExtLink);
                  } else {
                      var newIntLink = new function () {
                          this.caption = $scope.newCaption;
                          this.link = $scope.newInternal;
                          this.newWindow = $scope.newNewWindow;
                          this.internal = $scope.newInternal;
                          this.edit = false;
                          this.isInternal = true;
                          this.internalName = $scope.newInternalName;
                          this.internalIcon = $scope.newInternalIcon;
                          this.type = 'internal';
                          this.title = $scope.newCaption;
                      }();
                      $scope.model.value.push(newIntLink);
                  }
                  $scope.newCaption = '';
                  $scope.newLink = 'http://';
                  $scope.newNewWindow = false;
                  $scope.newInternal = null;
                  $scope.newInternalName = '';
                  $scope.newInternalIcon = null;
              }
              $event.preventDefault();
          };
          $scope.switch = function ($event) {
              $scope.addExternal = !$scope.addExternal;
              $event.preventDefault();
          };
          $scope.switchLinkType = function ($event, link) {
              link.isInternal = !link.isInternal;
              link.type = link.isInternal ? 'internal' : 'external';
              if (!link.isInternal)
                  link.link = $scope.newLink;
              $event.preventDefault();
          };
          $scope.move = function (index, direction) {
              var temp = $scope.model.value[index];
              $scope.model.value[index] = $scope.model.value[index + direction];
              $scope.model.value[index + direction] = temp;
          };
          //helper for determining if a user can add items
          $scope.canAdd = function () {
              return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible();
          };
          //helper that returns if an item can be sorted
          $scope.canSort = function () {
              return countVisible() > 1;
          };
          $scope.sortableOptions = {
              axis: 'y',
              handle: '.handle',
              cursor: 'move',
              cancel: '.no-drag',
              containment: 'parent',
              placeholder: 'sortable-placeholder',
              forcePlaceholderSize: true,
              helper: function (e, ui) {
                  // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
                  ui.children().each(function () {
                      $(this).width($(this).width());
                  });
                  return ui;
              },
              items: '> tr:not(.unsortable)',
              tolerance: 'pointer',
              update: function (e, ui) {
                  // Get the new and old index for the moved element (using the URL as the identifier)
                  var newIndex = ui.item.index();
                  var movedLinkUrl = ui.item.attr('data-link');
                  var originalIndex = getElementIndexByUrl(movedLinkUrl);
                  // Move the element in the model
                  var movedElement = $scope.model.value[originalIndex];
                  $scope.model.value.splice(originalIndex, 1);
                  $scope.model.value.splice(newIndex, 0, movedElement);
              },
              start: function (e, ui) {
                  //ui.placeholder.html("<td colspan='5'></td>");
                  // Build a placeholder cell that spans all the cells in the row: http://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
                  var cellCount = 0;
                  $('td, th', ui.helper).each(function () {
                      // For each td or th try and get it's colspan attribute, and add that or 1 to the total
                      var colspan = 1;
                      var colspanAttr = $(this).attr('colspan');
                      if (colspanAttr > 1) {
                          colspan = colspanAttr;
                      }
                      cellCount += colspan;
                  });
                  // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
                  ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(ui.item.height());
              }
          };
          //helper to count what is visible
          function countVisible() {
              return $scope.model.value.length;
          }
          function isNumeric(n) {
              return !isNaN(parseFloat(n)) && isFinite(n);
          }
          function getElementIndexByUrl(url) {
              for (var i = 0; i < $scope.model.value.length; i++) {
                  if ($scope.model.value[i].link == url) {
                      return i;
                  }
              }
              return -1;
          }
          function select(data) {
              if ($scope.currentEditLink != null) {
                  $scope.currentEditLink.internal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                  $scope.currentEditLink.internalName = data.name;
                  $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon);
                  $scope.currentEditLink.link = $scope.model.config.idType === 'udi' ? data.udi : data.id;
              } else {
                  $scope.newInternal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                  $scope.newInternalName = data.name;
                  $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon);
              }
          }
      });
      angular.module('umbraco').controller('Umbraco.PropertyEditors.RTEController', function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) {
          $scope.isLoading = true;
          //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
          // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
          // we have this mini content editor panel that can be launched with MNTP.
          var d = new Date();
          var n = d.getTime();
          $scope.textAreaHtmlId = $scope.model.alias + '_' + n + '_rte';
          var alreadyDirty = false;
          function syncContent(editor) {
              editor.save();
              angularHelper.safeApply($scope, function () {
                  $scope.model.value = editor.getContent();
              });
              if (!alreadyDirty) {
                  //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                  // the angular bits because tinymce replaces the textarea.
                  var currForm = angularHelper.getCurrentForm($scope);
                  currForm.$setDirty();
                  alreadyDirty = true;
              }
          }
          tinyMceService.configuration().then(function (tinyMceConfig) {
              //config value from general tinymce.config file
              var validElements = tinyMceConfig.validElements;
              //These are absolutely required in order for the macros to render inline
              //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
              var extendedValidElements = '@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]';
              var invalidElements = tinyMceConfig.inValidElements;
              var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
                  if (plugin.useOnFrontend) {
                      return plugin.name;
                  }
              }).join(' ');
              var editorConfig = $scope.model.config.editor;
              if (!editorConfig || angular.isString(editorConfig)) {
                  editorConfig = tinyMceService.defaultPrevalues();
              }
              //config value on the data type
              var toolbar = editorConfig.toolbar.join(' | ');
              var stylesheets = [];
              var styleFormats = [];
              var await = [];
              if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
                  editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
              }
              //queue file loading
              if (typeof tinymce === 'undefined') {
                  // Don't reload tinymce if already loaded
                  await.push(assetsService.loadJs('lib/tinymce/tinymce.min.js', $scope));
              }
              //queue rules loading
              angular.forEach(editorConfig.stylesheets, function (val, key) {
                  stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + '/' + val + '.css?' + new Date().getTime());
                  await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
                      angular.forEach(rules, function (rule) {
                          var r = {};
                          r.title = rule.name;
                          if (rule.selector[0] == '.') {
                              r.inline = 'span';
                              r.classes = rule.selector.substring(1);
                          } else if (rule.selector[0] == '#') {
                              r.inline = 'span';
                              r.attributes = { id: rule.selector.substring(1) };
                          } else if (rule.selector[0] != '.' && rule.selector.indexOf('.') > -1) {
                              var split = rule.selector.split('.');
                              r.block = split[0];
                              r.classes = rule.selector.substring(rule.selector.indexOf('.') + 1).replace('.', ' ');
                          } else if (rule.selector[0] != '#' && rule.selector.indexOf('#') > -1) {
                              var split = rule.selector.split('#');
                              r.block = split[0];
                              r.classes = rule.selector.substring(rule.selector.indexOf('#') + 1);
                          } else {
                              r.block = rule.selector;
                          }
                          styleFormats.push(r);
                      });
                  }));
              });
              //stores a reference to the editor
              var tinyMceEditor = null;
              // these languages are available for localization
              var availableLanguages = [
                  'da',
                  'de',
                  'en',
                  'en_us',
                  'fi',
                  'fr',
                  'he',
                  'it',
                  'ja',
                  'nl',
                  'no',
                  'pl',
                  'pt',
                  'ru',
                  'sv',
                  'zh'
              ];
              //define fallback language
              var language = 'en_us';
              //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc.
              //wheras tinymce is in the format of ru, de, en, en_us, etc.
              var localeId = $locale.id.replace('-', '_');
              //try matching the language using full locale format
              var languageMatch = _.find(availableLanguages, function (o) {
                  return o === localeId;
              });
              //if no matches, try matching using only the language
              if (languageMatch === undefined) {
                  var localeParts = localeId.split('_');
                  languageMatch = _.find(availableLanguages, function (o) {
                      return o === localeParts[0];
                  });
              }
              //if a match was found - set the language
              if (languageMatch !== undefined) {
                  language = languageMatch;
              }
              //wait for queue to end
              $q.all(await).then(function () {
                  //create a baseline Config to exten upon
                  var baseLineConfigObj = {
                      mode: 'exact',
                      skin: 'umbraco',
                      plugins: plugins,
                      valid_elements: validElements,
                      invalid_elements: invalidElements,
                      extended_valid_elements: extendedValidElements,
                      menubar: false,
                      statusbar: false,
                      relative_urls: false,
                      height: editorConfig.dimensions.height,
                      width: editorConfig.dimensions.width,
                      maxImageSize: editorConfig.maxImageSize,
                      toolbar: toolbar,
                      content_css: stylesheets,
                      style_formats: styleFormats,
                      language: language,
                      //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix
                      cache_suffix: '?umb__rnd=' + Umbraco.Sys.ServerVariables.application.cacheBuster
                  };
                  if (tinyMceConfig.customConfig) {
                      //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to
                      // convert it to json instead of having it as a string since this is what tinymce requires
                      for (var i in tinyMceConfig.customConfig) {
                          var val = tinyMceConfig.customConfig[i];
                          if (val) {
                              val = val.toString().trim();
                              if (val.detectIsJson()) {
                                  try {
                                      tinyMceConfig.customConfig[i] = JSON.parse(val);
                                      //now we need to check if this custom config key is defined in our baseline, if it is we don't want to
                                      //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
                                      //if it's an object it will overwrite the baseline
                                      if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
                                          //concat it and below this concat'd array will overwrite the baseline in angular.extend
                                          tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
                                      }
                                  } catch (e) {
                                  }
                              }
                              if (val === 'true') {
                                  tinyMceConfig.customConfig[i] = true;
                              }
                              if (val === 'false') {
                                  tinyMceConfig.customConfig[i] = false;
                              }
                          }
                      }
                      angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
                  }
                  //set all the things that user configs should not be able to override
                  baseLineConfigObj.elements = $scope.textAreaHtmlId;
                  //this is the exact textarea id to replace!
                  baseLineConfigObj.setup = function (editor) {
                      //set the reference
                      tinyMceEditor = editor;
                      //enable browser based spell checking
                      editor.on('init', function (e) {
                          editor.getBody().setAttribute('spellcheck', true);
                      });
                      //We need to listen on multiple things here because of the nature of tinymce, it doesn't
                      //fire events when you think!
                      //The change event doesn't fire when content changes, only when cursor points are changed and undo points
                      //are created. the blur event doesn't fire if you insert content into the editor with a button and then
                      //press save.
                      //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
                      //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
                      //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
                      //save before the timeout elapsed.
                      //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce
                      // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers
                      // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed.
                      // see: http://issues.umbraco.org/issue/U4-4485
                      //var alreadyDirty = false;
                      //editor.on('change', function (e) {
                      //    angularHelper.safeApply($scope, function () {
                      //        $scope.model.value = editor.getContent();
                      //        if (!alreadyDirty) {
                      //            //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                      //            // the angular bits because tinymce replaces the textarea.
                      //            var currForm = angularHelper.getCurrentForm($scope);
                      //            currForm.$setDirty();
                      //            alreadyDirty = true;
                      //        }
                      //    });
                      //});
                      //when we leave the editor (maybe)
                      editor.on('blur', function (e) {
                          editor.save();
                          angularHelper.safeApply($scope, function () {
                              $scope.model.value = editor.getContent();
                          });
                      });
                      //when buttons modify content
                      editor.on('ExecCommand', function (e) {
                          syncContent(editor);
                      });
                      // Update model on keypress
                      editor.on('KeyUp', function (e) {
                          syncContent(editor);
                      });
                      // Update model on change, i.e. copy/pasted text, plugins altering content
                      editor.on('SetContent', function (e) {
                          if (!e.initial) {
                              syncContent(editor);
                          }
                      });
                      editor.on('ObjectResized', function (e) {
                          var qs = '?width=' + e.width + '&height=' + e.height;
                          var srcAttr = $(e.target).attr('src');
                          var path = srcAttr.split('?')[0];
                          $(e.target).attr('data-mce-src', path + qs);
                          syncContent(editor);
                      });
                      tinyMceService.createLinkPicker(editor, $scope, function (currentTarget, anchorElement) {
                          $scope.linkPickerOverlay = {
                              view: 'linkpicker',
                              currentTarget: currentTarget,
                              show: true,
                              submit: function (model) {
                                  tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
                                  $scope.linkPickerOverlay.show = false;
                                  $scope.linkPickerOverlay = null;
                              }
                          };
                      });
                      //Create the insert media plugin
                      tinyMceService.createMediaPicker(editor, $scope, function (currentTarget, userData) {
                          $scope.mediaPickerOverlay = {
                              currentTarget: currentTarget,
                              onlyImages: true,
                              showDetails: true,
                              disableFolderSelect: true,
                              startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                              startNodeIsVirtual: userData.startMediaIds.length !== 1,
                              view: 'mediapicker',
                              show: true,
                              submit: function (model) {
                                  tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
                                  $scope.mediaPickerOverlay.show = false;
                                  $scope.mediaPickerOverlay = null;
                              }
                          };
                      });
                      //Create the embedded plugin
                      tinyMceService.createInsertEmbeddedMedia(editor, $scope, function () {
                          $scope.embedOverlay = {
                              view: 'embed',
                              show: true,
                              submit: function (model) {
                                  tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
                                  $scope.embedOverlay.show = false;
                                  $scope.embedOverlay = null;
                              }
                          };
                      });
                      //Create the insert macro plugin
                      tinyMceService.createInsertMacro(editor, $scope, function (dialogData) {
                          $scope.macroPickerOverlay = {
                              view: 'macropicker',
                              dialogData: dialogData,
                              show: true,
                              submit: function (model) {
                                  var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                                  tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
                                  $scope.macroPickerOverlay.show = false;
                                  $scope.macroPickerOverlay = null;
                              }
                          };
                      });
                  };
                  /** Loads in the editor */
                  function loadTinyMce() {
                      //we need to add a timeout here, to force a redraw so TinyMCE can find
                      //the elements needed
                      $timeout(function () {
                          tinymce.DOM.events.domLoaded = true;
                          tinymce.init(baseLineConfigObj);
                          $scope.isLoading = false;
                      }, 200, false);
                  }
                  loadTinyMce();
                  //here we declare a special method which will be called whenever the value has changed from the server
                  //this is instead of doing a watch on the model.value = faster
                  $scope.model.onValueChanged = function (newVal, oldVal) {
                      //update the display val again if it has changed from the server;
                      //uses an empty string in the editor when the value is null
                      tinyMceEditor.setContent(newVal || '', { format: 'raw' });
                      //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
                      // is required for our plugins listening to this event to execute
                      tinyMceEditor.fire('LoadContent', null);
                  };
                  //listen for formSubmitting event (the result is callback used to remove the event subscription)
                  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.
                      $scope.model.value = tinyMceEditor ? tinyMceEditor.getContent() : null;
                  });
                  //when the element is disposed we need to unsubscribe!
                  // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
                  // element might still be there even after the modal has been hidden.
                  $scope.$on('$destroy', function () {
                      unsubscribe();
                      if (tinyMceEditor !== undefined && tinyMceEditor != null) {
                          tinyMceEditor.destroy();
                      }
                  });
              });
          });
      });
      angular.module('umbraco').controller('Umbraco.PrevalueEditors.RteController', function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) {
          var cfg = tinyMceService.defaultPrevalues();
          if ($scope.model.value) {
              if (angular.isString($scope.model.value)) {
                  $scope.model.value = cfg;
              }
          } else {
              $scope.model.value = cfg;
          }
          if (!$scope.model.value.stylesheets) {
              $scope.model.value.stylesheets = [];
          }
          if (!$scope.model.value.toolbar) {
              $scope.model.value.toolbar = [];
          }
          if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
              $scope.model.value.maxImageSize = cfg.maxImageSize;
          }
          tinyMceService.configuration().then(function (config) {
              $scope.tinyMceConfig = config;
              // extend commands with properties for font-icon and if it is a custom command
              $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) {
                  var icon = getFontIcon(obj.frontEndCommand);
                  return angular.extend(obj, {
                      fontIcon: icon.name,
                      isCustom: icon.isCustom
                  });
              });
          });
          stylesheetResource.getAll().then(function (stylesheets) {
              $scope.stylesheets = stylesheets;
          });
          $scope.selected = function (cmd, alias, lookup) {
              if (lookup && angular.isArray(lookup)) {
                  cmd.selected = lookup.indexOf(alias) >= 0;
                  return cmd.selected;
              }
              return false;
          };
          $scope.selectCommand = function (command) {
              var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand);
              if (command.selected && index === -1) {
                  $scope.model.value.toolbar.push(command.frontEndCommand);
              } else if (index >= 0) {
                  $scope.model.value.toolbar.splice(index, 1);
              }
          };
          $scope.selectStylesheet = function (css) {
              var index = $scope.model.value.stylesheets.indexOf(css.name);
              if (css.selected && index === -1) {
                  $scope.model.value.stylesheets.push(css.name);
              } else if (index >= 0) {
                  $scope.model.value.stylesheets.splice(index, 1);
              }
          };
          // map properties for specific commands
          function getFontIcon(alias) {
              var icon = {
                  name: alias,
                  isCustom: false
              };
              switch (alias) {
              case 'codemirror':
                  icon.name = 'code';
                  icon.isCustom = false;
                  break;
              case 'styleselect':
              case 'fontsizeselect':
                  icon.name = 'icon-list';
                  icon.isCustom = true;
                  break;
              case 'umbembeddialog':
                  icon.name = 'icon-tv';
                  icon.isCustom = true;
                  break;
              case 'umbmediapicker':
                  icon.name = 'icon-picture';
                  icon.isCustom = true;
                  break;
              case 'umbmacro':
                  icon.name = 'icon-settings-alt';
                  icon.isCustom = true;
                  break;
              case 'umbmacro':
                  icon.name = 'icon-settings-alt';
                  icon.isCustom = true;
                  break;
              default:
                  icon.name = alias;
                  icon.isCustom = false;
              }
              return icon;
          }
          var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
              var commands = _.where($scope.tinyMceConfig.commands, { selected: true });
              $scope.model.value.toolbar = _.pluck(commands, 'frontEndCommand');
          });
          // when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
          // load TinyMCE skin which contains css for font-icons
          assetsService.loadCss('lib/tinymce/skins/umbraco/skin.min.css', $scope);
      });
      function sliderController($scope, $log, $element, assetsService, angularHelper) {
          //configure some defaults
          if (!$scope.model.config.orientation) {
              $scope.model.config.orientation = 'horizontal';
          }
          if (!$scope.model.config.enableRange) {
              $scope.model.config.enableRange = false;
          } else {
              $scope.model.config.enableRange = $scope.model.config.enableRange === '1' ? true : false;
          }
          if (!$scope.model.config.initVal1) {
              $scope.model.config.initVal1 = 0;
          } else {
              $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1);
          }
          if (!$scope.model.config.initVal2) {
              $scope.model.config.initVal2 = 0;
          } else {
              $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2);
          }
          if (!$scope.model.config.minVal) {
              $scope.model.config.minVal = 0;
          } else {
              $scope.model.config.minVal = parseFloat($scope.model.config.minVal);
          }
          if (!$scope.model.config.maxVal) {
              $scope.model.config.maxVal = 100;
          } else {
              $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal);
          }
          if (!$scope.model.config.step) {
              $scope.model.config.step = 1;
          } else {
              $scope.model.config.step = parseFloat($scope.model.config.step);
          }
          if (!$scope.model.config.handle) {
              $scope.model.config.handle = 'round';
          }
          if (!$scope.model.config.reversed) {
              $scope.model.config.reversed = false;
          } else {
              $scope.model.config.reversed = $scope.model.config.reversed === '1' ? true : false;
          }
          if (!$scope.model.config.tooltip) {
              $scope.model.config.tooltip = 'show';
          }
          if (!$scope.model.config.tooltipSplit) {
              $scope.model.config.tooltipSplit = false;
          } else {
              $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === '1' ? true : false;
          }
          if ($scope.model.config.tooltipFormat) {
              $scope.model.config.formatter = function (value) {
                  if (angular.isArray(value) && $scope.model.config.enableRange) {
                      return $scope.model.config.tooltipFormat.replace('{0}', value[0]).replace('{1}', value[1]);
                  } else {
                      return $scope.model.config.tooltipFormat.replace('{0}', value);
                  }
              };
          }
          if (!$scope.model.config.ticks) {
              $scope.model.config.ticks = [];
          } else {
              // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400]
              $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) {
                  return parseInt(item.trim());
              });
          }
          if (!$scope.model.config.ticksPositions) {
              $scope.model.config.ticksPositions = [];
          } else {
              // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100]
              $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) {
                  return parseInt(item.trim());
              });
          }
          if (!$scope.model.config.ticksLabels) {
              $scope.model.config.ticksLabels = [];
          } else {
              // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400']
              $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) {
                  return item.trim();
              });
          }
          if (!$scope.model.config.ticksSnapBounds) {
              $scope.model.config.ticksSnapBounds = 0;
          } else {
              $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds);
          }
          /** This creates the slider with the model values - it's called on startup and if the model value changes */
          function createSlider() {
              //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array
              var sliderVal = null;
              //configure the model value based on if range is enabled or not
              if ($scope.model.config.enableRange == true) {
                  //If no value saved yet - then use default value
                  //If it contains a single value - then also create a new array value
                  if (!$scope.model.value || $scope.model.value.indexOf(',') == -1) {
                      var i1 = parseFloat($scope.model.config.initVal1);
                      var i2 = parseFloat($scope.model.config.initVal2);
                      sliderVal = [
                          isNaN(i1) ? $scope.model.config.minVal : i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal,
                          isNaN(i2) ? $scope.model.config.maxVal : i2 >= i1 ? i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal : $scope.model.config.maxVal
                      ];
                  } else {
                      //this will mean it's a delimited value stored in the db, convert it to an array
                      sliderVal = _.map($scope.model.value.split(','), function (item) {
                          return parseFloat(item);
                      });
                  }
              } else {
                  //If no value saved yet - then use default value
                  if ($scope.model.value) {
                      sliderVal = parseFloat($scope.model.value);
                  } else {
                      sliderVal = $scope.model.config.initVal1;
                  }
              }
              // Initialise model value if not set
              if (!$scope.model.value) {
                  setModelValueFromSlider(sliderVal);
              }
              //initiate slider, add event handler and get the instance reference (stored in data)
              var slider = $element.find('.slider-item').bootstrapSlider({
                  max: $scope.model.config.maxVal,
                  min: $scope.model.config.minVal,
                  orientation: $scope.model.config.orientation,
                  selection: $scope.model.config.reversed ? 'after' : 'before',
                  step: $scope.model.config.step,
                  precision: $scope.model.config.precision,
                  tooltip: $scope.model.config.tooltip,
                  tooltip_split: $scope.model.config.tooltipSplit,
                  tooltip_position: $scope.model.config.tooltipPosition,
                  handle: $scope.model.config.handle,
                  reversed: $scope.model.config.reversed,
                  ticks: $scope.model.config.ticks,
                  ticks_positions: $scope.model.config.ticksPositions,
                  ticks_labels: $scope.model.config.ticksLabels,
                  ticks_snap_bounds: $scope.model.config.ticksSnapBounds,
                  formatter: $scope.model.config.formatter,
                  range: $scope.model.config.enableRange,
                  //set the slider val - we cannot do this with data- attributes when using ranges
                  value: sliderVal
              }).on('slideStop', function (e) {
                  var value = e.value;
                  angularHelper.safeApply($scope, function () {
                      setModelValueFromSlider(value);
                  });
              }).data('slider');
          }
          /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates
          the model with the currently selected slider value(s) **/
          function setModelValueFromSlider(sliderVal) {
              //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value
              if ($scope.model.config.enableRange == true) {
                  $scope.model.value = sliderVal.join(',');
              } else {
                  $scope.model.value = sliderVal.toString();
              }
          }
          //tell the assetsService to load the bootstrap slider
          //libs from the plugin folder
          assetsService.loadJs('lib/slider/js/bootstrap-slider.js').then(function () {
              createSlider();
              //here we declare a special method which will be called whenever the value has changed from the server
              //this is instead of doing a watch on the model.value = faster
              $scope.model.onValueChanged = function (newVal, oldVal) {
                  if (newVal != oldVal) {
                      createSlider();
                  }
              };
          });
          //load the separate css for the editor to avoid it blocking our js loading
          assetsService.loadCss('lib/slider/bootstrap-slider.css', $scope);
          assetsService.loadCss('lib/slider/bootstrap-slider-custom.css', $scope);
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.SliderController', sliderController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.TagsController', function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
          var $typeahead;
          $scope.isLoading = true;
          $scope.tagToAdd = '';
          assetsService.loadJs('lib/typeahead.js/typeahead.bundle.min.js', $scope).then(function () {
              $scope.isLoading = false;
              //load current value
              if ($scope.model.value) {
                  if (!$scope.model.config.storageType || $scope.model.config.storageType !== 'Json') {
                      //it is csv
                      if (!$scope.model.value) {
                          $scope.model.value = [];
                      } else {
                          if ($scope.model.value.length > 0) {
                              $scope.model.value = $scope.model.value.split(',');
                          }
                      }
                  }
              } else {
                  $scope.model.value = [];
              }
              // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
              $scope.validateMandatory = function () {
                  return {
                      isValid: !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value.length > 0,
                      errorMsg: 'Value cannot be empty',
                      errorKey: 'required'
                  };
              };
              //Helper method to add a tag on enter or on typeahead select
              function addTag(tagToAdd) {
                  if (tagToAdd != null && tagToAdd.length > 0) {
                      if ($scope.model.value.indexOf(tagToAdd) < 0) {
                          $scope.model.value.push(tagToAdd);
                          //this is required to re-validate
                          $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                      }
                  }
              }
              $scope.addTagOnEnter = function (e) {
                  var code = e.keyCode || e.which;
                  if (code == 13) {
                      //Enter keycode   
                      if ($element.find('.tags-' + $scope.model.alias).parent().find('.tt-dropdown-menu .tt-cursor').length === 0) {
                          //this is required, otherwise the html form will attempt to submit.
                          e.preventDefault();
                          $scope.addTag();
                      }
                  }
              };
              $scope.addTag = function () {
                  //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
                  //we need to use jquery because typeahead duplicates the text box
                  addTag($scope.tagToAdd);
                  $scope.tagToAdd = '';
                  //this clears the value stored in typeahead so it doesn't try to add the text again
                  // http://issues.umbraco.org/issue/U4-4947
                  $typeahead.typeahead('val', '');
              };
              $scope.removeTag = function (tag) {
                  var i = $scope.model.value.indexOf(tag);
                  if (i >= 0) {
                      $scope.model.value.splice(i, 1);
                      //this is required to re-validate
                      $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                  }
              };
              //vice versa
              $scope.model.onValueChanged = function (newVal, oldVal) {
                  //update the display val again if it has changed from the server
                  $scope.model.value = newVal;
                  if (!$scope.model.config.storageType || $scope.model.config.storageType !== 'Json') {
                      //it is csv
                      if (!$scope.model.value) {
                          $scope.model.value = [];
                      } else {
                          $scope.model.value = $scope.model.value.split(',');
                      }
                  }
              };
              //configure the tags data source
              //helper method to format the data for bloodhound
              function dataTransform(list) {
                  //transform the result to what bloodhound wants
                  var tagList = _.map(list, function (i) {
                      return { value: i.text };
                  });
                  // remove current tags from the list
                  return $.grep(tagList, function (tag) {
                      return $.inArray(tag.value, $scope.model.value) === -1;
                  });
              }
              // helper method to remove current tags
              function removeCurrentTagsFromSuggestions(suggestions) {
                  return $.grep(suggestions, function (suggestion) {
                      return $.inArray(suggestion.value, $scope.model.value) === -1;
                  });
              }
              var tagsHound = new Bloodhound({
                  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
                  queryTokenizer: Bloodhound.tokenizers.whitespace,
                  dupDetector: function (remoteMatch, localMatch) {
                      return remoteMatch['value'] == localMatch['value'];
                  },
                  //pre-fetch the tags for this category
                  prefetch: {
                      url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]),
                      //TTL = 5 minutes
                      ttl: 300000,
                      filter: dataTransform
                  },
                  //dynamically get the tags for this category (they may have changed on the server)
                  remote: {
                      url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]),
                      filter: dataTransform
                  }
              });
              tagsHound.initialize(true);
              //configure the type ahead
              $timeout(function () {
                  $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead({
                      //This causes some strangeness as it duplicates the textbox, best leave off for now.
                      hint: false,
                      highlight: true,
                      cacheKey: new Date(),
                      // Force a cache refresh each time the control is initialized
                      minLength: 1
                  }, {
                      //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
                      // name = the data set name, we'll make this the tag group name
                      name: $scope.model.config.group,
                      displayKey: 'value',
                      source: function (query, cb) {
                          tagsHound.get(query, function (suggestions) {
                              cb(removeCurrentTagsFromSuggestions(suggestions));
                          });
                      }
                  }).bind('typeahead:selected', function (obj, datum, name) {
                      angularHelper.safeApply($scope, function () {
                          addTag(datum['value']);
                          $scope.tagToAdd = '';
                          // clear the typed text
                          $typeahead.typeahead('val', '');
                      });
                  }).bind('typeahead:autocompleted', function (obj, datum, name) {
                      angularHelper.safeApply($scope, function () {
                          addTag(datum['value']);
                          $scope.tagToAdd = '';
                      });
                  }).bind('typeahead:opened', function (obj) {
                  });
              });
              $scope.$on('$destroy', function () {
                  tagsHound.clearPrefetchCache();
                  tagsHound.clearRemoteCache();
                  $element.find('.tags-' + $scope.model.alias).typeahead('destroy');
                  delete tagsHound;
              });
          });
      });
      //this controller simply tells the dialogs service to open a mediaPicker window
      //with a specified callback, this callback will receive an object with a selection on it
      angular.module('umbraco').controller('Umbraco.PropertyEditors.EmbeddedContentController', function ($rootScope, $scope, $log) {
          $scope.showForm = false;
          $scope.fakeData = [];
          $scope.create = function () {
              $scope.showForm = true;
              $scope.fakeData = angular.copy($scope.model.config.fields);
          };
          $scope.show = function () {
              $scope.showCode = true;
          };
          $scope.add = function () {
              $scope.showForm = false;
              if (!($scope.model.value instanceof Array)) {
                  $scope.model.value = [];
              }
              $scope.model.value.push(angular.copy($scope.fakeData));
              $scope.fakeData = [];
          };
      });
      function textAreaController($scope) {
          // macro parameter editor doesn't contains a config object,
          // so we create a new one to hold any properties 
          if (!$scope.model.config) {
              $scope.model.config = {};
          }
          if (!$scope.model.config.maxChars) {
              $scope.model.config.maxChars = false;
          }
          $scope.model.maxlength = false;
          if ($scope.model.config && $scope.model.config.maxChars) {
              $scope.model.maxlength = true;
              if ($scope.model.value == undefined) {
                  $scope.model.count = $scope.model.config.maxChars * 1;
              } else {
                  $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
              }
          }
          $scope.model.change = function () {
              if ($scope.model.config && $scope.model.config.maxChars) {
                  if ($scope.model.value == undefined) {
                      $scope.model.count = $scope.model.config.maxChars * 1;
                  } else {
                      $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
                  }
                  if ($scope.model.count < 0) {
                      $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1);
                      $scope.model.count = 0;
                  }
              }
          };
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.textAreaController', textAreaController);
      function textboxController($scope) {
          // macro parameter editor doesn't contains a config object,
          // so we create a new one to hold any properties
          if (!$scope.model.config) {
              $scope.model.config = {};
          }
          $scope.model.maxlength = false;
          if ($scope.model.config && $scope.model.config.maxChars) {
              $scope.model.maxlength = true;
          }
          if (!$scope.model.config.maxChars) {
              // 500 is the maximum number that can be stored
              // in the database, so set it to the max, even
              // if no max is specified in the config
              $scope.model.config.maxChars = 500;
          }
          if ($scope.model.maxlength) {
              if ($scope.model.value === undefined) {
                  $scope.model.count = $scope.model.config.maxChars * 1;
              } else {
                  $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
              }
          }
          $scope.model.change = function () {
              if ($scope.model.config && $scope.model.config.maxChars) {
                  if ($scope.model.value === undefined) {
                      $scope.model.count = $scope.model.config.maxChars * 1;
                  } else {
                      $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
                  }
                  if ($scope.model.count < 0) {
                      $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1);
                      $scope.model.count = 0;
                  }
              }
          };
      }
      angular.module('umbraco').controller('Umbraco.PropertyEditors.textboxController', textboxController);
      angular.module('umbraco').controller('Umbraco.PropertyEditors.UrlListController', function ($rootScope, $scope, $filter) {
          function formatDisplayValue() {
              if (angular.isArray($scope.model.value)) {
                  //it's the json value
                  $scope.renderModel = _.map($scope.model.value, function (item) {
                      return {
                          url: item.url,
                          linkText: item.linkText,
                          urlTarget: item.target ? item.target : '_blank',
                          icon: item.icon ? item.icon : 'icon-out'
                      };
                  });
              } else {
                  //it's the default csv value
                  $scope.renderModel = _.map($scope.model.value.split(','), function (item) {
                      return {
                          url: item,
                          linkText: '',
                          urlTarget: $scope.config && $scope.config.target ? $scope.config.target : '_blank',
                          icon: $scope.config && $scope.config.icon ? $scope.config.icon : 'icon-out'
                      };
                  });
              }
          }
          $scope.getUrl = function (valueUrl) {
              if (valueUrl.indexOf('/') >= 0) {
                  return valueUrl;
              }
              return '#';
          };
          formatDisplayValue();
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again
              formatDisplayValue();
          };
      });
      (function () {
          'use strict';
          function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) {
              var vm = this;
              var node = $scope.dialogOptions.currentNode;
              var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
              vm.creatingFolder = false;
              vm.folderName = '';
              vm.createFolderError = '';
              vm.fileExtension = '';
              vm.createFile = createFile;
              vm.showCreateFolder = showCreateFolder;
              vm.createFolder = createFolder;
              function createFile() {
                  $location.path('/settings/scripts/edit/' + node.id).search('create', 'true');
                  navigationService.hideMenu();
              }
              function showCreateFolder() {
                  vm.creatingFolder = true;
              }
              function createFolder(form) {
                  if (formHelper.submitForm({
                          scope: $scope,
                          formCtrl: form,
                          statusMessage: localizeCreateFolder
                      })) {
                      codefileResource.createContainer('scripts', node.id, vm.folderName).then(function (saved) {
                          navigationService.hideMenu();
                          navigationService.syncTree({
                              tree: 'scripts',
                              path: saved.path,
                              forceReload: true,
                              activate: true
                          });
                          formHelper.resetForm({ scope: $scope });
                          var section = appState.getSectionState('currentSection');
                      }, function (err) {
                          vm.createFolderError = err;
                          formHelper.showNotifications(err.data);
                      });
                  }
              }
          }
          angular.module('umbraco').controller('Umbraco.Editors.Scripts.CreateController', ScriptsCreateController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Scripts.DeleteController
   * @function
   *
   * @description
   * The controller for deleting scripts
   */
      function ScriptsDeleteController($scope, codefileResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              codefileResource.deleteByPath('scripts', $scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Scripts.DeleteController', ScriptsDeleteController);
      (function () {
          'use strict';
          function ScriptsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) {
              var vm = this;
              var currentPosition = null;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.page.loading = true;
              vm.page.menu = {};
              vm.page.menu.currentSection = appState.getSectionState('currentSection');
              vm.page.menu.currentNode = null;
              vm.page.saveButtonState = 'init';
              //Used to toggle the keyboard shortcut modal
              //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
              vm.showKeyboardShortcut = false;
              //Keyboard shortcuts for help dialog
              vm.page.keyboardShortcutsOverview = [];
              vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
              vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
              vm.script = {};
              // bind functions to view model
              vm.save = save;
              /* Function bound to view model */
              function save() {
                  vm.page.saveButtonState = 'busy';
                  vm.script.content = vm.editor.getValue();
                  contentEditingHelper.contentEditorPerformSave({
                      statusMessage: localizeSaving,
                      saveMethod: codefileResource.save,
                      scope: $scope,
                      content: vm.script,
                      // We do not redirect on failure for scripts - this is because it is not possible to actually save the script
                      // when server side validation fails - as opposed to content where we are capable of saving the content
                      // item if server side validation fails
                      redirectOnFailure: false,
                      rebindCallback: function (orignal, saved) {
                      }
                  }).then(function (saved) {
                      localizationService.localizeMany([
                          'speechBubbles_fileSavedHeader',
                          'speechBubbles_fileSavedText'
                      ]).then(function (data) {
                          var header = data[0];
                          var message = data[1];
                          notificationsService.success(header, message);
                      });
                      //check if the name changed, if so we need to redirect
                      if (vm.script.id !== saved.id) {
                          contentEditingHelper.redirectToRenamedContent(saved.id);
                      } else {
                          vm.page.saveButtonState = 'success';
                          vm.script = saved;
                          //sync state
                          editorState.set(vm.script);
                          // sync tree
                          navigationService.syncTree({
                              tree: 'scripts',
                              path: vm.script.path,
                              forceReload: true
                          }).then(function (syncArgs) {
                              vm.page.menu.currentNode = syncArgs.node;
                          });
                      }
                  }, function (err) {
                      vm.page.saveButtonState = 'error';
                      localizationService.localizeMany([
                          'speechBubbles_validationFailedHeader',
                          'speechBubbles_validationFailedMessage'
                      ]).then(function (data) {
                          var header = data[0];
                          var message = data[1];
                          notificationsService.error(header, message);
                      });
                  });
              }
              /* Local functions */
              function init() {
                  //we need to load this somewhere, for now its here.
                  assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                  if ($routeParams.create) {
                      codefileResource.getScaffold('scripts', $routeParams.id).then(function (script) {
                          ready(script, false);
                      });
                  } else {
                      codefileResource.getByPath('scripts', $routeParams.id).then(function (script) {
                          ready(script, true);
                      });
                  }
              }
              function ready(script, syncTree) {
                  vm.page.loading = false;
                  vm.script = script;
                  //sync state
                  editorState.set(vm.script);
                  if (syncTree) {
                      navigationService.syncTree({
                          tree: 'scripts',
                          path: vm.script.path,
                          forceReload: true
                      }).then(function (syncArgs) {
                          vm.page.menu.currentNode = syncArgs.node;
                      });
                  }
                  vm.aceOption = {
                      mode: 'javascript',
                      theme: 'chrome',
                      showPrintMargin: false,
                      advanced: {
                          fontSize: '14px',
                          enableSnippets: true,
                          enableBasicAutocompletion: true,
                          enableLiveAutocompletion: false
                      },
                      onLoad: function (_editor) {
                          vm.editor = _editor;
                          //Update the auto-complete method to use ctrl+alt+space
                          _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                          //Unassigns the keybinding (That was previously auto-complete)
                          //As conflicts with our own tree search shortcut
                          _editor.commands.bindKey('ctrl-space', null);
                          //TODO: Move all these keybinding config out into some helper/service
                          _editor.commands.addCommands([//Disable (alt+shift+K)
                              //Conflicts with our own show shortcuts dialog - this overrides it
                              {
                                  name: 'unSelectOrFindPrevious',
                                  bindKey: 'Alt-Shift-K',
                                  exec: function () {
                                      //Toggle the show keyboard shortcuts overlay
                                      $scope.$apply(function () {
                                          vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                      });
                                  },
                                  readOnly: true
                              }]);
                          // initial cursor placement
                          // Keep cursor in name field if we are create a new script
                          // else set the cursor at the bottom of the code editor
                          if (!$routeParams.create) {
                              $timeout(function () {
                                  vm.editor.navigateFileEnd();
                                  vm.editor.focus();
                              });
                          }
                          vm.editor.on('change', changeAceEditor);
                      }
                  };
                  function changeAceEditor() {
                      setFormState('dirty');
                  }
                  function setFormState(state) {
                      // get the current form
                      var currentForm = angularHelper.getCurrentForm($scope);
                      // set state
                      if (state === 'dirty') {
                          currentForm.$setDirty();
                      } else if (state === 'pristine') {
                          currentForm.$setPristine();
                      }
                  }
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Scripts.EditController', ScriptsEditController);
      }());
      /**
   * @ngdoc controller
   * @name Umbraco.Editors.Templates.DeleteController
   * @function
   *
   * @description
   * The controller for the template delete dialog
   */
      function TemplatesDeleteController($scope, templateResource, treeService, navigationService) {
          $scope.performDelete = function () {
              //mark it for deletion (used in the UI)
              $scope.currentNode.loading = true;
              templateResource.deleteById($scope.currentNode.id).then(function () {
                  $scope.currentNode.loading = false;
                  //get the root node before we remove it
                  var rootNode = treeService.getTreeRoot($scope.currentNode);
                  //TODO: Need to sync tree, etc...
                  treeService.removeNode($scope.currentNode);
                  navigationService.hideMenu();
              });
          };
          $scope.cancel = function () {
              navigationService.hideDialog();
          };
      }
      angular.module('umbraco').controller('Umbraco.Editors.Templates.DeleteController', TemplatesDeleteController);
      (function () {
          'use strict';
          function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper) {
              var vm = this;
              var oldMasterTemplateAlias = null;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.page.loading = true;
              vm.templates = [];
              //menu
              vm.page.menu = {};
              vm.page.menu.currentSection = appState.getSectionState('currentSection');
              vm.page.menu.currentNode = null;
              //Used to toggle the keyboard shortcut modal
              //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
              vm.showKeyboardShortcut = false;
              //Keyboard shortcuts for help dialog
              vm.page.keyboardShortcutsOverview = [];
              vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
              vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
              vm.page.keyboardShortcutsOverview.push(templateHelper.getTemplateEditorShortcuts());
              vm.save = function () {
                  vm.page.saveButtonState = 'busy';
                  vm.template.content = vm.editor.getValue();
                  contentEditingHelper.contentEditorPerformSave({
                      statusMessage: localizeSaving,
                      saveMethod: templateResource.save,
                      scope: $scope,
                      content: vm.template,
                      //We do not redirect on failure for templates - this is because it is not possible to actually save the template
                      // type when server side validation fails - as opposed to content where we are capable of saving the content
                      // item if server side validation fails
                      redirectOnFailure: false,
                      rebindCallback: function (orignal, saved) {
                      }
                  }).then(function (saved) {
                      localizationService.localizeMany([
                          'speechBubbles_templateSavedHeader',
                          'speechBubbles_templateSavedText'
                      ]).then(function (data) {
                          var header = data[0];
                          var message = data[1];
                          notificationsService.success(header, message);
                      });
                      vm.page.saveButtonState = 'success';
                      vm.template = saved;
                      //sync state
                      editorState.set(vm.template);
                      // sync tree
                      // if master template alias has changed move the node to it's new location
                      if (oldMasterTemplateAlias !== vm.template.masterTemplateAlias) {
                          // When creating a new template the id is -1. Make sure We don't remove the root node.
                          if (vm.page.menu.currentNode.id !== '-1') {
                              // move node to new location in tree
                              //first we need to remove the node that we're working on
                              treeService.removeNode(vm.page.menu.currentNode);
                          }
                          // update stored alias to the new one so the node won't move again unless the alias is changed again
                          oldMasterTemplateAlias = vm.template.masterTemplateAlias;
                          navigationService.syncTree({
                              tree: 'templates',
                              path: vm.template.path,
                              forceReload: true,
                              activate: true
                          }).then(function (args) {
                              vm.page.menu.currentNode = args.node;
                          });
                      } else {
                          // normal tree sync
                          navigationService.syncTree({
                              tree: 'templates',
                              path: vm.template.path,
                              forceReload: true
                          }).then(function (syncArgs) {
                              vm.page.menu.currentNode = syncArgs.node;
                          });
                      }
                      // clear $dirty state on form
                      setFormState('pristine');
                  }, function (err) {
                      vm.page.saveButtonState = 'error';
                      localizationService.localizeMany([
                          'speechBubbles_validationFailedHeader',
                          'speechBubbles_validationFailedMessage'
                      ]).then(function (data) {
                          var header = data[0];
                          var message = data[1];
                          notificationsService.error(header, message);
                      });
                  });
              };
              vm.init = function () {
                  //we need to load this somewhere, for now its here.
                  assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                  //load templates - used in the master template picker
                  templateResource.getAll().then(function (templates) {
                      vm.templates = templates;
                  });
                  if ($routeParams.create) {
                      templateResource.getScaffold($routeParams.id).then(function (template) {
                          vm.ready(template);
                      });
                  } else {
                      templateResource.getById($routeParams.id).then(function (template) {
                          vm.ready(template);
                      });
                  }
              };
              vm.ready = function (template) {
                  vm.page.loading = false;
                  vm.template = template;
                  //sync state
                  editorState.set(vm.template);
                  navigationService.syncTree({
                      tree: 'templates',
                      path: vm.template.path,
                      forceReload: true
                  }).then(function (syncArgs) {
                      vm.page.menu.currentNode = syncArgs.node;
                  });
                  // save state of master template to use for comparison when syncing the tree on save
                  oldMasterTemplateAlias = angular.copy(template.masterTemplateAlias);
                  // ace configuration
                  vm.aceOption = {
                      mode: 'razor',
                      theme: 'chrome',
                      showPrintMargin: false,
                      advanced: {
                          fontSize: '14px',
                          enableSnippets: false,
                          //The Razor mode snippets are awful (Need a way to override these)
                          enableBasicAutocompletion: true,
                          enableLiveAutocompletion: false
                      },
                      onLoad: function (_editor) {
                          vm.editor = _editor;
                          //Update the auto-complete method to use ctrl+alt+space
                          _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                          //Unassigns the keybinding (That was previously auto-complete)
                          //As conflicts with our own tree search shortcut
                          _editor.commands.bindKey('ctrl-space', null);
                          // Assign new keybinding
                          _editor.commands.addCommands([
                              //Disable (alt+shift+K)
                              //Conflicts with our own show shortcuts dialog - this overrides it
                              {
                                  name: 'unSelectOrFindPrevious',
                                  bindKey: 'Alt-Shift-K',
                                  exec: function () {
                                      //Toggle the show keyboard shortcuts overlay
                                      $scope.$apply(function () {
                                          vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertUmbracoValue',
                                  bindKey: 'Alt-Shift-V',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openPageFieldOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertPartialView',
                                  bindKey: 'Alt-Shift-P',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openPartialOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertDictionary',
                                  bindKey: 'Alt-Shift-D',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openDictionaryItemOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertUmbracoMacro',
                                  bindKey: 'Alt-Shift-M',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openMacroOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertQuery',
                                  bindKey: 'Alt-Shift-Q',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openQueryBuilderOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'insertSection',
                                  bindKey: 'Alt-Shift-S',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openSectionsOverlay();
                                      });
                                  },
                                  readOnly: true
                              },
                              {
                                  name: 'chooseMasterTemplate',
                                  bindKey: 'Alt-Shift-T',
                                  exec: function () {
                                      $scope.$apply(function () {
                                          openMasterTemplateOverlay();
                                      });
                                  },
                                  readOnly: true
                              }
                          ]);
                          // initial cursor placement
                          // Keep cursor in name field if we are create a new template
                          // else set the cursor at the bottom of the code editor
                          if (!$routeParams.create) {
                              $timeout(function () {
                                  vm.editor.navigateFileEnd();
                                  vm.editor.focus();
                                  persistCurrentLocation();
                              });
                          }
                          //change on blur, focus
                          vm.editor.on('blur', persistCurrentLocation);
                          vm.editor.on('focus', persistCurrentLocation);
                          vm.editor.on('change', changeAceEditor);
                      }
                  };
              };
              vm.openPageFieldOverlay = openPageFieldOverlay;
              vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
              vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
              vm.openMacroOverlay = openMacroOverlay;
              vm.openInsertOverlay = openInsertOverlay;
              vm.openSectionsOverlay = openSectionsOverlay;
              vm.openPartialOverlay = openPartialOverlay;
              vm.openMasterTemplateOverlay = openMasterTemplateOverlay;
              vm.selectMasterTemplate = selectMasterTemplate;
              vm.getMasterTemplateName = getMasterTemplateName;
              vm.removeMasterTemplate = removeMasterTemplate;
              function openInsertOverlay() {
                  vm.insertOverlay = {
                      view: 'insert',
                      allowedTypes: {
                          macro: true,
                          dictionary: true,
                          partial: true,
                          umbracoField: true
                      },
                      hideSubmitButton: true,
                      show: true,
                      submit: function (model) {
                          switch (model.insert.type) {
                          case 'macro':
                              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                              insert(macroObject.syntax);
                              break;
                          case 'dictionary':
                              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                              insert(code);
                              break;
                          case 'partial':
                              var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name);
                              insert(code);
                              break;
                          case 'umbracoField':
                              insert(model.insert.umbracoField);
                              break;
                          }
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.insertOverlay.show = false;
                          vm.insertOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openMacroOverlay() {
                  vm.macroPickerOverlay = {
                      view: 'macropicker',
                      dialogData: {},
                      show: true,
                      title: localizationService.localize('template_insertMacro'),
                      submit: function (model) {
                          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                          insert(macroObject.syntax);
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                      },
                      close: function (oldModel) {
                          // close the dialog
                          vm.macroPickerOverlay.show = false;
                          vm.macroPickerOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openPageFieldOverlay() {
                  vm.pageFieldOverlay = {
                      submitButtonLabel: 'Insert',
                      closeButtonlabel: 'Cancel',
                      view: 'insertfield',
                      show: true,
                      title: localizationService.localize('template_insertPageField'),
                      submit: function (model) {
                          insert(model.umbracoField);
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                      },
                      close: function (model) {
                          // close the dialog
                          vm.pageFieldOverlay.show = false;
                          vm.pageFieldOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openDictionaryItemOverlay() {
                  vm.dictionaryItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'dictionary',
                      entityType: 'dictionary',
                      multiPicker: false,
                      show: true,
                      title: localizationService.localize('template_insertDictionaryItem'),
                      emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                      select: function (node) {
                          var code = templateHelper.getInsertDictionarySnippet(node.name);
                          insert(code);
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.dictionaryItemOverlay.show = false;
                          vm.dictionaryItemOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openPartialOverlay() {
                  vm.partialItemOverlay = {
                      view: 'treepicker',
                      section: 'settings',
                      treeAlias: 'partialViews',
                      entityType: 'partialView',
                      multiPicker: false,
                      show: true,
                      title: localizationService.localize('template_insertPartialView'),
                      filter: function (i) {
                          if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                              return true;
                          }
                      },
                      filterCssClass: 'not-allowed',
                      select: function (node) {
                          var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name);
                          insert(code);
                          vm.partialItemOverlay.show = false;
                          vm.partialItemOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.partialItemOverlay.show = false;
                          vm.partialItemOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openQueryBuilderOverlay() {
                  vm.queryBuilderOverlay = {
                      view: 'querybuilder',
                      show: true,
                      title: localizationService.localize('template_queryBuilder'),
                      submit: function (model) {
                          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                          insert(code);
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.queryBuilderOverlay.show = false;
                          vm.queryBuilderOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openSectionsOverlay() {
                  vm.sectionsOverlay = {
                      view: 'templatesections',
                      isMaster: vm.template.isMasterTemplate,
                      submitButtonLabel: 'Insert',
                      show: true,
                      submit: function (model) {
                          if (model.insertType === 'renderBody') {
                              var code = templateHelper.getRenderBodySnippet();
                              insert(code);
                          }
                          if (model.insertType === 'renderSection') {
                              var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
                              insert(code);
                          }
                          if (model.insertType === 'addSection') {
                              var code = templateHelper.getAddSectionSnippet(model.sectionName);
                              wrap(code);
                          }
                          vm.sectionsOverlay.show = false;
                          vm.sectionsOverlay = null;
                      },
                      close: function (model) {
                          // close dialog
                          vm.sectionsOverlay.show = false;
                          vm.sectionsOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function openMasterTemplateOverlay() {
                  // make collection of available master templates
                  var availableMasterTemplates = [];
                  // filter out the current template and the selected master template
                  angular.forEach(vm.templates, function (template) {
                      if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) {
                          availableMasterTemplates.push(template);
                      }
                  });
                  vm.masterTemplateOverlay = {
                      view: 'itempicker',
                      title: localizationService.localize('template_mastertemplate'),
                      availableItems: availableMasterTemplates,
                      show: true,
                      submit: function (model) {
                          var template = model.selectedItem;
                          if (template && template.alias) {
                              vm.template.masterTemplateAlias = template.alias;
                              setLayout(template.alias + '.cshtml');
                          } else {
                              vm.template.masterTemplateAlias = null;
                              setLayout(null);
                          }
                          vm.masterTemplateOverlay.show = false;
                          vm.masterTemplateOverlay = null;
                      },
                      close: function (oldModel) {
                          // close dialog
                          vm.masterTemplateOverlay.show = false;
                          vm.masterTemplateOverlay = null;
                          // focus editor
                          vm.editor.focus();
                      }
                  };
              }
              function selectMasterTemplate(template) {
                  if (template && template.alias) {
                      vm.template.masterTemplateAlias = template.alias;
                      setLayout(template.alias + '.cshtml');
                  } else {
                      vm.template.masterTemplateAlias = null;
                      setLayout(null);
                  }
              }
              function getMasterTemplateName(masterTemplateAlias, templates) {
                  if (masterTemplateAlias) {
                      var templateName = '';
                      angular.forEach(templates, function (template) {
                          if (template.alias === masterTemplateAlias) {
                              templateName = template.name;
                          }
                      });
                      return templateName;
                  }
              }
              function removeMasterTemplate() {
                  vm.template.masterTemplateAlias = null;
                  // call set layout with no paramters to set layout to null
                  setLayout();
              }
              function setLayout(templatePath) {
                  var templateCode = vm.editor.getValue();
                  var newValue = templatePath;
                  var layoutDefRegex = new RegExp('(@{[\\s\\S]*?Layout\\s*?=\\s*?)("[^"]*?"|null)(;[\\s\\S]*?})', 'gi');
                  if (newValue !== undefined && newValue !== '') {
                      if (layoutDefRegex.test(templateCode)) {
                          // Declaration exists, so just update it
                          templateCode = templateCode.replace(layoutDefRegex, '$1"' + newValue + '"$3');
                      } else {
                          // Declaration doesn't exist, so prepend to start of doc
                          //TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
                          templateCode = '@{\n\tLayout = "' + newValue + '";\n}\n' + templateCode;
                      }
                  } else {
                      if (layoutDefRegex.test(templateCode)) {
                          // Declaration exists, so just update it
                          templateCode = templateCode.replace(layoutDefRegex, '$1null$3');
                      }
                  }
                  vm.editor.setValue(templateCode);
                  vm.editor.clearSelection();
                  vm.editor.navigateFileStart();
                  vm.editor.focus();
                  // set form state to $dirty
                  setFormState('dirty');
              }
              function insert(str) {
                  vm.editor.focus();
                  vm.editor.moveCursorToPosition(vm.currentPosition);
                  vm.editor.insert(str);
                  // set form state to $dirty
                  setFormState('dirty');
              }
              function wrap(str) {
                  var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange());
                  str = str.replace('{0}', selectedContent);
                  vm.editor.insert(str);
                  vm.editor.focus();
                  // set form state to $dirty
                  setFormState('dirty');
              }
              function persistCurrentLocation() {
                  vm.currentPosition = vm.editor.getCursorPosition();
              }
              function changeAceEditor() {
                  setFormState('dirty');
              }
              function setFormState(state) {
                  // get the current form
                  var currentForm = angularHelper.getCurrentForm($scope);
                  // set state
                  if (state === 'dirty') {
                      currentForm.$setDirty();
                  } else if (state === 'pristine') {
                      currentForm.$setPristine();
                  }
              }
              vm.init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Templates.EditController', TemplatesEditController);
      }());
      (function () {
          'use strict';
          function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.page.rootIcon = 'icon-folder';
              vm.userGroup = {};
              vm.labels = {};
              vm.goToPage = goToPage;
              vm.openSectionPicker = openSectionPicker;
              vm.openContentPicker = openContentPicker;
              vm.openMediaPicker = openMediaPicker;
              vm.openUserPicker = openUserPicker;
              vm.removeSelectedItem = removeSelectedItem;
              vm.clearStartNode = clearStartNode;
              vm.save = save;
              vm.openGranularPermissionsPicker = openGranularPermissionsPicker;
              vm.setPermissionsForNode = setPermissionsForNode;
              function init() {
                  vm.loading = true;
                  var labelKeys = [
                      'general_cancel',
                      'defaultdialogs_selectContentStartNode',
                      'defaultdialogs_selectMediaStartNode',
                      'defaultdialogs_selectNode',
                      'general_groups',
                      'content_contentRoot',
                      'media_mediaRoot'
                  ];
                  localizationService.localizeMany(labelKeys).then(function (values) {
                      vm.labels.cancel = values[0];
                      vm.labels.selectContentStartNode = values[1];
                      vm.labels.selectMediaStartNode = values[2];
                      vm.labels.selectNode = values[3];
                      vm.labels.groups = values[4];
                      vm.labels.contentRoot = values[5];
                      vm.labels.mediaRoot = values[6];
                  });
                  localizationService.localize('general_add').then(function (name) {
                      vm.labels.add = name;
                  });
                  localizationService.localize('user_noStartNode').then(function (name) {
                      vm.labels.noStartNode = name;
                  });
                  if ($routeParams.create) {
                      // get user group scaffold
                      userGroupsResource.getUserGroupScaffold().then(function (userGroup) {
                          vm.userGroup = userGroup;
                          setSectionIcon(vm.userGroup.sections);
                          makeBreadcrumbs();
                          vm.loading = false;
                      });
                  } else {
                      // get user group
                      userGroupsResource.getUserGroup($routeParams.id).then(function (userGroup) {
                          vm.userGroup = userGroup;
                          formatGranularPermissionSelection();
                          setSectionIcon(vm.userGroup.sections);
                          makeBreadcrumbs();
                          vm.loading = false;
                      });
                  }
              }
              function save() {
                  vm.page.saveButtonState = 'busy';
                  contentEditingHelper.contentEditorPerformSave({
                      statusMessage: localizeSaving,
                      saveMethod: userGroupsResource.saveUserGroup,
                      scope: $scope,
                      content: vm.userGroup,
                      // We do not redirect on failure for users - this is because it is not possible to actually save a user
                      // when server side validation fails - as opposed to content where we are capable of saving the content
                      // item if server side validation fails
                      redirectOnFailure: false,
                      rebindCallback: function (orignal, saved) {
                      }
                  }).then(function (saved) {
                      vm.userGroup = saved;
                      formatGranularPermissionSelection();
                      setSectionIcon(vm.userGroup.sections);
                      makeBreadcrumbs();
                      vm.page.saveButtonState = 'success';
                  }, function (err) {
                      vm.page.saveButtonState = 'error';
                  });
              }
              function goToPage(ancestor) {
                  $location.path(ancestor.path).search('subview', ancestor.subView);
              }
              function openSectionPicker() {
                  vm.sectionPicker = {
                      view: 'sectionpicker',
                      selection: vm.userGroup.sections,
                      closeButtonLabel: vm.labels.cancel,
                      show: true,
                      submit: function (model) {
                          vm.sectionPicker.show = false;
                          vm.sectionPicker = null;
                      },
                      close: function (oldModel) {
                          if (oldModel.selection) {
                              vm.userGroup.sections = oldModel.selection;
                          }
                          vm.sectionPicker.show = false;
                          vm.sectionPicker = null;
                      }
                  };
              }
              function openContentPicker() {
                  vm.contentPicker = {
                      title: vm.labels.selectContentStartNode,
                      view: 'contentpicker',
                      hideSubmitButton: true,
                      hideHeader: false,
                      show: true,
                      submit: function (model) {
                          if (model.selection) {
                              vm.userGroup.contentStartNode = model.selection[0];
                              if (vm.userGroup.contentStartNode.id === '-1') {
                                  vm.userGroup.contentStartNode.name = vm.labels.contentRoot;
                                  vm.userGroup.contentStartNode.icon = 'icon-folder';
                              }
                          }
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      },
                      close: function (oldModel) {
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      }
                  };
              }
              function openMediaPicker() {
                  vm.contentPicker = {
                      title: vm.labels.selectMediaStartNode,
                      view: 'treepicker',
                      section: 'media',
                      treeAlias: 'media',
                      entityType: 'media',
                      hideSubmitButton: true,
                      hideHeader: false,
                      show: true,
                      submit: function (model) {
                          if (model.selection) {
                              vm.userGroup.mediaStartNode = model.selection[0];
                              if (vm.userGroup.mediaStartNode.id === '-1') {
                                  vm.userGroup.mediaStartNode.name = vm.labels.mediaRoot;
                                  vm.userGroup.mediaStartNode.icon = 'icon-folder';
                              }
                          }
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      },
                      close: function (oldModel) {
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      }
                  };
              }
              function openUserPicker() {
                  vm.userPicker = {
                      view: 'userpicker',
                      selection: vm.userGroup.users,
                      show: true,
                      submit: function (model) {
                          vm.userPicker.show = false;
                          vm.userPicker = null;
                      },
                      close: function (oldModel) {
                          vm.userPicker.show = false;
                          vm.userPicker = null;
                      }
                  };
              }
              /**
           * The granular permissions structure gets returned from the server in the dictionary format with each key being the permission category
           * however the list to display the permissions isn't via the dictionary way so we need to format it
           */
              function formatGranularPermissionSelection() {
                  angular.forEach(vm.userGroup.assignedPermissions, function (node) {
                      formatGranularPermissionSelectionForNode(node);
                  });
              }
              function formatGranularPermissionSelectionForNode(node) {
                  //the dictionary is assigned via node.permissions we will reformat to node.allowedPermissions
                  node.allowedPermissions = [];
                  angular.forEach(node.permissions, function (permissions, key) {
                      angular.forEach(permissions, function (p) {
                          if (p.checked) {
                              node.allowedPermissions.push(p);
                          }
                      });
                  });
              }
              function openGranularPermissionsPicker() {
                  vm.contentPicker = {
                      title: vm.labels.selectNode,
                      view: 'contentpicker',
                      hideSubmitButton: true,
                      show: true,
                      submit: function (model) {
                          if (model.selection) {
                              var node = model.selection[0];
                              //check if this is already in our selection
                              var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                  return i.id === node.id;
                              });
                              node = found ? found : node;
                              setPermissionsForNode(node);
                          }
                      },
                      close: function (oldModel) {
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      }
                  };
              }
              function setPermissionsForNode(node) {
                  //clone the current defaults to pass to the model
                  if (!node.permissions) {
                      node.permissions = angular.copy(vm.userGroup.defaultPermissions);
                  }
                  vm.nodePermissions = {
                      view: 'nodepermissions',
                      node: node,
                      show: true,
                      submit: function (model) {
                          if (model && model.node && model.node.permissions) {
                              formatGranularPermissionSelectionForNode(node);
                              if (!vm.userGroup.assignedPermissions) {
                                  vm.userGroup.assignedPermissions = [];
                              }
                              //check if this is already in our selection
                              var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                  return i.id === node.id;
                              });
                              if (!found) {
                                  vm.userGroup.assignedPermissions.push(node);
                              }
                          }
                          // close node permisssions overlay
                          vm.nodePermissions.show = false;
                          vm.nodePermissions = null;
                          // close content picker overlay
                          if (vm.contentPicker) {
                              vm.contentPicker.show = false;
                              vm.contentPicker = null;
                          }
                      },
                      close: function (oldModel) {
                          vm.nodePermissions.show = false;
                          vm.nodePermissions = null;
                      }
                  };
              }
              function removeSelectedItem(index, selection) {
                  if (selection && selection.length > 0) {
                      selection.splice(index, 1);
                  }
              }
              function clearStartNode(type) {
                  if (type === 'content') {
                      vm.userGroup.contentStartNode = null;
                  } else if (type === 'media') {
                      vm.userGroup.mediaStartNode = null;
                  }
              }
              function makeBreadcrumbs() {
                  vm.breadcrumbs = [
                      {
                          'name': vm.labels.groups,
                          'path': '/users/users/overview',
                          'subView': 'groups'
                      },
                      { 'name': vm.userGroup.name }
                  ];
              }
              function setSectionIcon(sections) {
                  angular.forEach(sections, function (section) {
                      section.icon = 'icon-section ' + section.cssclass;
                  });
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Users.GroupController', UserGroupEditController);
      }());
      (function () {
          'use strict';
          function UsersOverviewController($scope, $location, $timeout, navigationService, localizationService) {
              var vm = this;
              var usersUri = $location.search().subview;
              if (!usersUri) {
                  $location.search('subview', 'users');
                  //exit after this, we don't want to initialize anything further since this
                  //is going to change the route
                  return;
              }
              //note on the below, we dont assign a view unless it's the right route since if we did that it will load in that controller
              //for the view which is unecessary and will cause extra overhead/requests to occur
              vm.page = {};
              vm.page.name = localizationService.localize('user_userManagement');
              vm.page.navigation = [
                  {
                      'name': localizationService.localize('sections_users'),
                      'icon': 'icon-user',
                      'action': function () {
                          $location.search('subview', 'users');
                      },
                      'view': !usersUri || usersUri === 'users' ? 'views/users/views/users/users.html' : null,
                      'active': !usersUri || usersUri === 'users'
                  },
                  {
                      'name': localizationService.localize('general_groups'),
                      'icon': 'icon-users',
                      'action': function () {
                          $location.search('subview', 'groups');
                      },
                      'view': usersUri === 'groups' ? 'views/users/views/groups/groups.html' : null,
                      'active': usersUri === 'groups'
                  }
              ];
              function init() {
                  $timeout(function () {
                      navigationService.syncTree({
                          tree: 'users',
                          path: '-1'
                      });
                  });
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Users.OverviewController', UsersOverviewController);
      }());
      (function () {
          'use strict';
          function UserEditController($scope, eventsService, $q, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
              var vm = this;
              vm.page = {};
              vm.page.rootIcon = 'icon-folder';
              vm.user = { changePassword: null };
              vm.breadcrumbs = [];
              vm.avatarFile = {};
              vm.labels = {};
              vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB';
              vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
              vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
              //create the initial model for change password
              vm.changePasswordModel = {
                  config: {},
                  isChanging: false
              };
              vm.goToPage = goToPage;
              vm.openUserGroupPicker = openUserGroupPicker;
              vm.openContentPicker = openContentPicker;
              vm.openMediaPicker = openMediaPicker;
              vm.removeSelectedItem = removeSelectedItem;
              vm.disableUser = disableUser;
              vm.enableUser = enableUser;
              vm.unlockUser = unlockUser;
              vm.changeAvatar = changeAvatar;
              vm.clearAvatar = clearAvatar;
              vm.save = save;
              vm.toggleChangePassword = toggleChangePassword;
              function init() {
                  vm.loading = true;
                  var labelKeys = [
                      'general_saving',
                      'general_cancel',
                      'defaultdialogs_selectContentStartNode',
                      'defaultdialogs_selectMediaStartNode',
                      'sections_users',
                      'content_contentRoot',
                      'media_mediaRoot',
                      'user_noStartNodes'
                  ];
                  localizationService.localizeMany(labelKeys).then(function (values) {
                      vm.labels.saving = values[0];
                      vm.labels.cancel = values[1];
                      vm.labels.selectContentStartNode = values[2];
                      vm.labels.selectMediaStartNode = values[3];
                      vm.labels.users = values[4];
                      vm.labels.contentRoot = values[5];
                      vm.labels.mediaRoot = values[6];
                      vm.labels.noStartNodes = values[7];
                  });
                  // get user
                  usersResource.getUser($routeParams.id).then(function (user) {
                      vm.user = user;
                      makeBreadcrumbs(vm.user);
                      setUserDisplayState();
                      formatDatesToLocal(vm.user);
                      vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username;
                      //go get the config for the membership provider and add it to the model
                      authResource.getMembershipProviderConfig().then(function (data) {
                          vm.changePasswordModel.config = data;
                          //the user has a password if they are not states: Invited, NoCredentials
                          vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                          vm.changePasswordModel.config.disableToggle = true;
                          //this is only relavent for membership providers now (it's basically obsolete)
                          vm.changePasswordModel.config.enableReset = false;
                          //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password
                          //if the user has access to the user section. So if this editor is being access, the user of course has access to this section.
                          //the authorization check is also done on the server side when submitted.
                          vm.changePasswordModel.config.allowManuallyChangingPassword = !vm.user.isCurrentUser;
                          vm.loading = false;
                      });
                  });
              }
              function getLocalDate(date, culture, format) {
                  if (date) {
                      var dateVal;
                      var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                      var localOffset = new Date().getTimezoneOffset();
                      var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                      if (serverTimeNeedsOffsetting) {
                          dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset);
                      } else {
                          dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
                      }
                      return dateVal.locale(culture).format(format);
                  }
              }
              function toggleChangePassword() {
                  vm.changePasswordModel.isChanging = !vm.changePasswordModel.isChanging;
                  //reset it
                  vm.user.changePassword = null;
              }
              function save() {
                  if (formHelper.submitForm({
                          scope: $scope,
                          statusMessage: vm.labels.saving
                      })) {
                      //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
                      if (vm.user.changePassword) {
                          vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
                      }
                      vm.page.saveButtonState = 'busy';
                      vm.user.resetPasswordValue = null;
                      //save current nav to be restored later so that the tabs dont change
                      var currentNav = vm.user.navigation;
                      usersResource.saveUser(vm.user).then(function (saved) {
                          //if the user saved, then try to execute all extended save options
                          extendedSave(saved).then(function (result) {
                              //if all is good, then reset the form
                              formHelper.resetForm({
                                  scope: $scope,
                                  notifications: saved.notifications
                              });
                          }, function (err) {
                              //otherwise show the notifications for the user being saved
                              formHelper.showNotifications(saved);
                          });
                          vm.user = _.omit(saved, 'navigation');
                          //restore
                          vm.user.navigation = currentNav;
                          setUserDisplayState();
                          formatDatesToLocal(vm.user);
                          vm.changePasswordModel.isChanging = false;
                          //the user has a password if they are not states: Invited, NoCredentials
                          vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                          vm.page.saveButtonState = 'success';
                      }, function (err) {
                          contentEditingHelper.handleSaveError({
                              redirectOnFailure: false,
                              err: err
                          });
                          //show any notifications
                          if (err.data) {
                              formHelper.showNotifications(err.data);
                          }
                          vm.page.saveButtonState = 'error';
                      });
                  }
              }
              /**
           * Used to emit the save event and await any async operations being performed by editor extensions
           * @param {any} savedUser
           */
              function extendedSave(savedUser) {
                  //used to track any promises added by the event handlers to be awaited
                  var promises = [];
                  var args = {
                      //getPromise: getPromise,
                      user: savedUser,
                      //a promise can be added by the event handler if the handler needs an async operation to be awaited
                      addPromise: function (p) {
                          promises.push(p);
                      }
                  };
                  //emit the event
                  eventsService.emit('editors.user.editController.save', args);
                  //await all promises to complete
                  var resultPromise = $q.all(promises);
                  return resultPromise;
              }
              function goToPage(ancestor) {
                  $location.path(ancestor.path).search('subview', ancestor.subView);
              }
              function openUserGroupPicker() {
                  vm.userGroupPicker = {
                      view: 'usergrouppicker',
                      selection: vm.user.userGroups,
                      closeButtonLabel: vm.labels.cancel,
                      show: true,
                      submit: function (model) {
                          // apply changes
                          if (model.selection) {
                              vm.user.userGroups = model.selection;
                          }
                          vm.userGroupPicker.show = false;
                          vm.userGroupPicker = null;
                      },
                      close: function (oldModel) {
                          // rollback on close
                          if (oldModel.selection) {
                              vm.user.userGroups = oldModel.selection;
                          }
                          vm.userGroupPicker.show = false;
                          vm.userGroupPicker = null;
                      }
                  };
              }
              function openContentPicker() {
                  vm.contentPicker = {
                      title: vm.labels.selectContentStartNode,
                      view: 'contentpicker',
                      multiPicker: true,
                      selection: vm.user.startContentIds,
                      hideHeader: false,
                      show: true,
                      submit: function (model) {
                          // select items
                          if (model.selection) {
                              angular.forEach(model.selection, function (item) {
                                  if (item.id === '-1') {
                                      item.name = vm.labels.contentRoot;
                                      item.icon = 'icon-folder';
                                  }
                                  multiSelectItem(item, vm.user.startContentIds);
                              });
                          }
                          // close overlay
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      },
                      close: function (oldModel) {
                          // close overlay
                          vm.contentPicker.show = false;
                          vm.contentPicker = null;
                      }
                  };
              }
              function openMediaPicker() {
                  vm.mediaPicker = {
                      title: vm.labels.selectMediaStartNode,
                      view: 'treepicker',
                      section: 'media',
                      treeAlias: 'media',
                      entityType: 'media',
                      multiPicker: true,
                      hideHeader: false,
                      show: true,
                      submit: function (model) {
                          // select items
                          if (model.selection) {
                              angular.forEach(model.selection, function (item) {
                                  if (item.id === '-1') {
                                      item.name = vm.labels.mediaRoot;
                                      item.icon = 'icon-folder';
                                  }
                                  multiSelectItem(item, vm.user.startMediaIds);
                              });
                          }
                          // close overlay
                          vm.mediaPicker.show = false;
                          vm.mediaPicker = null;
                      },
                      close: function (oldModel) {
                          // close overlay
                          vm.mediaPicker.show = false;
                          vm.mediaPicker = null;
                      }
                  };
              }
              function multiSelectItem(item, selection) {
                  var found = false;
                  // check if item is already in the selected list
                  if (selection.length > 0) {
                      angular.forEach(selection, function (selectedItem) {
                          if (selectedItem.udi === item.udi) {
                              found = true;
                          }
                      });
                  }
                  // only add the selected item if it is not already selected
                  if (!found) {
                      selection.push(item);
                  }
              }
              function removeSelectedItem(index, selection) {
                  selection.splice(index, 1);
              }
              function disableUser() {
                  vm.disableUserButtonState = 'busy';
                  usersResource.disableUsers([vm.user.id]).then(function (data) {
                      vm.user.userState = 1;
                      setUserDisplayState();
                      vm.disableUserButtonState = 'success';
                      formHelper.showNotifications(data);
                  }, function (error) {
                      vm.disableUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function enableUser() {
                  vm.enableUserButtonState = 'busy';
                  usersResource.enableUsers([vm.user.id]).then(function (data) {
                      vm.user.userState = 0;
                      setUserDisplayState();
                      vm.enableUserButtonState = 'success';
                      formHelper.showNotifications(data);
                  }, function (error) {
                      vm.enableUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function unlockUser() {
                  vm.unlockUserButtonState = 'busy';
                  usersResource.unlockUsers([vm.user.id]).then(function (data) {
                      vm.user.userState = 0;
                      setUserDisplayState();
                      vm.unlockUserButtonState = 'success';
                      formHelper.showNotifications(data);
                  }, function (error) {
                      vm.unlockUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function clearAvatar() {
                  // get user
                  usersResource.clearAvatar(vm.user.id).then(function (data) {
                      vm.user.avatars = data;
                  });
              }
              function changeAvatar(files, event) {
                  if (files && files.length > 0) {
                      upload(files[0]);
                  }
              }
              ;
              function upload(file) {
                  vm.avatarFile.uploadProgress = 0;
                  Upload.upload({
                      url: umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSetAvatar', { id: vm.user.id }),
                      fields: {},
                      file: file
                  }).progress(function (evt) {
                      if (vm.avatarFile.uploadStatus !== 'done' && vm.avatarFile.uploadStatus !== 'error') {
                          // set uploading status on file
                          vm.avatarFile.uploadStatus = 'uploading';
                          // calculate progress in percentage
                          var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                          // set percentage property on file
                          vm.avatarFile.uploadProgress = progressPercentage;
                      }
                  }).success(function (data, status, headers, config) {
                      // set done status on file
                      vm.avatarFile.uploadStatus = 'done';
                      vm.avatarFile.uploadProgress = 100;
                      vm.user.avatars = data;
                  }).error(function (evt, status, headers, config) {
                      // set status done
                      vm.avatarFile.uploadStatus = 'error';
                      // If file not found, server will return a 404 and display this message
                      if (status === 404) {
                          vm.avatarFile.serverErrorMessage = 'File not found';
                      } else if (status == 400) {
                          //it's a validation error
                          vm.avatarFile.serverErrorMessage = evt.message;
                      } else {
                          //it's an unhandled error
                          //if the service returns a detailed error
                          if (evt.InnerException) {
                              vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                              //Check if its the common "too large file" exception
                              if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                  vm.avatarFile.serverErrorMessage = 'File too large to upload';
                              }
                          } else if (evt.Message) {
                              vm.avatarFile.serverErrorMessage = evt.Message;
                          }
                      }
                  });
              }
              function makeBreadcrumbs() {
                  vm.breadcrumbs = [
                      {
                          'name': vm.labels.users,
                          'path': '/users/users/overview',
                          'subView': 'users'
                      },
                      { 'name': vm.user.name }
                  ];
              }
              function setUserDisplayState() {
                  vm.user.userDisplayState = usersHelper.getUserStateFromValue(vm.user.userState);
              }
              function formatDatesToLocal(user) {
                  // get current backoffice user and format dates
                  userService.getCurrentUser().then(function (currentUser) {
                      user.formattedLastLogin = getLocalDate(user.lastLoginDate, currentUser.locale, 'LLL');
                      user.formattedLastLockoutDate = getLocalDate(user.lastLockoutDate, currentUser.locale, 'LLL');
                      user.formattedCreateDate = getLocalDate(user.createDate, currentUser.locale, 'LLL');
                      user.formattedUpdateDate = getLocalDate(user.updateDate, currentUser.locale, 'LLL');
                      user.formattedLastPasswordChangeDate = getLocalDate(user.lastPasswordChangeDate, currentUser.locale, 'LLL');
                  });
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Users.UserController', UserEditController);
      }());
      (function () {
          'use strict';
          function UserGroupsController($scope, $timeout, $location, userService, userGroupsResource, formHelper, localizationService) {
              var vm = this;
              vm.userGroups = [];
              vm.selection = [];
              vm.createUserGroup = createUserGroup;
              vm.clickUserGroup = clickUserGroup;
              vm.clearSelection = clearSelection;
              vm.selectUserGroup = selectUserGroup;
              vm.deleteUserGroups = deleteUserGroups;
              var currentUser = null;
              function onInit() {
                  vm.loading = true;
                  userService.getCurrentUser().then(function (user) {
                      currentUser = user;
                      // Get usergroups
                      userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                          // only allow editing and selection if user is member of the group or admin
                          vm.userGroups = _.map(userGroups, function (ug) {
                              return {
                                  group: ug,
                                  hasAccess: user.userGroups.indexOf(ug.alias) !== -1 || user.userGroups.indexOf('admin') !== -1
                              };
                          });
                          vm.loading = false;
                      });
                  });
              }
              function createUserGroup() {
                  // clear all query params
                  $location.search({});
                  // go to create user group
                  $location.path('users/users/group/-1').search('create', 'true');
                  ;
              }
              function clickUserGroup(userGroup) {
                  // only allow editing if user is member of the group or admin
                  if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                      return;
                  }
                  if (vm.selection.length > 0) {
                      selectUserGroup(userGroup, vm.selection);
                  } else {
                      goToUserGroup(userGroup.group.id);
                  }
              }
              function selectUserGroup(userGroup, selection, event) {
                  // Only allow selection if user is member of the group or admin
                  if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                      return;
                  }
                  // Disallow selection of the admin/translators group, the checkbox is not visible in the UI, but clicking(and thus selecting) is still possible.
                  // Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group.
                  if (userGroup.group.alias === 'admin' || userGroup.group.alias === 'translator')
                      return;
                  if (userGroup.selected) {
                      var index = selection.indexOf(userGroup.group.id);
                      selection.splice(index, 1);
                      userGroup.selected = false;
                  } else {
                      userGroup.selected = true;
                      vm.selection.push(userGroup.group.id);
                  }
                  if (event) {
                      event.preventDefault();
                      event.stopPropagation();
                  }
              }
              function deleteUserGroups() {
                  if (vm.selection.length > 0) {
                      localizationService.localize('defaultdialogs_confirmdelete').then(function (value) {
                          var confirmResponse = confirm(value);
                          if (confirmResponse === true) {
                              userGroupsResource.deleteUserGroups(vm.selection).then(function (data) {
                                  clearSelection();
                                  onInit();
                                  formHelper.showNotifications(data);
                              }, function (error) {
                                  formHelper.showNotifications(error.data);
                              });
                          }
                      });
                  }
              }
              function clearSelection() {
                  angular.forEach(vm.userGroups, function (userGroup) {
                      userGroup.selected = false;
                  });
                  vm.selection = [];
              }
              function goToUserGroup(userGroupId) {
                  $location.path('users/users/group/' + userGroupId).search('create', null);
              }
              onInit();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Users.GroupsController', UserGroupsController);
      }());
      (function () {
          'use strict';
          function UsersController($scope, $timeout, $location, $routeParams, usersResource, userGroupsResource, userService, localizationService, contentEditingHelper, usersHelper, formHelper, notificationsService, dateHelper) {
              var vm = this;
              var localizeSaving = localizationService.localize('general_saving');
              vm.page = {};
              vm.users = [];
              vm.userGroups = [];
              vm.userStates = [];
              vm.selection = [];
              vm.newUser = {};
              vm.usersOptions = {};
              vm.userSortData = [
                  {
                      label: 'Name (A-Z)',
                      key: 'Name',
                      direction: 'Ascending'
                  },
                  {
                      label: 'Name (Z-A)',
                      key: 'Name',
                      direction: 'Descending'
                  },
                  {
                      label: 'Newest',
                      key: 'CreateDate',
                      direction: 'Descending'
                  },
                  {
                      label: 'Oldest',
                      key: 'CreateDate',
                      direction: 'Ascending'
                  },
                  {
                      label: 'Last login',
                      key: 'LastLoginDate',
                      direction: 'Descending'
                  }
              ];
              angular.forEach(vm.userSortData, function (userSortData) {
                  var key = 'user_sort' + userSortData.key + userSortData.direction;
                  localizationService.localize(key).then(function (value) {
                      var reg = /^\[[\S\s]*]$/g;
                      var result = reg.test(value);
                      if (result === false) {
                          // Only translate if key exists
                          userSortData.label = value;
                      }
                  });
              });
              vm.userStatesFilter = [];
              vm.newUser.userGroups = [];
              vm.usersViewState = 'overview';
              vm.selectedBulkUserGroups = [];
              vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
              vm.allowDisableUser = true;
              vm.allowEnableUser = true;
              vm.allowUnlockUser = true;
              vm.allowSetUserGroup = true;
              vm.layouts = [
                  {
                      'icon': 'icon-thumbnails-small',
                      'path': '1',
                      'selected': true
                  },
                  {
                      'icon': 'icon-list',
                      'path': '2',
                      'selected': true
                  }
              ];
              vm.activeLayout = {
                  'icon': 'icon-thumbnails-small',
                  'path': '1',
                  'selected': true
              };
              //don't show the invite button if no email is configured
              if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) {
                  vm.defaultButton = {
                      labelKey: 'user_inviteUser',
                      handler: function () {
                          vm.setUsersViewState('inviteUser');
                      }
                  };
                  vm.subButtons = [{
                          labelKey: 'user_createUser',
                          handler: function () {
                              vm.setUsersViewState('createUser');
                          }
                      }];
              } else {
                  vm.defaultButton = {
                      labelKey: 'user_createUser',
                      handler: function () {
                          vm.setUsersViewState('createUser');
                      }
                  };
              }
              vm.toggleFilter = toggleFilter;
              vm.setUsersViewState = setUsersViewState;
              vm.selectLayout = selectLayout;
              vm.selectUser = selectUser;
              vm.clearSelection = clearSelection;
              vm.clickUser = clickUser;
              vm.disableUsers = disableUsers;
              vm.enableUsers = enableUsers;
              vm.unlockUsers = unlockUsers;
              vm.openBulkUserGroupPicker = openBulkUserGroupPicker;
              vm.openUserGroupPicker = openUserGroupPicker;
              vm.removeSelectedUserGroup = removeSelectedUserGroup;
              vm.selectAll = selectAll;
              vm.areAllSelected = areAllSelected;
              vm.searchUsers = searchUsers;
              vm.getFilterName = getFilterName;
              vm.setUserStatesFilter = setUserStatesFilter;
              vm.setUserGroupFilter = setUserGroupFilter;
              vm.setOrderByFilter = setOrderByFilter;
              vm.changePageNumber = changePageNumber;
              vm.createUser = createUser;
              vm.inviteUser = inviteUser;
              vm.getSortLabel = getSortLabel;
              vm.toggleNewUserPassword = toggleNewUserPassword;
              vm.copySuccess = copySuccess;
              vm.copyError = copyError;
              vm.goToUser = goToUser;
              function init() {
                  vm.usersOptions.orderBy = 'Name';
                  vm.usersOptions.orderDirection = 'Ascending';
                  if ($routeParams.create) {
                      setUsersViewState('createUser');
                  } else if ($routeParams.invite) {
                      setUsersViewState('inviteUser');
                  }
                  // Get users
                  getUsers();
                  // Get user groups
                  userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                      vm.userGroups = userGroups;
                  });
              }
              function getSortLabel(sortKey, sortDirection) {
                  var found = _.find(vm.userSortData, function (i) {
                      return i.key === sortKey && i.direction === sortDirection;
                  });
                  return found ? found.label : sortKey;
              }
              function toggleFilter(type) {
                  // hack: on-outside-click prevents us from closing the dropdown when clicking on another link
                  // so I had to do this manually
                  switch (type) {
                  case 'state':
                      vm.page.showStatusFilter = !vm.page.showStatusFilter;
                      vm.page.showGroupFilter = false;
                      vm.page.showOrderByFilter = false;
                      break;
                  case 'group':
                      vm.page.showGroupFilter = !vm.page.showGroupFilter;
                      vm.page.showStatusFilter = false;
                      vm.page.showOrderByFilter = false;
                      break;
                  case 'orderBy':
                      vm.page.showOrderByFilter = !vm.page.showOrderByFilter;
                      vm.page.showStatusFilter = false;
                      vm.page.showGroupFilter = false;
                      break;
                  }
              }
              function setUsersViewState(state) {
                  if (state === 'createUser') {
                      clearAddUserForm();
                      $location.search('create', 'true');
                      $location.search('invite', null);
                  } else if (state === 'inviteUser') {
                      $location.search('create', null);
                      $location.search('invite', 'true');
                  } else if (state === 'overview') {
                      $location.search('create', null);
                      $location.search('invite', null);
                  }
                  vm.usersViewState = state;
              }
              function selectLayout(selectedLayout) {
                  angular.forEach(vm.layouts, function (layout) {
                      layout.active = false;
                  });
                  selectedLayout.active = true;
                  vm.activeLayout = selectedLayout;
              }
              function selectUser(user, selection, event) {
                  // prevent the current user to be selected
                  if (!user.isCurrentUser) {
                      if (user.selected) {
                          var index = selection.indexOf(user.id);
                          selection.splice(index, 1);
                          user.selected = false;
                      } else {
                          user.selected = true;
                          vm.selection.push(user.id);
                      }
                      setBulkActions(vm.users);
                      if (event) {
                          event.preventDefault();
                          event.stopPropagation();
                      }
                  }
              }
              function clearSelection() {
                  angular.forEach(vm.users, function (user) {
                      user.selected = false;
                  });
                  vm.selection = [];
              }
              function clickUser(user) {
                  if (vm.selection.length > 0) {
                      selectUser(user, vm.selection);
                  } else {
                      goToUser(user.id);
                  }
              }
              function disableUsers() {
                  vm.disableUserButtonState = 'busy';
                  usersResource.disableUsers(vm.selection).then(function (data) {
                      // update userState
                      angular.forEach(vm.selection, function (userId) {
                          var user = getUserFromArrayById(userId, vm.users);
                          if (user) {
                              user.userState = 1;
                          }
                      });
                      // show the correct badges
                      setUserDisplayState(vm.users);
                      formHelper.showNotifications(data);
                      vm.disableUserButtonState = 'init';
                      clearSelection();
                  }, function (error) {
                      vm.disableUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function enableUsers() {
                  vm.enableUserButtonState = 'busy';
                  usersResource.enableUsers(vm.selection).then(function (data) {
                      // update userState
                      angular.forEach(vm.selection, function (userId) {
                          var user = getUserFromArrayById(userId, vm.users);
                          if (user) {
                              user.userState = 0;
                          }
                      });
                      // show the correct badges
                      setUserDisplayState(vm.users);
                      // show notification
                      formHelper.showNotifications(data);
                      vm.enableUserButtonState = 'init';
                      clearSelection();
                  }, function (error) {
                      vm.enableUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function unlockUsers() {
                  vm.unlockUserButtonState = 'busy';
                  usersResource.unlockUsers(vm.selection).then(function (data) {
                      // update userState
                      angular.forEach(vm.selection, function (userId) {
                          var user = getUserFromArrayById(userId, vm.users);
                          if (user) {
                              user.userState = 0;
                          }
                      });
                      // show the correct badges
                      setUserDisplayState(vm.users);
                      // show notification
                      formHelper.showNotifications(data);
                      vm.unlockUserButtonState = 'init';
                      clearSelection();
                  }, function (error) {
                      vm.unlockUserButtonState = 'error';
                      formHelper.showNotifications(error.data);
                  });
              }
              function getUserFromArrayById(userId, users) {
                  return _.find(users, function (u) {
                      return u.id === userId;
                  });
              }
              function openBulkUserGroupPicker(event) {
                  var firstSelectedUser = getUserFromArrayById(vm.selection[0], vm.users);
                  vm.selectedBulkUserGroups = _.clone(firstSelectedUser.userGroups);
                  vm.userGroupPicker = {
                      title: localizationService.localize('user_selectUserGroups'),
                      view: 'usergrouppicker',
                      selection: vm.selectedBulkUserGroups,
                      closeButtonLabel: localizationService.localize('general_cancel'),
                      show: true,
                      submit: function (model) {
                          usersResource.setUserGroupsOnUsers(model.selection, vm.selection).then(function (data) {
                              // sorting to ensure they show up in right order when updating the UI
                              vm.selectedBulkUserGroups.sort(function (a, b) {
                                  return a.alias > b.alias ? 1 : a.alias < b.alias ? -1 : 0;
                              });
                              // apply changes to UI
                              _.each(vm.selection, function (userId) {
                                  var user = getUserFromArrayById(userId, vm.users);
                                  user.userGroups = vm.selectedBulkUserGroups;
                              });
                              vm.selectedBulkUserGroups = [];
                              vm.userGroupPicker.show = false;
                              vm.userGroupPicker = null;
                              formHelper.showNotifications(data);
                              clearSelection();
                          }, function (error) {
                              formHelper.showNotifications(error.data);
                          });
                      },
                      close: function (oldModel) {
                          vm.selectedBulkUserGroups = [];
                          vm.userGroupPicker.show = false;
                          vm.userGroupPicker = null;
                      }
                  };
              }
              function openUserGroupPicker(event) {
                  vm.userGroupPicker = {
                      title: localizationService.localize('user_selectUserGroups'),
                      view: 'usergrouppicker',
                      selection: vm.newUser.userGroups,
                      closeButtonLabel: localizationService.localize('general_cancel'),
                      show: true,
                      submit: function (model) {
                          // apply changes
                          if (model.selection) {
                              vm.newUser.userGroups = model.selection;
                          }
                          vm.userGroupPicker.show = false;
                          vm.userGroupPicker = null;
                      },
                      close: function (oldModel) {
                          // rollback on close
                          if (oldModel.selection) {
                              vm.newUser.userGroups = oldModel.selection;
                          }
                          vm.userGroupPicker.show = false;
                          vm.userGroupPicker = null;
                      }
                  };
              }
              function removeSelectedUserGroup(index, selection) {
                  selection.splice(index, 1);
              }
              function selectAll() {
                  if (areAllSelected()) {
                      vm.selection = [];
                      angular.forEach(vm.users, function (user) {
                          user.selected = false;
                      });
                  } else {
                      // clear selection so we don't add the same user twice
                      vm.selection = [];
                      // select all users
                      angular.forEach(vm.users, function (user) {
                          // prevent the current user to be selected
                          if (!user.isCurrentUser) {
                              user.selected = true;
                              vm.selection.push(user.id);
                          }
                      });
                  }
              }
              function areAllSelected() {
                  // we need to check if the current user is part of the selection and 
                  // subtract the user from the total selection to find out if all users are selected
                  var includesCurrentUser = vm.users.some(function (user) {
                      return user.isCurrentUser === true;
                  });
                  if (includesCurrentUser) {
                      if (vm.selection.length === vm.users.length - 1) {
                          return true;
                      }
                  } else {
                      if (vm.selection.length === vm.users.length) {
                          return true;
                      }
                  }
              }
              var search = _.debounce(function () {
                  $scope.$apply(function () {
                      getUsers();
                  });
              }, 500);
              function searchUsers() {
                  search();
              }
              function getFilterName(array) {
                  var name = 'All';
                  var found = false;
                  angular.forEach(array, function (item) {
                      if (item.selected) {
                          if (!found) {
                              name = item.name;
                              found = true;
                          } else {
                              name = name + ', ' + item.name;
                          }
                      }
                  });
                  return name;
              }
              function setUserStatesFilter(userState) {
                  if (!vm.usersOptions.userStates) {
                      vm.usersOptions.userStates = [];
                  }
                  //If the selection is "ALL" then we need to unselect everything else since this is an 'odd' filter
                  if (userState.key === 'All') {
                      angular.forEach(vm.userStatesFilter, function (i) {
                          i.selected = false;
                      });
                      //we can't unselect All
                      userState.selected = true;
                      //reset the selection passed to the server
                      vm.usersOptions.userStates = [];
                  } else {
                      angular.forEach(vm.userStatesFilter, function (i) {
                          if (i.key === 'All') {
                              i.selected = false;
                          }
                      });
                      var indexOfAll = vm.usersOptions.userStates.indexOf('All');
                      if (indexOfAll >= 0) {
                          vm.usersOptions.userStates.splice(indexOfAll, 1);
                      }
                  }
                  if (userState.selected) {
                      vm.usersOptions.userStates.push(userState.key);
                  } else {
                      var index = vm.usersOptions.userStates.indexOf(userState.key);
                      vm.usersOptions.userStates.splice(index, 1);
                  }
                  getUsers();
              }
              function setUserGroupFilter(userGroup) {
                  if (!vm.usersOptions.userGroups) {
                      vm.usersOptions.userGroups = [];
                  }
                  if (userGroup.selected) {
                      vm.usersOptions.userGroups.push(userGroup.alias);
                  } else {
                      var index = vm.usersOptions.userGroups.indexOf(userGroup.alias);
                      vm.usersOptions.userGroups.splice(index, 1);
                  }
                  getUsers();
              }
              function setOrderByFilter(value, direction) {
                  vm.usersOptions.orderBy = value;
                  vm.usersOptions.orderDirection = direction;
                  getUsers();
              }
              function changePageNumber(pageNumber) {
                  vm.usersOptions.pageNumber = pageNumber;
                  getUsers();
              }
              function createUser(addUserForm) {
                  if (formHelper.submitForm({
                          formCtrl: addUserForm,
                          scope: $scope,
                          statusMessage: 'Saving...'
                      })) {
                      vm.newUser.id = -1;
                      vm.newUser.parentId = -1;
                      vm.page.createButtonState = 'busy';
                      usersResource.createUser(vm.newUser).then(function (saved) {
                          vm.page.createButtonState = 'success';
                          vm.newUser = saved;
                          setUsersViewState('createUserSuccess');
                          getUsers();
                      }, function (err) {
                          formHelper.handleError(err);
                          vm.page.createButtonState = 'error';
                      });
                  }
              }
              function inviteUser(addUserForm) {
                  if (formHelper.submitForm({
                          formCtrl: addUserForm,
                          scope: $scope,
                          statusMessage: 'Saving...'
                      })) {
                      vm.newUser.id = -1;
                      vm.newUser.parentId = -1;
                      vm.page.createButtonState = 'busy';
                      usersResource.inviteUser(vm.newUser).then(function (saved) {
                          //success
                          vm.page.createButtonState = 'success';
                          vm.newUser = saved;
                          setUsersViewState('inviteUserSuccess');
                          getUsers();
                      }, function (err) {
                          //error
                          formHelper.handleError(err);
                          vm.page.createButtonState = 'error';
                      });
                  }
              }
              function toggleNewUserPassword() {
                  vm.newUser.showPassword = !vm.newUser.showPassword;
              }
              // copy to clip board success
              function copySuccess() {
                  vm.page.copyPasswordButtonState = 'success';
              }
              // copy to clip board error
              function copyError() {
                  vm.page.copyPasswordButtonState = 'error';
              }
              function goToUser(userId) {
                  $location.path('users/users/user/' + userId);
              }
              // helpers
              function getUsers() {
                  vm.loading = true;
                  // Get users
                  usersResource.getPagedResults(vm.usersOptions).then(function (data) {
                      vm.users = data.items;
                      vm.usersOptions.pageNumber = data.pageNumber;
                      vm.usersOptions.pageSize = data.pageSize;
                      vm.usersOptions.totalItems = data.totalItems;
                      vm.usersOptions.totalPages = data.totalPages;
                      formatDates(vm.users);
                      setUserDisplayState(vm.users);
                      vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates);
                      vm.loading = false;
                  }, function (error) {
                      vm.loading = false;
                  });
              }
              function setUserDisplayState(users) {
                  angular.forEach(users, function (user) {
                      user.userDisplayState = usersHelper.getUserStateFromValue(user.userState);
                  });
              }
              function formatDates(users) {
                  angular.forEach(users, function (user) {
                      if (user.lastLoginDate) {
                          var dateVal;
                          var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                          var localOffset = new Date().getTimezoneOffset();
                          var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                          if (serverTimeNeedsOffsetting) {
                              dateVal = dateHelper.convertToLocalMomentTime(user.lastLoginDate, serverOffset);
                          } else {
                              dateVal = moment(user.lastLoginDate, 'YYYY-MM-DD HH:mm:ss');
                          }
                          // get current backoffice user and format date
                          userService.getCurrentUser().then(function (currentUser) {
                              user.formattedLastLogin = dateVal.locale(currentUser.locale).format('LLL');
                          });
                      }
                  });
              }
              function setBulkActions(users) {
                  // reset all states
                  vm.allowDisableUser = true;
                  vm.allowEnableUser = true;
                  vm.allowUnlockUser = true;
                  vm.allowSetUserGroup = true;
                  var firstSelectedUserGroups;
                  angular.forEach(users, function (user) {
                      if (!user.selected) {
                          return;
                      }
                      // if the current user is selected prevent any bulk actions with the user included
                      if (user.isCurrentUser) {
                          vm.allowDisableUser = false;
                          vm.allowEnableUser = false;
                          vm.allowUnlockUser = false;
                          vm.allowSetUserGroup = false;
                          return;
                      }
                      if (user.userDisplayState && user.userDisplayState.key === 'Disabled') {
                          vm.allowDisableUser = false;
                      }
                      if (user.userDisplayState && user.userDisplayState.key === 'Active') {
                          vm.allowEnableUser = false;
                      }
                      if (user.userDisplayState && user.userDisplayState.key === 'Invited') {
                          vm.allowEnableUser = false;
                      }
                      if (user.userDisplayState && user.userDisplayState.key === 'LockedOut') {
                          vm.allowEnableUser = false;
                      }
                      if (user.userDisplayState && user.userDisplayState.key !== 'LockedOut') {
                          vm.allowUnlockUser = false;
                      }
                      // store the user group aliases of the first selected user
                      if (!firstSelectedUserGroups) {
                          firstSelectedUserGroups = user.userGroups.map(function (ug) {
                              return ug.alias;
                          });
                          vm.allowSetUserGroup = true;
                      } else if (vm.allowSetUserGroup === true) {
                          // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing.
                          // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't
                          // really support that in the user group picker.
                          var userGroups = user.userGroups.map(function (ug) {
                              return ug.alias;
                          });
                          if (_.difference(firstSelectedUserGroups, userGroups).length > 0) {
                              vm.allowSetUserGroup = false;
                          }
                      }
                  });
              }
              function clearAddUserForm() {
                  // clear form data
                  vm.newUser.name = '';
                  vm.newUser.email = '';
                  vm.newUser.userGroups = [];
                  vm.newUser.message = '';
                  // clear button state
                  vm.page.createButtonState = 'init';
              }
              init();
          }
          angular.module('umbraco').controller('Umbraco.Editors.Users.UsersController', UsersController);
      }());
  }());