// Check when version 6 is uploaded // Only in case all host have version 5 // Delete code with comment REMOVE! var Conf = { appName: 'Verdnatura', odbcPath: 'HKCU\\SOFTWARE\\ODBC\\ODBC.INI\\', regPath: 'HKCU\\SOFTWARE\\Verdnatura\\vn-access', defaultModule: 'vn', defaultLocale: 'es', defaultBranch: 'master', defaultDatasource: 'production', defaultRemoteURL: 'https://salix.verdnatura.es', defaultServer: 'db.verdnatura.es', dbName: 'vn2008', maxCorruptSize: 600, odbcDriver: 'MySQL ODBC 8.0 Unicode Driver', driverPath: '\\MySQL\\Connector ODBC 8.0\\myodbc8w.dll', version: 5, cdnURL: 'https://cdn.verdnatura.es/vn-access', identifier: '.vn' }; var Locale = { es: { "Enter a username": "Introduce un nombre de usuario", "Enter a password": "Introduce una contraseña", "Server can't be reached": "No se ha podido conectar con el servidor", "Updating": "Actualizando", "Bad login": "Usuario o contraseña incorrectos, recuerda que se hace distinción entre mayúsculas y minúsculas", "Application it's already open": "La aplicación ya está abierta", "Loading": "Cargando", "Error while updating": "Error al actualizar", "Microsoft Access 2003 is not installed": "Microsoft Access 2003 no está instalado en el sistema", "MDB file not found": "No se encontró el fichero MDB", "Cache files have been deleted": "Se han borrado todos los ficheros almacenados en la caché" } }; var App = { shell: new ActiveXObject('WScript.Shell'), fso: new ActiveXObject('scripting.filesystemobject'), init: function() { // Specify the size of window var width = 420; var height = 570; window.resizeTo(width, height); window.moveTo((screen.width - width) / 2, (screen.height - height) / 2); }, onUnload: function() { this.disableUi(false); }, onLoad: function() { // Initializes the global variables var split = Verdnatura.commandLine.match(/(?:[^\s"]+|"[^"]*")+/g); if (split.length > 1) this.module = split[1].replace(/^"+|"+$/g, ''); if (!this.module) this.module = Conf.defaultModule; this.appDir = this.getEnv('ProgramFiles') +'\\'+ Conf.appName; this.moduleDir = this.shell.SpecialFolders('AppData') +'\\'+ Conf.appName; this.compressFile = this.getEnv('TEMP') +'\\'+ this.module +'.7z'; this.certFile = this.appDir +'\\cacert.pem'; this.lockFile = this.moduleDir +'\\' + this.module +'.ldb'; this.mdbFile = this.moduleDir +'\\' + this.module +'.mdb'; var defaultDsName = this.formatDatasource(Conf.defaultDatasource); var mdbDsName = this.mdbGetValue( 'SELECT dsName FROM TblConfig', 'dsName', String ); this.dsName = mdbDsName || defaultDsName; // Creates the necessary registry entries var lastVersion = this.regRead(Conf.regPath, 'lastExecutedVersion'); if (!lastVersion || lastVersion != Conf.version) { var path = 'HKCU\\Software\\Microsoft\\Office\\11.0\\Access\\'; // Creates the Access configuration entries this.regWrites(path +'Settings', { 'Confirm Document Deletions': false, 'Confirm Action Queries': false, 'Confirm Record Changes': false }); this.regWrite(path + 'Security', 'Level', 1); // Creates the MySQL ODBC connection this.createODBC(Conf.odbcPath, defaultDsName, Conf.odbcDriver, Conf.defaultDatasource ); this.regWrite(Conf.regPath, 'remoteURL', Conf.defaultRemoteURL); this.regWrite(Conf.regPath, 'lastExecutedVersion', Conf.version); // REMOVE! this.createODBC(Conf.odbcPath, 'verdnatura', Conf.odbcDriver, 'keep for backward compatibility' ); // ----- this.regDelete(Conf.regPath +'\\configured'); this.regDelete(Conf.regPath + '\\remember'); } var notSignOut = this.regRead(Conf.regPath, 'notSignOut'); // Branch options this.request('GET', 'mdbBranches', null, function(err, res) { if (err) throw new Error ('Version could not be retrieved: '+ err.message +': '); var selectBranch = document.querySelector('#branch'); var option = []; for (var x in res) { option[x] = document.createElement('option'); option[x].text = res[x].name option[x].value = res[x].name selectBranch.options.add(option[x]) } }); var branch = this.mdbGetValue( 'SELECT branch FROM tblVariables', 'branch', String ); if (!branch) branch = Conf.defaultBranch; this.$('branch').value = branch; this.onChangeBranch(); // Datasource options var selectDatarouce = document.querySelector('#datasource'); var option = []; var allOdbc = this.enumValues(Conf.odbcPath +'ODBC Data Sources\\'); var idLen = Conf.identifier.length; for (var y in allOdbc) { if (allOdbc[y].slice(-idLen) == Conf.identifier) { var odbcName = allOdbc[y]; option[y] = document.createElement('option'); option[y].text = this.getDatasource(odbcName); option[y].value = odbcName; selectDatarouce.options.add(option[y]) } } // REMOVE! if (this.dsName == 'verdnatura') { this.dsName = Conf.defaultDatasource; this.$('datasource').value = this.dsName + Conf.identifier; this.$('datasource').text = this.dsName; } else // ----- this.$('datasource').value = this.dsName; if (notSignOut && password) { this.$('notSignOut').checked = true; this.onChangeDatasource(true); this.onEnterClick(); } else { this.resetForm(true); this.onChangeDatasource(true); } }, formatDatasource: function(datasource) { return datasource + Conf.identifier; }, getDatasource: function(dsName) { return dsName.substr(0, dsName.length - Conf.identifier.length); }, resetForm: function(clearPassword) { if (clearPassword) this.$('password').value = ''; this.$('user').focus(); this.$('user').select(); }, createODBC: function(path, dsName, driverName, description) { var driverPath = this.getEnv('ProgramFiles') + Conf.driverPath; var params = { Driver: driverPath, DESCRIPTION: description, SERVER: Conf.defaultServer, DATABASE: Conf.dbName, SSLCA: this.certFile, SSLMODE: 'VERIFY_IDENTITY', SSLCIPHER: 'AES256-SHA', AUTO_RECONNECT: 1, NO_PROMPT: 1, }; this.regWrite(path + 'ODBC Data Sources', dsName, driverName); this.regWrites(path + dsName, params); }, updateODBC: function() { var odbcPath = this.getOdbcPath(); var myUID = this.regRead(odbcPath, 'remoteUser'); var myPWD = this.regRead(odbcPath, 'remotePass'); var params = []; if (myUID && myPWD) { params['UID'] = myUID; params['PWD'] = myPWD; } else { params['UID'] = this.regRead(odbcPath, 'UID'); params['PWD'] = this.regRead(odbcPath, 'PWD'); } this.regWrites(odbcPath, params); }, disableUi: function(disabled, loadMessage) { if (disabled) this.hideMessage(); else loadMessage = ''; this.$('loading-message').innerHTML = loadMessage; this.$('user').disabled = disabled; this.$('password').disabled = disabled; this.$('notSignOut').disabled = disabled; this.$('enter').disabled = disabled; var display = disabled ? 'block' : 'none'; this.$('background').style.display = display; this.$('spinner').style.display = display; }, onCleanCacheClick: function() { setTimeout(function() { App.cleanCache(); }); }, onShowBranchOptionsClick: function() { var style = this.$("branchSelector").style; style.display = style.display == 'none' || !style.display ? 'inline' : 'none'; }, onShowDatasourceOptionsClick: function() { var style = this.$("datasourceSelector").style; style.display = style.display == 'none' || !style.display ? 'inline' : 'none'; if (!this.$("datasourceLogo").className) this.$("datasourceLogo").className = "on"; else this.$("datasourceLogo").className = ""; }, onChangeBranch: function() { if (this.$("branch").value == "master"||"test"||"dev") { this.$("branchButton").className = this.$("branch").value; this.$("branch").className = this.$("branch").value; } else { this.$("branchButton").className = null; this.$("branch").className = null; } this.$('user').focus(); }, /** * Changes the datasource, and optionally do the following * * @param {Boolean} hasUpdate */ onChangeDatasource: function(hasUpdate) { this.dsName = this.$("datasource").value; var myDatasource = this.getDatasource(this.dsName); if (myDatasource == "production"||"test"||"dev") { this.$("datasourceButton").className = myDatasource; this.$("datasource").className = myDatasource; } else { this.$("datasourceButton").className = null; this.$("datasource").className = null; }; if (!hasUpdate) { this.updateODBC(); }; var odbcPath = this.getOdbcPath(); var myUID = this.regRead(odbcPath, 'remoteUser'); var myPWD = this.regRead(odbcPath, 'remotePass'); var notSignOut = this.regRead(Conf.regPath, 'notSignOut'); if (myUID && myPWD && !notSignOut) { this.$('user').value = myUID; this.$('password').value = myPWD; } else { var user = this.regRead(odbcPath, 'UID') if (user) this.$('user').value = user if (!notSignOut) this.resetForm(true) else this.$('password').value = this.regRead(odbcPath, 'PWD') } this.$('user').focus(); }, cleanCache: function() { if (this.fso.folderExists(this.moduleDir)) { var folder = this.fso.getFolder(this.moduleDir); var files = new Enumerator(folder.files); for (; !files.atEnd(); files.moveNext()) { var file = files.item(); if (/\.mdb$/.test(file.name) && file.name != 'config.mdb') try { file.Delete(); } catch (e) {} } } this.showMessage(_('Cache files have been deleted'), 'notice'); }, onKeyPress: function(event) { switch (event.keyCode) { case 13: // Enter this.onEnterPress(event); break; case 27: // Esc window.close(); break; } }, onEnterPress: function(event) { var target = event.target || event.srcElement; if (target && target.id == 'user' && this.$('password').value == '') { this.$('password').focus(); return; } this.onEnterClick(); }, onEnterClick: function() { this.disableUi(true, _('Loading')); setTimeout(function() { App.login(); }); }, login: function() { try { var user = this.$('user').value; if (!user) throw new Error(_('Enter a username')); var password = this.$('password').value; if (!password) throw new Error(_('Enter a password')); this.regWrite(this.getOdbcPath(), 'UID', user); this.regWrite(this.getOdbcPath(), 'PWD', password); var mysqlConn = new ActiveXObject('ADODB.Connection'); // Check credentials try { mysqlConn.open(this.dsName) } catch (err) { var dbErrors = mysqlConn && mysqlConn.errors; if (dbErrors && dbErrors.count > 0) { var dbError = dbErrors.item(0); switch (dbError.NativeError) { case 1045: // Access denied clearPassword = true; err = new Error(_('Bad login')); err.name = 'BadLogin'; break; case 2003: // Can't connect err = new Error(_('Server can\'t be reached')); break; default: err = new Error(dbError.description); } dbErrors.clear(); } throw err; } mysqlConn.close(); // Check the cretentials and return the last version number this.fetchVersion(); } catch (err) { this.catchError(err); } }, /** * Gets information about the version to download. * cmdle * @return {Number|Boolean} Version number, %false if cannot * fetch or %null if local is up-to-date */ fetchVersion: function() { // To obtain the lastest version of this module var params = { filter: { fields: ['version'], where: { app: this.module, branchFk: this.$('branch').value }, } }; this.request('GET', 'MdbVersions/findOne', params, function(err, res) { App.onVersionRequest(err, res); } ); }, onVersionRequest: function(err, res) { if (err) throw new Error ('Version could not be retrieved: '+ err.message +': '); var lastVersion; // Checks if it's already open if (this.fso.fileExists(this.lockFile)) try { this.fso.deleteFile(this.lockFile); lastVersion = res.version; } catch (e) { throw new Error(_('Application it\'s already open')); } // Checks if MDB exists if (!this.fso.fileExists(this.mdbFile)) lastVersion = res.version; // If it's abnormaly bigger, maybe is corrupted, so force download if (!lastVersion) { var file = this.fso.getFile(this.mdbFile); if (file.size > Conf.maxCorruptSize * 1024 * 1024) lastVersion = res.version; // Obtains the local version number from the MDB file var localVersion = this.mdbGetValue( 'SELECT Version FROM tblVariables', 'Version', parseInt ); if (!localVersion) localVersion = false; // Determines if should download !localVersion || res.version === false || localVersion != res.version ? lastVersion = res.version : lastVersion = null; } // Check if there is a new version, and if there is, download it if (lastVersion) { this.disableUi(true, _('Updating')); var remoteFile = lastVersion ? '.archive/'+ this.module +'/'+ lastVersion +'.7z' : this.module +'.7z?'+ new Date().getTime(); remoteFile = Conf.cdnURL +'/'+ remoteFile; var request = new ActiveXObject('MSXML2.XMLHTTP.6.0'); request.open('GET', remoteFile, true); request.onreadystatechange = function() { App.onRequestReady(request); }; request.send(); } else App.openMdb(); }, mdbGetValue: function(query, field, parseFn) { var value; try { if (this.fso.fileExists(this.mdbFile)) { var mdbConn = new ActiveXObject('ADODB.Connection'); mdbConn.open(this.getODBCString({ 'Provider': 'Microsoft.Jet.OLEDB.4.0', 'Data Source': this.mdbFile })); try { var rs = new ActiveXObject('ADODB.Recordset'); rs.Open(query, mdbConn); value = rs.EOF ? null : parseFn(rs(field)); rs.close(); } catch (e) {} mdbConn.close(); } } catch (e) {} return value; }, getODBCString: function(options) { var optionsArray = []; for (var option in options) optionsArray.push(option +'='+ options[option]); return optionsArray.join(';'); }, onRequestReady: function(request) { if (request.readyState !== 4) return; try { if (request.status !== 200) throw new Error('HTTP: '+ request.statusText + ' ' + request.status); if (this.fso.fileExists(this.compressFile)) this.fso.deleteFile(this.compressFile); var stream = new ActiveXObject('ADODB.Stream'); stream.open(); stream.Type = 1; // adTypeBinary stream.write(request.responseBody); stream.Position = 0; stream.saveToFile(this.compressFile, 2); stream.close(); if (this.fso.fileExists(this.mdbFile)) this.fso.deleteFile(this.mdbFile); this.run('7za e "'+ this.compressFile +'" -o"'+ this.moduleDir +'"', true); this.fso.deleteFile(this.compressFile); } catch (e) { alert(_('Error while updating') +': '+ e.message); } try { if (!this.fso.fileExists(this.mdbFile)) throw new Error(_('MDB file not found')); this.openMdb(); } catch (e) { this.catchError(e); } }, openMdb: function() { var notSignOut = !!this.$('notSignOut').checked; this.regWrite(Conf.regPath, 'notSignOut', notSignOut); var programFiles = this.getEnv('ProgramFiles'); var accessBin = programFiles +'\\Microsoft Office\\OFFICE11\\MSACCESS.EXE'; if (!this.fso.fileExists(accessBin)) throw new Error(_('Microsoft Access 2003 is not installed')); // Open the mdb and pass it the argument this.shell.exec('"'+ accessBin +'" "'+ this.mdbFile +'" /cmd "'+ this.dsName +'"'); window.close(); }, catchError: function(err) { var clearPassword = err.name == 'BadLogin'; if (this.$('notSignOut').checked) { this.regWrite(this.getOdbcPath(), 'PWD', ''); this.regWrite(Conf.regPath, 'notSignOut', 0) this.$('notSignOut').checked = false } this.disableUi(false); this.showMessage(err.message, 'error'); this.resetForm(clearPassword); }, /** * Displays a non-intrusive message. * * @param {String} message Message to display * @param {String} className Message type */ showMessage: function(message, className) { setTimeout(function() { App.showMessageAsync(message, className); }); }, showMessageAsync: function(message, className) { if (this.messageTimeout) clearTimeout(this.messageTimeout); var messageDiv = this.$('message'); messageDiv.className = className; messageDiv.innerHTML = message; messageDiv.style.display = 'block'; this.messageTimeout = setTimeout(function() { App.hideMessage(); }, 10000); }, onBodyClick: function() { this.hideMessage(); }, openOptions: function() { this.onShowBranchOptionsClick(); this.onShowDatasourceOptionsClick(); }, /** * Hides the last displayed non-intrusive message. */ hideMessage: function() { if (this.messageTimeout) { this.$('message').style.display = 'none'; clearTimeout(this.messageTimeout); this.messageTimeout = null; } }, /** * Obtains a DOM element by it's identifier. * * @param {String} id The element id */ $: function(id) { return document.getElementById(id); }, run: function(command, wait) { if (!wait) wait = false; this.shell.run(command, 0, wait); }, getEnv: function(varName) { return this.shell.expandEnvironmentStrings('%'+ varName +'%'); }, regRead: function(path, key) { try { var value = this.shell.regRead(path +'\\'+ key); } catch (e) { var value = null; } return value; }, regWrite: function(path, key, value, type) { if (!type) switch (typeof (value)) { case 'boolean': type = 'REG_DWORD'; value = value ? 1 : 0; break; case 'number': type = 'REG_DWORD'; break; default: type = 'REG_SZ'; if (!value) value = ''; } this.shell.regWrite(path + '\\' + key, value.toString(), type); }, regWrites: function(path, values, type) { for(var key in values) this.regWrite(path, key, values[key], type); }, regDelete: function(path) { try { this.shell.regDelete(path); } catch (e) {} }, request: function (method, url, data, cb) { var remoteURL = this.regRead(Conf.regPath,'remoteURL'); if (!remoteURL) remoteURL = Conf.defaultRemoteURL; var fullUrl = remoteURL +'/api/'+ url; var isGet = method == 'GET'; if (isGet) { var isFirst = true; for (var param in data) { fullUrl += isFirst ? '?' : '&'; isFirst = false; var value = data[param]; if (typeof value == 'object') value = JSON.stringify(value); fullUrl += param +'='+ encodeURIComponent(value); } } var req = new ActiveXObject('MSXML2.XMLHTTP.6.0'); req.open(method, fullUrl, false); req.onreadystatechange = function() { App.onRequestStateChange(req, cb) }; if (isGet) { req.setRequestHeader('Content-Type', 'application/json'); req.send(JSON.stringify(data)); } else req.send(); }, onRequestStateChange: function(req, cb) { if (req.readyState !== 4) return; var status = req.status; if (status >= 100 && status < 400) { var res = JSON.parse(req.responseText); cb(null, res); } else { err = new Error(req.statusText); err.name = 'HttpError'; err.status = status; err.req = req; if (status >= 400 && status < 600) err.error = JSON.parse(req.responseText); cb(err); } }, /** * Get the odbc path * * @return {string} Remote server */ getOdbcPath: function() { return Conf.odbcPath + this.dsName; }, enumValues: function(RegKey) { var RootKey = new Object() RootKey["HKCR"] = RootKey["HKEY_CLASSES_ROOT"] = 0x80000000; RootKey["HKCU"] = RootKey["HKEY_CURRENT_USER"] = 0x80000001; RootKey["HKLM"] = RootKey["HKEY_LOCAL_MACHINE"] = 0x80000002; RootKey["HKUS"] = RootKey["HKEY_USERS"] = 0x80000003; RootKey["HKCC"] = RootKey["HKEY_CURRENT_CONFIG"] = 0x80000005; var RootVal = RootKey[RegKey.substr(0, RegKey.indexOf("\\"))] if (RootVal != undefined) { Locator = new ActiveXObject("WbemScripting.SWbemLocator"); ServerConn = Locator.ConnectServer(null, "root\\default"); Registry = ServerConn.Get("StdRegProv"); Method = Registry.Methods_.Item("enumValues"); p_In = Method.InParameters.SpawnInstance_(); p_In.hDefKey = RootVal; p_In.sSubKeyName = RegKey.substr(RegKey.indexOf("\\") + 1) p_Out = Registry.ExecMethod_(Method.Name, p_In); return p_Out.sNames.toArray(); } }, }; App.init(); function _(string) { var translation = Locale[Conf.defaultLocale][string]; return translation ? translation : string; }