/************************************************************************ * This file is part of EspoCRM. * * EspoCRM - Open Source CRM application. * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko * Website: http://www.espocrm.com * * EspoCRM is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * EspoCRM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with EspoCRM. If not, see http://www.gnu.org/licenses/. ************************************************************************/ var Espo = Espo || {}; Espo.App = function (options, callback) { var options = options || {}; this.useCache = options.useCache || this.useCache; this.url = options.url || this.url; this.controllers = {}; if (this.useCache) { this.cache = new Espo.Cache(); if (options.cacheTimestamp) { this.cache.handleActuality(options.cacheTimestamp); } else { this.cache.storeTimestamp(); } } this.storage = new Espo.Storage(); this.loader = Espo.loader; this.loader.cache = this.cache; this._setupAjax(); this.settings = new Espo['Models.Settings'](null, {cache: this.cache}); this.language = new Espo.Language(this.cache); this.metadata = new Espo.Metadata(this.cache); this.fieldManager = new Espo.FieldManager(); var proceed = function () { this.user = new Espo['Models.User'](); this.preferences = new Espo['Models.Preferences'](); this.preferences.settings = this.settings; this.acl = new Espo.Acl(this.user); this._modelFactory = new Espo.ModelFactory(this.loader, this.metadata, this.user); this._collectionFactory = new Espo.CollectionFactory(this.loader, this._modelFactory); this._initDateTime(); this._initView(); this._initBaseController(); this._preLoader = new Espo.PreLoader(this.cache, this._viewFactory); this._preLoad(function () { callback.call(this, this); }); }.bind(this); var countLoaded = 0; var handleCallback = function () { countLoaded++; if (countLoaded == 2) { proceed(); } }; this.settings.load(handleCallback); this.language.load(handleCallback); } _.extend(Espo.App.prototype, { useCache: false, user: null, preferences: null, settings: null, metadata: null, language: null, fieldManager: null, cache: null, loader: null, url: 'api/v1', auth: null, baseController: null, controllers: null, router: null, _modelFactory: null, _collectionFactory: null, _viewFactory: null, _viewLoader: null, _viewHelper: null, start: function () { this._initAuth(); var onAuth = function () { this.metadata.load(function () { this.fieldManager.defs = this.metadata.get('fields'); this.fieldManager.metadata = this.metadata; this.settings.defs = this.metadata.get('entityDefs.Settings'); this.user.defs = this.metadata.get('entityDefs.User'); this.preferences.defs = this.metadata.get('entityDefs.Preferences'); this.loader.addLibsConfig(this.metadata.get('app.jsLibs') || {}); this._initRouter(); }.bind(this)); }.bind(this); if (!this.auth) { this.baseController.login(); } else { this._initUserData(null, function () { onAuth(); }); } this.on('auth', onAuth); }, _initRouter: function () { this.router = new Espo.Router(); this._viewHelper.router = this.router; this.baseController._router = this.router; this.router.on('routed', function (params) { this.doAction(params); }.bind(this)); try { Backbone.history.start(); } catch (e) { Backbone.history.loadUrl(); } }, doAction: function (params) { this.trigger('action', params); this.getController(params.controller, function (controller) { try { controller.doAction(params.action, params.options); this.trigger('action:done'); } catch (e) { switch (e.name) { case 'AccessDenied': this.baseController.error403(); break; case 'NotFound': this.baseController.error404(); break; default: throw e; } } }.bind(this)); }, _initBaseController: function () { this.baseController = new Espo['Controllers.Base']({}, this.getControllerInjection()); this._viewHelper.baseController = this.baseController; }, getControllerInjection: function () { return { viewFactory: this._viewFactory, modelFactory: this._modelFactory, collectionFactory: this._collectionFactory, settings: this.settings, user: this.user, preferences: this.preferences, acl: this.acl, cache: this.cache, router: this.router, storage: this.storage, metadata: this.metadata, dateTime: this.dateTime, }; }, getController: function (name, callback) { if (!(name || false)) { callback(this.baseController); return; } if (!(name in this.controllers)) { try { var className = this.metadata.get('clientDefs.' + name + '.controller'); if (!className) { var module = this.metadata.get('scopes.' + name + '.module'); className = Espo.Utils.composeClassName(module, name, 'Controllers'); } this.loader.load(className, function (controllerClass) { this.controllers[name] = new controllerClass(this.baseController.params, this.getControllerInjection()); this.controllers[name].name = name; callback(this.controllers[name]); }.bind(this), function () { this.baseController.error404(); }.bind(this)); return; } catch (e) { this.baseController.error404(); } } callback(this.controllers[name]); }, _preLoad: function (callback) { this._preLoader.load(callback, this); }, _initDateTime: function () { this.dateTime = new Espo.DateTime(); this._modelFactory.dateTime = this.dateTime; this.dateTime.setSettingsAndPreferences(this.settings, this.preferences); }, _initView: function () { var helper = this._viewHelper = new Espo.ViewHelper(); helper.layoutManager = new Espo.LayoutManager({cache: this.cache}); helper.settings = this.settings; helper.user = this.user; helper.preferences = this.preferences; helper.acl = this.acl; helper.modelFactory = this._modelFactory; helper.collectionFactory = this._collectionFactory; helper.storage = this.storage; helper.dateTime = this.dateTime; helper.language = this.language; helper.metadata = this.metadata; helper.fieldManager = this.fieldManager; helper.cache = this.cache; helper.storage = this.storage; this._viewLoader = function (viewName, callback) { this.loader.load(Espo.Utils.composeViewClassName(viewName), callback); }.bind(this); var self = this; var getResourceInnerPath = function (type, name) { switch (type) { case 'template': return 'res/templates/' + name.split('.').join('/') + '.tpl'; case 'layoutTemplate': return 'res/layout-types/' + name + '.tpl'; case 'layout': return 'res/layouts/' + name + '.json'; } }; var getResourcePath = function (type, name) { var path; if (name.indexOf(':') != -1) { var arr = name.split(':'); name = arr[1]; var mod = arr[0]; if (mod == 'custom') { path = 'client/custom/' + getResourceInnerPath(type, name); } else { path = 'client/modules/' + mod + '/' + getResourceInnerPath(type, name); } } else { path = 'client/' + getResourceInnerPath(type, name); } return path; }; this._viewFactory = new Bull.Factory({ useCache: false, defaultViewName: 'Base', helper: helper, viewLoader: this._viewLoader, resources: { loaders: { 'template': function (name, callback) { var path = getResourcePath('template', name); self.loader.load('res!' + path, callback); }, 'layoutTemplate': function (name, callback) { var path = getResourcePath('layoutTemplate', name); self.loader.load('res!' + path, callback); } } } }); }, _initAuth: function () { this.auth = this.storage.get('user', 'auth') || null; this.baseController.on('login', function (data) { this.auth = Base64.encode(data.auth.userName + ':' + data.auth.token); this.storage.set('user', 'auth', this.auth); this._initUserData(data, function () { this.trigger('auth'); }.bind(this)); }.bind(this)); this.baseController.on('logout', function () { this.logout(); }.bind(this)); }, logout: function () { if (this.auth) { var arr = Base64.decode(this.auth).split(':'); if (arr.length > 1) { $.ajax({ url: 'App/action/destroyAuthToken', type: 'POST', data: JSON.stringify({ token: arr[1] }) }); } } this.auth = null; this.user.clear(); this.preferences.clear(); this.acl.clear(); this.storage.clear('user', 'auth'); this.doAction({action: 'login'}); this.language.clearCache(); xhr = new XMLHttpRequest; xhr.open('GET', this.url + '/', !1, 'logout', 'logout'); xhr.send(''); xhr.abort(); }, _initUserData: function (options, callback) { options = options || {}; var userIsLoaded = false; var langIsLoaded = false; if (options.user) { userIsLoaded = true; } var process = function () { if (!userIsLoaded) { return; } this.dateTime.setLanguage(this.language); var userData = options.user || null; var preferencesData = options.preferences || null; var aclData = options.acl || null; this.user.set(userData); this.preferences.set(preferencesData); this.acl.set(aclData); if (!this.auth) { return; } var arr = Base64.decode(this.auth).split(':'); var xhr = new XMLHttpRequest(); xhr.open('GET', this.url + '/', false, arr[0], arr[1]); xhr.send(''); if (callback) { callback(); } }.bind(this); var handleProcess = function () { if (langIsLoaded && userIsLoaded) { process(); } }; if (this.auth !== null) { this.language.load(function () { langIsLoaded = true; handleProcess(); }.bind(this)); if (!userIsLoaded) { $.ajax({ url: 'App/user', }).done(function (data) { userIsLoaded = true; options = data; handleProcess(); }); } } }, _setupAjax: function () { var self = this; $.ajaxSetup({ beforeSend: function (xhr, options) { if (!options.local && self.url) { this.url = Espo.Utils.trimSlash(self.url) + '/' + this.url; } if (self.auth !== null) { xhr.setRequestHeader('Authorization', 'Basic ' + self.auth); xhr.setRequestHeader('Espo-Authorization', self.auth); } }, dataType: 'json', timeout: 60000, contentType: 'application/json' }); $(document).ajaxError(function (event, xhr, options) { if (xhr.errorIsHandled) { return; } switch (xhr.status) { case 0: if (xhr.statusText == 'timeout') { Espo.Ui.error(self.language.translate('Timeout')); } break; case 200: Espo.Ui.error(self.language.translate('Bad server response')); console.error('Bad server response: ' + xhr.responseText); break; case 401: if (!options.login) { Espo.Ui.error(self.language.translate('Auth error')); if (self.auth) { self.logout(); } } break; case 403: if (options.main) { self.baseController.error403(); } else { Espo.Ui.error(self.language.translate('Error') + ' ' + xhr.status); } break; case 404: if (options.main) { self.baseController.error404(); } else { Espo.Ui.error(self.language.translate('Error') + ' ' + xhr.status); } break; default: Espo.Ui.error(self.language.translate('Error') + ' ' + xhr.status); } var statusReason = xhr.getResponseHeader('X-Status-Reason'); if (statusReason) { console.error('Server side error: ' + statusReason); } }); }, }, Backbone.Events);