/*! umbraco
 * https://github.com/umbraco/umbraco-cms/
 * Copyright (c) 2016 Umbraco HQ;
 * Licensed 
 */

(function() { 


/**
 * @ngdoc controller
 * @name Umbraco.MainController
 * @function
 * 
 * @description
 * The main application controller
 * 
 */
function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale) {

    //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;
    }));

    //when the app is read/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();
        }

        //Load locale file
        if ($scope.user.locale) {
            tmhDynamicLocale.set($scope.user.locale);
        }

        if ($scope.user.emailHash) {

            //let's attempt to load the avatar, it might not exist or we might not have 
            // internet access, well get an empty string back
            $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl"))
                .then(
                    function successCallback(response) {
                        // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything
                        if (response.data !== "null") {
                            if ($scope.user && $scope.user.emailHash) {
                                var avatarBaseUrl = "https://www.gravatar.com/avatar/";
                                var hash = $scope.user.emailHash;

                                $scope.avatar = [
                                    { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" },
                                    { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" },
                                    { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" }
                                ];
                            }
                        }

                    }, function errorCallback(response) {
                        //cannot load it from the server so we cannot do anything
                    });
        }
    }));

    evts.push(eventsService.on("app.ysod", function (name, error) {
        $scope.ysodOverlay = {
            view: "ysod",
            error: error,
            show: true
        };
    }));

    //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 groupIndex = -1;
    var itemIndex = -1;
    $scope.selectedItem = undefined;
        

    function iterateResults(up){
        //default group
        if(!group){
            group = $scope.groups[0];
            groupIndex = 0;
        }

        if(up){
            if(itemIndex === 0){
                if(groupIndex === 0){
                    gotoGroup($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 === $scope.groups.length-1){
                    gotoGroup(0);
                }else{
                    gotoGroup(groupIndex+1);
                }
            }
        }
    }

    function gotoGroup(index, up){
        groupIndex = index;
        group = $scope.groups[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() {
            if ($scope.searchTerm) {
                if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                    $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) {
                        $scope.groups = _.filter(result, function (group) { return group.results.length > 0; });
                        //set back to null so it can be re-created
                        canceler = null;
                    });
                }
            }
            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")
			    .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");
			        assetsService.loadCss(cssPath);
			    });
});
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) {
            //error
        });
    }

    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.usertype = user.userType;
        	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) {
                    // not json           
                }
            }
            
            //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) {

	            if (!$scope.target.path) {
	                entityResource.getPath($scope.target.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($scope.target.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.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.startMediaId,
	                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, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) {

        var setFieldFocus = function(form, field) {
            $timeout(function() {
                $("form[name='" + form + "'] input[name='" + field + "']").focus();
            });
        }

        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.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) {
            
            //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;
            }

            userService.authenticate(login, password)
                .then(function (data) {
                    $scope.submit(true);
                }, function (reason) {
                    $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.username.$invalid) {
                    $scope.loginForm.username.$setValidity('auth', true);
                }
            });
            $scope.loginForm.password.$viewChangeListeners.push(function () {
                if ($scope.loginForm.password.$invalid) {
                    $scope.loginForm.password.$setValidity('auth', true);
                }
            });
        };

        $scope.requestPasswordResetSubmit = function (email) {

            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;
            }

            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();
        }

    });

//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 //clear the children of the parent
                            });

                            $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
        currentUserResource.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) {

                    //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);

/**
 * @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) {

      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;

      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) {

      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: []
      };

      function nodeSelectHandler(ev, args) {
          args.event.preventDefault();
          args.event.stopPropagation();

          if (args.node.metaData.listViewNode) {
              //check if list view 'search' node was selected

              vm.searchInfo.showSearch = true;
              vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
              vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
          }
          else {
              //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) {
          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"]
                          }
                      ];
                  }
              });
          }
      }

      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);
      });


	}

	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.usertype = user.userType;
        	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 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: []
	    };

	    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) {

	            if (!$scope.model.target.path) {
	                entityResource.getPath($scope.model.target.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($scope.model.target.id).then(function (url) {
	                $scope.model.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.model.target.id = args.node.id;
	            $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) {
	        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) {
				$scope.mediaPickerOverlay = {
					view: "mediapicker",
					startNodeId: userData.startMediaId,
					show: true,
					submit: function(model) {
						var media = model.selectedImages[0];

						$scope.model.target.id = media.id;
						$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);
	    });
	});

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.wizardStep = "macroSelect";
    $scope.model.macroParams = [];
    $scope.noMacroParams = false;

    $scope.changeMacro = function() {
        if ($scope.wizardStep === "macroSelect") {
            editParams();
        } else {
            submitForm();
        }
    };

    /** 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.noMacroParams = true;

                } 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 '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, $cookieStore, 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 = $cookieStore.get("umbLastOpenedMediaNodeId");
            if ($scope.onlyImages) {
                $scope.acceptedFileTypes = mediaHelper
                    .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
            } else {
                $scope.acceptedFileTypes = !mediaHelper
                    .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
            }
            $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";

            $scope.model.selectedImages = [];

            $scope.acceptedMediatypes = [];
            mediaTypeHelper.getAllowedImagetypes($scope.startNodeId)
                .then(function(types) {
                    $scope.acceptedMediatypes = types;
                });

            //preload selected item
            $scope.target = undefined;
            if (dialogOptions.currentTarget) {
                $scope.target = dialogOptions.currentTarget;
            }

            $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) {
                    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 //clear the children of the parent
                            });

                            $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) {
                            // 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 : [];

                        // set already selected images to selected
                        for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) {
                            var folderImage = $scope.images[folderImageIndex];
                            var imageIsSelected = false;

                            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;
                            }
                        }
                    });
                $scope.currentFolder = folder;

                // for some reason i cannot set cookies with cookieStore
                document.cookie = "umbLastOpenedMediaNodeId=" + 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;
                        $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() {
                $scope.gotoFolder($scope.currentFolder);
            };

            $scope.onFilesQueue = function() {
                $scope.activeDrag = false;
            };

            //default root item
            if (!$scope.target) {
                if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
                    entityResource.getById($scope.lastOpenedNode, "media")
                        .then(function(node) {
                                // make sure that las 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" });
                                } else {
                                    $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });
                                }
                            },
                            function(err) {
                                $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });
                            });
                } else {
                    $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;
                };
            };
        });

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) {

      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: []
      };

      function nodeSelectHandler(ev, args) {
          args.event.preventDefault();
          args.event.stopPropagation();

          if (args.node.metaData.listViewNode) {
              //check if list view 'search' node was selected

              vm.searchInfo.showSearch = true;
              vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
              vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
          }
          else {
              //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) {
          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"]
                          }
                      ];
                  }
              });
          }
      }

      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);
      });


	}

	angular.module("umbraco").controller("Umbraco.Overlays.MoveOverlay", MoveOverlay);

})();

//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
	function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {

	    var tree = null;
	    var dialogOptions = $scope.model;
	    $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: []
        }

		  $scope.model.selection = [];

		  $scope.init = function(contentType) {

			  if(contentType === "content") {
				  entityType = "Document";
				  if(!$scope.model.title) {
					  $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
				  }
			  } else if(contentType === "member") {
				  entityType = "Member";
				  if(!$scope.model.title) {
					  $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
				  }
			  } else if(contentType === "media") {
				  entityType = "Media";
				  if(!$scope.model.title) {
					  $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
				  }
			  }
		  }

	    //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) {

						if (entity) {
							 multiSelectItem(entity);
						} else {
							 //otherwise we have to get it from the server
							 entityResource.getById(id, entityType).then(function (ent) {
								 multiSelectItem(ent);
							 });
						}

	            }
	            else {
	                var node = {
	                    alias: null,
	                    icon: "icon-folder",
	                    id: id,
	                    name: text
	                };
						 $scope.model.selection.push(node);
	                $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, 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, 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, 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);
	    });
	});

angular.module("umbraco")
    .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, 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 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
        currentUserResource.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) {

                    //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.newPassword = "";
           $scope.changePasswordModel.confirm = "";
        }

    });

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;
            }
        }

    });

angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
	function ($scope, 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: []
	    }

	    var node = dialogOptions.currentNode;

	    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("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) {
	        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.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);
	    });
	});

/**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.CreateController
 * @function
 * 
 * @description
 * The controller for the content creation dialog
 */
function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) {

    contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
        $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
    });
}

angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController);
/**
 * @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, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {

    //setup scope vars
    $scope.defaultButton = null;
    $scope.subButtons = [];

    $scope.page = {};
    $scope.page.loading = false;
    $scope.page.menu = {};
    $scope.page.menu.currentNode = null;
    $scope.page.menu.currentSection = appState.getSectionState("currentSection");
    $scope.page.listViewPath = null;
    $scope.page.isNew = $routeParams.create;
    $scope.page.buttonGroupState = "init";

    function init(content) {

        var buttons = contentEditingHelper.configureContentEditorButtons({
            create: $routeParams.create,
            content: content,
            methods: {
                saveAndPublish: $scope.saveAndPublish,
                sendToPublish: $scope.sendToPublish,
                save: $scope.save,
                unPublish: $scope.unPublish
            }
        });
        $scope.defaultButton = buttons.defaultButton;
        $scope.subButtons = buttons.subButtons;

        editorState.set($scope.content);

        //We fetch all ancestors of the node to generate the footer breadcrumb navigation
        if (!$routeParams.create) {
            if (content.parentId && content.parentId != -1) {
                entityResource.getAncestors(content.id, "document")
               .then(function (anc) {
                   $scope.ancestors = anc;
               });
            }
        }
    }

    /** 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: "content", 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: "content", 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;
                });
        }
    }

    // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
    function performSave(args) {
        var deferred = $q.defer();

        $scope.page.buttonGroupState = "busy";

        contentEditingHelper.contentEditorPerformSave({
            statusMessage: args.statusMessage,
            saveMethod: args.saveMethod,
            scope: $scope,
            content: $scope.content,
            action: args.action
        }).then(function (data) {
            //success            
            init($scope.content);
            syncTreeNode($scope.content, data.path);

            $scope.page.buttonGroupState = "success";

            deferred.resolve(data);
        }, function (err) {
            //error
            if (err) {
                editorState.set($scope.content);
            }

            $scope.page.buttonGroupState = "error";

            deferred.reject(err);
        });

        return deferred.promise;
    }

    function resetLastListPageNumber(content) {
        // We're using rootScope to store the page number for list views, so if returning to the list
        // we can restore the page.  If we've moved on to edit a piece of content that's not the list or it's children
        // we should remove this so as not to confuse if navigating to a different list
        if (!content.isChildOfListView && !content.isContainer) {
            $rootScope.lastListViewPageViewed = null;
        }
    }

    if ($routeParams.create) {

      $scope.page.loading = true;

        //we are creating so get an empty content item
        contentResource.getScaffold($routeParams.id, $routeParams.doctype)
            .then(function (data) {

                $scope.content = data;

                init($scope.content);                

                resetLastListPageNumber($scope.content);

                $scope.page.loading = false;

            });
    }
    else {

        $scope.page.loading = true;

        //we are editing so get the content item from the server
        contentResource.getById($routeParams.id)
            .then(function (data) {

                $scope.content = data;

                if (data.isChildOfListView && data.trashed === false) {
                    $scope.page.listViewPath = ($routeParams.page)
                        ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page
                        : "/content/content/edit/" + data.parentId;
                }

                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();

                syncTreeNode($scope.content, data.path, true);

                resetLastListPageNumber($scope.content);

                $scope.page.loading = false;

            });
    }


    $scope.unPublish = function () {

        if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {

           $scope.page.buttonGroupState = "busy";

            contentResource.unPublish($scope.content.id)
                .then(function (data) {

                    formHelper.resetForm({ scope: $scope, notifications: data.notifications });

                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                    });

                    init($scope.content);

                    syncTreeNode($scope.content, data.path);

                    $scope.page.buttonGroupState = "success";

                });
        }

    };

    $scope.sendToPublish = function () {
        return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" });
    };

    $scope.saveAndPublish = function () {
        return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" });
    };

    $scope.save = function () {
        return performSave({ saveMethod: contentResource.save, statusMessage: "Saving...", action: "save" });
    };

    $scope.preview = function (content) {


        if (!$scope.busy) {

            // Chromes popup blocker will kick in if a window is opened 
            // outwith the initial scoped request. This trick will fix that.
            //  
            var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
            $scope.save().then(function (data) {
                // Build the correct path so both /#/ and #/ work.
                var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
                previewWindow.location.href = redirect;
            });


        }

    };

}

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, 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: []
	    }

	    var node = dialogOptions.currentNode;

	    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("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) {
	        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.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 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;
                    $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);
	    });
	});
/**
 * @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;

				}, 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 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(dashboardResource, assetsService) {
    var vm = this;
    vm.loading = true;
    vm.showDefault = false;
    
    //proxy remote css through the local server
    assetsService.loadCss( dashboardResource.getRemoteDashboardCssUrl("content") );
    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;
        });
}

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 start node is the dashboard
        if(currentUser.startMediaId === -1) {

            //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 {
            // redirect to start node
            $location.path("/media/media/edit/" + currentUser.startMediaId);
        }

    });

}
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) {

               //TODO: Handle errors
            });
        };
    }

    $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
            });
        }
    }

    //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);

                    dataTypeHelper.rebindChangedProperties($scope.content, data);
                });
        }

    };

    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 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]);
                    }
                }
            });
        }
    };

    $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 = [];

        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/documenttypes/views/design/design.html",
			    "active": true
			},
			{
			    "name": localizationService.localize("general_listView"),
			    "icon": "icon-list",
			    "view": "views/documenttypes/views/listview/listview.html"
			},
			{
			    "name": localizationService.localize("general_rights"),
			    "icon": "icon-keychain",
			    "view": "views/documenttypes/views/permissions/permissions.html"
			},
			{
			    "name": localizationService.localize("treeHeaders_templates"),
			    "icon": "icon-layout",
			    "view": "views/documenttypes/views/templates/templates.html"
			}
        ];

        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 = {
                    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) {

                                // 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;

            });

        }


        /* ---------- 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);
        });
    });

/**
 * @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, mediaTypeResource, iconHelper) {
    
    mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
        $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
    });
    
}

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);

                $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;
                        });
                }

                $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, eventsService, mediaResource, appState, treeService, navigationService) {
	    var dialogOptions = $scope.dialogOptions;

	    $scope.dialogTreeEventHandler = $({});
	    var node = dialogOptions.currentNode;

	    function nodeSelectHandler(ev, args) {
	        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;
	    }

	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);


	    $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);
	    });
	});
/**
 * @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) {

               //TODO: Handle errors
            });
        };
    }

    $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.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;
        }

    };

}

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) {

               //TODO: Handle errors
            });
        };
    }

    $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.DocumentType.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
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) {

        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) {

                // set view state to uploading
                vm.state = 'uploading';

                // calculate progress in percentage
                var progressPercentage = parseInt(100.0 * 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.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) {
                        file.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");
                        vm.installState.progress = "50";
                        return packageResource.installFiles(pack);
                    },
                    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 = "33";
                        return packageResource.installFiles(pack);
                    },
                    error)
                .then(function(pack) {
                        vm.installState.status = localizationService.localize("packager_installStateRestarting");
                        vm.installState.progress = "66";
                        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 imageFilePickerController($scope) {

    $scope.pick = function() {
        $scope.mediaPickerDialog = {};
        $scope.mediaPickerDialog.view = "mediapicker";
        $scope.mediaPickerDialog.show = true;

        $scope.mediaPickerDialog.submit = function(model) {
            $scope.model.value = model.selectedImages[0].image;
            $scope.mediaPickerDialog.show = false;
            $scope.mediaPickerDialog = null;
        };

        $scope.mediaPickerDialog.close = function(oldModel) {
            $scope.mediaPickerDialog.show = false;
            $scope.mediaPickerDialog = 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 = [];   

    var dialogOptions = {
        multiPicker: false,
        entityType: "Media",
        section: "media",
        treeAlias: "media"
    };

    $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, event){
        event.preventDefault();
        $scope.renderModel.splice(index, 1);
    };

    $scope.clear = function() {
        $scope.renderModel = [];
    };

    $scope.add = function (item) {
        var currIds = _.map($scope.renderModel, function (i) {
            return i.id;
        });
        if (currIds.indexOf(item.id) < 0) {
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
        }	
    };

    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();
    });

    //load media data
    var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
    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 });
        });
    });
    
}

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;
       
        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;
                    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);
                }
            }
        };

        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 = [];


	    var config = {
	        multiPicker: false,
	        entityType: "Document",
	        type: "content",
	        treeAlias: "content"
	    };
		
		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});
				});
			});
		}

		$scope.openContentPicker = function() {
			$scope.treePickerOverlay = {};
			$scope.treePickerOverlay.section = config.type;
			$scope.treePickerOverlay.treeAlias = config.treeAlias;
			$scope.treePickerOverlay.multiPicker = config.multiPicker;
			$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){
			if($scope.ids.indexOf(item.id) < 0){
				item.icon = iconHelper.convertFromLegacyIcon(item.icon);

				$scope.ids.push(item.id);
				$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
				$scope.model.value = trim($scope.ids.join(), ",");
			}	
		};


	    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.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",
				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 = 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) {
        
        function resetModel(isNew) {
            //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 (!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;
            }

            //the value to compare to match passwords
            if (!isNew) {
                $scope.model.confirm = "";
            }
            else if ($scope.model.value.newPassword && $scope.model.value.newPassword.length > 0) {
                //if it is new and a new password has been set, then set the confirm password too
                $scope.model.confirm = $scope.model.value.newPassword;
            }
            
        }

        resetModel($routeParams.create);

        //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
        //with validators turned on.
        $scope.changing = $scope.model.config.disableToggle === true || !$scope.model.config.hasPassword;

        //we're not currently changing so set the model to null
        if (!$scope.changing) {
            $scope.model.value = null;
        }

        $scope.doChange = function() {
            resetModel();
            $scope.changing = true;
            //if there was a previously generated password displaying, clear it
            $scope.model.value.generatedPassword = null;
        };

        $scope.cancelChange = function() {
            $scope.changing = false;
            //set model to null
            $scope.model.value = null;
        };

        var unsubscribe = [];

        //listen for the saved event, when that occurs we'll 
        //change to changing = false;
        unsubscribe.push($scope.$on("formSubmitted", function() {
            if ($scope.model.config.disableToggle === false) {
                $scope.changing = false;
            }
        }));
        unsubscribe.push($scope.$on("formSubmitting", function() {
            //if there was a previously generated password displaying, clear it
            if ($scope.changing && $scope.model.value) {
                $scope.model.value.generatedPassword = null;
            }
            else if (!$scope.changing) {
                //we are not changing, so the model needs to be null
                $scope.model.value = null;
            }
        }));

        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            for (var u in unsubscribe) {
                unsubscribe[u]();
            }
        });

        $scope.showReset = function() {
            return $scope.model.config.hasPassword && $scope.model.config.enableReset;
        };

        $scope.showOldPass = function() {
            return $scope.model.config.hasPassword &&
                !$scope.model.config.allowManuallyChangingPassword &&
                !$scope.model.config.enablePasswordRetrieval && !$scope.model.value.reset;
        };

        $scope.showNewPass = function () {
            return !$scope.model.value.reset;
        };

        $scope.showConfirmPass = function() {
            return !$scope.model.value.reset;
        };
        
        $scope.showCancelBtn = function() {
            return $scope.model.config.disableToggle !== true && $scope.model.config.hasPassword;
        };

    });

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.toggleItem = function (color) {
        if ($scope.model.value == color) {
            $scope.model.value = "";
            //this is required to re-validate
            $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
        }
        else {
            $scope.model.value = color;
            //this is required to re-validate
            $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
        }
    };
    // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
    $scope.validateMandatory = function () {
        return {
            isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""),
            errorMsg: "Value cannot be empty",
            errorKey: "required"
        };
    }
    $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
}

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";
        
        $scope.newColor = defaultColor;
        $scope.hasError = false;

        assetsService.load([
            //"lib/spectrum/tinycolor.js",
            "lib/spectrum/spectrum.js"          
        ], $scope).then(function () {
            var elem = $element.find("input");
            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) {
                items.push({
                    value: $scope.model.value[i],
                    id: i
                });
            }
            //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.newColor) {
                var exists = _.find($scope.model.value, function(item) {
                    return item.value.toUpperCase() == $scope.newColor.toUpperCase();
                });
                if (!exists) {
                    $scope.model.value.push({ value: $scope.newColor });
                    //$scope.newColor = defaultColor;
                    // set colorpicker to default color
                    //var elem = $element.find("input");
                    //elem.spectrum("set", $scope.newColor);
                    $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");
    });

//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, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {

    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 i.id;
            }).join();
        }, function (newVal) {
            var currIds = _.map($scope.renderModel, function (i) {
                return 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);
            }
        });
    }

    $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,
        startNode: {
            query: "",
            type: "content",
	            id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
        }
    };

    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" || entityType === "Media";
    $scope.allowEditButton = entityType === "Document";

    //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
    };

    //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 we have a query for the startnode, we will use that. 
    if ($scope.model.config.startNode.query) {
        var rootId = $routeParams.id;
        entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
            dialogOptions.startNodeId = 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 i.id;
        });

        if (currIds.indexOf(item.id) < 0) {
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path });
        }
    };

    $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();
    });

    //load current data
    var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
    entityResource.getByIds(modelIds, entityType).then(function (data) {

        //Ensure we populate the render model in the same order that the ids were stored!
        _.each(modelIds, function (id, i) {
            var entity = _.find(data, function (d) {                
                return d.id == id;
            });
           
            if (entity) {
                entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
                $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path });
            }
            
           
        });

        //everything is loaded, start the watch on the model
        startWatch();

    });
}

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').then(function() {

        var filesToLoad = ["lib/moment/moment-with-locales.js",
						   "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 = "";
            }
        }
        
    });

/** 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(",") };
        });
    });

    //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) {
            //now we need to check if we need to re-initialize our structure which is kind of tricky
            // since we only want to do that if the server has changed the value, not if this controller
            // has changed the value. There's only 2 scenarios where we change the value internall so
            // we know what those values can be, if they are not either of them, then we'll re-initialize.

            if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) {
                initialize($scope.rebuildInput.index + 1);
            }

        }
    });
};
angular.module("umbraco")
    .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
    .run(function(mediaHelper, umbRequestHelper, assetsService){
        if (mediaHelper && mediaHelper.registerFileResolver) {
            assetsService.load(["lib/moment/moment-with-locales.js"]).then(
                function () {
                    //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
                    // they contain different data structures so if we need to query against it we need to be aware of this.
                    mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
                        if (thumbnail) {
                            if (mediaHelper.detectIfImageByExtension(property.value)) {
                                //get default big thumbnail from image processor
                                var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
                                return thumbnailUrl;
                            }
                            else {
                                return null;
                            }
                        }
                        else {
                            return property.value;
                        }
                    });
                }
            );
        }
    });

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('http://www.google.com/jsapi')
            .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) {

        $scope.setImage = function(){
            $scope.mediaPickerOverlay = {};
            $scope.mediaPickerOverlay.view = "mediapicker";
            $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,
                    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.startMediaId,
                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: 999999999999999999,
            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;

        $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: 999999999999999999,
            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 allowedEditors = $(event.target).scope().area.allowed;

                if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) {

                    $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) {

                // 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: false,
              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()) * 0x10000)
                           .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 && angular.isString($scope.model.config.items.columns)) {
                $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
            } else {
                $scope.model.config.items.columns = 12;
            }

            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;

            $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) {
        var h = ui.clone();

        h.children().each(function () {
            $(this).width($(this).width());            
        });
        h.css("background-color", "lightgray");

        return h;
    };

    $scope.sortableOptions = {
        helper: fixHelper,
        handle: ".handle",
        opacity: 0.5,
        axis: 'y',
        containment: 'parent',
        cursor: 'move',
        items: '> tr',
        tolerance: 'pointer',
        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;

      vm.nodeId = $scope.contentId;
      //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
      vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
      vm.maxFileSize = Umbraco.Sys.ServerVariables.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);

          if($scope.entityType === 'media') {
            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;

      vm.nodeId = $scope.contentId;
        //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
        vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
      vm.maxFileSize = Umbraco.Sys.ServerVariables.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;

      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);
      }

      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) {
         var selectedKey = getItemKey(selected.id);
         return selectedKey;
      };
      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'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
      };
   }

   //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);
        }

        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.reloadView = function (id) {

      $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 ($scope.entityType === 'media') {

            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.actionInProgress = true;
         $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,
                           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);
   }

   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 = [];
		
		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);
				}
			});
		}

		
		function collectDetails(macro){
			macro.details = "";
			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);
				}

				$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);
		};

	    $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 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 () {
                    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()) {
                            angularHelper.getCurrentForm($scope).$setDirty();
                            $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");
        })
}

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) {

        //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.startMediaId;
            });
        }

        function setupViewModel() {
            $scope.images = [];
            $scope.ids = [];

            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 metatData property to get at the thumbnail
                // value.

                entityResource.getByIds(ids, "Media").then(function (medias) {

                    _.each(medias, function (media, i) {

                        //only show non-trashed items
                        if (media.parentId >= -1) {

                            if (!media.thumbnail) {
                                media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                            }

                            $scope.images.push(media);
                            $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,
               multiPicker: multiPicker,
               onlyImages: onlyImages,
               disableFolderSelect: disableFolderSelect,
               show: true,
               submit: function(model) {

                   _.each(model.selectedImages, function(media, i) {

                       if (!media.thumbnail) {
                           media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                       }

                       $scope.images.push(media);
                       $scope.ids.push(media.id);
                   });

                   $scope.sync();

                   $scope.mediaPickerOverlay.show = false;
                   $scope.mediaPickerOverlay = null;

               }
           };

       };

       $scope.sortableOptions = {
           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(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 = [];
    
    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 = [];

    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) {
            return i.id;
        });

        if (currIds.indexOf(item.id) < 0) {
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
        }
    };

    $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();
    });

    //load member data
    var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
    entityResource.getByIds(modelIds, "Member").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 });
        });
    });
}


angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController);

function MultipleTextBoxController($scope) {

    $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.add = function () {
        if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
            $scope.model.value.push({ value: "" });
        }
    };

    $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.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.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.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 ($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 = data.id;
                    $scope.currentEditLink.internalName = data.name;
                    $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon);
                    $scope.currentEditLink.link = data.id;
                } else {
                    $scope.newInternal = data.id;
                    $scope.newInternalName = data.name;
                    $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon);
                }
            }
        });

angular.module("umbraco")
    .controller("Umbraco.PropertyEditors.RTEController",
    function ($rootScope, $scope, $q, 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;

            //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,
                    height: editorConfig.dimensions.height,
                    width: editorConfig.dimensions.width,
                    maxImageSize: editorConfig.maxImageSize,
                    toolbar: toolbar,
                    content_css: stylesheets,
                    relative_urls: false,
                    style_formats: styleFormats
                };


                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) {
                                    //cannot parse, we'll just leave it
                                }
                            }
                        }
                    }

                    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.startMediaId,
                            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;
                    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.getContent();
                });

                //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();
                });
            });
        });

    });

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":
                    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");
    });
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());
        });
        console.log($scope.model.config.ticksPositions);
    }

    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");
    assetsService.loadCss("lib/slider/bootstrap-slider-custom.css");
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController);
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.TagsController",
    function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element, $sanitize) {

        var $typeahead;

        $scope.isLoading = true;
        $scope.tagToAdd = "";

        assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").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) {
                tagToAdd = String(tagToAdd).htmlEncode();
                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) {
                    //console.log("opened ");
                });
            });

            $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 = [];
	};
});
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();
	    };

	});

})();