1/* global $, RfapiJS, RFO */ 2 3var RFO = {}; 4try { 5 RFO = JSON.parse(document.querySelector('meta[name="rfo-config"]').content); 6 RFO.PAGE_TYPE = document.querySelector('meta[name="page-type"]').content 7} catch (e) {} 8 9RFO.DIR_REQUEST = '/requests/'; 10RFO.POLICIES = ''; 11RFO.ACCOUNT_INFO = {}; 12RFO.COMPANY_INFO = {}; 13RFO.IDPStateData = null; 14RFO.bFrameRelocated = false; 15 16RFO.ACCOUNT_LOGIN_MIN = 6; // chars 17RFO.ACCOUNT_LOGIN_MAX = 80; // bytes 18RFO.ACCOUNT_NAME_MIN = 2; // chars 19RFO.ACCOUNT_NAME_MAX = 80; // bytes 20RFO.GROUP_NAME_MIN = 1; // chars 21RFO.GROUP_NAME_MAX = 60; // bytes 22RFO.PWD_MIN = 12; // chars 23RFO.PWD_MAX = 500; // bytes 24RFO.PWD_NONNUM_MIN = 4; // chars 25RFO.COMPANY_NAME_MIN = 2; // chars 26RFO.COMPANY_NAME_MAX = 60; // bytes 27RFO.COMPANY_URL_MIN = 7; // chars 28RFO.COMPANY_URL_MAX = 60; // bytes 29 30var RFMgr = new RfapiJS.RfDataManager(); 31RFMgr.SetServerUrl(location.protocol + '//' + location.host); 32RFMgr.setBrowserParam(RFO.CLIENT_TYPE); 33 34$.fn.selectText = function(){ 35 var el = this[0]; 36 var doc = document; 37 if (doc.body.createTextRange) { 38 var range = doc.body.createTextRange(); 39 range.moveToElementText(el); 40 range.select(); 41 } else if (window.getSelection) { 42 var selection = window.getSelection(); 43 var range = doc.createRange(); 44 range.selectNodeContents(el); 45 selection.removeAllRanges(); 46 selection.addRange(range); 47 } 48 if (el.setSelectionRange) { // textarea in Chrome 49 el.setSelectionRange(0,el.value.length); 50 } 51}; 52 53// reposition tooltip after inserting content dynamically 54// usage: $element.popover('reposition') 55$.fn.popover.Constructor.prototype.reposition = function(){ 56 var $tip = this.tip(); 57 var placement = typeof this.options.placement === 'function' ? this.options.placement.call(this, $tip[0], this.$element[0]) : this.options.placement; 58 var pos = this.getPosition(); 59 var actualWidth = $tip[0].offsetWidth; 60 var actualHeight = $tip[0].offsetHeight; 61 var orgPlacement = placement; 62 var viewportDim = this.getPosition(this.$viewport); 63 placement = placement === 'bottom' && 64 pos.bottom + actualHeight > viewportDim.bottom ? 'top' : placement === 'top' && 65 pos.top - actualHeight < viewportDim.top ? 'bottom' : placement === 'right' && 66 pos.right + actualWidth > viewportDim.width ? 'left' : placement === 'left' && 67 pos.left - actualWidth < viewportDim.left ? 'right' : placement; 68 $tip.removeClass(orgPlacement).addClass(placement); 69 var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight); 70 this.applyPlacement(calculatedOffset, placement); 71}; 72 73// This code is needed for IE11 only as it is not support Array.from 74// Production steps of ECMA-262, Edition 6, 22.1.2.1 75// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from 76if (!Array.from) { 77 Array.from = (function () { 78 var symbolIterator; 79 try { 80 symbolIterator = Symbol.iterator 81 ? Symbol.iterator 82 : 'Symbol(Symbol.iterator)'; 83 } catch (e) { 84 symbolIterator = 'Symbol(Symbol.iterator)'; 85 } 86 87 var toStr = Object.prototype.toString; 88 var isCallable = function (fn) { 89 return ( 90 typeof fn === 'function' || 91 toStr.call(fn) === '[object Function]' 92 ); 93 }; 94 var toInteger = function (value) { 95 var number = Number(value); 96 if (isNaN(number)) return 0; 97 if (number === 0 || !isFinite(number)) return number; 98 return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 99 }; 100 var maxSafeInteger = Math.pow(2, 53) - 1; 101 var toLength = function (value) { 102 var len = toInteger(value); 103 return Math.min(Math.max(len, 0), maxSafeInteger); 104 }; 105 106 var setGetItemHandler = function setGetItemHandler(isIterator, items) { 107 var iterator = isIterator && items[symbolIterator](); 108 return function getItem(k) { 109 return isIterator ? iterator.next() : items[k]; 110 }; 111 }; 112 113 var getArray = function getArray( 114 T, 115 A, 116 len, 117 getItem, 118 isIterator, 119 mapFn 120 ) { 121 // 16. Let k be 0. 122 var k = 0; 123 124 // 17. Repeat, while k < len⦠or while iterator is done (also steps a - h) 125 while (k < len || isIterator) { 126 var item = getItem(k); 127 var kValue = isIterator ? item.value : item; 128 129 if (isIterator && item.done) { 130 return A; 131 } else { 132 if (mapFn) { 133 A[k] = 134 typeof T === 'undefined' 135 ? mapFn(kValue, k) 136 : mapFn.call(T, kValue, k); 137 } else { 138 A[k] = kValue; 139 } 140 } 141 k += 1; 142 } 143 144 if (isIterator) { 145 throw new TypeError( 146 'Array.from: provided arrayLike or iterator has length more then 2 ** 52 - 1' 147 ); 148 } else { 149 A.length = len; 150 } 151 152 return A; 153 }; 154 155 // The length property of the from method is 1. 156 return function from(arrayLikeOrIterator /*, mapFn, thisArg */) { 157 // 1. Let C be the this value. 158 var C = this; 159 160 // 2. Let items be ToObject(arrayLikeOrIterator). 161 var items = Object(arrayLikeOrIterator); 162 var isIterator = isCallable(items[symbolIterator]); 163 164 // 3. ReturnIfAbrupt(items). 165 if (arrayLikeOrIterator == null && !isIterator) { 166 throw new TypeError( 167 'Array.from requires an array-like object or iterator - not null or undefined' 168 ); 169 } 170 171 // 4. If mapfn is undefined, then let mapping be false. 172 var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 173 var T; 174 if (typeof mapFn !== 'undefined') { 175 // 5. else 176 // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 177 if (!isCallable(mapFn)) { 178 throw new TypeError( 179 'Array.from: when provided, the second argument must be a function' 180 ); 181 } 182 183 // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 184 if (arguments.length > 2) { 185 T = arguments[2]; 186 } 187 } 188 189 // 10. Let lenValue be Get(items, "length"). 190 // 11. Let len be ToLength(lenValue). 191 var len = toLength(items.length); 192 193 // 13. If IsConstructor(C) is true, then 194 // 13. a. Let A be the result of calling the [[Construct]] internal method 195 // of C with an argument list containing the single item len. 196 // 14. a. Else, Let A be ArrayCreate(len). 197 var A = isCallable(C) ? Object(new C(len)) : new Array(len); 198 199 return getArray( 200 T, 201 A, 202 len, 203 setGetItemHandler(isIterator, items), 204 isIterator, 205 mapFn 206 ); 207 }; 208 })(); 209} 210// ==== End of code for Array.from =============== 211 212 213$(document).ready(function(){ 214 215 // location.hash looks like "#opParams={opParams}" - operator followed the link from main to additional server 216 if (location.hash && location.hash.substr(0, 9) === '#opParams') { 217 if ($('#login_form').length) { 218 // not logged in - redirect to correct login page 219 location.href = RFO.ROOT_PATH + 'login?authReturnUrl=op_login&opParams=' + location.hash.substr(10); 220 } else { 221 // logged in - clear hash 222 location.href = location.pathname + location.search; 223 } 224 } 225 226 if ($('input[data-vk-id]').length) { 227 new VKI({ 228 // attach keyboard without image button, 229 // show keyboard by clicking to the element with #id = 'data-vk-id' attribute 230 VKI_imageURI: "#" 231 }); 232 } else { 233 var vkKeyboardImg = "/js/keyboard/keyboard.png"; 234 (new Image()).src = vkKeyboardImg; // preload 235 new VKI({ 236 offset_down:20, 237 VKI_imageURI: vkKeyboardImg 238 }); 239 } 240 241 // check for old browser 242 if ($('#old-browser-msg').css('opacity') === '1') { 243 $('#old-browser-msg').removeClass('hidden'); 244 $('body.loading').removeClass('loading'); 245 } else { 246 var sNotSupported = ''; 247 // we do not support browsers without atob and btoa functions (IE9 for example) 248 if (!window.atob) { 249 sNotSupported = _STR.CL_OLD_BROWSER_MSG; 250 } else { 251 // we do not support browsers with disabled cookies 252 $.cookie('_testCookie_', '1'); 253 if ($.cookie('_testCookie_') === '1') { 254 $.cookie('_testCookie_', null); 255 // we do not support browsers without localStorage 256 if (!Utils.Storage.test()) { 257 sNotSupported = _STR.CL_LOCAL_STORAGE_NOT_SUPPORTED; 258 } 259 } else { 260 sNotSupported = _STR.CL_DISABLED_COOKIES_NOT_SUPPORTED; 261 } 262 } 263 if (sNotSupported) { 264 $('.b-main').addClass('hidden'); 265 $('#old-browser-msg .b3').html(sNotSupported); 266 $('#old-browser-msg').removeClass('hidden').css('opacity', '1'); 267 $('body.loading').removeClass('loading'); 268 } 269 } 270 RFO.bFrameRelocated = RelocateFrame(); 271 if (!$('#old-browser-msg').hasClass('hidden')) { 272 return; 273 } 274 275 // scrollable tables 276 $('.tableScrollable').each(function(){ 277 var $scrollableTable = $(this); 278 $scrollableTable.on('adjustHeader', function(){ 279 if (!$scrollableTable.hasClass('_adjusted')) { 280 var headerHeight = $scrollableTable.find('thead th, thead td')[0].offsetHeight+1; // +1 needed for Chrome 281 $scrollableTable.addClass('_adjusted').css('margin-top', '-'+headerHeight+'px').find('thead div').css('height', headerHeight+'px'); 282 $scrollableTable.parent().css('margin-top', headerHeight+'px'); 283 } 284 }).on('resetScrollState', function(){ 285 $scrollableTable.parent().scrollLeft(0); 286 $scrollableTable.trigger('scroll'); 287 }); 288 // add divs to the table headers to adjust its width 289 $scrollableTable.find('thead th, thead td').each(function(){ 290 var $this = $(this); 291 $this.append($('<div>'+$this.html()+'</div>')); 292 }); 293 $scrollableTable.filter(':visible').trigger('adjustHeader'); 294 // needed for horizontal table scrolling 295 var currentOffset = 0; 296 var $theadDiv = $scrollableTable.find('thead div'); 297 $scrollableTable.parent().on('scroll', function(){ 298 var offset = $scrollableTable.position().left; 299 if (offset !== currentOffset) { 300 var value = 'translateX('+offset+'px)'; 301 $theadDiv.css({ 302 'transform': value,
303 '-moz-transform': value, 304 '-webkit-transform': value, 305 '-o-transform': value, 306 '-ms-transform': value 307 }); 308 currentOffset = offset; 309 } 310 }); 311 }); 312 313 // sortable tables 314 $('.tableSortable').each(function(){ 315 var $table = $(this); 316 var $tbody = $table.children('tbody'); 317 var FILTERED_LIST = $tbody.children('tr'); 318 var bRegularTable = true; 319 $table.off('sort.sortable').on('sort.sortable', function(e, newRowsArray){ 320 if (newRowsArray) { 321 FILTERED_LIST = newRowsArray; 322 bRegularTable = (FILTERED_LIST[0] && FILTERED_LIST[0].nodeName); 323 } 324 $table.children('thead').find('.headerSortUp, .headerSortDown').trigger('sortTH.sortable'); 325 }).off('reset.sortable').on('reset.sortable', function(e, newRowsArray){ 326 if (newRowsArray) { 327 FILTERED_LIST = newRowsArray; 328 bRegularTable = (FILTERED_LIST[0] && FILTERED_LIST[0].nodeName); 329 } 330 $table.children('thead').find('.headerSortUp, .headerSortDown').removeClass('headerSortUp headerSortDown'); 331 }).children('thead').find('td, th').each(function(col, th){ 332 var $th = $(th); 333 var dataSort = $th.attr('data-sort'); 334 if (dataSort) { 335 $th.off('sortTH.sortable').on('sortTH.sortable', function(){ 336 var dir = $th.hasClass('headerSortDown') ? 1 : 0; 337 if (bRegularTable) { 338 var bSortInGroups = $table.attr('data-sort-in-groups'); 339 FILTERED_LIST.sort(function (a, b) { 340 var a_sortGroup = bSortInGroups ? parseInt($(a).attr('data-sort-group'), 10) : 0; 341 var b_sortGroup = bSortInGroups ? parseInt($(b).attr('data-sort-group'), 10) : 0; 342 if (a_sortGroup === b_sortGroup) { 343 var compare = 0; 344 var $a = $($(a)[0].cells[col]); 345 var $b = $($(b)[0].cells[col]); 346 var a_val = $a.attr('data-sort-value') || $a.text().trim().toLowerCase(); 347 var b_val = $b.attr('data-sort-value') || $b.text().trim().toLowerCase(); 348 switch (dataSort) { 349 case 'alpha': 350 compare = a_val > b_val ? 1 : (a_val < b_val ? -1 : 0); 351 break; 352 case 'number': 353 compare = parseFloat(a_val ? a_val : 0) - parseFloat(b_val ? b_val : 0); 354 break; 355 } 356 return compare * -(dir || -1); // * -1 if want opposite order 357 } 358 return (a_sortGroup - b_sortGroup); 359 }); 360 } else { 361 FILTERED_LIST.sort(function (a, b) { 362 var compare = 0; 363 var a_val = a['_data_'].sortValue[col]; 364 var b_val = b['_data_'].sortValue[col]; 365 switch (dataSort) { 366 case 'alpha': 367 compare = a_val > b_val ? 1 : (a_val < b_val ? -1 : 0); 368 break; 369 case 'number': 370 compare = parseFloat(a_val ? a_val : 0) - parseFloat(b_val ? b_val : 0); 371 break; 372 } 373 return compare * -(dir || -1); // * -1 if want opposite order 374 }); 375 } 376 $table.trigger('reset.sortable'); 377 $th.addClass(dir ? 'headerSortUp' : 'headerSortDown'); 378 }).off('click').on('click', function(){ 379 $th.trigger('sortTH.sortable'); 380 if ($table.data('paginator')) { 381 $table.trigger('init.paginator', [FILTERED_LIST]); 382 } else { 383 FILTERED_LIST.appendTo($tbody); 384 } 385 }).addClass('headerSort').append($('<i></i>')).find('div').append($('<i></i>')); 386 } 387 }); 388 }); 389 390 // init RFMgr 391 if (RFO.LOGGED_ACCOUNT_ID) { 392 RFMgr.SetRfoOptions ({showAllGroups: true}); 393 RFMgr.GetAccountInfo(RFO.LOGGED_ACCOUNT_ID, 394 // ************ OK (AnswerCallBack) ************ 395 function (data) { 396 RFO.ACCOUNT_INFO = data; 397 if (!Utils.restoreMasterPassword()) { 398 return; 399 } 400 RFO.POLICIES = data.policies ? Utils.parseINI(Utils.base64Decode(data.policies)) : ''; 401 if (!RFO.ACCOUNT_INFO.passwordExpired) { 402 RFMgr.UpdateSecurityScore({ 403 accountId: RFO.LOGGED_ACCOUNT_ID, 404 accountInfo: RFO.ACCOUNT_INFO, 405 itemsCounters: false 406 }); 407 } 408 if (RFO.ACCOUNT_INFO.companies) { 409 RFO.ACCOUNT_INFO.companiesPolicies = {}; 410 $.each(RFO.ACCOUNT_INFO.companies, function(i, companyData){ 411 RFO.ACCOUNT_INFO.companiesPolicies[companyData.companyId] = companyData.policies ? Utils.parseINI(Utils.base64Decode(companyData.policies)) : ''; 412 if (RFO.ACCOUNT_INFO.companies.length === 1 || companyData.companyId === $.cookie('selectedCompany')) { 413 RFO.COMPANY_INFO = companyData; 414 } 415 }); 416 if (RFO.COMPANY_INFO.companyId && RFO.COMPANY_INFO.isAdmin) { 417 Utils.createJSONRequest('/idp/get-settings', {companyId: RFO.COMPANY_INFO.companyId}).then(function (data) { 418 RFO.IDPStateData = data; 419 $(RFMgr).trigger('init'); 420 }).catch(function (e) { 421 $(RFMgr).trigger('init'); 422 }); 423 } else { 424 $(RFMgr).trigger('init'); 425 } 426 } else { 427 $(RFMgr).trigger('init'); 428 } 429 }, 430 // *********** ERROR (ErrorCallBack) *********** 431 function (errObject) { 432 if (errObject.Code && (errObject.Code === 401 || errObject.Code === 403 && errObject.HttpRequest.getResponseHeader('x-sib-reason') === 'need-idp-auth')) { 433 //access denied error (session expired) 434 doLogout(RFO.ROOT_PATH + 'login?relogin'); 435 } else { 436 showResultMsg(Utils.stripTags(errObject.Description), true); 437 } 438 } 439 ); 440 } else { 441 $(RFMgr).trigger('init'); 442 } 443 444 // IE10 viewport hack for Surface/desktop Windows 8 bug 445 // See the Getting Started docs for more information: 446 // http://getbootstrap.com/getting-started/#support-ie10-width 447 if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 448 var msViewportStyle = document.createElement('style'); 449 msViewportStyle.appendChild( 450 document.createTextNode(
451 '@-ms-viewport{width:auto!important}' 452 ) 453 ); 454 document.querySelector('head').appendChild(msViewportStyle); 455 } 456 457 // enables all tooltips on the page 458 $('[data-toggle="popover"]').popover({ 459 html: true, 460 trigger: 'hover', 461 container: 'body', 462 delay: { "show": 200, "hide": 0 } 463 }); 464 465 // set sticky navbar 466 var affixOffsetHeight = $('.b-navbar-1').outerHeight(true); 467 var affixOffsetMargin = $('.b-navbar-2').outerHeight(true); 468 $('.b-navbar-2').on('affixed.bs.affix', function(){ 469 $('.b-navbar-1').addClass('invisible').css('margin-top', affixOffsetMargin+'px'); 470 $('.modal-dialog').addClass('modal-dialog-affix'); 471 }).on('affixed-top.bs.affix', function(){ 472 $('.b-navbar-1').css('margin-top', '').removeClass('invisible'); 473 $('.modal-dialog').removeClass('modal-dialog-affix'); 474 }).affix({offset: {top: affixOffsetHeight}}); 475 476 // set language selector 477 var f = location.search.match(/&?\??lang=(\w\w)/); 478 var lang = f ? f[1] : $.cookie('lang'); 479 $('#selectLangDialog ul').html($('.b-lang-alt .dropdown-menu').html()); 480 $('.b-lang, .b-lang-alt, #selectLangDialog').find('li a').each(function(){ 481 var $this = $(this); 482 if ($this.attr('data-lang') === lang) { 483 $this.closest('.dropdown').find('._current').text($this.text()); 484 } 485 $this.on('click', function(e){ 486 e.preventDefault(); 487 var urlParts = location.toString().split('#'); 488 urlParts[0] = (urlParts[0].replace(/&?(\??)lang=\w\w/, '$1') + (location.search ? '&' : '?') + 'lang=' + $this.attr('data-lang')).replace('?&', '?'); 489 location.assign(urlParts.join('#')); 490 }); 491 }); 492 $('._selectLang').on('click', function(e){ 493 e.preventDefault(); 494 $('#selectLangDialog').modal('show'); 495 }); 496 497 // show/hide dropdown menu on hover 498 $('#mainMenuNavbar .dropdown').each(function(){ 499 var timerId; 500 var $this = $(this); 501 $this.on('mouseover', function(){ 502 clearTimeout(timerId); 503 timerId = setTimeout(function(){ 504 var $toggle = $this.addClass('open').find('.dropdown-toggle').on('click.myevent', function(){ 505 // prevent hiding the menu on the click event when it just appeared 506 return false; 507 }); 508 setTimeout(function(){ 509 // and then grant hiding the menu by the click event 510 $toggle.off('click.myevent'); 511 }, 700); 512 }, 0); 513 }).on('mouseout', function(){ 514 clearTimeout(timerId); 515 timerId = setTimeout(function(){ 516 $this.removeClass('open'); 517 }, 200); 518 }); 519 }); 520 521 $('.b-nav-dashboard').each(function () { 522 var $thisEl = $(this).removeClass('fixedHeight'); 523 if ($thisEl.prop('scrollHeight') > 55) { 524 $thisEl.find('.info-block-bordered.visible-lg').removeClass('visible-lg').hide(); 525 $thisEl.find('.info-block-bordered.hidden-lg').removeClass('hidden-lg').show(); 526 } 527 }); 528 529 // 'Storage Type' and 'Usage Permission' selectors in dialogs 530 $("div._sync_selector, div._perm_selector").each(function(){ 531 var $this = $(this); 532 $this.find('.dropdown-menu tr').on('click', function(e){ 533 var $tr = $(this); 534 var val = $tr.attr('data'); 535 if (val!=0) { 536 var selText = $tr.find('td.value').html(); 537 } else { 538 var selText = "<span>"+$this.find('tr[data="'+$this.attr('defdata')+'"] td.value').text()+"</span> "+_STR.CL_VALUE_DEFAULT; 539 } 540 $this.find('.dropdown-toggle span._selector_value').html(selText); 541 $tr.siblings('tr').removeClass('selected'); 542 $tr.addClass('selected'); 543 }); 544 }); 545 546 // show/hide 'more settings' section in dialogs 547 $('._moreSettings').each(function(){ 548 var $moreSettings = $(this); 549 var $moreSettingsDiv = $moreSettings.find('.moreSettingsDiv'); 550 var $settingsLink = $moreSettings.find('.moreSettings'); 551 $moreSettings.on('collapse', function(){ 552 $moreSettingsDiv.hide(); 553 $settingsLink.removeClass('dropup'); 554 }); 555 $settingsLink.on('click', function(){ 556 if ($settingsLink.hasClass('dropup')) { 557 $settingsLink.removeClass('dropup'); 558 $moreSettingsDiv.hide(); 559 } else { 560 $settingsLink.addClass('dropup'); 561 $moreSettingsDiv.show(); 562 } 563 }); 564 }); 565 566 // restore current filemanager url 567 $('._files').on('click', function(e){ 568 e.preventDefault(); 569 document.location.href = $(this).attr('href') + (sessionStorage.filebrowserUrl || ''); 570 }); 571 572 $('._logout').on('click', function(e){ 573 e.preventDefault(); 574 doLogout(RFO.ROOT_PATH + 'logout'); 575 }); 576 577 $('._email-verified').each(function(){ 578 var urlParts = Utils.parseUrlQuery(); 579 var errText = ''; 580 if (urlParts.error === 'bad-code') { 581 errText = _STR.TEXT_EMAIL_VERIFY_BAD_CODE+"<br><br>"+Utils.xReplace(_STR.TEXT_EMAIL_VERIFY_INSTRUCTIONS, location.origin+'/login'); 582 } else if (urlParts.error === 'expired-code') { 583 errText = _STR.TEXT_EMAIL_VERIFY_EXPIRED_CODE+"<br><br>"+Utils.xReplace(_STR.TEXT_EMAIL_VERIFY_INSTRUCTIONS, location.origin+'/login'); 584 } else if (urlParts.error === 'try-later' && urlParts.url) { 585 errText = _STR.TEXT_EMAIL_VERIFY_FAILED + '<br>' + Utils.xReplace(_STR.TEXT_ALT_AUTH_EMAIL_VERIFY_LINK, 'href="'+encodeURIComponent(urlParts.url)+'"'); 586 } else if (urlParts.error) { 587 errText = _STR.ERR_UNEXPECTED; 588 } 589 $(this).find('.alert').addClass(errText ? 'alert-danger' : 'alert-success').html(errText || _STR.TEXT_SUCCESS_VERIFY_EMAIL); 590 }); 591 592 // disable spellchecking for all text inputs and textareas 593 $('input[type="text"], textarea').attr('spellcheck', 'false'); 594 595 if (RFO.PAGE_TYPE === 'account_deleted') { 596 $('#account').text(sessionStorage.UserID); 597 $.cookie(null); 598 } 599}); 600 601$(window).on('load', function(){ 602 if (!RFO.bFrameRelocated) { 603 $('._autofocus').trigger('focus'); 604 var $inp = $("#scorebar").parent().find('input'); 605 if ($inp.length) { 606 $(window).on('resize', function(){ 607 $("#scorebar").css({'width': $inp.outerWidth()+'px', 'left': $inp.position().left + parseInt($inp.css('marginLeft'), 10) + 'px'}); 608 }).trigger('resize'); 609 } 610 } 611}); 612 613$(RFMgr).on('init', function() { 614 'use strict'; 615 616 if (RFO.LOGGED_ACCOUNT_ID) { 617 (new AuthChecker()).start(); 618 } 619 620 // analyze password strength 621 $('#signup_ent #signup_password').on('input focusin', function(){ 622 Utils.updateStrength(this.value); 623 }); 624 625 // breach password monitoring and check password compliance 626 $('#resetPass #password, #newPChange, #auth #signup_password, #login #signup_password').attr('maxlength', RFO.PWD_MAX).on('blur', function(){ 627 var $password = $(this); 628 var password = $password.val(); 629 if ($password.attr('VKI_shown') || PasswordChecker.lastCheckedPassword() === password) { 630 return; 631 } 632 var checks = PasswordChecker.checkPasswordForCompliance(password); 633 if (checks.problems.length) { 634 // password does not comply restriction, do not need to check for breachness 635 return; 636 } 637 var $qualityIndicator = $('.pwd-quality-ind').addClass('in-progress'); 638 PasswordChecker.isPasswordCompromised(password, function(isPasswordBreached){ 639 if (isPasswordBreached) { 640 $('.breach-warning').removeClass('hidden'); 641 $password.addClass('weak-value'); 642 } else { 643 $('.breach-warning').addClass('hidden'); 644 $password.removeClass('weak-value'); 645 } 646 $qualityIndicator.removeClass('in-progress'); 647 }); 648 }).on('input', function(){ 649 var $password = $(this); 650 var $qualityIndicator = $('.pwd-quality-ind').removeClass('hidden'); 651 $password.removeClass('bad-value weak-value'); 652 $('.breach-warning').addClass('hidden'); 653 PasswordChecker.stopCheckingPasswordForBreachness(); 654 PasswordChecker.updatePasswordQualityIndicator($password, $qualityIndicator, $qualityIndicator.find('.check-list')); 655 }).trigger('input'); 656 657 // check company invitations 658 if ( 659 RFO.ACCOUNT_INFO && RFO.ACCOUNT_INFO.receivedData && RFO.ACCOUNT_INFO.receivedData.companyInvitations.length && 660 (!RFO.ACCOUNT_INFO.companies || RFO.ACCOUNT_INFO.companies.length === 0) // not a member of other companies 661 ) { 662 $(document).on('click', '._companyInviteAccept, ._companyInviteReject', function(){ 663 var $this = $(this); 664 var companyID = $this.val().trim(); 665 var confirmText = $this.hasClass('_companyInviteAccept') ? _STR.CL_CONFIRM_ACCEPT_COMPANY : _STR.CL_CONFIRM_REJECT_COMPANY; 666 var successText = $this.hasClass('_companyInviteAccept') ? _STR.INVITATION_TEXT_SUCCESS_ACCEPT : _STR.INVITATION_TEXT_SUCCESS_REJECT; 667 var processFunc = $this.hasClass('_companyInviteAccept') ? RFMgr.JoinCompany : RFMgr.LeaveCompany; 668 if (!RFO.ACCOUNT_INFO.emailVerified && $this.hasClass('_companyInviteAccept')) { 669 return Utils.emailVerifyBeforeAction(_STR.CL_CONFIRM_ACCEPT_COMPANY_VERIFY_EMAIL, function(){ 670 $this.trigger('click'); 671 }); 672 } 673 Utils.confirm(Utils.xReplace(confirmText, $this.attr('data-company-name')), function(){ 674 processFunc.call(RFMgr, companyID, 675 // ************ OK (AnswerCallBack) ************ 676 function () { 677 showResultMsg(successText, false, undefined, function(){ 678 location.reload(); 679 }); 680 }, 681 // *********** ERROR (ErrorCallBack) *********** 682 RFMgrCommonErrorHandler.bind(this, null) 683 ); 684 }); 685 }); 686 687 $.each(RFO.ACCOUNT_INFO.receivedData.companyInvitations, function(){ 688 var $alert = $('._companyInvitation').clone().removeClass('_companyInvitation hidden').appendTo($('.alertsContainer')); 689 var $alertText = $alert.find('._companyInvitationText'); 690 $alertText.text(Utils.xReplace($alertText.text(), this.name)); 691 $alert.find('._companyInviteAccept, ._companyInviteReject').val(this.companyId).attr('data-company-name', this.name); 692 if (RFMgr.getAccountMode() === RfapiJS.accountMode.OFS) { 693 $alert.find('._companyInvitationTypeOld').hide(); 694 } else { 695 $alert.find('._companyInvitationTypeNew').hide(); 696 } 697 }); 698 } 699 700 // check emergency access invitations 701 if (RFO.ACCOUNT_INFO && RFO.ACCOUNT_INFO.receivedData && RFO.ACCOUNT_INFO.receivedData.emergencyAccess.length) { 702 $(document).on('click', '._emergencyInviteAccept, ._emergencyInviteReject', function(){ 703 var $this = $(this); 704 var accountID = $this.val().trim(); 705 var confirmText = $this.hasClass('_emergencyInviteAccept') ? _STR.CL_EA_ACCEPT_CONFIRM : _STR.CL_EA_REJECT_CONFIRM; 706 var successText = $this.hasClass('_emergencyInviteAccept') ? _STR.CL_EA_ACCEPT_SUCCESS : _STR.CL_EA_REJECT_SUCCESS; 707 var processFunc = $this.hasClass('_emergencyInviteAccept') ? RFMgr.AcceptEmergencyContactInvitation : RFMgr.RejectEmergencyContactInvitation; 708 if (!RFO.ACCOUNT_INFO.emailVerified && $this.hasClass('_emergencyInviteAccept')) { 709 return Utils.emailVerifyBeforeAction(_STR.CL_EA_ACCEPT_CONFIRM_VERIFY_EMAIL, function(){ 710 $this.trigger('click'); 711 }); 712 } 713 Utils.confirm(Utils.xReplace(confirmText, $this.attr('data-account-name')), function(){ 714 processFunc.call(RFMgr, accountID, 715 // ************ OK (AnswerCallBack) ************ 716 function () { 717 showResultMsg(successText, false, undefined, function(){ 718 location.reload(); 719 }); 720 }, 721 // *********** ERROR (ErrorCallBack) *********** 722 RFMgrCommonErrorHandler.bind(this, null) 723 ); 724 }); 725 }); 726 727 $.each(RFO.ACCOUNT_INFO.receivedData.emergencyAccess, function(){ 728 if (this.accessStatus) { 729 return; 730 } 731 var $alert = $('._emergencyInvitation').clone().removeClass('_emergencyInvitation hidden').appendTo($('.alertsContainer')); 732 var $alertText = $alert.find('._emergencyInvitationText'); 733 var nameText = this.name ? this.name+' ('+this.email+')' : this.email; 734 $alertText.text(Utils.xReplace($alertText.text(), nameText)); 735 $alert.find('._emergencyInviteAccept, ._emergencyInviteReject').val(this.accountId).attr('data-account-name', nameText); 736 if (RFMgr.getAccountMode() === RfapiJS.accountMode.OFS) { 737 $alert.find('._emergencyInvitationTypeOld').hide(); 738 } else { 739 $alert.find('._emergencyInvitationTypeNew').hide(); 740 } 741 }); 742 } 743 744 // show start screen after account creation 745 var $startScreenDialog = $('#startScreenWidget'); 746 if ($startScreenDialog.length && sessionStorage.showStartScreen) { 747 $startScreenDialog.one('show.bs.modal', function(){ 748 if (RFO.ACCOUNT_INFO.enterprise) { 749 $startScreenDialog.find('._enterprise').removeClass('hidden'); 750 } else { 751 var browserName = Utils.browserName(); 752 var $body = $startScreenDialog.find('._consumer').removeClass('hidden'); 753 $body.find('.text._text_extension').html(Utils.xReplace($body.find('.text').html(), _STR.CL_STARTSCREEN_CONSUMER_TEXT_EXTENSION, browserName)); 754 if (browserName === 'Android') { 755 $body.find('._howto_extension').addClass('hidden'); 756 $body.find('.text._text_extension').addClass('hidden'); 757 $body.find('.text._text_install').removeClass('hidden'); 758 $body.find('._android').removeClass('hidden'); 759 } else if (browserName === 'iOS') { 760 $body.find('._howto_extension').addClass('hidden'); 761 $body.find('.text._text_extension').addClass('hidden'); 762 $body.find('.text._text_install').removeClass('hidden'); 763 $body.find('._ios').removeClass('hidden'); 764 } else if (browserName === 'Internet Explorer') { 765 $body.find('._howto_extension').addClass('hidden'); 766 $body.find('.text._text_extension').addClass('hidden'); 767 $body.find('.text._text_install').removeClass('hidden'); 768 $body.find('._windows').removeClass('hidden'); 769 } else if (browserName === 'Firefox') { 770 $body.find('.text').html(Utils.xReplace($body.find('.text').html(), _STR.CL_STARTSCREEN_CONSUMER_TEXT_ADDON, browserName)); 771 $body.find('._firefox').removeClass('hidden'); 772 } else if (browserName === 'Safari') { 773 $body.find('._safari').removeClass('hidden'); 774 } else if (browserName === 'Edge') { 775 $body.find('._edge').removeClass('hidden'); 776 } else if (browserName === 'Chrome' || browserName === 'Opera') { 777 $body.find('._chrome').removeClass('hidden'); 778 } 779 } 780 }).modal('show'); 781 } 782 sessionStorage.removeItem('showStartScreen'); 783 784 $('.navbar-message a').on('click', function(e) { 785 e.preventDefault(); 786 let passwd = RFMgr.getMasterPwd(); 787 sessionStorage['rfo-cred'] = JSON.stringify({value: JSON.stringify({ 788 login: RFO.ACCOUNT_INFO.login, 789 passw
789ord: Utils.encrypt(passwd, RFO.ACCOUNT_INFO.clientKey), 790 })}); 791 window.location.href = $(this).attr('href'); 792 }); 793}); 794 795// auto close collapsed bootstrap navbar on click 796$(document).on('click','.navbar-collapse.in',function(e) { 797 if ($(e.target).is('a') && $(e.target).attr('class') !== 'dropdown-toggle') { 798 $(this).collapse('hide'); 799 } 800}); 801 802// Backdrop z-index fix for multiple bootstrap modals 803$(document).on('show.bs.modal', '.modal', function () { 804 var zIndex = 1040 + (10 * $('.modal:visible').length); 805 $(this).css('z-index', zIndex+''); 806 // use a setTimeout because the .modal-backdrop isn't created when the event show.bs.modal is triggered ' 807 setTimeout(function() { 808 $('.modal-backdrop').not('.modal-stack').css('z-index', (zIndex - 1)+'').addClass('modal-stack'); 809 }, 0); 810}); 811 812// Scrollbar fix for multiple bootstrap modals 813$(document).on('hidden.bs.modal', '.modal', function () { 814 $('.modal:visible').length && $(document.body).addClass('modal-open'); 815}); 816 817// --- utilities --- 818 819String.prototype.startsWith = function(searchString) { 820 return this.indexOf(searchString) === 0; 821}; 822 823String.prototype.endsWith = function(searchString) { 824 return this.indexOf(searchString, this.length - searchString.length) !== -1; 825}; 826 827var Utils = { 828 829 Storage: { 830 set: function(key, value) { 831 localStorage[key] = value; 832 }, 833 get: function(key) { 834 return localStorage[key]; 835 }, 836 remove: function(key) { 837 localStorage.removeItem(key); 838 }, 839 test: function(){ 840 var result = true; 841 try { 842 localStorage.setItem('localStorage', 'test'); 843 if (localStorage.getItem('localStorage') !== 'test') { 844 result = false;
845 } 846 localStorage.removeItem('localStorage'); 847 } catch (e) { 848 result = false; 849 } 850 return result; 851 } 852 }, 853 854 RFObjectProp: { 855 GOTOURL : "g", 856 FIELDS : "f", 857 NAME : "n", 858 DISPNAME : "t", 859 VALUE : "v", 860 TYPE : "t", 861 GROUPS : "g", 862 INSTANCES: "i", 863 MATCHES : "o", 864 NOTE : "n" 865 }, 866 867 passwordStarChar: '●', 868 869 // User/Groups permissions 870 // ===================================================== 871 // Convert from sync_selector to DB flags 872 serveronly_convert: {0: null, 1:true, 2:false}, 873 // ===================================================== 874 // Convert from perm_selector to DB flags 875 ismanager_convert : {0: false, 1:false, 2:false, 3:true}, 876 readonly_convert : {0: null, 1:true, 2:false, 3:false}, 877 showpasswords_convert: {0: null, 1:false, 2:true, 3:true}, 878 // ===================================================== 879 880 in_array: function(value, array) { 881 return !!~array.indexOf(value); 882 }, 883 884 xReplace: function(str) { 885 for (var i = 1; i < arguments.length; i++) { 886 str = str.split('%'+i).join(arguments[i]); 887 } 888 return str; 889 }, 890 891 escapeHtml: function(text) { 892 var map = { 893 '&': '&', 894 '<': '<', 895 '>': '>', 896 '"': '"', 897 "'": ''' 898 }; 899 return (text+'').replace(/[&<>"']/g, function(m) { return map[m]; }); 900 }, 901 902 stripTags: function(str) { 903 return $('<div>'+str+'</div>').text(); 904 }, 905 906 escapeRegExp : function(str) { 907 return (str+'').replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 908 }, 909 910 isValidEmail: function(email) { 911 var checkEmailRegexp = /^([a-z0-9_\-+']+\.)*[a-z0-9_\-+']+@([a-z0-9\-]*[a-z0-9]\.)+[a-z]{2,}$/i; 912 if (RFO.RFO_CUSTOM_MODE) { 913 // RFO_CUSTOM_MODE: email with domain without dot is considered as valid 914 checkEmailRegexp = /^([a-z0-9_\-+']+\.)*[a-z0-9_\-+']+@[a-z0-9]*([a-z0-9]+[\-.])*[a-z0-9]+$/i; 915 } 916 email = (email || '').trim(); 917 return email.length <= 75 && 918 (checkEmailRegexp).test(email) && 919 !email.match(/^[.@+]/) && 920 !email.match(/\+\+/); 921 }, 922 923 isValidPhone: function(phone) { 924 var phonedigits = phone.replace(/[^0-9]/g, ''); 925 var phonefixed = phone.replace(/[^\+0-9]/g, ''); 926 return (/^[\+0-9\ \-\(\)\.]+$/i).test(phone) && (/^\+[0-9]+$/i).test(phonefixed) && phonedigits.length>=9; 927 }, 928 929 isAllowedPhone: function(phone) { 930 var phonefixed = phone.replace(/[^\+0-9]/g, ''); 931 if ((/^\+254/i).test(phonefixed)) { 932 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Kenya"); 933 } 934 if ((/^\+420/i).test(phonefixed)) { 935 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Czech Republic"); 936 } 937 if ((/^\+375/i).test(phonefixed)) { 938 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Belarus"); 939 } 940 if ((/^\+63/i).test(phonefixed)) { 941 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Philippines"); 942 } 943 if ((/^\+65/i).test(phonefixed)) { 944 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Singapore"); 945 } 946 if ((/^\+7/i).test(phonefixed)) { 947 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Russia/Kazakhstan"); 948 } 949 if ((/^\+84/i).test(phonefixed)) { 950 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Vietnam"); 951 } 952 if ((/^\+86/i).test(phonefixed)) { 953 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "China"); 954 } 955 if ((/^\+90/i).test(phonefixed)) { 956 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Turkey"); 957 } 958 if ((/^\+94/i).test(phonefixed)) { 959 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Sri Lanka"); 960 } 961 if ((/^\+961/i).test(phonefixed)) { 962 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Lebanon"); 963 } 964 if ((/^\+965/i).test(phonefixed)) { 965 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Kuwait"); 966 } 967 if ((/^\+994/i).test(phonefixed)) { 968 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Azerbaijan"); 969 } 970 if ((/^\+996/i).test(phonefixed)) { 971 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Kyrgyzstan"); 972 } 973 if ((/^\+998/i).test(phonefixed)) { 974 return Utils.xReplace(_STR.ERR_COUNTRY_SMS_NOT_SUPPORTED, "Uzbekistan"); 975 } 976 return true; 977 }, 978 979 isValidCompanyPhone: function(phone) { 980 var phonedigits = phone.replace(/[^0-9]/g, ''); 981 return (/^[\+0-9\ \-\(\)\.\,ext]+$/i).test(phone) && phonedigits.length>=6; 982 }, 983 984 // simple formatting, just add " " between the country code and the rest of the number. For +1 and +7 also format number as +1 (XXX) XXX-XXXX 985 // County codes list is taken from Wikipedia: https://en.wikipedia.org/wiki/List_of_country_calling_codes 986 formatPhoneNumber: function(phone) { 987 phone = phone.replace(/[^\+0-9]/g, ''); 988 if (!phone || phone[0] !== '+') { 989 return phone; 990 } 991 var match; 992 if (phone.substring(0, 2) === '+1' && (match = phone.match(/^\+1(\d\d\d)(\d\d\d)(.+)$/)) !== null) { 993 return '+1 ('+match[1]+') '+match[2]+'-'+match[3]; 994 } 995 if (phone.substring(0, 2) === '+7' && (match = phone.match(/^\+7(\d\d\d)(\d\d\d)(.+)$/)) !== null) { 996 return '+7 ('+match[1]+') '+match[2]+'-'+match[3]; 997 } 998 if ( 999 (match = phone.match(/^(\+\d\d)(.+)$/)) !== null && 1000 Utils.in_array(match[1], ['+20','+27','+28','+30','+31','+32','+33','+34','+36','+39','+40','+41','+43','+44','+45','+46','+47','+48','+49','+51','+52','+53','+54','+55','+56','+57','+58','+60','+61','+62','+63','+64','+65','+66','+81','+82','+83','+84','+86','+89','+90','+91','+92','+93','+94','+95','+98']) 1001 ) { 1002 return match[1]+' '+match[2]; 1003 } 1004 if ((match = phone.match(/^(\+\d\d\d)(.+)$/)) !== null) { 1005 return match[1]+' '+match[2]; 1006 } 1007 return phone; 1008 }, 1009 1010 parseUrlQuery: function() { 1011 var data = {}; 1012 var urlParts= [];
1013 [location.hash, location.search].forEach(function (urlPart) { 1014 if (urlPart) { 1015 urlParts.push(urlPart.substring(1)); 1016 } 1017 }); 1018 urlParts.join('&').replace(/\+/g, ' ').split('&').forEach(function (pair) { 1019 if (pair) { 1020 var param = pair.split('='); 1021 data[param[0]] = param[1] !== undefined ? decodeURIComponent(param[1]) : true; 1022 } 1023 }); 1024 return data; 1025 }, 1026 1027 // returns the byte length of an utf8 string 1028 byteLength: function(str) { 1029 // see description of the algorithm: http://stackoverflow.com/questions/5515869/string-length-in-bytes-in-javascript 1030 var s = str.length; 1031 for (var i = str.length-1; i >= 0; i--) { 1032 var code = str.charCodeAt(i); 1033 if (code > 0x7f && code <= 0x7ff) { 1034 s++; 1035 } else if (code > 0x7ff && code <= 0xffff) { 1036 s+=2; 1037 } 1038 if (code >= 0xDC00 && code <= 0xDFFF) { 1039 i--; //trail surrogate 1040 } 1041 } 1042 return s; 1043 }, 1044 1045 // counting UTF-8 characters 1046 countChars: function(str) { 1047 return Array.from(str).length; 1048 }, 1049 1050 // fixing charAt() to support non-Basic-Multilingual-Plane (BMP) characters 1051 fixedCharAt: function(str, idx) { 1052 // see description of the algorithm: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt#fixing_charat_to_support_non-basic-multilingual-plane_bmp_characters 1053 var ret = ''; 1054 str += ''; 1055 var end = str.length; 1056 var surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; 1057 while ((surrogatePairs.exec(str)) != null) { 1058 var li = surrogatePairs.lastIndex; 1059 if (li - 2 < idx) { 1060 idx++; 1061 } else { 1062 break; 1063 } 1064 } 1065 if (idx >= end || idx < 0) { 1066 return ''; 1067 } 1068 ret += str.charAt(idx); 1069 if (/[\uD800-\uDBFF]/.test(ret) && /[\uDC00-\uDFFF]/.test(str.charAt(idx + 1))) { 1070 // go one further, since one of the "characters" is part of a surrogate pair 1071 ret += str.charAt(idx + 1); 1072 } 1073 return ret; 1074 }, 1075 1076 // returns the max chars length from utf8 string limited by bytes length 1077 maxCharsLength: function(str, bytesLimit) { 1078 var chars = 0; 1079 var s = 0; 1080 var cnt = Utils.countChars(str); 1081 for (var i = 0; i < cnt; i++) { 1082 s += Utils.byteLength(Utils.fixedCharAt(str, i)); 1083 if (s > bytesLimit) { 1084 break; 1085 } 1086 chars++; 1087 } 1088 return chars; 1089 }, 1090 1091 filterBadChars: function(str) { 1092 return (str+'').replace(/[\:\*\?\"\<\>\|\\\/\x00-\x1F]/g, ''); 1093 }, 1094 1095 checkIfRfDataItemNameIsInvalid: function (name) { 1096 if (!name) { 1097 return _STR.ERR_NAME_CANNOT_BE_EMPTY; 1098 } 1099 if (name[0] === ' ') { 1100 return _STR.ERR_NAME_CANNOT_START_WITH_SPACES; 1101 } 1102 if (name[0] === '.') { 1103 return _STR.ERR_NAME_CANNOT_START_WITH_PERIODS; 1104 } 1105 if (name[name.length - 1] === '.') { 1106 return _STR.ERR_NAME_CANNOT_FINISH_WITH_PERIODS; 1107 }
1108 if (Utils.byteLength(name) > 255) { 1109 return _STR.ERR_TOO_LONG_NAME; 1110 } 1111 if (/[\\/:*?"<>|]/.test(name)) { 1112 return _STR.ERR_NAME_CANNOT_CONTAIN_CHARS; 1113 } 1114 return false; 1115 }, 1116 1117 normalizeAndCheckName: function (name, type) { 1118 var normalizedName = ''; 1119 var sErrMsg = Utils.checkIfRfDataItemNameIsInvalid(name); 1120 if (!sErrMsg) { 1121 normalizedName = RfapiJS.utils.NormalizeAndCheckFileName(name); 1122 if (normalizedName === undefined) { 1123 sErrMsg = _STR.ERR_NAME_CANNOT_CONTAIN_CHARS; 1124 } else if (normalizedName === RFO.ENT_ADSYNC_GROUP) { 1125 switch (type) { 1126 case 'group': sErrMsg = _STR.ERR_WRONG_GROUPNAME_SPECIAL; break; 1127 case 'share': sErrMsg = _STR.ERR_WRONG_SHAREDFOLDER_NAME_SPECIAL; break; 1128 case 'folder': sErrMsg = _STR.ERR_WRONG_FOLDER_NAME_SPECIAL; break; 1129 } 1130 } else if (Utils.in_array(type, ['group', 'share'])) { 1131 if (normalizedName.length < RFO.GROUP_NAME_MIN) { 1132 sErrMsg = Utils.xReplace(_STR.ERR_NAME_WRONG_LENGTH, RFO.GROUP_NAME_MIN, RFO.GROUP_NAME_MAX); 1133 } else { 1134 var maxCharsLength = Utils.maxCharsLength(normalizedName, RFO.GROUP_NAME_MAX); 1135 if (Utils.countChars(normalizedName) > maxCharsLength) { 1136 sErrMsg = Utils.xReplace(_STR.ERR_NAME_WRONG_LENGTH, RFO.GROUP_NAME_MIN, maxCharsLength); 1137 } 1138 } 1139 } 1140 } 1141 return { name: normalizedName, error: sErrMsg }; 1142 }, 1143 1144 makeValidFileName: function (name) { 1145 return RfapiJS.utils.MakeValidFileName(name, '_', 200); 1146 }, 1147 1148 basename: function(path) { 1149 var parts = path.split('/'); 1150 return parts[parts.length-1]; 1151 }, 1152 1153 nameWithoutExt: function(name) { 1154 var pos = name.lastIndexOf('.'); 1155 if (pos != -1 && pos != 0) { 1156 name = name.substr(0, pos); 1157 } 1158 return name; 1159 }, 1160 1161 encrypt: function(text, key) { 1162 return RfapiJS.utils.EncryptAESPBKDF2ToBase64String(text, key); 1163 }, 1164 1165 decrypt: function(text, key) { 1166 return RfapiJS.utils.DecryptAESPBKDF2FromBase64String(text, key); 1167 }, 1168 1169 unescape: function(hex) { 1170 return unescape(hex); 1171 }, 1172 1173 escape: function(bin) { 1174 return escape(bin); 1175 }, 1176 1177 base64Encode: function(str) { 1178 try { 1179 var data = window.btoa(unescape(encodeURIComponent(str))); 1180 } catch (e) { 1181 return false; 1182 } 1183 return data; 1184 }, 1185 1186 base64Decode: function(str) { 1187 try { 1188 var data = decodeURIComponent(escape(window.atob(str))); 1189 } catch (e) { 1190 return false; 1191 } 1192 return data; 1193 }, 1194 1195 parseJSON: function(str) { 1196 try { 1197 var jsonData = JSON.parse(str); 1198 } catch (e) { 1199 return false; 1200 } 1201 return jsonData; 1202 }, 1203 1204 beautifyJSON: function(str) { 1205 if (str === '') { 1206 return ''; 1207 } 1208 var jsonObj = Utils.parseJSON(str); 1209 if (jsonObj !== false) { 1210 return JSON.stringify(jsonObj, null, 4); 1211 } 1212 return false; 1213 }, 1214 1215 parseCSV: function(str) { 1216 str = (str || '').trim() + "\r\n"; 1217 1218 var arr = []; 1219 var quote = false; // true means we're inside a quoted field 1220 var groupData = false; // true means we're inside a groupData field (marked with <>) (example: GroupName<Server,Limited>) 1221 1222 // iterate over each character, keep track of current row and column (of the returned array) 1223 var row, col, c; 1224 for (row = col = c = 0; c < str.length; c++) { 1225 var cc = str[c], nc = str[c+1]; // current character, next character 1226 arr[row] = arr[row] || []; // create a new row if necessary 1227 arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary 1228 1229 // If the current character is a quotation mark, and we're inside a 1230 // quoted field, and the next character is also a quotation mark, 1231 // add a quotation mark to the current column and skip the next character 1232 if (cc === '"' && quote && nc === '"') { arr[row][col] += cc; ++c; continue; } 1233 1234 // If it's just one quotation mark, begin/end quoted field 1235 if (cc === '"') { quote = !quote; continue; } 1236 1237 if (cc === '<' && !groupData) { groupData = true; } 1238 if (cc === '>' && groupData) { groupData = false; } 1239 1240 // If it's a comma and we're not in a quoted field or a groupData field, move on to the next column 1241 if ((cc === ',' || cc === ';' || cc === '\t') && !quote && !groupData) { ++col; continue; } 1242 1243 // If it's a newline and we're not in a quoted field, move on to the next 1244 // row and move to column 0 of that new row 1245 if (cc === '\r' && nc === '\n' && !quote) { ++row; col = 0; ++c; continue; } 1246 if ((cc === '\r' || cc === '\n') && !quote) { ++row; col = 0; continue; } 1247 1248 // Otherwise, append the current character to the current column 1249 arr[row][col] += cc; 1250 } 1251 return arr; 1252 }, 1253 1254 parseINI: function(str) { 1255 var regex = { 1256 section: /^\s*\[\s*([^\]]*)\s*\]\s*$/, 1257 param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/, 1258 comment: /^\s*;.*$/ 1259 }; 1260 var value = {}; 1261 var lines = str.split(/\r\n|\r|\n/); 1262 var section = null; 1263 var match;
1264 lines.forEach(function(line) { 1265 if (!regex.comment.test(line)) { 1266 if ((match = line.match(regex.param)) !== null) { 1267 if (section) { 1268 value[section][match[1]] = match[2]; 1269 } else { 1270 value[match[1]] = match[2]; 1271 } 1272 } else if ((match = line.match(regex.section)) !== null) { 1273 value[match[1]] = {}; 1274 section = match[1]; 1275 } else if (line.length === 0 && section) { 1276 section = null; 1277 } 1278 } 1279 }); 1280 return value; 1281 }, 1282 1283 stringifyINI: function(value, delimiter) { 1284 var delim = delimiter || '\r\n'; 1285 // Recursively walks through each nodes and all subnodes within it 1286 // and creates appropriate sections combining the names from names of all subnodes. 1287 var walk = function(result, value, upperLevel) { 1288 var section = []; 1289 for (var p in value) { 1290 if (value.hasOwnProperty(p)) { 1291 var v = value[p]; 1292 if (typeof v === 'object') { 1293 section.push(delim + '[' + upperLevel + p + ']'); 1294 walk(section, v, upperLevel + p); 1295 continue; 1296 } 1297 v = [].concat(v); 1298 for (var i = 0; i < v.length; i++) { 1299 result.push(p.trim() + '=' + v[i]); 1300 } 1301 } 1302 } 1303 result.push.apply(result, section); 1304 }; 1305 var result = []; 1306 walk(result, value, ''); 1307 return result.join(delim).trim(); 1308 }, 1309 1310 humanReadableSize: function(iSize) { 1311 var sSize; 1312 iSize = parseInt(iSize, 10); 1313 if (iSize < 1000) { 1314 sSize = iSize + 'b'; 1315 } else if (iSize < 1024*100) { 1316 sSize = (iSize/1024).toFixed(0) + 'Kb'; 1317 } else { 1318 sSize = (iSize/1024/1024).toFixed(1) + 'Mb'; 1319 } 1320 return sSize; 1321 }, 1322 1323 getErrorDescription: function(errObject) { 1324 var errorMessage = ''; 1325 // retry-after error 1326 if (errObject.Code && Utils.in_array(errObject.Code, [403, 503])) { 1327 if (errObject.HttpRequest && errObject.HttpRequest.getResponseHeader('retry-after')) { 1328 errorMessage = _STR.ERR_RETRY_LATER; 1329 } else { 1330 errorMessage = Utils.stripTags(errObject.Description); 1331 } 1332 // wrong params 1333 } else if (errObject.Code === RfapiJS.RfErrType.RfErr_WrongParams) { 1334 errorMessage = Utils.stripTags(errObject.Description); 1335 // connection error 1336 } else if (errObject.Code === 0) { 1337 errorMessage = _STR.ERR_NO_CONNECTION; 1338 // other errors 1339 } else { 1340 if (errObject.Description) { 1341 errorMessage += Utils.stripTags(errObject.Description)+" "; 1342 } 1343 if (errObject.oException && errObject.oException.sibError) { 1344 errorMessage += "("+Utils.stripTags(errObject.oException.sibError)+"): "; 1345 } 1346 if (errObject.oException && errObject.oException.description) { 1347 errorMessage += Utils.stripTags(errObject.oException.description)+" "; 1348 } 1349 if (errorMessage === "Server Error " && errObject.HttpRequest.responseText) { 1350 // Display the message returned by server, not generic "Server Error" 1351 errorMessage = Utils.stripTags(errObject.HttpRequest.responseText)+" "; 1352 } 1353 if (errorMessage === '') { 1354 errorMessage = _STR.ERR_UNEXPECTED; 1355 } 1356 } 1357 return errorMessage; 1358 }, 1359 1360 getLocationHash: function() { 1361 // Firefox bug: location.hash returns url-decoded hash. 1362 // So we have to take the whole location string, then get the hash part of it. 1363 var locationParts = location.toString().split('#'); 1364 return (typeof locationParts[1] !== 'undefined' ? '#'+locationParts[1] : ''); 1365 }, 1366 1367 // simple check for IP range format like 192.168.1.1-192.168.1.100 1368 checkRegularIPRangeFormat: function(ip) { 1369 return ip.match(/^(\d+\.){3}\d+\-(\d+\.){3}\d+$/); 1370 }, 1371 1372 // check IP matching the format: x.y.z.w OR x.y.z.* OR x.y.z1-z2.* 1373 checkIPMask: function(ip) { 1374 if (ip === '') { 1375 return true; 1376 } 1377 // returns: 1378 // - false if not valid 1379 // - 1 if valid regular number
1380 // - 2 if valid interval or * 1381 var checkInterval = function(part) { 1382 if (part !== '*') { 1383 var ipPartsArr = part.split('-'); 1384 var re = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/; 1385 if (ipPartsArr.length > 2 || !ipPartsArr[0].match(re)) { 1386 return false; 1387 } 1388 if (ipPartsArr.length === 1) { 1389 return 1; 1390 } 1391 if (!ipPartsArr[1].match(re) || parseInt(ipPartsArr[0], 10) > parseInt(ipPartsArr[1], 10)) { 1392 return false; 1393 } 1394 } 1395 return 2; 1396 }; 1397 var checkIpAddress = function(ipAddr) { 1398 var ipParts = ipAddr.split('.'); 1399 if (ipParts.length !== 4) { 1400 return false; 1401 } 1402 while (ipParts.length > 0) { 1403 var check = checkInterval(ipParts.shift()); 1404 if (check === false) { 1405 return false; 1406 } 1407 if (check === 2) { // this is an interval - all other parts must be '*' 1408 for (var i = 0; i < ipParts.length; i++) { 1409 if (ipParts[i] !== '*') { 1410 return false; 1411 } 1412 } 1413 } 1414 } 1415 return true; 1416 }; 1417 if (Utils.checkRegularIPRangeFormat(ip)) { 1418 var ipAddress = ip.split('-'); 1419 if (!checkIpAddress(ipAddress[0]) || !checkIpAddress(ipAddress[1]) || Utils.ipToInt(ipAddress[0]) > Utils.ipToInt(ipAddress[1])) { 1420 return false; 1421 } 1422 } else { 1423 return checkIpAddress(ip); 1424 } 1425 return true; 1426 }, 1427 1428 // Merge overlapping intervals 1429 // Note: [1,2] and [3,4] consider as overlapping, so produce [1,4] 1430 mergeIntervals: function(intervals) { 1431 if (!intervals.length) { 1432 return intervals; 1433 } 1434 // sort intervals based on start value 1435 intervals = intervals.sort(function(a, b){ 1436 return a[0] - b[0]; 1437 }); 1438 // push the first interval onto the stack 1439 var stack = [intervals[0]]; 1440 // start from next interval, merge if necessary 1441 for (var i = 1; i < intervals.length; i++) { 1442 // get the top of the stack 1443 var top = stack[stack.length-1]; 1444 // if current interval is not overlapping with the stack top, push it to the stack 1445 if (top[1]+1 < intervals[i][0]) { 1446 stack.push(intervals[i]); 1447 // update end value of previous interval range if the current interval end value is larger 1448 } else if (top[1] < intervals[i][1]) { 1449 stack[stack.length-1][1] = intervals[i][1]; 1450 } 1451 } 1452 return stack; 1453 }, 1454 1455 // convert dotted IP address to integer 1456 ipToInt: function(ip) { 1457 var d = ip.split('.'); 1458 return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]); 1459 }, 1460 1461 // convert integer to dotted IP address 1462 intToIP: function(integer) { 1463 var part1 = integer & 255; 1464 var part2 = ((integer >> 8) & 255); 1465 var part3 = ((integer >> 16) & 255); 1466 var part4 = ((integer >> 24) & 255); 1467 return part4 + "." + part3 + "." + part2 + "." + part1; 1468 }, 1469 1470 // unmask IP range: 3.7.10-20.* => [3.7.10.0, 3.7.20.255] 1471 unmaskIPRange: function(ip) { 1472 var intervalStart, intervalEnd; 1473 var ipParts = ip.split('-'); 1474 if (ipParts.length > 1) { 1475 if (Utils.checkRegularIPRangeFormat(ip)) { 1476 intervalStart = ipParts[0]; 1477 intervalEnd = ipParts[1]; 1478 } else { 1479 var dotPos0 = ipParts[0].lastIndexOf('.'); 1480 var dotPos1 = ipParts[1].indexOf('.'); 1481 intervalStart = ipParts[0] + (dotPos1 !== -1 ? ipParts[1].substring(dotPos1) : ''); 1482 intervalEnd = (dotPos0 !== -1 ? ipParts[0].substring(0, dotPos0+1) : '') + ipParts[1]; 1483 } 1484 } else { 1485 intervalStart = intervalEnd = ip; 1486 } 1487 intervalStart = intervalStart.split('*').join('0'); 1488 intervalEnd = intervalEnd.split('*').join('255'); 1489 return [intervalStart, intervalEnd]; 1490 }, 1491 1492 // mask IP by interval 1493 // 1.2.3.6 - 10.10.200.0 => 1.2.3.6 - 10.10.200.0 1494 // 3.7.10.0 - 3.7.20.255 => 3.7.10-20.* 1495 maskIP: function(interval) { 1496 if (interval[0] === interval[1]) { 1497 return interval[0]; 1498 } 1499 var getParts = function(ip){ 1500 return ip.split('.').map(function(val){ 1501 return parseInt(val, 10); 1502 }); 1503 }; 1504 var parts1 = getParts(interval[0]); 1505 var parts2 = getParts(interval[1]); 1506 for (var i = 0; i < 4; i++) { 1507 if (parts1[i] !== parts2[i]) { 1508 var bValid = true; 1509 for (var k = i+1; k < 4; k++) { 1510 if (parts1[k] !== 0 || parts2[k] !== 255) { 1511 bValid = false;
1512 break; 1513 } else { 1514 parts1[k] = parts2[k] = '*'; 1515 } 1516 } 1517 if (bValid) { 1518 parts1[i] += '-' + parts2[i]; 1519 return parts1.join('.').replace('.0-255', '.*'); 1520 } 1521 break; 1522 } 1523 } 1524 return interval.join('-'); 1525 }, 1526 1527 getFormattedIPRangesArray: function(text) { 1528 var formattedIP = []; 1529 $.each(text.split(';'), function(){ 1530 if (Utils.checkRegularIPRangeFormat(this)) { 1531 formattedIP.push(this.replace('-', ' - ')); 1532 } else { 1533 formattedIP.push(this); 1534 } 1535 }); 1536 return formattedIP; 1537 }, 1538 1539 compileIPRanges: function(IPRangesText) { 1540 var intervalsMasked = []; 1541 var intervalsUnmasked = []; 1542 var allowedIP = (IPRangesText || '').trim() 1543 .replace(/\s*\-\s*/g, '-') 1544 .replace(/[;,\s]/g, "\n") 1545 .replace(/\n+/g, "\n") 1546 .split("\n"); 1547 if (allowedIP.length && allowedIP[0] === '') { 1548 return ''; 1549 } 1550 for (var i = 0; i < allowedIP.length; i++) { 1551 if (!Utils.checkIPMask(allowedIP[i])) { 1552 return false; 1553 } 1554 var ranges = Utils.unmaskIPRange(allowedIP[i]); 1555 intervalsUnmasked.push([Utils.ipToInt(ranges[0]), Utils.ipToInt(ranges[1])]); 1556 } 1557 // union and sort IP intervals 1558 intervalsUnmasked = Utils.mergeIntervals(intervalsUnmasked); 1559 // mask IP intervals 1560 $.each(intervalsUnmasked, function(){ 1561 var interval = this; 1562 interval[0] = Utils.intToIP(interval[0]); 1563 interval[1] = Utils.intToIP(interval[1]); 1564 intervalsMasked = intervalsMasked.concat(Utils.maskIP(interval)); 1565 }); 1566 return intervalsMasked.join(';'); 1567 }, 1568 1569 isValidURI: function (uri) { 1570 var url; 1571 try { 1572 url = new URL(uri); 1573 return url.protocol === "http:" || url.protocol === "https:"; 1574 } catch (_) { 1575 // fallback for IE11 and URL without prepended http|https:// 1576 var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol 1577 '((([a-zа-ÑÑÐ-ЯÐ\\d]([a-zа-ÑÑÐ-ЯÐ\\d-]*[a-zа-ÑÑÐ-ЯÐ\\d])*)\\.)+[a-zа-ÑÑÐ-ЯÐ]{2,}|'+ // domain name 1578 '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address 1579 '(\\:\\d+)?(\\/[-a-zа-ÑÑÐ-ЯÐ\\d%_.~+]*)*'+ // port and path 1580 '(\\?[;&a-zа-ÑÑÐ-ЯÐ\\d%_.~+=-]*)?'+ // query string 1581 '(\\#[-a-zа-ÑÑÐ-ЯÐ\\d_]*)?$','i'); // fragment locator 1582 return !!pattern.test(uri); 1583 } 1584 }, 1585 1586 updateStrength: function(passwd) { 1587 var colorImgHeight = 500; 1588 var scorebar_height = $('#scorebar')[0].offsetHeight; 1589 1590 var strength = RfapiJS.SecurityScore.CalcPasswordStrength(passwd); 1591 if (passwd.length != 0 && strength==0) { 1592 strength = 1; // we display 0 with gray color, so need to change to something else. 1593 } 1594 var pos = (colorImgHeight/100)*(strength > 100 ? 100 : strength); 1595 if (pos > colorImgHeight) { 1596 pos = colorImgHeight; 1597 } 1598 $('#scorebar').css('background-position', '0 '+(scorebar_height-pos)+'px'); 1599 1600 var $text = $('#scorebar_text'); 1601 if (passwd.length === 0) { 1602 $text.css('visibility', 'hidden'); 1603 } else { 1604 $text.css('visibility', 'visible'); 1605 } 1606 1607 if (passwd.length < 8) { 1608 $text.html(_STR.RFJS_TEXT_PASSWORD_TOO_SHORT); 1609 $text.css('color', '#AD0C0C'); 1610 } else if (strength <= 25) { 1611 $text.html(_STR.RFJS_TEXT_PASSWORD_WEAK); 1612 $text.css('color', '#AD0C0C'); 1613 } else if (strength <= 50) { 1614 $text.html(_STR.RFJS_TEXT_PASSWORD_MEDIUM); 1615 $text.css('color', '#DD9900'); 1616 } else if (strength <= 75) { 1617 $text.html(_STR.RFJS_TEXT_PASSWORD_GOOD); 1618 $text.css('color', '#339933'); 1619 } else { 1620 $text.html(_STR.RFJS_TEXT_PASSWORD_STRONG); 1621 $text.css('color', '#339933'); 1622 } 1623 }, 1624 1625 indicateError: function($el){ 1626 $el.one($el.attr('type') === 'text' ? 'input' : 'change', function(){ 1627 $(this).parent().removeClass('has-error'); 1628 }).trigger('focus').parent().addClass('has-error'); 1629 }, 1630 1631 showPopupError: function($el, content, placement, container){ 1632 $el.popover({ 1633 trigger: 'focus', 1634 html: true, 1635 container: typeof container !== 'undefined' ? container : 'body', 1636 placement: placement, 1637 content: content 1638 }).one('hide.bs.popover', function(){ 1639 $el.parent().removeClass('has-error'); 1640 }).one('hidden.bs.popover', function(){ 1641 $el.popover('destroy'); 1642 }).one('input', function(){ 1643 $el.popover('hide'); 1644 }).trigger('focus').popover('show').parent().addClass('has-error'); 1645 }, 1646 1647 calcReadOnly: function(node){ 1648 return node.Shared && (node.Shared.readOnly || !node.Shared.showPasswords) && !node.Shared.isManager && !node.Shared.isAdmin; 1649 }, 1650 1651 calcForbidIP: function(node){ 1652 return node.Shared && !node.Shared.accountInfo.isAllowedIP && !node.Shared.isAdmin && (node.Shared.company || !node.Shared.isManager); 1653 }, 1654 1655 calcSharedIconOverlayClass: function(node){ 1656 var shared = node.s || node.Shared || node.isShared || node.granted || node.received; 1657 var bEnterprise = node.company || (shared && shared.company); 1658 var bManager = node.isManager || (shared && shared.isManager) || node.isAdmin || (shared && shared.isAdmin) || node.granted;
1659 var bReadOnly = node.readOnly || (shared && shared.readOnly); 1660 if (shared) { 1661 if (bEnterprise) { 1662 if (bManager) { 1663 return 'entManager'; 1664 } else if (bReadOnly) { 1665 return 'entLimited'; 1666 } else { 1667 return 'entRegular'; 1668 } 1669 } else { 1670 if (bManager) { 1671 return 'consGranted'; 1672 } else if (bReadOnly) { 1673 return 'consLimited'; 1674 } else { 1675 return 'consReceived'; 1676 } 1677 } 1678 } 1679 return ''; 1680 }, 1681 1682 confirm: function(html, okCallback){ 1683 var $dialog = $('#confirmDialog'); 1684 $dialog.find('.modal-body').html(html); 1685 $dialog.one('shown.bs.modal', function(){ 1686 $dialog.find('._ok').off('click').one('click', function(){ 1687 $dialog.one('hidden.bs.modal', function(){ 1688 if (typeof okCallback === 'function') { 1689 okCallback($dialog); 1690 } 1691 }).modal('hide'); 1692 }); 1693 }).modal('show'); 1694 }, 1695 1696 confirmExt: function(params){ 1697 var $dialog = $('#confirmDialog').clone(); 1698 var $buttonTpl = $('<button type="button" class="btn"></button>'); 1699 var $footer = $dialog.find('.modal-footer').html(''); 1700 if (params.wide) { 1701 $dialog.find('.modal-dialog').removeClass('modal-sm'); 1702 } 1703 if (params.title) { 1704 $dialog.find('.modal-title').html(params.title); 1705 } 1706 if (params.html) { 1707 $dialog.find('.modal-body').html(params.html); 1708 } 1709 if (params.footnote) { 1710 $dialog.find('.modal-content').append('<div class="modal-footnote">' + params.footnote + '</div>'); 1711 } 1712 (params.buttons || []).forEach(function (button) { 1713 $footer.append($buttonTpl.clone() 1714 .addClass(button.class || '') 1715 .text(button.text || '') 1716 .one('click', function () { 1717 $dialog.one('hidden.bs.modal', function(){ 1718 $dialog.remove(); 1719 if (typeof button.click === 'function') { 1720 button.click(); 1721 } 1722 }).modal('hide'); 1723 }) 1724 ); 1725 }); 1726 $dialog.modal('show'); 1727 }, 1728 1729 resolveNamesConflict: function($dialog, $folderName, bGroup) { 1730 var folderName = RfapiJS.utils.NormalizeUnicodeStr($folderName.val().trim()); 1731 $('#resolveNamesConflictDialog').one('show.bs.modal', function(){ 1732 var $rDialog = $(this); 1733 $rDialog.find('.result-error').html('').hide(); 1734 $rDialog.find('.has-error').removeClass('has-error'); 1735 $rDialog.find('._name').text(folderName); 1736 $('#resolveName').val(folderName); 1737 if (bGroup) { 1738 $rDialog.find('._groupText').show(); 1739 $rDialog.find('._sharedfolderText').hide(); 1740 $rDialog.find('._groupName').text(_STR.LABEL_GROUP_NAME); 1741 $rDialog.find('._optionText').text(_STR.TEXT_RESOLVE_NAMES_CONFLICT_OPT1); 1742 } else { 1743 $rDialog.find('._groupText').hide(); 1744 $rDialog.find('._sharedfolderText').show(); 1745 $rDialog.find('._groupName').text(_STR.LABEL_SHAREDFOLDER_NAME); 1746 $rDialog.find('._optionText').text(_STR.TEXT_RESOLVE_NAMES_CONFLICT_OPT1_1); 1747 } 1748 var $rType = $rDialog.find('input[name="resolveType"]').on('click', function(){ 1749 if ($(this).val() === '0') { 1750 $rDialog.find('._groupName').show(); 1751 $rDialog.find('._folderName').hide(); 1752 } else { 1753 $rDialog.find('._groupName').hide(); 1754 $rDialog.find('._folderName').show(); 1755 } 1756 }); 1757 $($rType[0]).trigger('click'); // select first option 1758 $rDialog.find('._ok').off('click').on('click', function(){ 1759 $rDialog.find('.result-error').html('').hide(); 1760 var $newName = $('#resolveName'); 1761 var rType = $rDialog.find('input[name="resolveType"]:checked').val(); 1762 var nameData = Utils.normalizeAndCheckName($newName.val(), rType === '0' ? (bGroup ? 'group' : 'shared') : 'folder'); 1763 if (nameData.error) { 1764 $rDialog.find('.result-error').html(nameData.error).show(); 1765 Utils.indicateError($newName); 1766 return false;
1767 } 1768 var newName = nameData.name; 1769 if (rType === '0') { 1770 // create shared account 1771 $rDialog.off('hidden.bs.modal').one('hidden.bs.modal', function(){ 1772 $dialog.modal('show'); 1773 $folderName.val(newName); 1774 $dialog.find('._ok').trigger('click'); 1775 }).modal('hide'); 1776 } else { 1777 // rename folder 1778 var dataType = RfapiJS.EnRfDataItemType.RfItemType_Folder; 1779 var pathFrom = '/' + folderName; 1780 var pathTo = '/' + newName; 1781 $rDialog.off('hidden.bs.modal'); 1782 startProcessing($rDialog); 1783 RFMgr.MoveObject(pathFrom, dataType, pathTo, dataType, 1784 // ************ OK (AnswerCallBack) ************ 1785 function(){ 1786 stopProcessing($rDialog, function(){ 1787 // create shared account 1788 $dialog.modal('show'); 1789 $dialog.find('._ok').trigger('click'); 1790 }); 1791 }, 1792 // *********** ERROR (ErrorCallBack) *********** 1793 function(errObject){ 1794 if (errObject.Code && errObject.Code === 401) { 1795 //access denied error (session expired) 1796 doLogout(RFO.ROOT_PATH + 'login?relogin'); 1797 } else { 1798 stopProcessing($rDialog, function(){ 1799 var errorMsg = Utils.stripTags(errObject.Description); 1800 showResultMsg(errorMsg, true, false, function(){ 1801 $rDialog.one('hidden.bs.modal', function(){ 1802 $dialog.modal('show'); 1803 }).modal('show'); 1804 }); 1805 }); 1806 } 1807 } 1808 ); 1809 } 1810 }); 1811 }).one('shown.bs.modal', function(){ 1812 $(this).find('._autofocus').trigger('focus'); 1813 }).one('hidden.bs.modal', function(){ 1814 $dialog.modal('show'); 1815 }).modal('show'); 1816 }, 1817 1818 extractDomain: function(url) { 1819 return (url.indexOf("://") > -1 ? url.split('/')[2] : url.split('/')[0]).split(':')[0]; 1820 }, 1821 1822 checkBlackWhiteListPolicies: function(blacklist, whitelist, checkedURL) { 1823 var bValidUrl = true; 1824 var matchToList = function(list, checkedURL){ 1825 var isMatch = false; 1826 $.each(list, function(){ 1827 var curUrl = this.toLowerCase(); 1828 if (curUrl.startsWith('http://') || curUrl.startsWith('https://')) { 1829 // exact match 1830 if (checkedURL.startsWith(this)) { 1831 isMatch = true; 1832 } 1833 } else { 1834 // domain match 1835 if (Utils.extractDomain(checkedURL).endsWith(curUrl)) { 1836 isMatch = true; 1837 } 1838 } 1839 }); 1840 return isMatch; 1841 }; 1842 if (blacklist.length > 0) { 1843 bValidUrl = !matchToList(blacklist, checkedURL); 1844 } 1845 if (whitelist.length > 0 && bValidUrl) { 1846 bValidUrl = matchToList(whitelist, checkedURL); 1847 } 1848 return bValidUrl; 1849 }, 1850 1851 getRFType: function(path) { 1852 var parts = Utils.basename(path).split('.'); 1853 var fileext = parts[parts.length-1].toLowerCase(); 1854 var rfTypes = { 1855 'rfp': RfapiJS.EnRfDataItemType.RfItemType_Login, 1856 'rfb': RfapiJS.EnRfDataItemType.RfItemType_Bookmark, 1857 'rfq': RfapiJS.EnRfDataItemType.RfItemType_SearchCard, 1858 'rfx': RfapiJS.EnRfDataItemType.RfItemType_BlockingPasscard,
1859 'rft': RfapiJS.EnRfDataItemType.RfItemType_Identity, 1860 'rfc': RfapiJS.EnRfDataItemType.RfItemType_Contact, 1861 'rfn': RfapiJS.EnRfDataItemType.RfItemType_Safenote 1862 }; 1863 return typeof rfTypes[fileext] === 'undefined' ? RfapiJS.EnRfDataItemType.RfItemType_Undefined : rfTypes[fileext]; 1864 }, 1865 1866 getRFName: function(path) { 1867 var basename = Utils.basename(path); 1868 var parts = basename.split('.'); 1869 parts.pop(); 1870 if (parts.length) { 1871 return parts.join('.'); 1872 } 1873 return basename; 1874 }, 1875 1876 getRFDirPath: function(path) { 1877 return path.replace(/\/[^\/]*$/, ''); 1878 }, 1879 1880 createCompany: function(sCompanyAdmin, sCompanyName, nCompanyUsers, okCallback, errorCallback) { 1881 // define policies to save on create company 1882 var policiesINIBase64 = undefined; 1883 if (RFO.POLICIES_WIN_TYPES) { 1884 var policyData = {}; 1885 $.each(RFO.POLICIES_WIN_TYPES, function(section, sectionWinData){ 1886 $.each(sectionWinData, function(name, policyWinData){ 1887 if (typeof policyWinData['onCreateCompany'] !== 'undefined') { 1888 policyData[section] = policyData[section] || {}; 1889 policyData[section][name] = policyWinData['onCreateCompany']; 1890 } 1891 }); 1892 }); 1893 if (!$.isEmptyObject(policyData)) { 1894 policiesINIBase64 = Utils.base64Encode(Utils.stringifyINI(policyData)); 1895 } 1896 } 1897 RFMgr.CreateCompany(sCompanyName, okCallback, 1898 // *********** ERROR (ErrorCallBack) *********** 1899 function (errObject) { 1900 var errorMessage = ''; 1901 var bCompanyExists = false; 1902 // wrong params 1903 if (errObject.oException && errObject.oException.fields) { 1904 var errMsgList = []; 1905 var fields = errObject.oException.fields; 1906 for (var i = 0; i < fields.length; i++) { 1907 if (fields[i].name === 'name' && fields[i].exists) { 1908 errMsgList.push(Utils.xReplace(_STR.ERR_COMPANY_EXIST, sCompanyName)); 1909 bCompanyExists = true; 1910 } else { 1911 errMsgList.push(Utils.getErrorDescription(errObject)); 1912 break; 1913 } 1914 } 1915 errorMessage = errMsgList.join('<br>'); 1916 } else { 1917 errorMessage = Utils.getErrorDescription(errObject); 1918 } 1919 if (errorMessage !== '') { 1920 errorCallback(errorMessage, bCompanyExists); 1921 } 1922 }, 1923 undefined, 1924 sCompanyAdmin, 1925 undefined, 1926 undefined, 1927 policiesINIBase64, 1928 nCompanyUsers 1929 ); 1930 }, 1931 1932 getBrowserTimezone: function() { // return a string like "GMT+06:00" 1933 var nOffset = (new Date()).getTimezoneOffset(); 1934 var nHours = parseInt(Math.abs(nOffset / 60)); 1935 var sHours = ('00' + nHours).slice(-2); // left padding with zeros 1936 var nMinutes = Math.abs(nOffset % 60); 1937 var sMinutes = ('00' + nMinutes).slice(-2); 1938 return 'GMT' + (nOffset < 0 ? '+' : '-') + sHours + ':' + sMinutes; 1939 }, 1940 1941 isDstObserved: function() { 1942 var now = new Date(); 1943 var jan = new Date(now.getFullYear(), 0, 1).getTimezoneOffset(); 1944 var jul = new Date(now.getFullYear(), 6, 1).getTimezoneOffset(); 1945 return Math.max(jan, jul) != now.getTimezoneOffset(); 1946 }, 1947 1948 getSecurityScoreLevel: function(score){ 1949 if (score > 75) { 1950 return 'excellent'; 1951 } else if (score > 50) { 1952 return 'good'; 1953 } else if (score > 25) { 1954 return 'average'; 1955 } else if (score > 0) { 1956 return 'low'; 1957 } else { 1958 return 'undefined'; 1959 } 1960 }, 1961 1962 getPasswordStrengthLevel: function(pwdStrength){ 1963 if (pwdStrength > 3) { 1964 return 'strong'; 1965 } else if (pwdStrength > 2) { 1966 return 'good'; 1967 } else if (pwdStrength > 1) { 1968 return 'medium'; 1969 } else { 1970 return 'weak'; 1971 } 1972 }, 1973 1974 setDelayedHoverHint: function($this, content, extraParams) { 1975 var mouseEnterId; 1976 var params = $.extend({ 1977 html: true, 1978 trigger: 'manual', 1979 container: 'body', 1980 sanitize: false 1981 }, extraParams); 1982 if (content) { 1983 params.content = content; 1984 } 1985 $this.data('bHideTooltip', true).popover('destroy').popover(params).on('mouseenter', function(){ 1986 mouseEnterId = setTimeout(function(){ 1987 $this.popover('show'); 1988 }, 200); 1989 }).on('mouseleave', function(){ 1990 if (mouseEnterId) { 1991 clearTimeout(mouseEnterId); 1992 } 1993 setTimeout(function(){ 1994 if ($this.data('bHideTooltip')) { 1995 $this.popover('hide'); 1996 } 1997 }, 200); 1998 }); 1999 $this.data("bs.popover").tip().on('mouseenter', function(){ 2000 $this.data('bHideTooltip', false); 2001 }).on('mouseleave', function(){ 2002 $this.data('bHideTooltip', true).popover('hide'); 2003 }); 2004 }, 2005 2006 setSecurityStatsHint: function($el, aSecurityStats, aDialogInfoData) { 2007 var totalLogins = 0; 2008 var $securityStatsPreview = $('#securityStatsPreview').clone().removeAttr('id').removeClass('hidden'); 2009 if (aSecurityStats) { 2010 totalLogins = aSecurityStats['weak'] + aSecurityStats['medium'] + aSecurityStats['good'] + aSecurityStats['excellent']; 2011 } 2012 if (!totalLogins) { 2013 // show 'no data available' 2014 $securityStatsPreview.find('._data').remove(); 2015 Utils.setDelayedHoverHint($el, $securityStatsPreview[0].outerHTML); 2016 return false;
2017 } 2018 var setValues = function($content){ 2019 $content.find('._total').text(totalLogins); 2020 $content.find('._weak').text(aSecurityStats['weak']); 2021 $content.find('._medium').text(aSecurityStats['medium']); 2022 $content.find('._good').text(aSecurityStats['good']); 2023 $content.find('._strong').text(aSecurityStats['excellent']); 2024 $content.find('._reused').text(aSecurityStats['reused']); 2025 $content.find('._duplicates').text(aSecurityStats['duplicates']); 2026 }; 2027 setValues($securityStatsPreview); 2028 $securityStatsPreview.find('._nodata').remove(); 2029 Utils.setDelayedHoverHint($el, $securityStatsPreview[0].outerHTML); 2030 $el.on('click', function(){ 2031 $el.data('bHideTooltip', true).popover('hide'); 2032 if (aDialogInfoData) { 2033 aDialogInfoData.$dialog.one('show.bs.modal', function(){ 2034 aDialogInfoData.$dialog.find('.title').html(Utils.xReplace(_STR.CL_SECURITY_STATS_INFO_TITLE, '<span>' + Utils.escapeHtml(aDialogInfoData.name) + '</span>')); 2035 aDialogInfoData.$dialog.find('.secScoreMask').text(aDialogInfoData.value + '%'); 2036 aDialogInfoData.$dialog.find('.modal-header').removeClass().addClass('modal-header ' + Utils.getSecurityScoreLevel(aDialogInfoData.value)); 2037 setValues(aDialogInfoData.$dialog); 2038 }).modal('show'); 2039 } 2040 }); 2041 return true; 2042 }, 2043 2044 showSecurityStatsDetailedInfo: function(aDialogInfoData, errorCallback) { 2045 var $dialog = $('#securityStatsInfoDetail'); 2046 $dialog.find('.modal-dialog').removeClass('modal-lg').addClass('modal-sm'); 2047 $dialog.find('.modal-header').hide(); 2048 $dialog.one('show.bs.modal', function(){ 2049 startProcessing($dialog); 2050 }).one('shown.bs.modal', function(){ 2051 RFMgr.GetAccountSecurityInfo( 2052 { 2053 accountId: aDialogInfoData.accountId, 2054 countReceivedFiles: true, 2055 countGroups: true, 2056 countRegularSharedFolders: true 2057 }, 2058 function (data) { 2059 if (aDialogInfoData.name === '') { 2060 $dialog.find('._name').hide(); 2061 } else { 2062 $dialog.find('._name').text(aDialogInfoData.name).show(); 2063 } 2064 $dialog.find('.modal-header').removeClass().addClass('modal-header ' + Utils.getSecurityScoreLevel(data.securityStats.score)); 2065 $dialog.find('.secScoreMask').text(data.securityStats.score + '%'); 2066 $dialog.find('._all').text(data.items.all.length); 2067 $dialog.find('._reused').text(data.items.reused.length); 2068 $dialog.find('._duplicates').text(data.items.duplicates.length); 2069 2070 var createRows = function(items, $row, groupId){ 2071 var rowsContent = ''; 2072 $.each(items, function(i, itemData){ 2073 var m, $curRow = $row.clone().removeClass('hidden'); 2074 var rootName = itemData.accountInfo.accountId !== RFO.LOGGED_ACCOUNT_ID ? ('/' + itemData.accountInfo.name) : ''; 2075 var url = RFO.ROOT_PATH+'account#all_files::1.' + encodeURIComponent(rootName + itemData.path); 2076 $curRow.addClass('_group_row_'+groupId).attr('data-sort-group', groupId).find('.c_name').text(Utils.getRFName(itemData.path)).attr('href', url); 2077 if (itemData.gotoUrl && (m = itemData.gotoUrl.match(/^http(s)?:\/\/([^\/:]+)/))) { 2078 $curRow.find('.c_icon').attr('src', '/icons/?def=/css/images/new/icon_rfp.png&size=16&domain=' + m[2]); 2079 } 2080 $curRow.find('._c_url').text(itemData.gotoUrl); 2081 $curRow.find('._c_password').attr('data-value', itemData.password).html(itemData.password.replace(/./g, Utils.passwordStarChar)); 2082 $curRow.find('._c_strength').addClass(Utils.getPasswordStrengthLevel(itemData.pwdStrength)).attr('data-sort-value', itemData.pwdStrength); 2083 $curRow.find('._c_location').text(rootName + Utils.getRFDirPath(itemData.path)); 2084 $curRow.find('.c_age').text(itemData.modTime ? Math.floor((Date.now() / 1000 - itemData.modTime) / 86400) : ''); 2085 rowsContent += $curRow[0].outerHTML; 2086 }); 2087 return rowsContent; 2088 }; 2089 2090 $.each(['all', 'reused', 'duplicates'], function(i, type){ 2091 var $table = $('#tab-'+type).find('.modalTable'); 2092 var $row = $table.find('._row.hidden'); 2093 var rows = $row[0].outerHTML; 2094 if (type === 'all') { 2095 rows += createRows(data.items[type], $row, 0); 2096 } else { 2097 var $rowGroup = $table.attr('data-sort-in-groups', 1).find('._row_group.hidden'); 2098 rows += $rowGroup[0].outerHTML; 2099 $.each(data.items[type], function(i, itemGroupData){ 2100 var $curRowGroup = $rowGroup.clone().removeClass('hidden').attr('data-group-id', i*2).attr('data-sort-group', i*2-1); 2101 $curRowGroup.find('._group_url').text(itemGroupData.gotoUrl); 2102 rows += $curRowGroup[0].outerHTML + createRows(itemGroupData.items, $row, i*2); 2103 }); 2104 } 2105 $table.find('tbody').html(rows); 2106 }); 2107 2108 $dialog.find('.togglePass').off('click').on('click', function(){ 2109 var $this = $(this); 2110 $this.toggleClass($this.attr('data-toggle')); 2111 $this.closest('table').find('._c_password:visible').each(function(){ 2112 var $thisPass = $(this); 2113 if ($this.hasClass('rfoicon-hide') && $thisPass.attr('data-value')) { 2114 $thisPass.text($thisPass.attr('data-value')); 2115 } else { 2116 $thisPass.html($thisPass.attr('data-value').replace(/./g, Utils.passwordStarChar)); 2117 } 2118 }); 2119 }); 2120 2121 $dialog.find('._row_group').off('click').on('click', function(){ 2122 var $thisRow = $(this); 2123 var $thisIcon = $thisRow.find('.rfoicon'); 2124 $thisIcon.toggleClass($thisIcon.attr('data-toggle')); 2125 $thisRow.closest('table').find('._group_row_'+$thisRow.attr('data-group-id')).each(function(){ 2126 if ($thisIcon.hasClass('rfoicon-expand-more')) { 2127 $(this).show(); 2128 } else { 2129 $(this).hide(); 2130 } 2131 }); 2132 }); 2133
2134 $dialog.find('.modal-dialog').removeClass('modal-sm').addClass('modal-lg'); 2135 $dialog.find('.modal-header').show(); 2136 stopProcessing($dialog, undefined, true); 2137 2138 var $tabContent = $dialog.find('.tab-content'); 2139 $dialog.find('.nav-tabs li').removeClass('active').find('a').one('shown.bs.tab', function(e){ 2140 var $table = $tabContent.find('.tableScrollable:visible'); 2141 $table.trigger('adjustHeader').trigger('reset.sortable', [$table.find('tbody tr')]) 2142 .find('.togglePass').removeClass('rfoicon-hide').addClass('rfoicon-show'); 2143 }).eq(0).tab('show'); 2144 }, 2145 function (errObject) { 2146 if (errObject.Code && errObject.Code === 401) { 2147 //access denied error (session expired) 2148 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2149 } else { 2150 errorCallback(errObject); 2151 } 2152 } 2153 ); 2154 }).modal('show'); 2155 }, 2156 2157 enableSearch: function($search, $rows, $paginator){ 2158 var $doFilterBtn = $search.parent().find('span'); 2159 var classOn = $doFilterBtn.attr('data-on'); 2160 var classOff = $doFilterBtn.attr('data-off'); 2161 var filterTimerId; 2162 $search.val('').off('input').on('input', function(){ 2163 clearTimeout(filterTimerId); 2164 filterTimerId = setTimeout(function(){ 2165 var searchText = $search.val().trim().toLowerCase(); 2166 if (searchText !== '') { 2167 $rows.each(function(){ 2168 var bShow = false; 2169 var $thisRow = $(this); 2170 $thisRow.find('._filter').each(function(){ 2171 if (~$(this).text().trim().toLowerCase().indexOf(searchText)) {
2172 bShow = true; 2173 return false; // break each loop 2174 } 2175 }); 2176 if (bShow) { 2177 $paginator ? $thisRow.addClass('paginable') : $thisRow.show(); 2178 } else { 2179 $paginator ? $thisRow.removeClass('paginable') : $thisRow.hide(); 2180 } 2181 }); 2182 $doFilterBtn.removeClass(classOn).addClass(classOff); 2183 } else { 2184 $paginator ? $rows.addClass('paginable') : $rows.show(); 2185 $doFilterBtn.removeClass(classOff).addClass(classOn); 2186 } 2187 if ($paginator) { 2188 $paginator.trigger('init.paginator'); 2189 } 2190 }, 100); 2191 }); 2192 $doFilterBtn.off('click').on('click', function(){ 2193 if ($doFilterBtn.hasClass($doFilterBtn.attr('data-off'))) { 2194 $search.val(''); 2195 } 2196 $search.trigger('input'); 2197 }).removeClass(classOff).addClass(classOn); 2198 }, 2199 2200 enablePaginator: function($paginator, afterDisplayItems) { 2201 var $sourceTable = $('.' + $paginator.attr('data-source')).data('paginator', $paginator); 2202 var $visibleRows = $(); 2203 var nShownItems = parseInt($paginator.attr('data-items-count')); 2204 var $paginableRows, nFrom; 2205 2206 var $btnBegin = $paginator.find('.paginator-begin'); 2207 var $btnPrev = $paginator.find('.paginator-prev'); 2208 var $btnNext = $paginator.find('.paginator-next'); 2209 var $btnEnd = $paginator.find('.paginator-end'); 2210 var $shownText = $paginator.find('.paginator-text'); 2211 2212 var displayItems = function() { 2213 var nTo = Math.min(nFrom + nShownItems, $paginableRows.length); 2214 $visibleRows.hide(); 2215 $visibleRows = $(); 2216 for (var index = 0; index < $paginableRows.length; ++index) { 2217 if (index >= nFrom && index < nTo) { 2218 $paginableRows.eq(index).show(); 2219 $visibleRows.push($paginableRows[index]); 2220 } 2221 } 2222 $([$btnBegin[0], $btnPrev[0]]).prop('disabled', nFrom === 0); 2223 $([$btnNext[0], $btnEnd[0]]).prop('disabled', nTo === $paginableRows.length); 2224 $shownText.html(Utils.xReplace(_STR.PAGINATOR_TEXT, Math.min(nTo, nFrom + 1), nTo, $paginableRows.length)); 2225 if (typeof afterDisplayItems === 'function') { 2226 afterDisplayItems(); 2227 } 2228 }; 2229 2230 // by default - paginate through all rows (when using filters this count will be changed) 2231 $sourceTable.find('._row').addClass('paginable').hide(); 2232 2233 $btnBegin.off('click').on('click', function() { 2234 nFrom = 0; 2235 displayItems(); 2236 }); 2237 $btnPrev.off('click').on('click', function() { 2238 nFrom = Math.max(0, nFrom - nShownItems); 2239 displayItems(); 2240 }); 2241 $btnNext.off('click').on('click', function() { 2242 nFrom = Math.min(nFrom + nShownItems, $paginableRows.length - 1); 2243 displayItems(); 2244 }); 2245 $btnEnd.off('click').on('click', function() { 2246 nFrom = Math.floor(($paginableRows.length-1)/nShownItems) * nShownItems; 2247 displayItems(); 2248 }); 2249 2250 $paginator.off('init.paginator').on('init.paginator', function() { 2251 $paginableRows = $sourceTable.find('.paginable'); 2252 nFrom = 0; 2253 displayItems(); 2254 }).trigger('init.paginator'); 2255 }, 2256 2257 enableScrollPaginator: function($sourceTable, initialRowsArray, afterDisplayItems) { 2258 $sourceTable.data('paginator', 1); 2259 2260 var rowsArray = initialRowsArray; 2261 var $sourceTableBody = $sourceTable.find('tbody'); 2262 var $heightRow = $('<tr><td colspan="'+$sourceTable.find('thead th, thead td').length+'" style="padding:0;margin:0;border:none;"></td></tr>'); 2263 var $heightRowTD = $heightRow.find('td'); 2264 2265 var $scrollWrapper = $sourceTable.parent(); 2266 var $scroller = $scrollWrapper.find('._scroller'); 2267 if (!$scroller.length) {
2268 $scroller = $('<div class="_scroller"></div>').css('float', 'left').prependTo($scrollWrapper); 2269 } 2270 2271 var nShownItems = 1; 2272 var rowHeight = 0; 2273 var bUpdateAll = true; 2274 2275 var fillTable = function(nFrom, posOffset) { 2276 // add 1 row after the visible interval of rows in container - used for smooth scrolling 2277 var nTo = Math.min(nFrom + nShownItems + 1, rowsArray.length); 2278 var aChangedRowsIndexes = []; 2279 if (bUpdateAll || !posOffset || Math.abs(posOffset) >= nShownItems) { 2280 // update all rows 2281 var resultHTML = ''; 2282 for (var i = nFrom; i < nTo; i++) { 2283 resultHTML += "<tr class='_row' data-i='"+i+"'></tr>"; 2284 aChangedRowsIndexes.push(i); 2285 } 2286 // bug in IE11: if set an empty html body, then the inner html of the $heightRow object is removed 2287 $sourceTableBody.html(resultHTML || '<tr></tr>').prepend($heightRow); 2288 bUpdateAll = false; 2289 } else { 2290 // update only needed rows 2291 if (posOffset < 0) { 2292 $sourceTableBody.find('._row').slice(posOffset).each(function(idx){ 2293 var changeIndex = nFrom - posOffset - idx - 1; 2294 $(this).attr('data-i', changeIndex).insertAfter($heightRow); 2295 aChangedRowsIndexes.push(changeIndex); 2296 }); 2297 } else { 2298 $sourceTableBody.find('._row').slice(0, posOffset).each(function(idx){ 2299 var changeIndex = nTo - posOffset + idx; 2300 $(this).attr('data-i', changeIndex).appendTo($sourceTableBody); 2301 aChangedRowsIndexes.push(changeIndex); 2302 }); 2303 } 2304 } 2305 $('.popover').remove(); // remove all popover that could be shown 2306 if (typeof afterDisplayItems === 'function') { 2307 afterDisplayItems(rowsArray, aChangedRowsIndexes); 2308 } 2309 if (rowHeight === 0) { 2310 rowHeight = $sourceTable.find('._row').eq(0).height() || 0; 2311 } 2312 }; 2313 2314 if ($sourceTable.attr('data-paginator-items-count')) { 2315 nShownItems = parseInt($sourceTable.attr('data-paginator-items-count'), 10); 2316 } else { 2317 var containerHeight = $(window).height() - $sourceTable.position().top - $('.b-navbar-2').height()*2; 2318 fillTable(0); // it is needed to calc rowHeight 2319 if (rowHeight > 0) { 2320 nShownItems = Math.ceil(containerHeight / rowHeight); 2321 } 2322 } 2323 2324 var nOldPos = 0; 2325 $scrollWrapper.off('scroll.paginator').on('scroll.paginator', function(e){ 2326 var scrollTop = $scrollWrapper[0].scrollTop; 2327 var pos = Math.floor(scrollTop / rowHeight); 2328 if (pos > rowsArray.length - nShownItems - 1) { 2329 pos = rowsArray.length - nShownItems - 1; 2330 pos = pos > 0 ? pos : 0; 2331 } 2332 if (pos === nOldPos) { 2333 // scroll event occurs too many times, so we repaint table only if the interval of visible rows changes 2334 // all other steps uses internal browser scrolling process and we can see smooth scrolling effect 2335 return; 2336 } 2337 fillTable(pos, pos - nOldPos); 2338 nOldPos = pos; 2339 $heightRowTD.css('height', (pos * rowHeight)+'px'); 2340 }); 2341 2342 $sourceTable.off('init.paginator').on('init.paginator', function(e, newRowsArray) { 2343 bUpdateAll = true; 2344 if (newRowsArray) { 2345 rowsArray = newRowsArray; 2346 } 2347 fillTable(0); 2348 nOldPos = 0; 2349 $heightRowTD.css('height', '0px'); 2350 $scroller.css('height', (rowsArray.length * rowHeight)+'px'); 2351 $scrollWrapper.css('max-height', (nShownItems * rowHeight)+'px').scrollTop(0); 2352 }).trigger('init.paginator'); 2353 2354 $sourceTable.trigger('reset.sortable', [initialRowsArray]); 2355 }, 2356 2357 enableScrollPaginatorSearch: function($table, $search, rowsArray, okCallback) { 2358 var FILTERED_LIST = rowsArray; 2359 var $doFilterBtn = $search.parent().find('span'); 2360 var classOn = $doFilterBtn.attr('data-on'); 2361 var classOff = $doFilterBtn.attr('data-off'); 2362 var filterTimerId; 2363 $search.val('').off('input').on('input', function(){ 2364 clearTimeout(filterTimerId); 2365 filterTimerId = setTimeout(function(){ 2366 var searchText = $search.val().trim().toLowerCase(); 2367 if (searchText !== '') { 2368 FILTERED_LIST = rowsArray.filter(function(thisRow){ 2369 return ~thisRow['_data_'].textFilter.indexOf(searchText); 2370 }); 2371 $doFilterBtn.removeClass(classOn).addClass(classOff); 2372 } else { 2373 FILTERED_LIST = rowsArray; 2374 $doFilterBtn.removeClass(classOff).addClass(classOn); 2375 } 2376 if (typeof okCallback === 'function') { 2377 okCallback(FILTERED_LIST); 2378 } 2379 $table.trigger('sort.sortable', [FILTERED_LIST]).trigger('init.paginator', [FILTERED_LIST]); 2380 }, 100); 2381 }); 2382 $doFilterBtn.off('click').on('click', function(){ 2383 if ($doFilterBtn.hasClass($doFilterBtn.attr('data-off'))) { 2384 $search.val(''); 2385 } 2386 $search.trigger('input'); 2387 }).removeClass(classOff).addClass(classOn); 2388 }, 2389 2390 virtualList: function(params) { 2391 var $container = params.container.html(''); 2392 var itemHeight = params.itemHeight; 2393 var countInRow = params.countInRow || 1; 2394 var createItem = params.createItem; 2395
2396 var $scroller = $('<div class="_scroller"></div>').css('float', 'left').prependTo($container); 2397 var $scrollContent = $('<div class="_scrollContent"></div>').appendTo($container); 2398 2399 var mItems = []; 2400 var lastPos = 0; 2401 2402 // Define maximum items that could be visible in scrollable area. 2403 // It can be assumed that number of items filled all window height will be enough. 2404 var maxShownItems = Math.ceil($(window).height() / (itemHeight / countInRow)); 2405 2406 var updateViewport = function(from) { 2407 from = typeof from !== 'undefined' ? from : lastPos; 2408 var fragment = document.createDocumentFragment(); 2409 var to = Math.min(from + maxShownItems, mItems.length); 2410 for (var i = from; i < to; i++) { 2411 fragment.appendChild(createItem(mItems[i], i)); 2412 } 2413 $scrollContent.html('').append(fragment).css('padding-top', (from * itemHeight / countInRow) + 'px'); 2414 lastPos = from; 2415 }; 2416 2417 return { 2418 UpdateItems: function(items) { 2419 if (items) { 2420 mItems = items; 2421 $container.scrollTop(0); 2422 $scroller.css('height', mItems.length * itemHeight / countInRow); 2423 var oldPos = -1; 2424 $container.off('scroll.virtuallist').on('scroll.virtuallist', function() { 2425 var scroll_top = $container[0].scrollTop; 2426 var pos = Math.floor(scroll_top / itemHeight) * countInRow; 2427 if (pos === oldPos) { 2428 // scroll event occures too much times, so we repaint table only if the interval of visible rows changes 2429 // all other steps uses internal browser scrolling process and we can see smooth scrolling effect 2430 return; 2431 } 2432 oldPos = pos; 2433 updateViewport(pos); 2434 }); 2435 } 2436 updateViewport(); 2437 } 2438 }; 2439 }, 2440 2441 processGroupCreate: function(companyId, okCallback) { 2442 var companyPolicies = RFO.ACCOUNT_INFO.companiesPolicies[companyId] ? RFO.ACCOUNT_INFO.companiesPolicies[companyId] : ''; 2443 $('#createEditGroupWidget').one('show.bs.modal', function(){ 2444 var $this = $(this); 2445 $this.find('form')[0].reset(); 2446 $this.find('._caption').html($this.find('._caption').attr('data-caption-create')); 2447 $this.find('.result-error').html('').hide(); 2448 $this.find('.has-error').removeClass('has-error'); 2449 $this.find('._moreSettings').trigger('collapse'); 2450 $this.find('._groupActions').hide(); 2451 2452 var $disableAccessFrom = $('#disableAccessFrom'); 2453 var $disableAccessFromFieldset = $('.disableAccessFrom'); 2454 var $disableAccessFromTypes = $disableAccessFromFieldset.find('input[type="checkbox"]'); 2455 var $disableAccessFromHint = $this.find('.disableAccessFromHint'); 2456 $disableAccessFrom.prop('checked', false).prop('disabled', false).off('click').on('click', function(){ 2457 if ($disableAccessFrom.prop('checked')) { 2458 $disableAccessFromFieldset.prop('disabled', false); 2459 $disableAccessFromTypes.prop('disabled', false); 2460 } else { 2461 $disableAccessFromFieldset.prop('disabled', true); 2462 $disableAccessFromTypes.prop('checked', false).prop('disabled', true); 2463 } 2464 }); 2465 $disableAccessFromHint.hide(); 2466 var companyPolicyDisableDevices = []; 2467 if (companyPolicies && companyPolicies.CONFIG && companyPolicies.CONFIG['DisableDevices']) { 2468 companyPolicyDisableDevices = companyPolicies.CONFIG['DisableDevices'].split(' '); 2469 } 2470 $disableAccessFromTypes.each(function(){ 2471 var $curDevType = $(this); 2472 if (Utils.in_array($curDevType.val(), companyPolicyDisableDevices)) { 2473 $curDevType.prop('checked', true).prop('disabled', true); 2474 $disableAccessFrom.prop('checked', true).prop('disabled', true); 2475 $disableAccessFromHint.show(); 2476 } else { 2477 $curDevType.prop('checked', false).prop('disabled', false); 2478 } 2479 }); 2480 if (!$disableAccessFrom.prop('checked')) { 2481 $disableAccessFrom.prop('checked', true).trigger('click'); 2482 } else { 2483 $disableAccessFromFieldset.prop('disabled', false); 2484 } 2485 2486 var $groupAllowedIP = $('#groupAllowedIP'); 2487 var bAllowEditIPRanges = !(companyPolicies && companyPolicies.CONFIG && companyPolicies.CONFIG['AllowedIPRanges']); 2488 if (bAllowEditIPRanges) { 2489 $groupAllowedIP.val('').prop('disabled', false).parent().find('label').removeClass('disabled'); 2490 $this.find('.allowedIPGroupPolicyHint').hide(); 2491 } else { 2492 $groupAllowedIP.val('').prop('disabled', true).parent().find('label').addClass('disabled'); 2493 $this.find('.allowedIPGroupPolicyHint').show(); 2494 } 2495 2496 $this.find('._ok').off('click').on('click', function () { 2497 var policyArr = {}; 2498 var allowedIP = Utils.compileIPRanges($groupAllowedIP.val()); 2499 // set 'Allowed IP Addresses' as policy 2500 if (bAllowEditIPRanges && allowedIP) { 2501 if (!policyArr.CONFIG) { 2502 policyArr.CONFIG = {}; 2503 } 2504 policyArr.CONFIG['EnableGroupForIPRanges'] = allowedIP; 2505 } 2506 // set 'Disable Access from device' as policy 2507 var disableDevTypePolicyValue = ''; 2508 $disableAccessFromTypes.each(function(){ 2509 var $curDevType = $(this); 2510 var sDevType = $curDevType.val(); 2511 if (!Utils.in_array(sDevType, companyPolicyDisableDevices) && $curDevType.prop('checked')) { 2512 disableDevTypePolicyValue += ' ' + sDevType; 2513 } 2514 }); 2515 if (disableDevTypePolicyValue) { 2516 if (!policyArr.CONFIG) { 2517 policyArr.CONFIG = {}; 2518 } 2519 policyArr.CONFIG['DisableGroupForDevices'] = disableDevTypePolicyValue; 2520 } 2521 var policyBase64 = undefined; 2522 if (!$.isEmptyObject(policyArr)) { 2523 policyBase64 = Utils.base64Encode(Utils.stringifyINI(policyArr)); 2524 } 2525 Utils.groupCreate( 2526 $this, 2527 companyId, 2528 policyBase64, 2529 $groupAllowedIP, 2530 okCallback, 2531 true 2532 ); 2533 }); 2534 $this.find('form').off('submit').on('submit', function(){ 2535 $this.find('._ok').trigger('click'); 2536 return false;
2537 }); 2538 $groupAllowedIP.on('keydown', function(){ 2539 $(this).popover('hide'); 2540 }); 2541 }).one('shown.bs.modal', function(){ 2542 $(this).find('._autofocus').trigger('focus'); 2543 }).modal('show'); 2544 }, 2545 2546 groupCreate: function($dialog, companyId, policyBase64, $groupAllowedIP, okCallback, bStopProcessingOnOK) { 2547 var $groupNameEl = $dialog.find('input[name="groupName"]'); 2548 2549 var nameData = Utils.normalizeAndCheckName($groupNameEl.val(), 'group'); 2550 if (nameData.error) { 2551 $dialog.find('.result-error').html(nameData.error).show(); 2552 Utils.indicateError($groupNameEl); 2553 return false; 2554 } 2555 var groupNameElVal = nameData.name; 2556 2557 if ($groupAllowedIP && Utils.compileIPRanges($groupAllowedIP.val()) === false) { 2558 $dialog.find('.result-error').html(_STR.CL_WRONG_IP_FORMAT).show(); 2559 Utils.indicateError($groupAllowedIP); 2560 return false; 2561 } 2562 2563 var sync_value = $dialog.find('._sync_selector tr.selected').attr('data'); 2564 var perm_value = $dialog.find('._perm_selector tr.selected').attr('data'); 2565 2566 var processFunc = function() { 2567 RFMgr.CreateSharedAccount( 2568 // ************ OK (AnswerCallBack) ************ 2569 function (status, groupID) { 2570 if (bStopProcessingOnOK) { 2571 stopProcessing($dialog, function(){ 2572 showResultMsg(_STR.CL_USERGROUP_GROUPS_SUCCESS_CREATE, false, undefined, function(){ 2573 if (typeof okCallback === 'function') { 2574 okCallback(groupNameElVal, groupID); 2575 } 2576 }); 2577 }); 2578 } else { 2579 if (typeof okCallback === 'function') { 2580 okCallback(groupNameElVal, groupID); 2581 } 2582 } 2583 }, 2584 // *********** ERROR (ErrorCallBack) *********** 2585 function (errObject) { 2586 if (errObject.Code && errObject.Code === 401) { 2587 //access denied error (session expired) 2588 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2589 } else if (errObject.Code && errObject.Code === RfapiJS.RfErrType.RfErr_AccountExists) { 2590 stopProcessing($dialog, function(){ 2591 var errorMsg = Utils.xReplace(_STR.ERR_CANNOT_CREATE_GROUP_ALREADY_EXISTS, Utils.escapeHtml(groupNameElVal)); 2592 showResultMsg(errorMsg, true, false, function(){ 2593 $dialog.modal('show'); 2594 }); 2595 }); 2596 } else if (errObject.Code && errObject.Code === RfapiJS.RfErrType.RfErr_FolderExists && !$('#quickstartWidget').hasClass('in')) { 2597 stopProcessing($dialog, function(){ 2598 Utils.resolveNamesConflict($dialog, $groupNameEl, true); 2599 }); 2600 } else { 2601 stopProcessing($dialog, function(){ 2602 var errorMsg = _STR.CL_USERGROUP_GROUPS_ERROR_CREATE + ':<br>' + Utils.stripTags(errObject.Description); 2603 showResultMsg(errorMsg, true, false, function(){ 2604 $dialog.modal('show'); 2605 }); 2606 }); 2607 } 2608 }, 2609 undefined, groupNameElVal, undefined, undefined, companyId, 2610 Utils.readonly_convert[perm_value], // new flag value 2611 Utils.showpasswords_convert[perm_value], // new flag value 2612 Utils.serveronly_convert[sync_value], // new flag value 2613 undefined, 2614 policyBase64, 2615 true 2616 ); 2617 }; 2618 startProcessing($dialog, function() { 2619 processFunc(); 2620 }); 2621 }, 2622 2623 processAdConnector: function($connectorPage) { 2624 $connectorPage.find('.connector-block').addClass('hidden'); 2625 $connectorPage.find('.connector-block.offline').removeClass('hidden').addClass('loader'); 2626 RFMgr.HandleADConnectorData(undefined, 2627 function(data){ 2628 $connectorPage.find('.loader').removeClass('loader'); 2629 data = Utils.parseJSON(data); 2630 if (!data || data.reset || $.isEmptyObject(data.adSyncSettings)) { 2631 return; 2632 } 2633 $connectorPage.find('.connector-block').removeClass('hidden'); 2634 $connectorPage.find('.connector-block.offline').addClass('hidden'); 2635 }, 2636 function (errObject) { 2637 $connectorPage.find('.loader').removeClass('loader'); 2638 if (errObject.Code && errObject.Code === 401) { 2639 // access denied error (session expired) 2640 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2641 // code=6: no groups or no AdSyncGroup or no ad_state.json file 2642 } else if (!errObject.Code || errObject.Code !== 6) { 2643 showResultMsg(Utils.getErrorDescription(errObject), true); 2644 } 2645 } 2646 ); 2647 }, 2648 2649 processUserCreate: function($dialog, companyId, isAdmin, okCallback) {
2650 var $name = $dialog.find('input[name="userName"]'); 2651 var $email = $dialog.find('input[name="userEmail"]'); 2652 var name = RfapiJS.utils.NormalizeUnicodeStr($name.val().trim()); 2653 var email = $email.val().trim(); 2654 if (name.length < RFO.ACCOUNT_NAME_MIN) { 2655 Utils.indicateError($name); 2656 $dialog.find('.result-error').html(Utils.xReplace(_STR.ERR_NAME_WRONG_LENGTH, RFO.ACCOUNT_NAME_MIN, RFO.ACCOUNT_NAME_MAX)).show(); 2657 return; 2658 } 2659 var maxCharsLength = Utils.maxCharsLength(name, RFO.ACCOUNT_NAME_MAX); 2660 if (Utils.countChars(name) > maxCharsLength) { 2661 Utils.indicateError($name); 2662 $dialog.find('.result-error').html(Utils.xReplace(_STR.ERR_NAME_WRONG_LENGTH, RFO.ACCOUNT_NAME_MIN, maxCharsLength)).show(); 2663 return; 2664 } 2665 if (name !== Utils.filterBadChars(name)) { 2666 Utils.indicateError($name); 2667 $dialog.find('.result-error').html(_STR.ERR_INVALID_USERNAME).show(); 2668 return; 2669 } 2670 if (name === RFO.ENT_ADSYNC_USER) { 2671 Utils.indicateError($name); 2672 $dialog.find('.result-error').html(_STR.ERR_WRONG_USERNAME_SPECIAL).show(); 2673 return; 2674 } 2675 if (!Utils.isValidEmail(email)) { 2676 Utils.indicateError($email); 2677 $dialog.find('.result-error').html(_STR.ERR_INVALID_EMAIL).show(); 2678 return; 2679 } 2680 var processFunc = function() { 2681 var pwd = RfapiJS.utils.generatePassword(12, true, true, true, true, undefined, undefined, false, true, 4); 2682 RFMgr.CreateCompanyMember({ 2683 'memberLogin': email, 2684 'memberPwd': pwd, 2685 'memberName': name, 2686 'memberMail': email, 2687 'company': companyId, 2688 'isAdmin': isAdmin, 2689 'otpOn': undefined 2690 }, 2691 // ************ OK (AnswerCallBack) ************ 2692 function() { 2693 stopProcessing($dialog, function(){ 2694 if (typeof okCallback === 'function') { 2695 okCallback(); 2696 } 2697 }); 2698 }, 2699 // *********** ERROR (ErrorCallBack) *********** 2700 function (errObject) { 2701 if (errObject.oException && errObject.oException.fields) { 2702 var errMsgList = []; 2703 var fields = errObject.oException.fields; 2704 for (var i = 0; i < fields.length; i++) { 2705 if ((fields[i].name === 'login' || fields[i].name === 'email') && fields[i].exists) { 2706 errMsgList.push(Utils.xReplace(_STR.ERR_EMAIL_EXISTS1, email)); 2707 } 2708 if (fields[i].name === 'email' && !fields[i].exists) { 2709 errMsgList.push(_STR.ERR_INVALID_EMAIL); 2710 } 2711 if (fields[i].name === 'name') { 2712 errMsgList.push(Utils.xReplace(_STR.ERR_NAME_WRONG_LENGTH, RFO.ACCOUNT_NAME_MIN, RFO.ACCOUNT_NAME_MAX)); 2713 } 2714 } 2715 stopProcessing($dialog, function(){ 2716 showResultMsg(errMsgList.join('<br>'), true, false, function(){ 2717 $dialog.modal('show'); 2718 }); 2719 }); 2720 } else if (errObject.Code && errObject.Code === 401) { 2721 //access denied error (session expired) 2722 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2723 } else { 2724 stopProcessing($dialog, function(){ 2725 showResultMsg(Utils.getErrorDescription(errObject), true, false, function(){ 2726 $dialog.modal('show'); 2727 }); 2728 }); 2729 } 2730 } 2731 ); 2732 }; 2733 startProcessing($dialog, function() { 2734 processFunc(); 2735 }); 2736 }, 2737 2738 processUserJoin: function($dialog, COMPANY_INFO, okCallback) { 2739 var errorMsg = ''; 2740 $dialog.find('.result-error').html('').hide(); 2741 var $joinUserIDEl = $dialog.find('input[name="userJoinID"]'); 2742 var joinUserID = $joinUserIDEl.val().trim(); 2743 if (joinUserID === '') { 2744 errorMsg = _STR.CL_USERGROUP_USERS_EMPTY_USERID; 2745 } else if (!/^[a-zA-Z0-9_\-@\.\+]+$/.test(joinUserID)) { 2746 errorMsg = _STR.CL_USERGROUP_USERS_INVALID_USERID; 2747 } 2748 if (errorMsg) { 2749 $dialog.find('.result-error').html(errorMsg).show(); 2750 Utils.indicateError($joinUserIDEl); 2751 return false;
2752 } 2753 var processFunc = function() { 2754 RFMgr.CompanyAddUser(joinUserID, COMPANY_INFO.companyId, 2755 // ************ OK (AnswerCallBack) ************ 2756 function () { 2757 stopProcessing($dialog, function(){ 2758 if (typeof okCallback === 'function') { 2759 okCallback(); 2760 } 2761 }); 2762 }, 2763 // *********** ERROR (ErrorCallBack) *********** 2764 function (errObject) { 2765 if (errObject.Code && errObject.Code === 401) { 2766 //access denied error (session expired) 2767 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2768 } else { 2769 var errorMsg = Utils.getErrorDescription(errObject); 2770 if (errObject.Code && errObject.Code === 403) { 2771 var errReason = errObject.HttpRequest.getResponseHeader("x-sib-reason"); 2772 if (errReason && errReason === 'all-company-licenses-used') { 2773 errorMsg = _STR.CL_USERGROUP_USERS_ERROR_REACH_LICENSE_LIMIT; 2774 } else if (errReason && errReason === 'company-license-expired') { 2775 errorMsg = Utils.xReplace(_STR.CL_USERGROUP_USERS_ERROR_LICENSE_EXPIRED, COMPANY_INFO.name); 2776 } else if (errReason && errReason === 'user-not-found') { 2777 errorMsg = joinUserID.match(/@/) ? _STR.CL_USERGROUP_USERS_EMAIL_NOTFOUND : _STR.CL_USERGROUP_USERS_ID_NOTFOUND; 2778 } else if (errReason && errReason === 'already-joined') { 2779 errorMsg = Utils.xReplace(_STR.ERR_ACC_ALREADY_JOINED, joinUserID); 2780 } else if (errReason && errReason === 'one-company-limit') { 2781 errorMsg = _STR.CL_USERGROUP_USERS_MULTIPLE_COMPANIES; 2782 } else if (errReason && errReason === 'redirect') { 2783 var serverID = Utils.detectServerID(errObject.HttpRequest.getResponseHeader("Location")); 2784 var helpCenterURL = $('._help_center').attr('href'); 2785 if (helpCenterURL) { 2786 errorMsg = serverID 2787 ? Utils.xReplace(_STR.CL_USERGROUP_USERS_ERROR_ANOTHER_SERVER, serverID, 'target="_blank" href="' + helpCenterURL + '"') 2788 : Utils.xReplace(_STR.CL_USERGROUP_USERS_ERROR_ANOTHER_SERVER1, 'target="_blank" href="' + helpCenterURL + '"'); 2789 } else { 2790 errorMsg = serverID 2791 ? Utils.xReplace(Utils.stripTags(_STR.CL_USERGROUP_USERS_ERROR_ANOTHER_SERVER), serverID) 2792 : Utils.stripTags(_STR.CL_USERGROUP_USERS_ERROR_ANOTHER_SERVER1); 2793 } 2794 } 2795 } 2796 stopProcessing($dialog, function(){ 2797 showResultMsg(errorMsg, true, false, function(){ 2798 $dialog.modal('show'); 2799 }); 2800 }); 2801 } 2802 } 2803 ); 2804 }; 2805 startProcessing($dialog, function() { 2806 processFunc(); 2807 }); 2808 }, 2809 2810 includeIntoGroups: function(items, okCallback, errorCallback, progressCallback) { 2811 var promises = []; 2812 var aTotalProgress = {}; 2813 var oLastError; 2814 var aErrors = []; 2815 var itemsLength = Object.keys(items).length; 2816 var batch = Utils.createBatchOperations(); 2817 $.each(items, function(accountId, recipients){ 2818 var DEF = $.Deferred(); 2819 promises.push(DEF.promise()); 2820 batch.run(function(){ 2821 RFMgr.GrantAccount(undefined, accountId, undefined, undefined, undefined, undefined, 2822 function (reports) {
2823 reports.forEach(function(report){ 2824 if (report['error']) { 2825 oLastError = report['error']; 2826 report['group'] = accountId; 2827 aErrors.push(report); 2828 } 2829 }); 2830 DEF.resolve(); 2831 batch.done(); 2832 }, 2833 function (errObject) { 2834 if (errObject.Code && errObject.Code === 401) { 2835 //access denied error (session expired) 2836 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2837 } else { 2838 oLastError = errObject; 2839 DEF.reject(); 2840 } 2841 batch.done(); 2842 }, 2843 undefined, undefined, undefined, {recipients: recipients}, 2844 function(n) { 2845 if (typeof progressCallback !== 'undefined') { 2846 var nTotalProgress = 0; 2847 aTotalProgress[accountId] = n; 2848 $.each(aTotalProgress, function(i, progress){ 2849 nTotalProgress += progress; 2850 }); 2851 progressCallback(nTotalProgress / itemsLength); 2852 } 2853 } 2854 ); 2855 }); 2856 }); 2857 $.when.apply($, promises).done(function() { 2858 if (oLastError && typeof errorCallback !== 'undefined') { 2859 errorCallback(oLastError, aErrors); 2860 } else if (typeof okCallback !== 'undefined') { 2861 okCallback(); 2862 } 2863 }); 2864 $.when.apply($, promises).fail(function() { 2865 if (typeof errorCallback !== 'undefined') { 2866 errorCallback(oLastError, aErrors); 2867 } 2868 }); 2869 }, 2870 2871 processCompanyLogoChange: function($fileInput, okCallback, errorCallback) { 2872 // Check for the various File API support. 2873 if (!window.File && window.FileReader && window.FileList) { 2874 showResultMsg('The File APIs are not fully supported in this browser.', true); 2875 return false; 2876 } 2877 var $logoPreview = $fileInput.closest('form').find('.companyLogoPreview'); 2878 var $clearLogo = $fileInput.closest('form').find('input[name="clearLogo"]'); 2879 var logoLimitMb = $fileInput.attr('data-limit'); 2880 var file = $fileInput[0].files[0]; 2881 var errorHandler = function(errMsg) { 2882 $logoPreview.addClass('hidden'); 2883 $clearLogo.prop('checked', true); 2884 showResultMsg(errMsg, true); 2885 if (typeof errorCallback === 'function') { 2886 errorCallback(); 2887 } 2888 return false; 2889 }; 2890 if (file) { 2891 if (!Utils.in_array(file.type, ['image/jpg', 'image/jpeg', 'image/png', 'image/bmp', 'image/gif'])) { 2892 return errorHandler(_STR.CL_USERGROUP_EDITCOMPANY_BAD_IMAGE); 2893 } 2894 if (file.size > logoLimitMb * 1024 * 1024) { 2895 return errorHandler(Utils.xReplace(_STR.CL_USERGROUP_EDITCOMPANY_BAD_IMAGE_SIZE, logoLimitMb)); 2896 } 2897 var reader = new FileReader(); 2898 reader.onloadend = function(){ 2899 var sLogoBase64 = reader.result; 2900 $('<img/>').on('load', function(){ 2901 $logoPreview.removeClass('hidden').addClass('_changed').find('img').attr({src: sLogoBase64, width: this.naturalWidth, height: this.naturalHeight}); 2902 $clearLogo.prop('checked', false); 2903 if (typeof okCallback === 'function') { 2904 okCallback(); 2905 } 2906 }).on('error', function(){ 2907 errorHandler(_STR.CL_USERGROUP_EDITCOMPANY_BAD_IMAGE); 2908 }).attr('src', sLogoBase64); 2909 }; 2910 reader.onerror = function(){ 2911 errorHandler(_STR.CL_USERGROUP_EDITCOMPANY_BAD_IMAGE); 2912 };
2913 reader.readAsDataURL(file); 2914 } 2915 }, 2916 2917 processUpdateCompanyProperties: function(companyId, sCompanyName, sCompanyURL, sLogo, okCallback) { 2918 RFMgr.UpdateCompanyProperties( 2919 { 2920 companyId: companyId, 2921 name: sCompanyName, 2922 url: sCompanyURL, 2923 logo: sLogo 2924 }, 2925 function(){ 2926 showResultMsg(_STR.CL_USERGROUP_EDITCOMPANY_SUCCESS, false, document.location.href); 2927 if (typeof okCallback === 'function') { 2928 okCallback(); 2929 } 2930 }, 2931 function (errObject) { 2932 if (errObject.Code && errObject.Code === 401) { 2933 // access denied error (session expired) 2934 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2935 } else { 2936 showResultMsg(_STR.CL_USERGROUP_EDITCOMPANY_ERROR + ':<br>' + Utils.getErrorDescription(errObject), true); 2937 } 2938 } 2939 ); 2940 }, 2941 2942 typeaheadMemberList: function($el, memberType, bAddCurrentAdmin, onInput, filterFunction) { 2943 var lastQuery, lastUserList; 2944 // create autocomplete combobox 2945 $el.typeahead('destroy').typeahead({ 2946 source: function(query, process) { 2947 var processList = function(items) { 2948 var userList = []; 2949 items.forEach(function(userData) { 2950 if ( 2951 userData.name === RFO.ENT_ADSYNC_USER || 2952 memberType === 'member' && !userData.company || 2953 memberType === 'manager' && !(userData.isAdmin || userData.isManager) || 2954 memberType === 'admin' && !userData.isAdmin || 2955 typeof filterFunction === 'function' && !filterFunction(userData) 2956 ) { 2957 return; 2958 } 2959 userList.push({name: userData.name, email: userData.email, isAdmin: userData.isAdmin}); 2960 }); 2961 if (bAddCurrentAdmin && ~((RFO.ACCOUNT_INFO.name || '') + ' ' + RFO.ACCOUNT_INFO.email).toLowerCase().indexOf(query)) { 2962 userList.push({name: RFO.ACCOUNT_INFO.name || '', email: RFO.ACCOUNT_INFO.email, isAdmin: true}); 2963 } 2964 process(userList); 2965 }; 2966 if (lastQuery === query) { 2967 processList(lastUserList); 2968 return; 2969 } 2970 var sortParams = '-isAdmin'; 2971 if (memberType === 'manager') { 2972 sortParams += ',-isManager'; 2973 } else if (memberType === 'member') { 2974 sortParams += ',-company'; 2975 } 2976 RFMgr.GetUserKnownRecipients({filter: '%'+query+'%', sort: sortParams}, 2977 function(data) { 2978 lastQuery = query; 2979 lastUserList = data.recipients; 2980 processList(lastUserList); 2981 }, 2982 function(errObject) { 2983 if (errObject.Code && errObject.Code === 401) { 2984 // access denied error (session expired) 2985 doLogout(RFO.ROOT_PATH + 'login?relogin'); 2986 } 2987 } 2988 ); 2989 }, 2990 menuClass: 'typeahead-users', 2991 fitToElement: true, 2992 displayText: function(item) { 2993 if (item.selectedText) { 2994 return item.selectedText; 2995 } 2996 if (item.name) { 2997 // space between tags is needed to properly convert html -> text 2998 return '<div>'+Utils.escapeHtml(item.name)+'</div> <em>'+Utils.escapeHtml(item.email)+'</em>'; 2999 } else { 3000 return '<div>'+Utils.escapeHtml(item.email)+'</div>'; 3001 } 3002 }, 3003 updater: function(item) { 3004 return {selectedText: item.email}; 3005 }, 3006 sorter: function(items) { 3007 // response from server already sorted by name+email 3008 return items; 3009 }, 3010 onInput: onInput, 3011 showHintOnFocus: true, 3012 selectOnBlur: false, 3013 items: 'all' 3014 }).addClass('_typeahead'); 3015 }, 3016 3017 detectServerID: function(url) { 3018 var serverID = ''; 3019 url += '/'; 3020 if (RFO.RFO_SERVER_URLS && ~url.indexOf(RFO.RFO_SERVER_URLS.main || '')) { 3021 serverID = 'US'; 3022 } else if (RFO.RFO_SERVER_URLS && ~url.indexOf(RFO.RFO_SERVER_URLS.add_eu || '')) { 3023 serverID = 'EU'; 3024 } 3025 return serverID; 3026 }, 3027 3028 userFormatDate: function(timestamp, bWithTime) { 3029 if (!timestamp) { 3030 return ''; 3031 } 3032 var formattedDT = ''; 3033 var d = new Date(timestamp * 1000); 3034 var curOffset = d.getTimezoneOffset(); 3035 if (RFO.DATE_FORMATTER_SETTINGS && RFO.DATE_FORMATTER_SETTINGS.offsetMinutes !== -1*curOffset) { 3036 // convert to msec, add local time zone offset, get UTC time in msec 3037 var utc = d.getTime() + (curOffset * 60000); 3038 // create new Date object using supplied offset 3039 d = new Date(utc + (60000 * RFO.DATE_FORMATTER_SETTINGS.offsetMinutes)); 3040 } 3041 if (RFO.DATE_FORMATTER_SETTINGS && RFO.DATE_FORMATTER_SETTINGS.format && Utils.in_array(RFO.DATE_FORMATTER_SETTINGS.format, RFO.DATE_FORMATTER_SETTINGS.customFormats)) { 3042 var addZero = function(n) { 3043 return (n < 10 ? '0' : '') + n; 3044 }; 3045 formattedDT = RFO.DATE_FORMATTER_SETTINGS.format.replace('yyyy', d.getFullYear()).replace('MM', addZero(d.getMonth() + 1)).replace('dd', addZero(d.getDate())); 3046 } else { 3047 formattedDT = d.toLocaleDateString().replace(/\u200E/g, ''); 3048 } 3049 // 2019-Jul-05 Igor: use .replace(/\u200E/g, '') to fix bug in IE11 3050 // see: https://stackoverflow.com/questions/21413757/tolocaledatestring-changes-in-ie11#comment52437188_21413757 3051 return formattedDT + (bWithTime ? ' ' + d.toLocaleTimeString().replace(/\u200E/g, '')
3051: ''); 3052 }, 3053 3054 getCompanyInfoByID: function(companies, id){ 3055 var companyInfo = {}; 3056 var companies = companies || []; 3057 $.each(companies, function(i, company){ 3058 if (company.companyId === id) { 3059 companyInfo = company; 3060 return false; 3061 } 3062 }); 3063 return companyInfo; 3064 }, 3065 3066 // convert number of seconds to the format hh:mm:ss 3067 secondsToHms: function(d){ 3068 d = Number(d); 3069 var h = Math.floor(d / 3600); 3070 var m = Math.floor(d % 3600 / 60); 3071 var s = Math.floor(d % 3600 % 60); 3072 return ('0' + h).slice(-2) + ':' + ('0' + m).slice(-2) + ':' + ('0' + s).slice(-2); 3073 }, 3074 3075 // converts YYYY-MM-DD 00:00:00 to YYYY-MM-DD; 3076 // else drops seconds YYYY-MM-DD HH:MM, and marks them to display in smaller size. 3077 dateZeroTime: function(nTimestamp) { 3078 if (!nTimestamp) { 3079 return ''; 3080 } 3081 var d = new Date(nTimestamp * 1000); 3082 var date = 'Y-m-d'.replace('Y', d.getFullYear()).replace('m', ('0'+(d.getMonth()+1)).slice(-2)).replace('d', ('0'+d.getDate()).slice(-2)); 3083 if (d.getHours() + d.getMinutes() + d.getSeconds() !== 0) { 3084 date += '<span class="ztime">' + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2) + '</span>'; 3085 } 3086 return date; 3087 }, 3088 3089 getFileIconName: function(nFileType) { 3090 var icon; 3091 switch (nFileType) { 3092 case RfapiJS.EnRfDataItemType.RfItemType_Login : icon = 'icon_rfp.png'; break; 3093 case RfapiJS.EnRfDataItemType.RfItemType_Bookmark : icon = 'icon_rfb.png'; break; 3094 case RfapiJS.EnRfDataItemType.RfItemType_SearchCard : icon = 'icon_rfq.png'; break; 3095 case RfapiJS.EnRfDataItemType.RfItemType_BlockingPasscard : icon = 'icon_rfx.png'; break; 3096 case RfapiJS.EnRfDataItemType.RfItemType_Identity : icon = 'icon_rft.png'; break; 3097 case RfapiJS.EnRfDataItemType.RfItemType_Contact : icon = 'icon_rfc.png'; break; 3098 case RfapiJS.EnRfDataItemType.RfItemType_Safenote : icon = 'icon_rfn.png'; break; 3099 case RfapiJS.EnRfDataItemType.RfItemType_Folder : icon = 'icon_folder.png'; break; 3100 default: icon = 'icon_unknown.png'; 3101 } 3102 return icon; 3103 }, 3104 3105 createRFObjectPreview: function(data, $tpl, fieldsLimit) { 3106 var rf_object = Utils.parseJSON(data.rf_object); 3107 3108 // filter wrong json objects 3109 if (rf_object === false) { 3110 return ''; 3111 } 3112 3113 var objType = Utils.getRFType(data.path); 3114 3115 // filter blocking passcards 3116 if (objType === RfapiJS.EnRfDataItemType.RfItemType_BlockingPasscard) { 3117 return ''; 3118 } 3119 3120 var prop = Utils.RFObjectProp; 3121 3122 var $item = $tpl.find('._itemTpl').clone(); 3123 var $itemContent = $item.find('.itemContent'); 3124 $item.find('.itemIcon').addClass('icon-' + objType); 3125 $item.find('.itemName').text(Utils.getRFName(data.path)); 3126 $item.find('._download').attr('data-path', data.path); 3127 3128 switch (objType) { 3129 case RfapiJS.EnRfDataItemType.RfItemType_Login: 3130 case RfapiJS.EnRfDataItemType.RfItemType_SearchCard: 3131 case RfapiJS.EnRfDataItemType.RfItemType_Bookmark: 3132 // process GoToURL 3133 if (rf_object[prop.GOTOURL] !== '') { 3134 $('<div>').addClass('gotoURL').text(rf_object[prop.GOTOURL]).appendTo($itemContent); 3135 } 3136 // process other fields 3137 if (rf_object[prop.FIELDS]) { 3138 for (var i = 0; i < rf_object[prop.FIELDS].length && (!fieldsLimit || (i < fieldsLimit)); i++) { 3139 var field = rf_object[prop.FIELDS][i], 3140 type = parseInt(field[prop.TYPE] || 0, 10), 3141 name = field[prop.NAME], 3142 value = field[prop.VALUE] || ''; 3143 if (value === '' && fieldsLimit) { 3144 fieldsLimit++; 3145 continue; 3146 } 3147 if (!Utils.in_array(name, ['Note$', '$Note$'])) { 3148 if (type === 2 || (type === 0 && name === 'Password$')) { 3149 var stars = value.replace(/./g, Utils.passwordStarChar); 3150 var $field = $tpl.find('._passwordTpl').clone().removeClass('_passwordTpl'); 3151 $field.attr('title', name).find('.pwd').attr('data-value', value).html(stars); 3152 $field.appendTo($itemContent); 3153 } else { 3154 $('<div>').attr('title', name).text(value).appendTo($itemContent); 3155 } 3156 } 3157 } 3158 } 3159 break; 3160 case RfapiJS.EnRfDataItemType.RfItemType_Identity: 3161 case RfapiJS.EnRfDataItemType.RfItemType_Contact: 3162 if (rf_object[prop.GROUPS] && rf_object[prop.GROUPS].length && rf_object[prop.GROUPS][0][prop.INSTANCES] && rf_object[prop.GROUPS][0][prop.INSTANCES].length) { 3163 var group = rf_object[prop.GROUPS][0]; 3164 var instance = group[prop.INSTANCES][0]; 3165 if (group[prop.NAME] !== 'Custom') { 3166 for (var k = 0; k < instance[prop.FIELDS].length && k < 4; k++) { 3167 $('<div>').attr('title', instance[prop.FIELDS][k][prop.NAME]).text(instance[prop.FIELDS][k][prop.VALUE]).appendTo($itemContent); 3168 } 3169 } 3170 } 3171 break; 3172 case RfapiJS.EnRfDataItemType.RfItemType_Safenote: 3173 var text = rf_object[prop.NOTE] || ''; 3174 $('<div class="safenote">').text(text).appendTo($itemContent); 3175 break; 3176 } 3177 3178 return $item.html(); 3179 }, 3180 3181 suspendRestoreUsers: function (action, items, okCallback, errorCallback, progressCallback, company) { 3182 var promises = []; 3183 var sError = ''; 3184 var nCount = 0; 3185 var batch = Utils.createBatchOperations(); 3186 $.each(items, function(i, item){ 3187 var DEF = $.Deferred(); 3188 promises.push(DEF.promise()); 3189 batch.run(function(){ 3190 RFMgr.SetAccountInfo( 3191 function () { 3192 if (typeof progressCallback !== 'undefined') { 3193 nCount++; 3194 progressCallback(Math.round(nCount / items.length * 100)); 3195 } 3196 DEF.resolve(); 3197 batch.done(); 3198 }, 3199 function (errObject) { 3200 if (errObject.Code && errObject.Code === 401) { 3201 //access denied error (session expired) 3202 doLogout(RFO.ROOT_PATH + 'login?relogin'); 3203 } else { 3204 sError = Utils.getErrorDescription(errObject); 3205 DEF.reject(); 3206 } 3207 batch.done(); 3208 }, 3209 item, undefined, undefined, undefined, (action === 'suspend'), 3210 undefined, undefined, undefined, undefined, c
3210ompany 3211 ); 3212 }); 3213 }); 3214 $.when.apply($, promises).done(function() { 3215 if (typeof okCallback !== 'undefined') { 3216 okCallback(); 3217 } 3218 }); 3219 $.when.apply($, promises).fail(function() { 3220 if (typeof errorCallback !== 'undefined') { 3221 errorCallback(sError); 3222 } 3223 }); 3224 }, 3225 3226 arrayToCSV: function(data) { 3227 var result = ''; 3228 var columnDelimiter = RFO.CSV_LIST_SEPARATOR || ','; 3229 var lineDelimiter = '\n'; 3230 (data || []).forEach(function(item) { 3231 result += item.map(function(val) { 3232 // Avoid CSV Injection in Logs: fields that begin with any of the above symbols [=, -, +, @] 3233 // should be prepended by a single quote or apostrophe (') character. 3234 // Microsoft Excel will preserve data integrity by hiding this character when rendering the spreadsheet. 3235 // So e.g. name =A2+B2 should be exported as '=A2+B2 3236 if (~"=-+@'".indexOf(val[0])) { 3237 val = "'" + val; 3238 } 3239 return '"' + (val+'').replace(/"/g, '""') + '"'; 3240 }).join(columnDelimiter) + lineDelimiter; 3241 }); 3242 // need to add UTF-8 BOM for correct display csv in excel 3243 // see: https://technet.microsoft.com/en-us/2yfce773(v=vs.118) 3244 var universalBOM = "\uFEFF"; 3245 return universalBOM + result; 3246 }, 3247 3248 downloadFile: function(content, fileName, mimeType) { 3249 var a = document.createElement('a'); 3250 mimeType = mimeType || 'application/octet-stream'; 3251 if (navigator.msSaveBlob) { // IE10+ 3252 navigator.msSaveBlob(new Blob([content], { 3253 type: mimeType 3254 }), fileName); 3255 } else if (URL && 'download' in a) { //html5 A[download] 3256 a.href = URL.createObjectURL(new Blob([content], { 3257 type: mimeType 3258 })); 3259 a.setAttribute('download', fileName); 3260 document.body.appendChild(a); 3261 a.click(); 3262 document.body.removeChild(a); 3263 } else { 3264 location.href = 'data:application/octet-stream,' + encodeURIComponent(content); // only this mime type is supported 3265 } 3266 }, 3267 3268 downloadFileCSV: function(content, fileName) { 3269 Utils.downloadFile(content, fileName, 'text/csv;encoding:utf-8'); 3270 }, 3271 3272 createBatchOperations: function() { 3273 var nProcessing = 0; 3274 return { 3275 run: function(processingFunction) { 3276 var process = function(){ 3277 if (nProcessing > 100) { // allow no more than 100 simultaneous operations. 3278 // do not run the next operation, instead delay for N ms. 3279 setTimeout(process, 10); 3280 return; 3281 } 3282 nProcessing++; 3283 processingFunction(); 3284 }; 3285 process(); 3286 }, 3287 done: function() { 3288 nProcessing--; 3289 } 3290 }; 3291 }, 3292 3293 createBatchOperationsSequential: function() { 3294 var batches = []; 3295 var bStarted = false; 3296 return { 3297 run: function(processingFunction) { 3298 if (bStarted) { 3299 batches.push(processingFunction); 3300 } else {
3301 bStarted = true; 3302 processingFunction(); 3303 } 3304 }, 3305 done: function() { 3306 if (batches.length) { 3307 var processingFunction = batches.shift(); 3308 processingFunction(); 3309 } 3310 } 3311 }; 3312 }, 3313 3314 processChangeEmailPhone: function(params, okCallback, errorCallback) { 3315 var email = params.email; 3316 var phone = params.phone; 3317 if (!email && !phone) { 3318 return; 3319 } 3320 var $verifyEmailAndRedirect = $('#verifyEmailAndRedirect'); 3321 $('#confirm_otp_widget').one('show.bs.modal', function(){ 3322 var $dialog = $(this); 3323 var errFuncCallback = function (errObject) { 3324 if (errObject.Code && errObject.Code === 401) { 3325 // access denied error (session expired) 3326 doLogout(RFO.ROOT_PATH + 'login?relogin'); 3327 } else { 3328 stopProcessing($dialog, function(){ 3329 errorCallback(errObject); 3330 }); 3331 } 3332 }; 3333 var sendCodeOkCallback = function(){ 3334 $dialog.find('.modal-title').text(email ? _STR.TEXT_CONFIRM_TITLE_EMAIL : _STR.TEXT_CONFIRM_TITLE_PHONE); 3335 $dialog.find('._code_sent').html( 3336 email 3337 ? Utils.xReplace(_STR.TEXT_CONFIRM_CODE_SENT_EMAIL, email) 3338 : Utils.xReplace(_STR.TEXT_CONFIRM_CODE_SENT_PHONE, Utils.formatPhoneNumber(phone)) 3339 ); 3340 $dialog.find('._confirm_text').html(email ? _STR.TEXT_ENTER_CONFIRM_CODE_EMAIL : _STR.TEXT_ENTER_CONFIRM_CODE_PHONE); 3341 var $verifyCode = $dialog.find('input[name="otp_confirm"]').val(''); 3342 var $error = $dialog.find('._error').text('').hide(); 3343 $dialog.find('.has-error').removeClass('has-error'); 3344 $dialog.find('.yes').off('click').on('click', function(){ 3345 $error.text('').hide(); 3346 $dialog.find('.has-error').removeClass('has-error'); 3347 var verifyCode = $verifyCode.val().trim(); 3348 if (verifyCode === '') { 3349 Utils.indicateError($verifyCode); 3350 return false; 3351 }
3352 setAccountInfo(verifyCode); 3353 }); 3354 $dialog.find('._autofocus').trigger('focus'); 3355 }; 3356 var sendCodeErrorCallback = function (errObject) { 3357 if (email && errObject.Code === 500 && Utils.stripTags(errObject.Description).match(/421|450|451/)) { 3358 stopProcessing($dialog, function(){ 3359 sendCodeOkCallback(); 3360 $dialog.find('._error').text(_STR.TEXT_CONFIRM_CODE_SEND_LATER).show(); 3361 }, true); 3362 } else if (errObject.Code === 503 && 3363 errObject.HttpRequest && 3364 errObject.HttpRequest.getResponseHeader('retry-after') && 3365 errObject.HttpRequest.getResponseHeader('reason') 3366 ) { 3367 stopProcessing($dialog, function(){ 3368 showResultMsg(errObject.HttpRequest.getResponseHeader('reason'), true); 3369 }); 3370 } else { 3371 errFuncCallback(errObject); 3372 } 3373 }; 3374 var setAccountInfo = function(verifyCode){ 3375 startProcessing($dialog); 3376 RFMgr.SetAccountInfo( 3377 function(){ 3378 var successFunc = function(){ 3379 stopProcessing($dialog, function(){ 3380 if ($verifyEmailAndRedirect.length) { 3381 document.location.href += '&verifyEmailAndRedirectDone=1'; 3382 } else { 3383 okCallback(); 3384 } 3385 }); 3386 }; 3387 if (params.oldEmail && params.oldEmail !== email) { 3388 var requestParams = { 3389 afterChangeEmail: 1, 3390 oldEmail: params.oldEmail, 3391 aid: RFO.AID 3392 }; 3393 $.post(RFO.DIR_REQUEST+'accountChange.php', requestParams, function(){ 3394 successFunc(); 3395 }).fail(function() { 3396 successFunc(); 3397 }); 3398 } else { 3399 successFunc(); 3400 } 3401 }, 3402 function(errObject){ 3403 if (errObject.Code === 403 && errObject.HttpRequest.getResponseHeader("x-sib-reason") === 'expired-code') { 3404 RFMgr.SendVerificationCode(email ? {email: email} : {phone: phone}, 3405 function () { 3406 stopProcessing($dialog, function(){ 3407 $dialog.find('._error').text(_STR.TEXT_CONFIRM_CODE_EXPIRED).show(); 3408 }, true); 3409 }, 3410 sendCodeErrorCallback 3411 ); 3412 } else { 3413 stopProcessing($dialog, function(){ 3414 $dialog.find('._error').text(Utils.stripTags(errObject.Description)).show(); 3415 }, true); 3416 } 3417 }, 3418 RFO.LOGGED_ACCOUNT_ID, 3419 undefined/*block*/, undefined/*unblock*/, 3420 phone === '-' && RFO.ACCOUNT_INFO['altAuth'] === 'sms' ? 'email' : undefined/*otpOn*/, 3421 undefined/*suspend*/, undefined/*security_score*/, undefined/*logins_count*/, undefined/*totpKey*/, undefined/*totp*/, undefined/*company*/, undefined/*name*/, 3422 email ? email : undefined/*email*/, 3423 undefined/*security_stats*/, undefined/*enroll_device*/, undefined/*unenroll_devices*/, 3424 phone ? (phone === '-' ? '' : phone) : undefined/*phone*/, 3425 verifyCode/*verificationCode*/ 3426 ); 3427 }; 3428 // the case when the user removes the phone 3429 if (phone === '-') {
3430 setAccountInfo(); 3431 return; 3432 } 3433 $dialog.find('.modal-title').text(email ? _STR.TEXT_VERIFY_SENDING_TITLE_EMAIL : _STR.TEXT_VERIFY_SENDING_TITLE_PHONE); 3434 startProcessing($dialog); 3435 RFMgr.SendVerificationCode(email ? {email: email} : {phone: phone}, 3436 function () { 3437 stopProcessing($dialog, function(){ 3438 sendCodeOkCallback(); 3439 }, true); 3440 }, 3441 sendCodeErrorCallback 3442 ); 3443 }).one('hide.bs.modal', function(){ 3444 $verifyEmailAndRedirect.remove(); 3445 }).modal('show'); 3446 }, 3447 3448 browserName: function() { 3449 if (navigator.userAgent.match(/\bAndroid/)) { 3450 return 'Android'; 3451 } 3452 if (navigator.userAgent.match(/\b(iPhone|iPad)/) && navigator.userAgent.match(/\bMobile\//)) { 3453 return 'iOS'; 3454 } 3455 3456 var match = navigator.userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*\d+/i) || []; 3457 var temp; 3458 if (/trident/i.test(match[1])) { 3459 return 'Internet Explorer'; 3460 } 3461 if (match[1] === 'Chrome') { 3462 temp = navigator.userAgent.match(/\b(OPR|Edge)\/\d+/); 3463 if (temp !== null) { 3464 return 'Opera'; 3465 } 3466 temp = navigator.userAgent.match(/\b(Edg)\/\d+/); 3467 if (temp !== null) { 3468 return 'Edge'; 3469 } 3470 return 'Chrome'; 3471 } 3472 return match[1] || ''; 3473 }, 3474 3475 isStatusInactive: function(timestamp) { 3476 // older than 90 days 3477 return RFO.CUSTOM_VARS && RFO.CUSTOM_VARS.useInactiveStatus && timestamp && (timestamp + 90*24*60*60)*1000 < Date.now(); 3478 }, 3479 3480 emailVerifyBeforeAction: function(confirmMessage, afterAction) { 3481 Utils.confirm(confirmMessage, function(){ 3482 Utils.processChangeEmailPhone({email: RFO.ACCOUNT_INFO.email}, 3483 function(){ 3484 RFO.ACCOUNT_INFO.emailVerified = true; 3485 showResultMsg(_STR.TEXT_SUCCESS_VERIFY_EMAIL, false, undefined, afterAction, undefined, undefined, _STR.BUTTON_CONTINUE); 3486 }, 3487 RFMgrCommonErrorHandler.bind(this, null) 3488 ); 3489 }); 3490 }, 3491 3492 createJSONRequest: function(url, post, headers) { 3493 var request = $.Deferred(); 3494 $.ajax({ 3495 url: url, 3496 method: 'post', 3497 data: JSON.stringify(post || ''), 3498 headers: headers || {}, 3499 success: request.resolve, 3500 error: function (e) { 3501 var error = e.responseJSON && e.responseJSON.error || {}; 3502 var errorMessage = 'Error: ' 3503 + (e.status ? e.status + ' - ' : '') 3504 + (error.code ? '[' + error.code + '] - ' : '') 3505 + (error.message || e.statusText || _STR.ERR_UNEXPECTED); 3506 request.reject(errorMessage); 3507 } 3508 }); 3509 return request.promise(); 3510 }, 3511 3512 switchLoginMethodConfirmDialog: function(dialogHtml, type, canIgnoreSwitch, confirmClick, ignoreClick) { 3513 let dialogButtons = []; 3514 let confirmButton = { 3515 class: 'btn-success', 3516 click: confirmClick 3517 }; 3518 let ignoreButton = { 3519 class: 'btn-danger', 3520 click: ignoreClick 3521 }; 3522 if (canIgnoreSwitch) { 3523 confirmButton.text = Utils.xReplace( 3524 _STR.CL_SWITCH_LOGIN_METHOD_BTN_CONFIRM, 3525 type === 'sso' ? _STR.CL_SWITCH_LOGIN_METHOD_BTN_TEXT_SSO : _STR.CL_SWITCH_LOGIN_METHOD_BTN_TEXT_MP 3526 ); 3527 ignoreButton.text = Utils.xReplace( 3528 _STR.CL_SWITCH_LOGIN_METHOD_BTN_IGNORE, 3529 type === 'sso' ? _STR.CL_SWITCH_LOGIN_METHOD_BTN_TEXT_MP : _STR.CL_SWITCH_LOGIN_METHOD_BTN_TEXT_SSO 3530 ); 3531 dialogButtons = [confirmButton, ignoreButton]; 3532 } else { 3533 confirmButton.text = _STR.BUTTON_CONTINUE; 3534 dialogButtons = [confirmButton]; 3535 } 3536 Utils.confirmExt({ 3537 wide: canIgnoreSwitch, 3538 html: dialogHtml, 3539 buttons: dialogButtons 3540 }); 3541 }, 3542 3543 encryptAndSaveMasterPassword: function(passwd, key) {
3544 Utils.Storage.set('_rfo-passwd', Utils.encrypt(passwd, key || RFO.ACCOUNT_INFO.clientKey)); 3545 }, 3546 3547 restoreMasterPassword: function() { 3548 try { 3549 if (!RFO.ACCOUNT_INFO || !RFO.ACCOUNT_INFO.clientKey) { 3550 throw 'Password error #0'; 3551 } 3552 let passwd, passwdCase; 3553 let setPasswdData = function(case_id, pwd_encrypted, pwd_key, need_reencrypt) { 3554 if (!pwd_encrypted) { 3555 passwdCase = passwdCase || (case_id + 'e'); 3556 return; 3557 } 3558 passwdCase = passwdCase || case_id; 3559 passwd = Utils.decrypt(pwd_encrypted, pwd_key); 3560 if (passwd) { 3561 if (need_reencrypt) { 3562 Utils.encryptAndSaveMasterPassword(passwd); 3563 } 3564 RFMgr.setMasterPwd(passwd); 3565 RFMgr.setAccountPwd(passwd); 3566 } 3567 }; 3568 // transition from RF extension - use mpkey from session 3569 if (RFO.MPKEY && Utils.Storage.get('passwd')) { 3570 let passwdEncrypted = Utils.Storage.get('passwd'); 3571 Utils.Storage.remove('passwd'); 3572 setPasswdData(1, passwdEncrypted, RFO.MPKEY, true); 3573 } 3574 // transition from new RFO 3575 if (!passwd && sessionStorage['rfo-cred']) { 3576 let rfoCred = Utils.parseJSON(sessionStorage['rfo-cred']); 3577 let cred = rfoCred && rfoCred.value && Utils.parseJSON(rfoCred.value) || {}; 3578 let passwdEncrypted = cred && cred.password || ''; 3579 sessionStorage.removeItem('rfo-cred'); 3580 setPasswdData(2, passwdEncrypted, RFO.ACCOUNT_INFO.clientKey, true); 3581 } 3582 // regular login 3583 if (!passwd && Utils.Storage.get('_rfo-passwd')) { 3584 setPasswdData(3, Utils.Storage.get('_rfo-passwd'), RFO.ACCOUNT_INFO.clientKey, false); 3585 } 3586 if (!passwd) { 3587 if (passwdCase) { 3588 // encrypted password cannot be restored - show error message with the first checked passwd case 3589 throw 'Password error #' + passwdCase + ' for ' + RFO.ACCOUNT_INFO.email; 3590 } else { 3591 // no encrypted password found - just logout with session expired error 3592 doLogout(RFO.ROOT_PATH + 'login?relogin'); 3593 } 3594 return false; 3595 } 3596 } catch (e) { 3597 // show error and logout with session expired error 3598 showResultMsg(e, true, undefined, function(){ 3599 doLogout(RFO.ROOT_PATH + 'login?relogin'); 3600 }); 3601 return false; 3602 } 3603 return true; 3604 } 3605}; 3606 3607// This item used to set its value later in async function 3608Utils.AsyncItem = function(asyncFunc) { 3609 this.value = ''; 3610 setTimeout(asyncFunc, 0); 3611}; 3612Utils.AsyncItem.prototype.toString = function(){ 3613 return this.value; 3614}; 3615 3616//////////////////// 3617 3618function startProcessing($dialog, readyCallback) { 3619 if ($('#quickstartWidget').hasClass('in')) { // quickstartWidget is shown 3620 $('#waitDialog').find('.modal-body').addClass('processing').html('<div class="wait-loader">' + _STR.TEXT_PLEASE_WAIT + '</div>').show(); 3621 if (typeof readyCallback === 'function') { 3622 $('#waitDialog').one('shown.bs.modal', function() { 3623 setTimeout(function() { 3624 readyCallback(); 3625 }, 20); 3626 }); 3627 } 3628 $('#waitDialog').modal('show'); 3629 } else { 3630 $dialog.off('keydown.dismiss.bs.modal'); // disable escape key 3631 $dialog.data('bs.modal').options.backdrop = 'static'; 3632 $dialog.find('.close').hide(); 3633 $dialog.find('.modal-body, .modal-footer').hide(); 3634 $dialog.find('.modal-body.processing').show(); 3635 if (typeof readyCallback === 'function') { 3636 readyCallback(); 3637 } 3638 } 3639} 3640 3641function stopProcessing($dialog, endProcessCallback, bNotHide) { 3642 var processStop = function(){ 3643 $dialog.data('bs.modal').escape(); // reset keyboard 3644 $dialog.data('bs.modal').options.backdrop = true; 3645 $dialog.find('.close').show();
3646 $dialog.find('.modal-body, .modal-footer').show(); 3647 $dialog.find('.modal-body.processing, .modal-body.progress-wrapper').hide(); 3648 if (typeof endProcessCallback === 'function') { 3649 endProcessCallback(); 3650 } 3651 }; 3652 if ($('#quickstartWidget').hasClass('in')) { // quickstartWidget is shown 3653 if (typeof endProcessCallback === 'function') { 3654 $('#waitDialog').one('hidden.bs.modal', function(){ 3655 setTimeout(function() { 3656 endProcessCallback(); 3657 }, 20); 3658 }); 3659 } 3660 $('#waitDialog').modal('hide'); 3661 } else if (bNotHide) { 3662 processStop(); 3663 } else { 3664 $dialog.one('hidden.bs.modal', function(){ 3665 processStop(); 3666 }).modal('hide'); 3667 } 3668} 3669 3670function DialogProgress($dialog) { 3671 this.$progressWrapper = $dialog.find('.modal-body.progress-wrapper'); 3672 this.fProgress = 0.0; // float, 0 .. 100 3673 this.fStep = 0.0; 3674 this.fDisplayProgressPrev = -1; 3675 this.nPrecision = 0; 3676 this.parts = {}; 3677 3678 this.start = function() { 3679 $dialog.off('keydown.dismiss.bs.modal'); // disable escape key 3680 $dialog.data('bs.modal').options.backdrop = 'static'; 3681 $dialog.find('.close').hide(); 3682 $dialog.find('.modal-body, .modal-footer').hide(); 3683 this.$progressWrapper.show(); 3684 this.setProgress(0); // display progress bar with 0% 3685 }; 3686 this.stop = function(endProcessCallback) { 3687 $dialog.one('hidden.bs.modal', function(){ 3688 $dialog.data('bs.modal').escape(); // reset keyboard 3689 $dialog.data('bs.modal').options.backdrop = true; 3690 $dialog.find('.close').show(); 3691 $dialog.find('.modal-body, .modal-footer').show(); 3692 $dialog.find('.modal-body.processing, .modal-body.progress-wrapper').hide(); 3693 if (typeof endProcessCallback === 'function') { 3694 endProcessCallback(); 3695 } 3696 }).modal('hide'); 3697 }; 3698 this.setParts = function(nParts) { 3699 this.fStep = 100 / nParts; 3700 if (this.fStep < 0.01) { 3701 this.nPrecision = 2; 3702 } else if (this.fStep < 0.1) { 3703 this.nPrecision = 1; 3704 } 3705 }; 3706 this.setPartProgress = function(sPart, n) { 3707 if (this.fProgress < 100) { 3708 n = Math.min(100, n); 3709 var fDelta = this.parts[sPart] ? (n - this.parts[sPart])/100 : n/100; 3710 this.parts[sPart] = n; 3711 this.setProgress(this.fProgress + this.fStep * fDelta); 3712 } 3713 }; 3714 this.setProgress = function(progress) { 3715 this.fProgress = Math.min(100, progress); 3716 var sDisplayProgress = this.fProgress.toFixed(this.nPrecision); 3717 var fDisplayProgress = parseFloat(sDisplayProgress); 3718 if (this.fDisplayProgressPrev < fDisplayProgress) { 3719 this.fDisplayProgressPrev = fDisplayProgress; 3720 this.$progressWrapper.find('.progressbar-bg').css('width', sDisplayProgress + '%'); 3721 this.$progressWrapper.find('.progressbar-value').html(_STR.CL_PROCESSING + ' ' + sDisplayProgress + '%'); 3722 } 3723 }; 3724 3725 this.start(); 3726} 3727 3728var YoutubePlayer = function() { 3729 this.playerID = undefined; 3730 this.loadingIntervalID = undefined; 3731 this.loadingIntervalDelay = 300; 3732 this.loadAPI = function() {
3733 if (window.YTPlayers) { 3734 return; 3735 } 3736 $('body').append($('<script />', { 3737 src: 'https://www.youtube.com/iframe_api' 3738 })); 3739 window.YTPlayers = { 3740 'users': { 3741 player: undefined, 3742 videoID: 'ioTcdPYcRBo', 3743 isIframeLoaded: false 3744 }, 3745 'group': { 3746 player: undefined, 3747 videoID: 'Hq3q2FmT3pM', 3748 isIframeLoaded: false 3749 }, 3750 'files': { 3751 player: undefined, 3752 videoID: 'irLiimFHdNQ', 3753 isIframeLoaded: false 3754 }, 3755 'files_nofiles': { 3756 player: undefined, 3757 videoID: 'irLiimFHdNQ', 3758 isIframeLoaded: false 3759 }, 3760 'policies': { 3761 player: undefined, 3762 videoID: 'BiCpwenMSRE', 3763 isIframeLoaded: false 3764 }, 3765 'reports': { 3766 player: undefined, 3767 videoID: '4Dw7bxX1BRw', 3768 isIframeLoaded: false 3769 } 3770 }; 3771 }; 3772 this.loadPlayer = function(playerID) {
3773 if (!window.YTPlayers) { 3774 this.loadAPI(); 3775 } 3776 var $iframe = $('#youtubeiframe_' + playerID); 3777 if (!$iframe.is('[src]')) { // not have 'src' attribute 3778 $iframe.attr('src', 'https://www.youtube.com/embed/' + window.YTPlayers[playerID].videoID + '?enablejsapi=1&origin=' + window.location.origin); 3779 $iframe.on('load', function() { 3780 window.YTPlayers[playerID].isIframeLoaded = true; 3781 }); 3782 } 3783 if ( 3784 window.YT && window.YT.Player && 3785 window.YTPlayers[playerID].isIframeLoaded && 3786 !window.YTPlayers[playerID].player 3787 ) { 3788 window.YTPlayers[playerID].player = new YT.Player('youtubeiframe_' + playerID); 3789 } 3790 }; 3791 this.play = function(playerID, readyCallback) { 3792 this.pause(); 3793 this.playerID = playerID; 3794 var obj = this; 3795 var startPlaying = function() { 3796 var isReady = obj.isReady(playerID); 3797 if (isReady) { 3798 obj.clearInterval(); 3799 if (obj.playerID != playerID) { 3800 // YT.Player has not been loaded/created yet but user already cancelled video playback 3801 return isReady; 3802 } 3803 window.YTPlayers[playerID].player.playVideo(); 3804 if (typeof readyCallback === 'function') { 3805 readyCallback(window.YTPlayers[playerID].player); 3806 } 3807 } else { 3808 obj.loadPlayer(playerID); 3809 } 3810 return isReady; 3811 }; 3812 if (!startPlaying()) { 3813 this.loadingIntervalID = setInterval(function() { 3814 startPlaying(); 3815 }, this.loadingIntervalDelay); 3816 } 3817 }; 3818 this.pause = function() { 3819 if (this.playerID && this.isReady(this.playerID)) { 3820 window.YTPlayers[this.playerID].player.pauseVideo(); 3821 } 3822 this.playerID = undefined; 3823 this.clearInterval(); 3824 }; 3825 this.setToBegining = function(playerID) { 3826 if (this.isReady(playerID)) { 3827 window.YTPlayers[playerID].player.seekTo(0); 3828 window.YTPlayers[playerID].player.pauseVideo(); 3829 } 3830 }; 3831 this.isReady = function(playerID) { 3832 return ( 3833 window.YTPlayers && 3834 window.YTPlayers[playerID] && 3835 window.YTPlayers[playerID].player && 3836 window.YTPlayers[playerID].player.playVideo 3837 ); 3838 }; 3839 this.clearInterval = function() { 3840 if (this.loadingIntervalID !== undefined) { 3841 clearInterval(this.loadingIntervalID); 3842 this.loadingIntervalID = undefined; 3843 } 3844 }; 3845}; 3846 3847var AuthChecker = function() { 3848 var checkShortPeriod = 1*60*1000; // 1 minute 3849 var checkLongPeriod = 5*60*1000; // 5 minutes 3850 3851 var isVisible, shortInterval, longInterval; 3852 3853 // Set the name of the hidden property and the change event for visibility 3854 var hidden, visibilityChange; 3855 if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support 3856 hidden = 'hidden'; 3857 visibilityChange = "visibilitychange"; 3858 } else if (typeof document.msHidden !== 'undefined') { 3859 hidden = 'msHidden'; 3860 visibilityChange = 'msvisibilitychange'; 3861 } else if (typeof document.webkitHidden !== 'undefined') { 3862 hidden = 'webkitHidden'; 3863 visibilityChange = 'webkitvisibilitychange'; 3864 } 3865 3866 var checkStatus = function() { 3867 $.get('/rf-api?test', function() { 3868 // session is valid, do nothing 3869 }).fail(function(err) { 3870 if (err.status === 401) { 3871 // access denied error (session expired) 3872 doLogout(RFO.ROOT_PATH + 'login?relogin'); 3873 } 3874 }); 3875 }; 3876 3877 var restartCheckers = function() { 3878 if (typeof isVisible !== 'undefined') { 3879 // check session status immediately, except first time 3880 checkStatus(); 3881 } 3882 // this checker used for check if user clicked something on the page after {checkShortPeriod} or more seconds of active state 3883 var setShortChecker = function(){ 3884 clearInterval(shortInterval); 3885 shortInterval = setInterval(function(){ 3886 $(document).off('click.AuthChecker').one('click.AuthChecker', restartCheckers); 3887 clearInterval(shortInterval); 3888 }, checkShortPeriod); 3889 }; 3890 $(document).off('click.AuthChecker').on('click.AuthChecker', setShortChecker); 3891 setShortChecker(); 3892 // this checker used for check session status periodically every {checkLongPeriod} seconds while the page is active 3893 clearInterval(longInterval); 3894 longInterval = setInterval(restartCheckers, checkLongPeriod); 3895 }; 3896 3897 var onVisible = function() { 3898 if (!isVisible) { // prevent double execution 3899 restartCheckers(); 3900 isVisible = true; 3901 } 3902 }; 3903 3904 var onHidden = function() { 3905 if (isVisible) { // prevent double execution 3906 clearInterval(shortInterval); 3907 clearInterval(longInterval); 3908 isVisible = false;
3909 } 3910 }; 3911 3912 this.start = function() { 3913 if (typeof visibilityChange !== 'undefined') { 3914 $(document).off(visibilityChange).on(visibilityChange, function() { 3915 document[hidden] ? onHidden() : onVisible(); 3916 }); 3917 onVisible(); 3918 } 3919 }; 3920}; 3921 3922var PasswordChecker = (function() { 3923 3924 var PasswordProblems = { 3925 TooShort: 0, 3926 TooLong: 1, 3927 BlanksOnly: 2, 3928 FewNonNumChars: 3, 3929 FewUpperCaseChars: 4, 3930 FewLowerCaseChars: 5, 3931 FewDigitChars: 6 3932 }; 3933 3934 var m_breach_check_tasks = []; 3935 var m_last_checked_password = ''; 3936 var m_cached_compromised_passwords = []; 3937 3938 function _GetMasterPasswordRestrictions() { 3939 var restrictions = { 3940 minLength: RFO.PWD_MIN, 3941 maxLength: RFO.PWD_MAX, 3942 minNonNumChars: RFO.PWD_NONNUM_MIN, 3943 minUpperCaseChars: 0, 3944 minLowerCaseChars: 0, 3945 minDigitChars: 0 3946 }; 3947 if (RFO.POLICIES && RFO.POLICIES.CONFIG && RFO.POLICIES.CONFIG.MasterPasswordMinLength) { 3948 restrictions.minLength = RFO.POLICIES.CONFIG.MasterPasswordMinLength; 3949 } 3950 if (RFO.POLICIES && RFO.POLICIES.CONFIG && RFO.POLICIES.CONFIG.MasterPasswordMinNonNumChars) { 3951 restrictions.minNonNumChars = RFO.POLICIES.CONFIG.MasterPasswordMinNonNumChars; 3952 } 3953 if (RFO.POLICIES && RFO.POLICIES.CONFIG && RFO.POLICIES.CONFIG.MasterPasswordMinUpperCaseChars) { 3954 restrictions.minUpperCaseChars = RFO.POLICIES.CONFIG.MasterPasswordMinUpperCaseChars; 3955 } 3956 if (RFO.POLICIES && RFO.POLICIES.CONFIG && RFO.POLICIES.CONFIG.MasterPasswordMinLowerCaseChars) { 3957 restrictions.minLowerCaseChars = RFO.POLICIES.CONFIG.MasterPasswordMinLowerCaseChars; 3958 } 3959 if (RFO.POLICIES && RFO.POLICIES.CONFIG && RFO.POLICIES.CONFIG.MasterPasswordMinDigitChars) { 3960 restrictions.minDigitChars = RFO.POLICIES.CONFIG.MasterPasswordMinDigitChars; 3961 } 3962 return restrictions; 3963 } 3964 3965 function stopCheckingPasswordForBreachness() { 3966 m_breach_check_tasks.forEach(function(DEF){ 3967 DEF.reject(); 3968 }); 3969 m_breach_check_tasks = []; 3970 m_last_checked_password = ''; 3971 } 3972 3973 function checkPasswordForCompliance(password) { 3974 password = password || ''; 3975 var problems = []; 3976 var restrictions = _GetMasterPasswordRestrictions(); 3977 var password_length = Utils.countChars(password); 3978 3979 if (password_length < restrictions.minLength) { 3980 problems.push(PasswordProblems.TooShort); 3981 } 3982 if (password_length > restrictions.maxLength) { 3983 problems.push(PasswordProblems.TooLong); 3984 } 3985 if (!password.trim()) { 3986 problems.push(PasswordProblems.BlanksOnly); 3987 } 3988 // non-numeric characters also need to be counted and checked always 3989 var upper_case_chars = 0; 3990 var lower_case_chars = 0; 3991 var digit_chars = 0; 3992 Array.from(password).forEach(function(character) { 3993 if (/[0-9]/.test(character)) { 3994 digit_chars++; 3995 } else if (character.toLowerCase() !== character.toUpperCase()) { 3996 if (character === character.toUpperCase()) { 3997 upper_case_chars++; 3998 } else if (character === character.toLowerCase()) { 3999 lower_case_chars++; 4000 } 4001 } 4002 }); 4003 if ((password_length - digit_chars) < restrictions.minNonNumChars) { 4004 problems.push(PasswordProblems.FewNonNumChars); 4005 } 4006 if (upper_case_chars < restrictions.minUpperCaseChars) { 4007 problems.push(PasswordProblems.FewUpperCaseChars); 4008 } 4009 if (lower_case_chars < restrictions.minLowerCaseChars) { 4010 problems.push(PasswordProblems.FewLowerCaseChars); 4011 } 4012 if (digit_chars < restrictions.minDigitChars) { 4013 problems.push(PasswordProblems.FewDigitChars); 4014 } 4015 return {problems: problems, restrictions: restrictions}; 4016 } 4017 4018 return { 4019 PasswordProblems: PasswordProblems, 4020 4021 stopCheckingPasswordForBreachness: stopCheckingPasswordForBreachness, 4022 checkPasswordForCompliance: checkPasswordForCompliance, 4023 4024 lastCheckedPassword: function() { 4025 return m_last_checked_password; 4026 }, 4027 4028 isPasswordCompromised: function(password, callback) { 4029 stopCheckingPasswordForBreachness(); 4030 4031 var DEF = $.Deferred(); 4032 m_breach_check_tasks.push(DEF); 4033 DEF.done(function(result){ 4034 m_breach_check_tasks = []; 4035 callback(result); 4036 }).fail(function(){ 4037 m_breach_check_tasks = []; 4038 callback(null); 4039 }); 4040 4041 if (!password) { 4042 DEF.reject(); 4043 return; 4044 } 4045 4046 m_last_checked_password = password; 4047 4048 if (Utils.in_array(password, m_cached_compromised_passwords)) { 4049 DEF.resolve(true); 4050 } else { 4051 $.get('/etc', function(data) { 4052 if (!data.breachMonUrl) { 4053 DEF.reject(); 4054 return; 4055 } 4056 var password_utf8_bytes = RfapiJS.UTF8.stringToBytes(password); 4057 var pwd_hash_sha1_hex_lo = RfapiJS.utils.SHA1AsLowerHexStr(password_utf8_bytes); 4058 var hash_head = pwd_hash_sha1_hex_lo.substr(0, 5); 4059 var oReq = new XMLHttpRequest(); 4060 oReq.open('GET', data.breachMonUrl + '?p=' + hash_head, true); 4061 oReq.responseType = 'arraybuffer'; 4062 oReq.onload = function() { 4063 // process server response - blob with hashes of compromised passwords 4064 if (!oReq.response) { 4065 DEF.reject(); 4066 return; 4067 } 4068 var isPasswordCompromised = false;
4069 var hashes_blob = new Uint8Array(oReq.response); 4070 // not 5, because length must be even 4071 var hash_tail_bin = RfapiJS.utils.hexToBytes(pwd_hash_sha1_hex_lo.substr(4)); 4072 var record_len = hash_tail_bin.length; 4073 var last_pos = hashes_blob.length - record_len; 4074 for (var pos = 0; pos <= last_pos; pos += record_len) { 4075 if (((hash_tail_bin[0] ^ hashes_blob[pos]) & 0x0F) === 0) { 4076 if ( 4077 hash_tail_bin.every(function(v, i){ 4078 return (i === 0 || v === hashes_blob[pos + i]); 4079 }) 4080 ) { 4081 isPasswordCompromised = true; 4082 m_cached_compromised_passwords.push(password); 4083 break; 4084 } 4085 } 4086 } 4087 DEF.resolve(isPasswordCompromised); 4088 }; 4089 oReq.onerror = oReq.onabort = function() { 4090 DEF.reject(); 4091 }; 4092 oReq.send(); 4093 }).fail(function(){ 4094 DEF.reject(); 4095 }); 4096 } 4097 }, 4098 4099 updatePasswordQualityIndicator: function($input, $qualityIndicator, $problemsList) { 4100 var password = $input.val(); 4101 var checkResult = password ? checkPasswordForCompliance(password) : null; 4102 var passwordRestrictions = checkResult ? checkResult.restrictions : _GetMasterPasswordRestrictions(); 4103 4104 function _CreateProblemsListItem(text, problem) { 4105 var status = checkResult && checkResult.problems 4106 ? Utils.in_array(problem, checkResult.problems) 4107 ? 'failed' 4108 : 'passed' 4109 : 'initial'; 4110 return '<div class="item '+status+'"><div class="icon"></div><div class="text">'+text+'</div></div>'; 4111 } 4112 4113 if (checkResult && checkResult.problems.length) { 4114 $input.addClass('bad-value'); 4115 $qualityIndicator.addClass('have-problems'); 4116 } else { 4117 $input.removeClass('bad-value'); 4118 $qualityIndicator.removeClass('have-problems'); 4119 } 4120 $problemsList.children().remove(); 4121 if (!checkResult) { 4122 $problemsList.append('<div class="title">'+_STR.PWD_QUALITY_HINT_TITLE+'</div>'); 4123 } 4124 $problemsList.append(_CreateProblemsListItem( 4125 Utils.xReplace(_STR.PWD_QUALITY_HINT_MIN_LENGTH, passwordRestrictions.minLength), 4126 PasswordProblems.TooShort 4127 )); 4128 if (checkResult && checkResult.problems && Utils.in_array(PasswordProblems.TooLong, checkResult.problems)) { 4129 $problemsList.append(_CreateProblemsListItem( 4130 Utils.xReplace(_STR.PWD_QUALITY_HINT_MAX_LENGTH, passwordRestrictions.maxLength), 4131 PasswordProblems.TooLong 4132 )); 4133 } 4134 if (passwordRestrictions.minNonNumChars !== 0) { 4135 $problemsList.append(_CreateProblemsListItem( 4136 Utils.xReplace(_STR.PWD_QUALITY_HINT_MIN_NON_NUM_CHARS, passwordRestrictions.minNonNumChars), 4137 PasswordProblems.FewNonNumChars 4138 )); 4139 } 4140 if (passwordRestrictions.minUpperCaseChars !== 0) { 4141 $problemsList.append(_CreateProblemsListItem( 4142 Utils.xReplace(_STR.PWD_QUALITY_HINT_MIN_UPPER_CASE_CHARS, passwordRestrictions.minUpperCaseChars), 4143 PasswordProblems.FewUpperCaseChars 4144 )); 4145 } 4146 if (passwordRestrictions.minLowerCaseChars !== 0) { 4147 $problemsList.append(_CreateProblemsListItem( 4148 Utils.xReplace(_STR.PWD_QUALITY_HINT_MIN_LOWER_CASE_CHARS, passwordRestrictions.minLowerCaseChars), 4149 PasswordProblems.FewLowerCaseChars 4150 )); 4151 } 4152 if (passwordRestrictions.minDigitChars !== 0) { 4153 $problemsList.append(_CreateProblemsListItem( 4154 Utils.xReplace(_STR.PWD_QUALITY_HINT_MIN_DIGIT_CHARS, passwordRestrictions.minDigitChars), 4155 PasswordProblems.FewDigitChars 4156 )); 4157 } 4158 } 4159 }; 4160})(); 4161 4162function showResultMsgWide(msg, bError, sGotoLocation, onHiddenResultDialog, onShownResultDialog, title, buttonTitle, bWarning) { 4163 showResultMsg_Generic(true, msg, bError, sGotoLocation, onHiddenResultDialog, onShownResultDialog, title, buttonTitle, bWarning); 4164} 4165 4166function showResultMsg(msg, bError, sGotoLocation, onHiddenResultDialog, onShownResultDialog, title, buttonTitle, bWarning) { 4167 showResultMsg_Generic(false, msg, bError, sGotoLocation, onHiddenResultDialog, onShownResultDialog, title, buttonTitle, bWarning); 4168} 4169 4170function showResultMsg_Generic(bWide, msg, bError, sGotoLocation, onHiddenResultDialog, onShownResultDialog, title, buttonTitle, bWarning) { 4171 var $resultDialog = $('#resultDialog'); 4172 if (bWide) {
4173 $resultDialog.addClass('wide').find('.modal-dialog').removeClass('modal-sm'); 4174 } else { 4175 $resultDialog.removeClass('wide').find('.modal-dialog').addClass('modal-sm'); 4176 } 4177 var $title = $resultDialog.find('.modal-title'); 4178 var sTitle = ''; 4179 if (bWarning) { 4180 $resultDialog.removeClass('error').addClass('warning'); 4181 sTitle = $title.attr('data-warning'); 4182 } else if (bError) { 4183 $resultDialog.removeClass('warning').addClass('error'); 4184 sTitle = $title.attr('data-error'); 4185 } else { 4186 $resultDialog.removeClass('warning error'); 4187 sTitle = $title.attr('data-success'); 4188 } 4189 if (typeof title !== 'undefined') { 4190 sTitle = title; 4191 } 4192 // replace the default server "legacy account" message with a more friendly. 4193 if (msg=='Forbidden: cannot share to legacy account') { 4194 msg=_STR.CL_USERGROUP_USERS_CANNOT_SHARE_TO_LEGACY_ACCOUNT; 4195 sTitle=_STR.CL_INFORMATION; 4196 } 4197 $title.html(sTitle ? sTitle : ' '); 4198 $resultDialog.find('.modal-body').html(msg); 4199 $resultDialog.find('.modal-footer .btn-default').html(buttonTitle || _STR.BUTTON_CLOSE); 4200 $resultDialog.one('show.bs.modal', function(){ 4201 // close result dialog by enter key 4202 $resultDialog.one('keydown', function(e){ 4203 if (e.which === 13) { 4204 $resultDialog.modal('hide'); 4205 } 4206 }); 4207 }); 4208 if ($('#quickstartWidget').hasClass('in')) { // quickstartWidget is shown 4209 sGotoLocation = onHiddenResultDialog = onShownResultDialog = false; 4210 } 4211 if (sGotoLocation) { 4212 $resultDialog.one('hide.bs.modal', function(){ 4213 document.location.href = sGotoLocation; 4214 }); 4215 } 4216 if (typeof onHiddenResultDialog === 'function') { 4217 $resultDialog.one('hidden.bs.modal', function(){ 4218 onHiddenResultDialog($resultDialog); 4219 }); 4220 } 4221 if (typeof onShownResultDialog === 'function') { 4222 $resultDialog.one('shown.bs.modal', function(){ 4223 onShownResultDialog($resultDialog); 4224 }); 4225 } 4226 $resultDialog.modal('show'); 4227} 4228 4229function doLogout(logoutURL) { 4230 var dontRemoveCookies = ['lang'], 4231 i, tempArr = {}, len = dontRemoveCookies.length; 4232 for (i = 0; i < len; i++) { 4233 tempArr[dontRemoveCookies[i]] = $.cookie(dontRemoveCookies[i]); 4234 } 4235 $.cookie(null); 4236 for (i = 0; i < len; i++) { 4237 $.cookie(dontRemoveCookies[i], tempArr[dontRemoveCookies[i]]); 4238 } 4239 Utils.Storage.remove('_rfo-passwd'); 4240 document.location.href = logoutURL + (sessionStorage.affid ? (~logoutURL.indexOf('?') ? '&' : '?') + 'affid=' + sessionStorage.affid : ''); 4241} 4242 4243// Do not allow the site to load in frame, except for plain signup in modern browser 4244function RelocateFrame() { 4245 if (RFO.PAGE_TYPE !== 'signup-plain' && top.location !== location) { 4246 top.location.href = location.href; 4247 return true; 4248 } 4249 return false; 4250} 4251 4252function RFMgrCommonErrorHandler($dialog, errObject) { 4253 if (errObject.Code && errObject.Code === RfapiJS.HTTP_Status_Code_Unauthorized) { 4254 //access denied error (session expired) 4255 doLogout(RFO.ROOT_PATH + 'login?relogin'); 4256 } else if ($dialog) { 4257 stopProcessing($dialog, function(){ 4258 showResultMsg(Utils.getErrorDescription(errObject), true); 4259 }); 4260 } else { 4261 showResultMsg(Utils.getErrorDescription(errObject), true); 4262 } 4263} 4264 4265function detectDeviceIcon(row) { 4266 sIcon = 'devices_unknown.png'; 4267 switch (row.id.charAt(0)) { 4268 case 'B': // Browser 4269 if (row.description.match(/^Safari/)) { 4270 sIcon = 'devices_safari.png'; 4271 } else if (row.description.match(/^Edge/)) { 4272 sIcon = 'devices_edge.png'; 4273 } else if (row.description.match(/^Chrome/)) { 4274 sIcon = 'devices_chrome.png'; 4275 } else if (row.description.match(/^Firefox/)) { 4276 sIcon = 'devices_firefox.png'; 4277 } else if (row.description.match(/^Opera/)) { 4278 sIcon = 'devices_opera.png'; 4279 } else if (row.description.match(/^IE(\d+)?/)) { 4280 sIcon = 'devices_ie.png'; 4281 } else if (row.description.match(/^RoboForm/i)) { 4282 sIcon = 'devices_roboform.png'; 4283 } else { 4284 sIcon = 'devices_browser.png'; 4285 } 4286 break; 4287 case 'W': // RF-Win 4288 sIcon = 'devices_laptop.png'; 4289 break; 4290 case 'M': // RF-Mac 4291 if (row.description.match(/iMac/)) { 4292 sIcon = 'devices_computer.png'; 4293 } else { 4294 sIcon = 'devices_laptop.png'; 4295 } 4296 break; 4297 case 'A': // Android 4298 sIcon = 'devices_phone.png'; 4299 break; 4300 case 'I': // iOS 4301 if (row.description.match(/iPad/)) { 4302 sIcon = 'devices_tablet.png'; 4303 } else { 4304 sIcon = 'devices_phone.png'; 4305 } 4306 break; 4307 case 'E': // RF-Edge 4308 sIcon = 'devices_edge.png'; 4309 break; 4310 case 'C': // RF-Chrome 4311 sIcon = 'devices_chrome.png'; 4312 break; 4313 case 'F': // RF-Firefox 4314 sIcon = 'devices_firefox.png'; 4315 break; 4316 case 'O': // RF-Opera 4317 sIcon = 'devices_opera.png'; 4318 break; 4319 } 4320 switch (row.client) { 4321 case 'RF-Win': 4322 sIcon = 'devices_laptop.png'; 4323 break; 4324 case 'RF-Mac': 4325 if (row.description.match(/iMac/)) { 4326 sIcon = 'devices_computer.png'; 4327 } else { 4328 sIcon = 'devices_laptop.png'; 4329 } 4330 break; 4331 case 'RF-iPhone': 4332 if (row.description.match(/iPad/)) { 4333 sIcon = 'devices_tablet.png'; 4334 } else { 4335 sIcon = 'devices_phone.png'; 4336 } 4337 break; 4338 case 'RF-Android': 4339 sIcon = 'devices_phone.png'; 4340 break; 4341 case 'RF-Edge': 4342 sIcon = 'devices_edge.png'; 4343 break; 4344 case 'RF-Chrome': 4345 sIcon = 'devices_chrome.png'; 4346 break; 4347 case 'RF-Firefox': 4348 sIcon = 'devices_firefox.png'; 4349 break; 4350 case 'RF-Opera': 4351 sIcon = 'devices_opera.png'; 4352 break; 4353 } 4354 return sIcon; 4355} 4356 4357 4358// AffID Script 4359var affid = ''; 4360(function(){ 4361 var affid_ = Utils.parseUrlQuery().affid; 4362 if (affid_) { 4363 affid = affid_.slice(0, 5); 4364 var aDomainComponents = location.hostname.split("."); 4365 aDomainComponents.push(""); 4366 // pass domain as .roboform.com obtained from www.roboform.com 4367 $.cookie('affid', affid, {expires: 7, path: '/', domain: "."+aDomainComponents.slice(-3, -1).join(".")}); 4368 // save affid from URL parameter to session storage too, 4369 // it will be used to get proper OpenID providers on login page after logout or session expiration 4370 sessionStorage.affid = affid_; 4371 } else if ($.cookie('affid')) { 4372 affid = $.cookie('affid').slice(0, 5); 4373 } 4374})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.