<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileOverview Main app library where is defined the global namespace "MM".
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @version 1.2
 */

/**
  * @namespace Holds all the MoodleMobile specific functionallity.
 */
var MM = {

    config: {},
    plugins: [],
    models: {},
    collections: {},
    deviceType: 'phone',
    clickType: 'click',
    quickClick: 'click',
    deviceReady: false,
    deviceOS: '',
    logData: [],
    inComputer: false,
    inNodeWK: false,
    touchMoving: false,
    scrollType: '',
    debugging: true,
    mq: 'only screen and (min-width: 768px) and (-webkit-min-device-pixel-ratio: 1)',

    /**
     * Initial setup of the app: device type detection, routes, models, settings.
     * This function is executed once the config.json file is loaded and previously to loading the app plugins.
     *
     * @this {MM}
     * @param {Object.<>} Settings loaded from /config.json.
     */
    init: function(config) {
        MM.log('Initializating app');
        this.config = config;
        this.setEventTypes();
        this.setDeviceType();
        this.setDeviceOS();
        this.setInComputerState();
        this.setInNodeWKState();
        this.loadCordova();
        this.loadBackboneRouter();
        this.checkAjax();
        this.loadCoreModels();
        this.loadRoutes();
        this.loadSettings();
    },

    /**
     * Set which events we should bind interaction handlers to.
     */
    setEventTypes: function() {
        if (MM.util.isTouchDevice()) {
            MM.clickType = 'touchend';
            MM.quickClick = 'touchstart';
        } else {
            MM.clickType = 'click';
            MM.quickClick = 'click';
        }
    },

    /**
     * Detect the current device type (tablet or phone).
     */
    setDeviceType: function() {
        if (matchMedia(MM.mq).matches) {
            this.deviceType = 'tablet';
            $('body').addClass('tablet');
        } else {
            this.deviceType = 'phone';
            $('body').addClass('phone');
        }
    },

    /**
     * Required to test against different user agents
     * @return string The userAgent in lowercase.
     */
    _getUserAgent: function() {
        return navigator.userAgent.toLowerCase();
    },

    /**
     * Set the current device OS, based on the userAgent string.
     */
    setDeviceOS: function() {
        this.deviceOS = 'null';
        var userAgent = MM._getUserAgent();
        if (userAgent.indexOf('ipad') !== -1) {
            this.deviceOS = 'ios';
        } else if (userAgent.indexOf('iphone') !== -1) {
            this.deviceOS = 'ios';
        } else if (userAgent.indexOf('android') !== -1) {
            this.deviceOS = 'android';
        }
    },

    /**
     * Set the inComputer flag if the app is running in a computer (as opposed to
     * a mobile device).
     */
    setInComputerState: function() {
        MM.inComputer = navigator.userAgent.indexOf('Chrome') >= 0 ||
                        navigator.userAgent.indexOf('Safari') >= 0 ||
                        navigator.userAgent.indexOf('MSIE') >= 0 ||
                        navigator.userAgent.indexOf('Firefox') >= 0;
        MM.inComputer = MM.inComputer && navigator.userAgent.indexOf('Mobile') == -1;
        // Check deviceOs
        MM.inComputer = MM.deviceOS != "ios" && MM.deviceOS != "android";
        // Check if Phonegap Javascript is loaded.
        MM.inComputer = MM.inComputer && typeof(window.device) == "undefined";
        MM.inComputer = MM.inComputer && !MM.deviceReady;

        MM.webApp = location.href.indexOf('http') == 0;
    },

    /**
     * Set the inNodeWK flag if the app is running using Node Webkit
     * on a computer.
     */
    setInNodeWKState: function() {
        MM.inNodeWK = typeof(process) !== "undefined";
    },

    /**
     * Load the cordova emulator if necessary (if the app isn't running on a
     * mobile device).
     */
    loadCordova: function() {
        // If we are testing in a computer, we load the Cordova emulating javascript, waiting a few seconds.
        // If meanwhile Phonegap is loaded, the loadEmulator will do nothing.
        if (MM.inComputer || MM.webApp) {
            // If we are running node-webkit, load the emulator immediately.
            var seconds = (MM.inNodeWK)? 100 : 4000;
            setTimeout(function() {
                MM.log('MM: Loading Cordova Emulator, we are in a ' + navigator.userAgent);
                MM.cordova.loadEmulator();
            }, seconds);
        }
    },

    /**
     * Load the Backbone URL router.
     */
    loadBackboneRouter: function() {
        var appRouter = Backbone.Router.extend();
        MM.Router = new appRouter;
    },

    /**
     * Set default values for future ajax requests.
     * Even jQuery say this isn't recommended.
     * TODO: Remove.
     */
    checkAjax: function() {
        $.ajaxSetup({'error': MM._defaultErrorFunction});
    },

    /**
     * The default error function as set in MM.checkAjax
     * Moved outside of checkAjax for ease of testing.
     */
    _defaultErrorFunction: function(xhr, textStatus, errorThrown) {
        var error = MM.lang.s('cannotconnect');
        if (xhr.status == 404) {
            error = MM.lang.s('invalidscheme');
        }
        MM.popErrorMessage(error);
    },

    /**
     * Load the core models.
     * @param {integer} myParam Description
     * @return {boolean} Description
     */
    loadCoreModels: function() {
        // Load Models.
        // Elements for the core storage model.
        var storage = {
            setting: {
                type: 'model',
                bbproperties: {
                    initialize: function() {
                        MM.config[this.get('name')] = this.get('value');
                   }
                }
            },
            settings: {type: 'collection', model: 'setting'},
            site: {type: 'model'},
            sites: {type: 'collection', model: 'site'},
            course: {type: 'model'},
            courses: {type: 'collection', model: 'course'},
            user: {type: 'model'},
            users: {type: 'collection', model: 'user'},
            cacheEl: {type: 'model'},
            cache: {type: 'collection', model: 'cacheEl'},
            syncEl: {type: 'model'},
            sync: {type: 'collection', model: 'syncEl'},
            service: {type: 'model'},
            services: {type: 'collection', model: 'service'}
        };
        this.loadModels(storage);
    },

    /**
     * Loads the settings from the database, putting each name value pair
     * in the config.
     * @return {void}
     */
    loadSettings: function() {
        // Load settings from database.
        MM.db.each('settings', function(e) {
            MM.config[e.get('name')] = e.get('value');
        });
        // Init global flag for debugging.
        MM.debugging = MM.config.dev_debug;
    },

    /**
     * Used to test the deviceConnected function as Jasmine can't mock
     * the navigator object.
     *
     * @return {Object} The network object if one exists.
     */
    _getNetwork: function() {
        return navigator.network;
    },

    /**
     * Checks if the device is connected to Internet
     * We use Cordova API for that, if the config setting "dev_offline" is set we return allways not connected.
     *
     * @return {boolean} True if the device is connected.
     */
    deviceConnected: function() {
        var connected = true;

        var network = MM._getNetwork();
        if (typeof(network) != 'undefined') {
            var networkState = network.connection.type;
            connected = (networkState != Connection.NONE && networkState != Connection.UNKNOWN);
            MM.log('Internet connection checked ' + connected);
        }

        var offline = MM.getConfig('dev_offline');
        if (typeof(offline) != 'undefined' && offline) {
            // Ugly hack for only emulate offline if we are logged in the app.
            var displayed = $('#main-wrapper').css('display');
            if (displayed == 'block') {
                MM.log('Returning not connected (forced by settings)');
                connected = false;
            }
        }
        return connected;
    },

    /**
     * Loads the non site specific CSS layout of the app and handles orientation/state changes.
     */
    loadLayout: function() {
        MM.log('Loading layout');
        var tpl = MM.tpl.render($('#add-site_template').html());
        $('#add-site').html(tpl);

        // Dom is ready!.
        Backbone.history.start();

        // Add site events.
        $('#add-site form').on('submit', this.checkSite);

        if (typeof MM.config.presets.url != 'undefined') {
            $('#url').val(MM.config.presets.url);
        }
        if (typeof MM.config.presets.username != 'undefined') {
            $('#username').val(MM.config.presets.username);
        }

        $(window).bind('orientationchange', MM.orientationChangeHandler);

        var mq = matchMedia(MM.mq);
        mq.addListener(MM.mediaQueryChangeHandler);
        if (mq.matches) {
            MM.setUpTabletModeLayout();
        } else {
            MM.setUpPhoneModeLayout();
        }

        // Make the background color fill all the screen for the add site page.
        $('#add-site').css('height', $(document).innerHeight());

        $("#panel-left, #panel-center, #panel-right").addClass("overflow-scroll");

        // First we try to use native scrolling for overflow divs.
        if (MM.util.isTouchDevice() && MM.util.overflowScrollingSupported()){
            MM.setUpOverflowScrolling();
        } else {
            if (MM.deviceType == "phone" && !MM.util.overflowScrollingSupported()) {
                MM.setUpNativeScrolling();
            } else {
                MM.setUpJavascriptScrolling();
            }
        }

        // Display the add site screen if no sites added.
        var current_site = MM.getConfig('current_site');

        if (typeof(current_site) != 'undefined' && current_site && current_site.id) {
            if (MM.db.get('sites', current_site.id)) {
                // We should wait for Phonegap/Cordova prior to start calling WS, etc..
                MM.loadSite(current_site.id);
                // Load additional Js files, see MOBILE-239
                MM.loadExtraJs();
                return;
            }
        }

        // Display the initial screen in first access.
        $('#add-site').css('display', 'block');
        // Load additional Js files, see MOBILE-239
        MM.loadExtraJs();
    },

    /**
     * Handle device orientation change events.
     * @param {object} e The event object.
     */
    orientationChangeHandler: function(e) {
        MM.log("MM: orientationchange fired, old width old height " + $(document).innerHeight());
        $('#main-wrapper, #panel-left').css('height', $(document).innerHeight());

        var dH = $(document).height();
        var wH = $(window).height();
        var diH = $(document).innerHeight();

        var newH = (dH > wH) ? dH : wH;
        newH = (diH > newH) ? diH : newH;

        headerHeight = $('.header-wrapper').height();
        $('#main-wrapper, #panel-left').css('height', newH);
        $('#panel-center, #panel-right').css('height', newH - headerHeight);

        if (MM.deviceType == 'phone') {
            $('#panel-right').css("width", $(document).innerWidth() + 50);
            $("#panel-right .content-index").css("width", $(document).innerWidth());
        } else {
            MM.panels.resizePanels();
        }

        MM.log("MM: orientation change fired, new height " + newH);
    },

    /**
     * Handle media query change events, refresh the viewport.
     * @param {object} mq The media query object.
     */
    mediaQueryChangeHandler: function(mq) {
        MM.log('media queries match');
        if (mq.matches && MM.deviceType == 'phone') {
            // We were in phone resolution view, now we are in tablet resolution view. Reload all the layout.
            location.href = "index.html";
        } else if (!mq.matches && MM.deviceType == 'tablet') {
            // We were in tablet resolution view, now we are in phone view. Reload all the layout
            location.href = "index.html";
        }
    },

    /**
     * Set up tablet mode layout.
     */
    setUpTabletModeLayout: function() {
        MM.panels.calculatePanelsSizes();
        MM.panels.fixPanelsSize();

        $('#mainmenu').bind(MM.quickClick, function(e) {
            MM.panels.menuShow();
            e.preventDefault();
            e.stopPropagation();
        });

        // Swipe detection.
        $('#panel-center, #panel-right').swipe({
            swipeLeft: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe' + direction + ' fingers ' + fingerCount);
                MM.panels.menuShow(false);
            },
            swipeRight: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe' + direction + ' fingers ' + fingerCount);
                MM.panels.menuShow(true);
            },
            click: function(event, direction, distance, duration, fingerCount) {
            },
            threshold: 50,
            excludedElements: "button, input, select, textarea, .noSwipe"
        });
    },

    /**
     * Set up phone mode layout.
     */
    setUpPhoneModeLayout: function() {
        $('#mainmenu').bind(MM.quickClick, function(e) {
            MM.panels.goBack();
            e.preventDefault();
        });

        var excludedElements = "button, input, select, textarea, .noSwipe";
        if (MM.deviceOS == 'android') {
            excludedElements += ", a";
        }

        $('#panel-center, #panel-right').swipe({
            swipeRight: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe right');
                MM.panels.goBack();
            },
            swipeLeft: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe left');
                MM.panels.goFront();
            },
            click: function(event, direction, distance, duration, fingerCount) {
            },
            threshold: 50,
            excludedElements: excludedElements
        });
    },

    /**
     * Set up overflow scrolling.
     */
    setUpOverflowScrolling: function() {
        MM.log('Overflow supported');
        MM.scrollType = "Native overflow scrolling touch";
        MM.util.setPanelsScreenHeight();
        $("#panel-left, #panel-center, #panel-right").addClass("touch-overflow-scroll");
        // We must detect that are touch moving for avoid opening links fired by javascript events...
        $("#panel-left, #panel-center, #panel-right").bind('touchmove', function(event) {
            MM.touchMoving = true;
        });
        $("#panel-left, #panel-center, #panel-right").bind('touchend', function(event) {
            MM.touchMoving = false;
        });
    },

    /**
     * Set up native scrolling.
     */
    setUpNativeScrolling: function() {
        MM.log("Omitting using overflow scroll");
        MM.scrollType = "Native scrolling";
        MM.util.setPanelsMinScreenHeight();
        $("#main-wrapper").css("overflow", "visible");
        $("#panel-left, #panel-center, #panel-right").addClass("no-overflow");
        $(".header-wrapper").addClass("header-fixed");
        MM.util.avoidHorizontalScrolling();
    },

    /**
     * Set up javascript scrolling.
     */
    setUpJavascriptScrolling: function() {
        MM.scrollType = "Javascript scrolling";
        MM.util.setPanelsScreenHeight();
        // These lines makes the iPad scroll working (not momentum).
        MM.util.touchScroll('left');
        MM.util.touchScroll('center');
        MM.util.touchScroll('right');
    },

    /**
     * Loads the HTML and CSS specific for a site.
     *
     * @param {string} siteId The site id.
     */
    loadSite: function(siteId) {
        MM.log('Loading site');
        MM.site = MM.db.get('sites', siteId);
        MM.sync.init();
        MM.loadCachedRemoteCSS();
        MM.setUpConfig();
        MM.setUpLanguages();

        // For loading a site, we need the list of courses.
        MM.moodleWSCall(
            method          = 'moodle_enrol_get_users_courses',
            data            = {userid: MM.site.get('userid')},
            callBack        = MM.loadCourses,
            preSets         = {omitExpires: true},
            errorCallBack   = MM.showAddSitePanel
        );
    },

    /**
     * Set up config.
     */
    setUpConfig: function() {
        if (MM.config.current_site.id != MM.site.id) {
            MM.setConfig('current_site', MM.site.toJSON());
        }
        MM.setConfig('current_token', MM.site.get('token'));
    },

    /**
     * Set up languages.
     */
    setUpLanguages: function() {
        MM.lang.setup();
        for (var el in MM.config.plugins) {
            var index = MM.config.plugins[el];
            var plugin = MM.plugins[index];
            if (typeof plugin == 'undefined') {
                continue;
            }
            if (plugin.settings.lang.component != "core") {
                MM.lang.setup(plugin.settings.name);
            }
        }
        MM.lang.sync();
    },

    /**
     * Load cached remote CSS.
     */
    loadCachedRemoteCSS: function() {

        // Load cached remote CSS
        var remoteCSS = MM.cache.getElement('css', true);
        if (remoteCSS) {
            $('#mobilecssurl').html(remoteCSS);
        } else {
            $('#mobilecssurl').html('');
        }

        // Forces sync CSS.
        MM.sync.css();
    },

    /**
     * Load courses.
     */
    loadCourses: function(courses) {
        var plugins = [];
        var coursePlugins = [];

        for (var el in MM.config.plugins) {
            var index = MM.config.plugins[el];
            var plugin = MM.plugins[index];
            if (typeof plugin == 'undefined') {
                continue;
            }
            // Check if the plugin is Visible.
            // If the iPluginVisible function is undefined, we assume the plugin is visible without additional checks.
            if (typeof(plugin.isPluginVisible) == 'function' && !plugin.isPluginVisible()) {
                continue;
            }
            if (plugin.settings.type == 'general') {
                plugins.push(plugin.settings);
            } else if (plugin.settings.type == 'course') {
                coursePlugins.push(plugin.settings);
            }
        }

        // Prepare info for loading main menu.
        values = {
            user: {
                fullname: MM.site.get('fullname'),
                profileimageurl: MM.site.get('userpictureurl')
            },
            siteurl: MM.site.get('siteurl'),
            coursePlugins: coursePlugins,
            courses: courses,
            plugins: plugins
        };

        // Load the main menu template.
        var output = MM.tpl.render($('#menu_template').html(), values);
        MM.panels.html('left', output);

        // TODO: Why???
        $('.submenu:not(:first)').hide();
        $('.submenu').hide();
        $('.toogler').bind(MM.clickType, function(e) {
            // This prevents open the toogler when we are scrolling.
            if (MM.touchMoving) {
                MM.touchMoving = false;
            } else {
                $(this).next().slideToggle(300);
                $(this).toggleClass("collapse expand");
            }
        });

        // Store the courses
        for (var el in courses) {
            // We clone the course object because we are going to modify it in a copy.
            var storedCourse = JSON.parse(JSON.stringify(courses[el]));
            storedCourse.courseid = storedCourse.id;
            storedCourse.siteid = MM.config.current_site.id;
            // For avoid collising between sites.
            storedCourse.id = MM.config.current_site.id + '-' + storedCourse.courseid;
            var r = MM.db.insert('courses', storedCourse);
        }

        // Hide the Add Site panel.
        $('#add-site').css('display', 'none');
        // Display the main panels.
        $('#main-wrapper').css('display', 'block');

        if (MM.deviceType == 'tablet') {
            MM.panels.html('center', '<div class="welcome">' + MM.lang.s("welcome") + '</div>');
            MM.panels.menuShow(true, {animate: false});
            MM.panels.hide('right', '');
        }
    },

    getClickType: function() {
        return MM.clickType;
    },

    getTouchMoving: function() {
        return MM.touchMoving;
    },

    /**
     * Show add site panel
     */
    showAddSitePanel: function() {
        $('#add-site').css('display', 'block');
    },

    /**
     * Function for determine with service we should be use (default or extended plugin)
     *
     * @param  {string} siteURL The site URL
     * @return {string}         The service shortname
     */
    _determineService: function(siteURL) {
        // We need to try siteURL in both https or http (due to loginhttps setting).

        // First http://
        siteURL = siteURL.replace("https://", "http://");
        var id = hex_md5(siteURL);
        var service = MM.db.get('services', id);
        if (service) {
            return service.get('service');
        }

        // Now https://
        siteURL = siteURL.replace("http://", "https://");
        id = hex_md5(siteURL);
        service = MM.db.get('services', id);
        if (service) {
            return service.get('service');
        }

        // Return default service.
        return MM.config.wsservice;
    },

    /**
     * Check if the local_mobile plugin is installed in the Moodle site
     * This plugin provide extended services
     * @param  {string} siteURL         The Moodle SiteURL
     * @param  {Object} successCallBack Success callback
     * @param  {Object} errorCallback   Error callback
     */
    _checkMobileLocalPlugin: function(siteURL, successCallBack, errorCallback) {
        // First check if is disabled by config.
        if (!MM.config.wsextservice) {
            errorCallback();
            return;
        }
        $.ajax({
            url: siteURL + "/local/mobile/check.php",
            type:'POST',
            data:{
                service: MM.config.wsextservice
            },
            dataType: "json",
            success:  successCallBack,
            error:    errorCallback
        });
    },

    /**
     * Reset the add site form
     */
    _resetAddSiteForm: function() {
        $('#username').val("");
        $('#password').val("");
        $("#url").removeAttr("disabled");
        $('#add-site form').unbind('submit');
        $('#add-site form').on('submit', MM.checkSite);
        $("#login-credentials").css("display", "none");
        $("#login-details").animate({paddingTop: "40px"});
        $("#resetsitebutton").css("display", "none");
        $('#username').focus();
    },

    /**
     * Expand the add site form with the username and password fields
     */
    _expandAddSiteForm: function() {
        $("#url").attr('disabled','disabled');
        $("#login-details").animate({paddingTop: "0px"});
        $("#login-credentials").css("display", "block");
        $("#resetsitebutton").css("display", "inline");
        $('#add-site form').on('submit', MM.addSite);
        $("#resetsitebutton").on(MM.clickType, function() {
            MM._resetAddSiteForm();
        });
        $('#username').focus();
    },

    _appLaunchedByURL: function(url) {

        // We have to wait until the app is initialized.
        if (typeof(MM.config.launchSiteURL) == "undefined") {
            setTimeout(function() {
                MM._appLaunchedByURL(url);
            }, 1000);
            return;
        }
        MM.showModalLoading(MM.lang.s("authenticating"));
        MM.log("App launched by URL");

        // Delete the URL scheme from the URL.
        url = url.replace("moodlemobile://token=", "");
        // Decode from base64.
        url = atob(url);

        // Split signature:::token
        var params = url.split(":::");

        launchSiteURL = MM.getConfig("launchSiteURL");
        passport = MM.getConfig("launchPassport");

        // Validate the signature.
        var signature = hex_md5(launchSiteURL + passport);
        if (signature == params[0]) {
            MM.log("Signature validated");
            MM.siteurl = launchSiteURL;
            MM._saveToken(params[1]);
        } else {
            MM.log("Inalid signature in the URL request yours: " + params[0] + " mine: " + signature);
        }
    },


    /**
     * Check if a site is valid and if it has specifics settings for authentication
     * (like force to log in using the browser)
     *
     * @param {!Object} Javascript event.
     * @param {string} originalProtocol The procol entered in the url.
     */

    checkSite: function(e, originalProtocol) {
        if (e) {
            e.preventDefault();
        }

        var siteurl = $('#url').val();
        var demoSite = false;

        // Check for keywords for demo sites
        $.each(MM.config.demo_sites, function (index, site) {
            if (siteurl == site.key) {
                siteurl = site.url;
                username = site.username;
                password = <PASSWORD>;
                demoSite = true;
                return false; // break
            }
        });

        // Load a demo site and exits thic checking function.
        if (demoSite) {
            originalProtocol = "http://";
            if (siteurl.indexOf('https://') === 0) {
                originalProtocol = "https://";
            } else {
                siteurl = siteurl.replace("http://", "https://");
            }
            MM.saveSite(username, password, siteurl, originalProtocol);
            return;
        }

        $("#url").addClass("url-loading");

        siteurl = MM.util.formatURL($('#url').val());
        if (siteurl.indexOf('http://localhost') == -1 && !MM.validateURL(siteurl)) {
            msg = MM.lang.s('siteurlrequired') + '<br/>';
            MM.popErrorMessage(msg);
            $("#url").removeClass("url-loading");
            return;
        }

        // First we try to connect using https but only if the user has entered a URL starting by http://
        // We are not going to never fallback to http from https

        var timeout = 30000;

        if (typeof(originalProtocol) == "undefined") {
            originalProtocol = "http://";
            if (siteurl.indexOf('https://') === 0) {
                originalProtocol = "https://";
            } else {
                siteurl = siteurl.replace("http://", "https://");
                timeout = 10000;
            }
        }

        // First, check that the site exists.
        $.ajax({
            url:  siteurl + "/login/token.php",
            type: 'HEAD',
            timeout: timeout,
            success:function() {
                // Now, check if the mobile local plugin is configured.
                // We need to check if we are going to use extended services and also if SSO is configured.
                MM._checkMobileLocalPlugin(siteurl,
                    // Success.
                    function(response) {
                        if (typeof(response.code) == "undefined") {
                            MM.popErrorMessage(MM.lang.s("unexpectederror"));
                            return;
                        }

                        var code = parseInt(response.code, 10);
                        if (response.error) {
                            switch (code) {
                                case 1:
                                    // Site in maintenance mode.
                                    msg = MM.lang.s("siteinmaintenance");
                                    MM.popErrorMessage(msg);
                                    break;
                                case 2:
                                    // Web services not enabled.
                                    msg = MM.lang.s("webservicesnotenabled");
                                    MM.popErrorMessage(msg);
                                    break;
                                case 3:
                                    // Extended service not enabled, but the official is enabled.
                                    MM._expandAddSiteForm();
                                    break;
                                case 4:
                                    // Neither extended or official services enabled.
                                    msg = MM.lang.s("mobileservicesnotenabled");
                                    MM.popErrorMessage(msg);
                                    break;
                                default:
                                    msg = MM.lang.s("unexpectederror");
                                    MM.popErrorMessage(msg);
                            }
                            return;
                        } else {
                            // Now we store here the service used by this site.
                            var service = {
                                id: hex_md5(siteurl),
                                siteurl: siteurl,
                                service: MM.config.wsextservice
                            };
                            MM.db.insert("services", service);

                            switch (code) {
                                case 1:
                                    // Normal login.
                                    MM._expandAddSiteForm();
                                    break;
                                case 2:
                                    MM.popConfirm(MM.lang.s('logininsiterequired'), function() {
                                        var passport = "abc";
                                        var url = siteurl + "/local/mobile/launch.php?service=" + MM.config.wsservice;
                                        url += "&passport=" + passport;

                                        MM.setConfig("launchSiteURL",  siteurl);
                                        MM.setConfig("launchPassport", passport);
                                        window.open(url, "_system");
                                        if (navigator.app) {
                                            navigator.app.exitApp();
                                        }
                                    });
                                    break;
                                default:
                                    MM._expandAddSiteForm();
                            }
                        }
                    },
                    // Error. The plugin is not installed.
                    // We are going to perform then a normal login process.
                    function() {
                        MM._expandAddSiteForm();
                    }
                );
            },
            error:function(xhr, textStatus, errorThrown) {
                var error = MM.lang.s('cannotconnect');

                if (siteurl.indexOf("https://") === 0) {
                    if (originalProtocol == "http://") {
                        MM.checkSite(null, originalProtocol);
                        return;
                    }
                    error = MM.lang.s('cannotconnectssl');
                }
                else if (xhr.status == 404) {
                    error = MM.lang.s('invalidscheme');
                }
                MM.popErrorMessage(error);
            },
            complete: function() {
                $("#url").removeClass("url-loading");
            }
        });

    },<commit_msg>MOBILE-555 Logging added and random passport<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileOverview Main app library where is defined the global namespace "MM".
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @version 1.2
 */

/**
  * @namespace Holds all the MoodleMobile specific functionallity.
 */
var MM = {

    config: {},
    plugins: [],
    models: {},
    collections: {},
    deviceType: 'phone',
    clickType: 'click',
    quickClick: 'click',
    deviceReady: false,
    deviceOS: '',
    logData: [],
    inComputer: false,
    inNodeWK: false,
    touchMoving: false,
    scrollType: '',
    debugging: true,
    mq: 'only screen and (min-width: 768px) and (-webkit-min-device-pixel-ratio: 1)',

    /**
     * Initial setup of the app: device type detection, routes, models, settings.
     * This function is executed once the config.json file is loaded and previously to loading the app plugins.
     *
     * @this {MM}
     * @param {Object.<>} Settings loaded from /config.json.
     */
    init: function(config) {
        MM.log('Initializating app');
        this.config = config;
        this.setEventTypes();
        this.setDeviceType();
        this.setDeviceOS();
        this.setInComputerState();
        this.setInNodeWKState();
        this.loadCordova();
        this.loadBackboneRouter();
        this.checkAjax();
        this.loadCoreModels();
        this.loadRoutes();
        this.loadSettings();
    },

    /**
     * Set which events we should bind interaction handlers to.
     */
    setEventTypes: function() {
        if (MM.util.isTouchDevice()) {
            MM.clickType = 'touchend';
            MM.quickClick = 'touchstart';
        } else {
            MM.clickType = 'click';
            MM.quickClick = 'click';
        }
    },

    /**
     * Detect the current device type (tablet or phone).
     */
    setDeviceType: function() {
        if (matchMedia(MM.mq).matches) {
            this.deviceType = 'tablet';
            $('body').addClass('tablet');
        } else {
            this.deviceType = 'phone';
            $('body').addClass('phone');
        }
    },

    /**
     * Required to test against different user agents
     * @return string The userAgent in lowercase.
     */
    _getUserAgent: function() {
        return navigator.userAgent.toLowerCase();
    },

    /**
     * Set the current device OS, based on the userAgent string.
     */
    setDeviceOS: function() {
        this.deviceOS = 'null';
        var userAgent = MM._getUserAgent();
        if (userAgent.indexOf('ipad') !== -1) {
            this.deviceOS = 'ios';
        } else if (userAgent.indexOf('iphone') !== -1) {
            this.deviceOS = 'ios';
        } else if (userAgent.indexOf('android') !== -1) {
            this.deviceOS = 'android';
        }
    },

    /**
     * Set the inComputer flag if the app is running in a computer (as opposed to
     * a mobile device).
     */
    setInComputerState: function() {
        MM.inComputer = navigator.userAgent.indexOf('Chrome') >= 0 ||
                        navigator.userAgent.indexOf('Safari') >= 0 ||
                        navigator.userAgent.indexOf('MSIE') >= 0 ||
                        navigator.userAgent.indexOf('Firefox') >= 0;
        MM.inComputer = MM.inComputer && navigator.userAgent.indexOf('Mobile') == -1;
        // Check deviceOs
        MM.inComputer = MM.deviceOS != "ios" && MM.deviceOS != "android";
        // Check if Phonegap Javascript is loaded.
        MM.inComputer = MM.inComputer && typeof(window.device) == "undefined";
        MM.inComputer = MM.inComputer && !MM.deviceReady;

        MM.webApp = location.href.indexOf('http') == 0;
    },

    /**
     * Set the inNodeWK flag if the app is running using Node Webkit
     * on a computer.
     */
    setInNodeWKState: function() {
        MM.inNodeWK = typeof(process) !== "undefined";
    },

    /**
     * Load the cordova emulator if necessary (if the app isn't running on a
     * mobile device).
     */
    loadCordova: function() {
        // If we are testing in a computer, we load the Cordova emulating javascript, waiting a few seconds.
        // If meanwhile Phonegap is loaded, the loadEmulator will do nothing.
        if (MM.inComputer || MM.webApp) {
            // If we are running node-webkit, load the emulator immediately.
            var seconds = (MM.inNodeWK)? 100 : 4000;
            setTimeout(function() {
                MM.log('MM: Loading Cordova Emulator, we are in a ' + navigator.userAgent);
                MM.cordova.loadEmulator();
            }, seconds);
        }
    },

    /**
     * Load the Backbone URL router.
     */
    loadBackboneRouter: function() {
        var appRouter = Backbone.Router.extend();
        MM.Router = new appRouter;
    },

    /**
     * Set default values for future ajax requests.
     * Even jQuery say this isn't recommended.
     * TODO: Remove.
     */
    checkAjax: function() {
        $.ajaxSetup({'error': MM._defaultErrorFunction});
    },

    /**
     * The default error function as set in MM.checkAjax
     * Moved outside of checkAjax for ease of testing.
     */
    _defaultErrorFunction: function(xhr, textStatus, errorThrown) {
        var error = MM.lang.s('cannotconnect');
        if (xhr.status == 404) {
            error = MM.lang.s('invalidscheme');
        }
        MM.popErrorMessage(error);
    },

    /**
     * Load the core models.
     * @param {integer} myParam Description
     * @return {boolean} Description
     */
    loadCoreModels: function() {
        // Load Models.
        // Elements for the core storage model.
        var storage = {
            setting: {
                type: 'model',
                bbproperties: {
                    initialize: function() {
                        MM.config[this.get('name')] = this.get('value');
                   }
                }
            },
            settings: {type: 'collection', model: 'setting'},
            site: {type: 'model'},
            sites: {type: 'collection', model: 'site'},
            course: {type: 'model'},
            courses: {type: 'collection', model: 'course'},
            user: {type: 'model'},
            users: {type: 'collection', model: 'user'},
            cacheEl: {type: 'model'},
            cache: {type: 'collection', model: 'cacheEl'},
            syncEl: {type: 'model'},
            sync: {type: 'collection', model: 'syncEl'},
            service: {type: 'model'},
            services: {type: 'collection', model: 'service'}
        };
        this.loadModels(storage);
    },

    /**
     * Loads the settings from the database, putting each name value pair
     * in the config.
     * @return {void}
     */
    loadSettings: function() {
        // Load settings from database.
        MM.db.each('settings', function(e) {
            MM.config[e.get('name')] = e.get('value');
        });
        // Init global flag for debugging.
        MM.debugging = MM.config.dev_debug;
    },

    /**
     * Used to test the deviceConnected function as Jasmine can't mock
     * the navigator object.
     *
     * @return {Object} The network object if one exists.
     */
    _getNetwork: function() {
        return navigator.network;
    },

    /**
     * Checks if the device is connected to Internet
     * We use Cordova API for that, if the config setting "dev_offline" is set we return allways not connected.
     *
     * @return {boolean} True if the device is connected.
     */
    deviceConnected: function() {
        var connected = true;

        var network = MM._getNetwork();
        if (typeof(network) != 'undefined') {
            var networkState = network.connection.type;
            connected = (networkState != Connection.NONE && networkState != Connection.UNKNOWN);
            MM.log('Internet connection checked ' + connected);
        }

        var offline = MM.getConfig('dev_offline');
        if (typeof(offline) != 'undefined' && offline) {
            // Ugly hack for only emulate offline if we are logged in the app.
            var displayed = $('#main-wrapper').css('display');
            if (displayed == 'block') {
                MM.log('Returning not connected (forced by settings)');
                connected = false;
            }
        }
        return connected;
    },

    /**
     * Loads the non site specific CSS layout of the app and handles orientation/state changes.
     */
    loadLayout: function() {
        MM.log('Loading layout');
        var tpl = MM.tpl.render($('#add-site_template').html());
        $('#add-site').html(tpl);

        // Dom is ready!.
        Backbone.history.start();

        // Add site events.
        $('#add-site form').on('submit', this.checkSite);

        if (typeof MM.config.presets.url != 'undefined') {
            $('#url').val(MM.config.presets.url);
        }
        if (typeof MM.config.presets.username != 'undefined') {
            $('#username').val(MM.config.presets.username);
        }

        $(window).bind('orientationchange', MM.orientationChangeHandler);

        var mq = matchMedia(MM.mq);
        mq.addListener(MM.mediaQueryChangeHandler);
        if (mq.matches) {
            MM.setUpTabletModeLayout();
        } else {
            MM.setUpPhoneModeLayout();
        }

        // Make the background color fill all the screen for the add site page.
        $('#add-site').css('height', $(document).innerHeight());

        $("#panel-left, #panel-center, #panel-right").addClass("overflow-scroll");

        // First we try to use native scrolling for overflow divs.
        if (MM.util.isTouchDevice() && MM.util.overflowScrollingSupported()){
            MM.setUpOverflowScrolling();
        } else {
            if (MM.deviceType == "phone" && !MM.util.overflowScrollingSupported()) {
                MM.setUpNativeScrolling();
            } else {
                MM.setUpJavascriptScrolling();
            }
        }

        // Display the add site screen if no sites added.
        var current_site = MM.getConfig('current_site');

        if (typeof(current_site) != 'undefined' && current_site && current_site.id) {
            if (MM.db.get('sites', current_site.id)) {
                // We should wait for Phonegap/Cordova prior to start calling WS, etc..
                MM.loadSite(current_site.id);
                // Load additional Js files, see MOBILE-239
                MM.loadExtraJs();
                return;
            }
        }

        // Display the initial screen in first access.
        $('#add-site').css('display', 'block');
        // Load additional Js files, see MOBILE-239
        MM.loadExtraJs();
    },

    /**
     * Handle device orientation change events.
     * @param {object} e The event object.
     */
    orientationChangeHandler: function(e) {
        MM.log("MM: orientationchange fired, old width old height " + $(document).innerHeight());
        $('#main-wrapper, #panel-left').css('height', $(document).innerHeight());

        var dH = $(document).height();
        var wH = $(window).height();
        var diH = $(document).innerHeight();

        var newH = (dH > wH) ? dH : wH;
        newH = (diH > newH) ? diH : newH;

        headerHeight = $('.header-wrapper').height();
        $('#main-wrapper, #panel-left').css('height', newH);
        $('#panel-center, #panel-right').css('height', newH - headerHeight);

        if (MM.deviceType == 'phone') {
            $('#panel-right').css("width", $(document).innerWidth() + 50);
            $("#panel-right .content-index").css("width", $(document).innerWidth());
        } else {
            MM.panels.resizePanels();
        }

        MM.log("MM: orientation change fired, new height " + newH);
    },

    /**
     * Handle media query change events, refresh the viewport.
     * @param {object} mq The media query object.
     */
    mediaQueryChangeHandler: function(mq) {
        MM.log('media queries match');
        if (mq.matches && MM.deviceType == 'phone') {
            // We were in phone resolution view, now we are in tablet resolution view. Reload all the layout.
            location.href = "index.html";
        } else if (!mq.matches && MM.deviceType == 'tablet') {
            // We were in tablet resolution view, now we are in phone view. Reload all the layout
            location.href = "index.html";
        }
    },

    /**
     * Set up tablet mode layout.
     */
    setUpTabletModeLayout: function() {
        MM.panels.calculatePanelsSizes();
        MM.panels.fixPanelsSize();

        $('#mainmenu').bind(MM.quickClick, function(e) {
            MM.panels.menuShow();
            e.preventDefault();
            e.stopPropagation();
        });

        // Swipe detection.
        $('#panel-center, #panel-right').swipe({
            swipeLeft: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe' + direction + ' fingers ' + fingerCount);
                MM.panels.menuShow(false);
            },
            swipeRight: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe' + direction + ' fingers ' + fingerCount);
                MM.panels.menuShow(true);
            },
            click: function(event, direction, distance, duration, fingerCount) {
            },
            threshold: 50,
            excludedElements: "button, input, select, textarea, .noSwipe"
        });
    },

    /**
     * Set up phone mode layout.
     */
    setUpPhoneModeLayout: function() {
        $('#mainmenu').bind(MM.quickClick, function(e) {
            MM.panels.goBack();
            e.preventDefault();
        });

        var excludedElements = "button, input, select, textarea, .noSwipe";
        if (MM.deviceOS == 'android') {
            excludedElements += ", a";
        }

        $('#panel-center, #panel-right').swipe({
            swipeRight: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe right');
                MM.panels.goBack();
            },
            swipeLeft: function(event, direction, distance, duration, fingerCount) {
                MM.log('Swipe left');
                MM.panels.goFront();
            },
            click: function(event, direction, distance, duration, fingerCount) {
            },
            threshold: 50,
            excludedElements: excludedElements
        });
    },

    /**
     * Set up overflow scrolling.
     */
    setUpOverflowScrolling: function() {
        MM.log('Overflow supported');
        MM.scrollType = "Native overflow scrolling touch";
        MM.util.setPanelsScreenHeight();
        $("#panel-left, #panel-center, #panel-right").addClass("touch-overflow-scroll");
        // We must detect that are touch moving for avoid opening links fired by javascript events...
        $("#panel-left, #panel-center, #panel-right").bind('touchmove', function(event) {
            MM.touchMoving = true;
        });
        $("#panel-left, #panel-center, #panel-right").bind('touchend', function(event) {
            MM.touchMoving = false;
        });
    },

    /**
     * Set up native scrolling.
     */
    setUpNativeScrolling: function() {
        MM.log("Omitting using overflow scroll");
        MM.scrollType = "Native scrolling";
        MM.util.setPanelsMinScreenHeight();
        $("#main-wrapper").css("overflow", "visible");
        $("#panel-left, #panel-center, #panel-right").addClass("no-overflow");
        $(".header-wrapper").addClass("header-fixed");
        MM.util.avoidHorizontalScrolling();
    },

    /**
     * Set up javascript scrolling.
     */
    setUpJavascriptScrolling: function() {
        MM.scrollType = "Javascript scrolling";
        MM.util.setPanelsScreenHeight();
        // These lines makes the iPad scroll working (not momentum).
        MM.util.touchScroll('left');
        MM.util.touchScroll('center');
        MM.util.touchScroll('right');
    },

    /**
     * Loads the HTML and CSS specific for a site.
     *
     * @param {string} siteId The site id.
     */
    loadSite: function(siteId) {
        MM.log('Loading site');
        MM.site = MM.db.get('sites', siteId);
        MM.sync.init();
        MM.loadCachedRemoteCSS();
        MM.setUpConfig();
        MM.setUpLanguages();

        // For loading a site, we need the list of courses.
        MM.moodleWSCall(
            method          = 'moodle_enrol_get_users_courses',
            data            = {userid: MM.site.get('userid')},
            callBack        = MM.loadCourses,
            preSets         = {omitExpires: true},
            errorCallBack   = MM.showAddSitePanel
        );
    },

    /**
     * Set up config.
     */
    setUpConfig: function() {
        if (MM.config.current_site.id != MM.site.id) {
            MM.setConfig('current_site', MM.site.toJSON());
        }
        MM.setConfig('current_token', MM.site.get('token'));
    },

    /**
     * Set up languages.
     */
    setUpLanguages: function() {
        MM.lang.setup();
        for (var el in MM.config.plugins) {
            var index = MM.config.plugins[el];
            var plugin = MM.plugins[index];
            if (typeof plugin == 'undefined') {
                continue;
            }
            if (plugin.settings.lang.component != "core") {
                MM.lang.setup(plugin.settings.name);
            }
        }
        MM.lang.sync();
    },

    /**
     * Load cached remote CSS.
     */
    loadCachedRemoteCSS: function() {

        // Load cached remote CSS
        var remoteCSS = MM.cache.getElement('css', true);
        if (remoteCSS) {
            $('#mobilecssurl').html(remoteCSS);
        } else {
            $('#mobilecssurl').html('');
        }

        // Forces sync CSS.
        MM.sync.css();
    },

    /**
     * Load courses.
     */
    loadCourses: function(courses) {
        var plugins = [];
        var coursePlugins = [];

        for (var el in MM.config.plugins) {
            var index = MM.config.plugins[el];
            var plugin = MM.plugins[index];
            if (typeof plugin == 'undefined') {
                continue;
            }
            // Check if the plugin is Visible.
            // If the iPluginVisible function is undefined, we assume the plugin is visible without additional checks.
            if (typeof(plugin.isPluginVisible) == 'function' && !plugin.isPluginVisible()) {
                continue;
            }
            if (plugin.settings.type == 'general') {
                plugins.push(plugin.settings);
            } else if (plugin.settings.type == 'course') {
                coursePlugins.push(plugin.settings);
            }
        }

        // Prepare info for loading main menu.
        values = {
            user: {
                fullname: MM.site.get('fullname'),
                profileimageurl: MM.site.get('userpictureurl')
            },
            siteurl: MM.site.get('siteurl'),
            coursePlugins: coursePlugins,
            courses: courses,
            plugins: plugins
        };

        // Load the main menu template.
        var output = MM.tpl.render($('#menu_template').html(), values);
        MM.panels.html('left', output);

        // TODO: Why???
        $('.submenu:not(:first)').hide();
        $('.submenu').hide();
        $('.toogler').bind(MM.clickType, function(e) {
            // This prevents open the toogler when we are scrolling.
            if (MM.touchMoving) {
                MM.touchMoving = false;
            } else {
                $(this).next().slideToggle(300);
                $(this).toggleClass("collapse expand");
            }
        });

        // Store the courses
        for (var el in courses) {
            // We clone the course object because we are going to modify it in a copy.
            var storedCourse = JSON.parse(JSON.stringify(courses[el]));
            storedCourse.courseid = storedCourse.id;
            storedCourse.siteid = MM.config.current_site.id;
            // For avoid collising between sites.
            storedCourse.id = MM.config.current_site.id + '-' + storedCourse.courseid;
            var r = MM.db.insert('courses', storedCourse);
        }

        // Hide the Add Site panel.
        $('#add-site').css('display', 'none');
        // Display the main panels.
        $('#main-wrapper').css('display', 'block');

        if (MM.deviceType == 'tablet') {
            MM.panels.html('center', '<div class="welcome">' + MM.lang.s("welcome") + '</div>');
            MM.panels.menuShow(true, {animate: false});
            MM.panels.hide('right', '');
        }
    },

    getClickType: function() {
        return MM.clickType;
    },

    getTouchMoving: function() {
        return MM.touchMoving;
    },

    /**
     * Show add site panel
     */
    showAddSitePanel: function() {
        $('#add-site').css('display', 'block');
    },

    /**
     * Function for determine with service we should be use (default or extended plugin)
     *
     * @param  {string} siteURL The site URL
     * @return {string}         The service shortname
     */
    _determineService: function(siteURL) {
        // We need to try siteURL in both https or http (due to loginhttps setting).

        // First http://
        siteURL = siteURL.replace("https://", "http://");
        var id = hex_md5(siteURL);
        var service = MM.db.get('services', id);
        if (service) {
            return service.get('service');
        }

        // Now https://
        siteURL = siteURL.replace("http://", "https://");
        id = hex_md5(siteURL);
        service = MM.db.get('services', id);
        if (service) {
            return service.get('service');
        }

        // Return default service.
        return MM.config.wsservice;
    },

    /**
     * Check if the local_mobile plugin is installed in the Moodle site
     * This plugin provide extended services
     * @param  {string} siteURL         The Moodle SiteURL
     * @param  {Object} successCallBack Success callback
     * @param  {Object} errorCallback   Error callback
     */
    _checkMobileLocalPlugin: function(siteURL, successCallBack, errorCallback) {
        // First check if is disabled by config.
        if (!MM.config.wsextservice) {
            errorCallback();
            return;
        }
        $.ajax({
            url: siteURL + "/local/mobile/check.php",
            type:'POST',
            data:{
                service: MM.config.wsextservice
            },
            dataType: "json",
            success:  successCallBack,
            error:    errorCallback
        });
    },

    /**
     * Reset the add site form
     */
    _resetAddSiteForm: function() {
        $('#username').val("");
        $('#password').val("");
        $("#url").removeAttr("disabled");
        $('#add-site form').unbind('submit');
        $('#add-site form').on('submit', MM.checkSite);
        $("#login-credentials").css("display", "none");
        $("#login-details").animate({paddingTop: "40px"});
        $("#resetsitebutton").css("display", "none");
        $('#username').focus();
    },

    /**
     * Expand the add site form with the username and password fields
     */
    _expandAddSiteForm: function() {
        $("#url").attr('disabled','disabled');
        $("#login-details").animate({paddingTop: "0px"});
        $("#login-credentials").css("display", "block");
        $("#resetsitebutton").css("display", "inline");
        $('#add-site form').on('submit', MM.addSite);
        $("#resetsitebutton").on(MM.clickType, function() {
            MM._resetAddSiteForm();
        });
        $('#username').focus();
    },

    _appLaunchedByURL: function(url) {

        // We have to wait until the app is initialized.
        if (typeof(MM.config.launchSiteURL) == "undefined") {
            setTimeout(function() {
                MM._appLaunchedByURL(url);
            }, 1000);
            return;
        }
        MM.showModalLoading(MM.lang.s("authenticating"));
        MM.log("App launched by URL");

        // Delete the URL scheme from the URL.
        url = url.replace("moodlemobile://token=", "");
        // Decode from base64.
        url = atob(url);

        // Split signature:::token
        var params = url.split(":::");

        launchSiteURL = MM.getConfig("launchSiteURL");
        passport = MM.getConfig("launchPassport");

        // Validate the signature.
        var signature = hex_md5(launchSiteURL + passport);
        if (signature == params[0]) {
            MM.log("Signature validated");
            MM.siteurl = launchSiteURL;
            MM._saveToken(params[1]);
        } else {
            MM.log("Inalid signature in the URL request yours: " + params[0] + " mine: " + signature);
        }
    },


    /**
     * Check if a site is valid and if it has specifics settings for authentication
     * (like force to log in using the browser)
     *
     * @param {!Object} Javascript event.
     * @param {string} originalProtocol The procol entered in the url.
     */

    checkSite: function(e, originalProtocol) {
        if (e) {
            e.preventDefault();
        }
        MM.log("Checking remote site");

        var siteurl = $('#url').val();
        var demoSite = false;

        // Check for keywords for demo sites
        $.each(MM.config.demo_sites, function (index, site) {
            if (siteurl == site.key) {
                siteurl = site.url;
                username = site.username;
                password = <PASSWORD>;
                demoSite = true;
                return false; // break
            }
        });

        // Load a demo site and exits thic checking function.
        if (demoSite) {
            originalProtocol = "http://";
            if (siteurl.indexOf('https://') === 0) {
                originalProtocol = "https://";
            } else {
                siteurl = siteurl.replace("http://", "https://");
            }
            MM.saveSite(username, password, siteurl, originalProtocol);
            return;
        }

        $("#url").addClass("url-loading");

        siteurl = MM.util.formatURL($('#url').val());
        if (siteurl.indexOf('http://localhost') == -1 && !MM.validateURL(siteurl)) {
            msg = MM.lang.s('siteurlrequired') + '<br/>';
            MM.popErrorMessage(msg);
            $("#url").removeClass("url-loading");
            return;
        }

        // First we try to connect using https but only if the user has entered a URL starting by http://
        // We are not going to never fallback to http from https

        var timeout = 30000;

        if (typeof(originalProtocol) == "undefined") {
            originalProtocol = "http://";
            if (siteurl.indexOf('https://') === 0) {
                originalProtocol = "https://";
            } else {
                siteurl = siteurl.replace("http://", "https://");
                timeout = 10000;
            }
        }

        // First, check that the site exists.
        $.ajax({
            url:  siteurl + "/login/token.php",
            type: 'HEAD',
            timeout: timeout,
            success:function() {
                // Now, check if the mobile local plugin is configured.
                // We need to check if we are going to use extended services and also if SSO is configured.
                MM._checkMobileLocalPlugin(siteurl,
                    // Success.
                    function(response) {
                        if (typeof(response.code) == "undefined") {
                            MM.popErrorMessage(MM.lang.s("unexpectederror"));
                            return;
                        }
                        MM.log("Checking site: local_mobile plugin installed");

                        var code = parseInt(response.code, 10);
                        if (response.error) {
                            MM.log("Checking site: Error returned: " + code);
                            switch (code) {
                                case 1:
                                    // Site in maintenance mode.
                                    msg = MM.lang.s("siteinmaintenance");
                                    MM.popErrorMessage(msg);
                                    break;
                                case 2:
                                    // Web services not enabled.
                                    msg = MM.lang.s("webservicesnotenabled");
                                    MM.popErrorMessage(msg);
                                    break;
                                case 3:
                                    // Extended service not enabled, but the official is enabled.
                                    MM._expandAddSiteForm();
                                    break;
                                case 4:
                                    // Neither extended or official services enabled.
                                    msg = MM.lang.s("mobileservicesnotenabled");
                                    MM.popErrorMessage(msg);
                                    break;
                                default:
                                    msg = MM.lang.s("unexpectederror");
                                    MM.popErrorMessage(msg);
                            }
                            return;
                        } else {
                            // Now we store here the service used by this site.
                            var service = {
                                id: hex_md5(siteurl),
                                siteurl: siteurl,
                                service: MM.config.wsextservice
                            };
                            MM.db.insert("services", service);

                            MM.log("Checking site: Extended service enabled, code: " + code);

                            switch (code) {
                                case 1:
                                    // Normal login.
                                    MM._expandAddSiteForm();
                                    break;
                                case 2:
                                    MM.popConfirm(MM.lang.s('logininsiterequired'), function() {
                                        var passport = Math.random() * 1000;
                                        var url = siteurl + "/local/mobile/launch.php?service=" + MM.config.wsservice;
                                        url += "&passport=" + passport;

                                        MM.setConfig("launchSiteURL",  siteurl);
                                        MM.setConfig("launchPassport", passport);
                                        window.open(url, "_system");
                                        if (navigator.app) {
                                            navigator.app.exitApp();
                                        }
                                    });
                                    break;
                                default:
                                    MM._expandAddSiteForm();
                            }
                        }
                    },
                    // Error. The plugin is not installed.
                    // We are going to perform then a normal login process.
                    function() {
                        MM.log("Checking site: local_mobile plugin is not installed");
                        MM._expandAddSiteForm();
                    }
                );
            },
            error:function(xhr, textStatus, errorThrown) {
                var error = MM.lang.s('cannotconnect');

                if (siteurl.indexOf("https://") === 0) {
                    if (originalProtocol == "http://") {
                        MM.checkSite(null, originalProtocol);
                        return;
                    }
                    error = MM.lang.s('cannotconnectssl');
                }
                else if (xhr.status == 404) {
                    error = MM.lang.s('invalidscheme');
                }
                MM.popErrorMessage(error);
            },
            complete: function() {
                $("#url").removeClass("url-loading");
            }
        });

    },
{'fraction_non_alphanumeric': 0.1301969365426696, 'fraction_numerical': 0.0026896425966447847, 'mean_word_length': 1.579430094461647, 'pattern_counts': {'":': 2, '<': 21, '<?xml version=': 0, '>': 33, 'https://': 20, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 82, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3561947', 'n_tokens_mistral': 17915, 'n_tokens_neox': 16910, 'n_words': 4381}