PageSourceSearch

https://bobr.by/assets/v367/corelib/cms.core.js

js bobr.by collected 2026-10-01 11:43:54 UTC 121,435 bytes, 2,597 lines download raw bytes

1/// <reference path="../../../../../.docs/jsdoc/index.d.ts" />
2
3Object.size = function(obj){ var size = 0, key; for (key in obj) {if (obj.hasOwnProperty(key)) size++;}return size;};
4
5const ACTION_SUBMIT_TYPE_ADD_RETURN_SECTION = 101;
6const ACTION_SUBMIT_TYPE_ADD_REPEAT = 102;
7const ACTION_SUBMIT_TYPE_ADD_SAME = 103;
8const ACTION_SUBMIT_TYPE_ADD_THEN_EDIT = 104;
9const ACTION_SUBMIT_TYPE_EDIT_APPLY = 201;
10const ACTION_SUBMIT_TYPE_EDIT_NEXT = 202;
11const ACTION_SUBMIT_TYPE_EDIT_RETURN_SECTION = 203;
12
13const _COMMON_LANGS = {
14    'ru' : {"fileFailure" : "Необходимо указать файл","connectionError" : "Извините, произошла ошибка при соединении с системой","unexpectedError" : "Извините, произошла неожиданная ошибка. Обратитесь к разработчикам","emptyAnswerError" : "Сервер вернул пустой ответ. Обратитесь к разработчикам","requestErno" : "Извините, произошла ошибка. Код : %CODE","requestError" : "Извините, произошла ошибка!","submit" : "Отправить","cancel" : "Отменить","search" : "Поиск","back" : "Вернуться","prev" : "пред.","next" : "след.","ok" : "OK","yes" : "Да","no" : "Нет","loading" : "Идёт обмен данными с сервером. Подождите..","update" : "Обновить","update_and_close" : "Обновить и закрыть","record_editor_title" : "Редактор записи объекта %OBJECT_ID% записи №%RECORD_ID%","submit" : "Отправить","error" : "Ошибка","error_query" : "Ошибка запроса","close" : "Закрыть","need_confirm" : "Необходимо подтверждение","no_default_action_warning" : "Внимание, действие не назначено! Обратитесь к разработчикам!","error_happened" : 'Извините, произошла непредвиденная ошибка!',"EMr" : 'Извините, произошла ошибка c отправкой запроса!',"EMn" : 'Извините, произошла ошибка. (Код ошибки : ',"EM" : 'Извините, произошла ошибка.','sending' : "Идёт отправка запроса...",'response_error' : "Ошибка ответа сервера",'select' : 'Выбрать','confirm' : "Подтвердите действие",'confirm_remove' : "Удалить запись N=%RECORD_ID%?",'clipboard_success': 'Скопировано в буфер','clipboard_failed': 'Не удалось скопировать'},
15    'en' : {"fileFailure" : "File not selected","connectionError" : "Sorry. Connection error","unexpectedError" : "Sorry. Unexpected error","emptyAnswerError" : "Sorry. Error. Answer from server is empty","requestErno" : "Sorry. Error happened. Code : %CODE","requestError" : "Sorry. Error happened!","submit" : "Submit","cancel" : "Cancel","search" : "Search","back" : "Go back","prev" : "prev.","next" : "next.","ok" : "OK","yes" : "Yes","no" : "No","loading" : "Transferring data with server.. Please, wait","update" : "Refresh","update_and_close" : "Update & close","record_editor_title" : "Record`s editor for object %OBJECT_ID% of record №%RECORD_ID%","error" : "Error","error_query" : "Error in query","close" : "Close","need_confirm" : "Confirmation needed","no_default_action_warning" : "Warning! Action isn`t assigned!","error_happened" : 'Sorry. We have an unexpected error!',"EMr" : 'Sorry. We have an error with request!',"EMn" : 'Sorrt. We have an error.(Code : ',"EM" : 'Sorry. We have an error.','sending' : "Sending request to server...",'response_error' : "Response error",'select' : 'Select','confirm' : "Confirm action",'confirm_remove' : "Are you sure to delete record N=%RECORD_ID%?",'clipboard_success': 'Copied to clipboard','clipboard_failed': 'Failed to copy to clipboard'}
16}
17
18
19// JDOM debug mode: set to 1 to throw exceptions on warnings/errors (for development)
20const DEBUG_JDOM = 1;
21
22// A small normalization layer that converts any DOM-related input (selectors, IDs, Elements, arrays, jQuery objects) into native Element or Element[]
23class JDOM {
24    static generate_new_unique_dom_id(dom_element){let el=null;if(dom_element instanceof Element)el=dom_element;else if(dom_element&&typeof dom_element==="object"&&dom_element.jquery){if(dom_element.length===1)el=dom_element[0];else return false;}else if(dom_element!==undefined){console.warn("Expected DOM element or jQuery object");return false;}if(el&&el.id)return el.id;const A="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";let t=25,id="";while(t--){id="generated_id_";for(let i=0;i<20;i++)id+=A[Math.floor(Math.random()*A.length)];if(!document.getElementById(id))break;id="";}if(!id)return false;if(el)el.id=id;return id;}
25    static get_dom_element(something,if_multiple_get_first_or_root_element=true){if(!something)return false;if(something instanceof Element)return something;if(typeof something==="string"){if(if_multiple_get_first_or_root_element instanceof Element||if_multiple_get_first_or_root_element instanceof NodeList||if_multiple_get_first_or_root_element instanceof HTMLCollection||(typeof if_multiple_get_first_or_root_element==="object"&&if_multiple_get_first_or_root_element.jquery))return this.find_dom_element(something,if_multiple_get_first_or_root_element);if(/^[A-Za-z_]/.test(something)){const e=document.getElementById(something);if(e)return e}const l=document.querySelectorAll(something);return l.length===1?l[0]:l.length>1?(if_multiple_get_first_or_root_element?l[0]:false):false}if(typeof something==="object"&&something.jquery)return something.length===1?something[0]:something.length>1?(if_multiple_get_first_or_root_element?something[0]:false):false;if(typeof something==="object"&&typeof something.length==="number")return something.length===1&&something[0] instanceof Element?something[0]:something.length>1&&something[0] instanceof Element?(if_multiple_get_first_or_root_element?something[0]:false):false;return false;}
26    static get_dom_array_elements(something){if(!something)return[];if(typeof something==="string")return Array.from(document.querySelectorAll(something));if(typeof something==="object"&&something.jquery)return something.length?Array.from(something):[];if(something instanceof Element)return[something];if(something instanceof NodeList||something instanceof HTMLCollection)return something.length?Array.from(something):[];if(Array.isArray(something))return something.every(el=>el instanceof Element)?something:(console.warn("get_dom_array_elements: array contains non-Element items"),DEBUG_JDOM?[]:[]);console.warn("get_dom_array_elements: invalid input type");if(DEBUG_JDOM)throw new Error("get_dom_array_elements: invalid input type (got: "+(typeof something)+")");return[];}
27    static multiQuery(selector,dom_root){if(!selector)return[];if(dom_root instanceof Element)return Array.from(dom_root.querySelectorAll(selector));if(!dom_root)return Array.from(document.querySelectorAll(selector));const roots=this.get_dom_array_elements(dom_root),out=[];for(const r of roots)out.push(...r.querySelectorAll(selector));return Array.from(new Set(out));}
28    static get_id(something, if_multiple_get_first = false, allow_add_id = true) { const e = el => el ? (el.id || (allow_add_id ? JDOM.generate_new_unique_dom_id(el) : false)) : false; if (something instanceof Element) return e(something); if (something && typeof something === "object" && something.jquery) return something.length === 1 ? e(something[0]) : (something.length > 1 ? (if_multiple_get_first ? (console.warn("JDOM.get_id: multiple elements, using first"), e(something[0])) : false) : false); if (typeof something === "object" && typeof something.length === "number") return something.length === 1 && something[0] instanceof Element ? e(something[0]) : (something.length > 1 && something[0] instanceof Element ? (if_multiple_get_first ? (console.warn("JDOM.get_id: multiple elements, using first"), e(something[0])) : false) : false); if (typeof something === "string") { if (/^[A-Za-z_]/.test(something)) { const el = document.getElementById(something); if (el) return e(el); } const list = document.querySelectorAll(something); return list.length === 1 ? e(list[0]) : (list.length > 1 ? (if_multiple_get_first ? (console.warn("JDOM.get_id: multiple elements, using first"), e(list[0])) : false) : false); } return false; }
29    static closest(dom_elements, css_selector) { if (typeof css_selector !== "string") { console.error("JDOM.closest: css_selector must be a string"); return []; } const elements = this.get_dom_array_elements(dom_elements); if (!elements.length) { console.error("JDOM.closest: invalid dom_elements input"); return []; } const tmp = []; for (const el of elements) { if (!(el instanceof Element)) continue; let found = null; if (el.closest) { found = el.closest(css_selector); } else { if (el.matches && el.matches(css_selector)) { found = el; } else { let parent = el.parentNode; while (parent && parent.nodeType === 1) { if (parent.matches && parent.matches(css_selector)) { found = parent; break; } parent = parent.parentNode; } } } if (found) tmp.push(found); } return tmp; }
30    static find_dom_element(css_selector, root_element = null) { if (typeof css_selector !== "string" || !css_selector) { console.error("JDOM.find_dom_element: css_selector must be a non-empty string"); return false; } let root = null; if (root_element === null) { root = document; } else if (root_element instanceof Element) { root = root_element; } else if (root_element && typeof root_element === "object" && root_element.jquery) { root = root_element.length > 0 ? root_element[0] : null; } else { console.error("JDOM.find_dom_element: root_element must be Element, jQuery object, or null"); return false; } if (!root) return false; const results = root.querySelectorAll(css_selector); if (results.length === 0) return false; if (results.length > 1) { console.warn("JDOM.find_dom_element: multiple elements found for selector '" + css_selector + "', returning first"); if (DEBUG_JDOM) throw new Error("JDOM.find_dom_element: multiple elements found for selector '" + css_selector + "'"); } return results[0]; }
31    static load_scripts(html){if(typeof html!=="string"||!html)return 0;let t=document.createElement("div");t.innerHTML=html;let s=t.querySelectorAll("script,link"),c=0;for(let i=0;i<s.length;i++){let n=document.createElement(s[i].tagName);if(s[i].href)n.href=s[i].href;if(s[i].src)n.src=s[i].src;if(s[i].textContent)n.textContent=s[i].textContent;if(s[i].type)n.type=s[i].type;if(s[i].rel)n.rel=s[i].rel;if(s[i].media)n.media=s[i].media;document.body.appendChild(n);c++;}return c;}
32}
33
34Object.freeze(JDOM);
35
36// jquery alternative for fadeIn, fadeOut, slideUp, slideDown, plugins etc.
37class JQ {
38    static fadeIn(el, duration = 400){el.style.opacity = 0;el.style.display = '';el.style.transition = `opacity ${duration}ms`;requestAnimationFrame(() => {el.style.opacity = 1;});}
39    static fadeOut(el, duration = 400){el.style.transition = `opacity ${duration}ms`;el.style.opacity = 1;requestAnimationFrame(() => {el.style.opacity = 0;});setTimeout(() => {el.style.display = 'none';}, duration);}
40    static slideUp(element, duration = 400, callback){element.style.overflow = 'hidden';let height = element.scrollHeight;let start = Date.now();const animate = () => {const progress = Math.min((Date.now() - start) / duration, 1);element.style.height = (height * (1 - progress)) + 'px';element.style.opacity = 1 - progress;progress < 1 ? requestAnimationFrame(animate) : (element.style.display = 'none', element.style.height = '', element.style.opacity = '', callback && callback());};animate();}
41    static slideDown(element, duration = 400){element.style.display = 'block';let height = element.scrollHeight;element.style.height = '0';element.style.opacity = '0';element.style.overflow = 'hidden';let start = Date.now();const animate = () => {const progress = Math.min((Date.now() - start) / duration, 1);element.style.height = (height * progress) + 'px';element.style.opacity = progress;progress < 1 ? requestAnimationFrame(animate) : (element.style.height = '', element.style.opacity = '', element.style.overflow = '');};animate();}
42}
43
44Object.freeze(JQ);
45
46
47// frequently used functions
48function stdcheck(result,callback,return_false_if_failed){
49    if (result !== null  && (typeof result == 'object') && (typeof result.passed != 'undefined') && result.passed) return result;
50    
51    var messages = [];
52    var msg='';
53    var error_id = 0;
54    if (result === null || typeof result != 'object'){
55        messages.push(Env.lang.error_query);
56    } else if (typeof result.passed != 'undefined'){
57        if (!result.passed){
58            if (result.error){
59                error_id = result.error;
60                if (result.error_message){
61                    messages.push(result.error_message);
62                    console.log('Error check: '+result.error_message);
63                }
64                if (typeof result.errors != 'undefined' && result.errors){
65                    for (var key in result.errors){
66                        messages.push(result.errors[key]);
67                        console.log('Error check: '+result.errors[key]);
68                    }
69                }
70                if (!messages && result.error) {
71                    msg = Env.lang.EMn+result.error+')';
72                    messages.push(msg);
73                }
74            } else {
75                messages.push(Env.lang.unexpectedError);
76            }
77        }
78    } else if(typeof result.passed == 'undefined'){
79        msg = Env.lang.EMr;
80        messages.push(Env.lang.EMr);
81
82    }
83    if (!messages){
84        messages.push(Env.lang.unexpectedError);
85    }
86
87
88    if (typeof callback == 'function'){
89        if (return_false_if_failed){
90            callback(result,messages);
91            return false;
92        } else {
93            return callback(result,messages);
94        }
95    } else {
96        if (return_false_if_failed){return false;}
97        if (typeof fDialogs._function_alert == 'function'){
98            //result,context_data,a,handler,error_message,connector
99            fDialogs._function_alert(messages,null,result);
100        } else {
101            alert(messages);
102        }
103        return result;
104    }
105
106}
107function simpleCheck(result){return stdcheck(result,false,true);}
108
109// constructor of Connector
110function cms_connector(module, onloading_start, onloading_finish, onloading_failed,onloading_system_failed) {
111
112    this.loading = false;
113    this.result = {};
114    this.cache_keys = [];
115    this.module = '_common';
116    this.cache_values = [];
117    this.queue = [];
118    this.queue_index = -1;
119    this.modal =0;
120    this.admin=0;
121    this._onloading_start = (typeof onloading_start == 'function') ? onloading_start : (onloading_start === false ? false : fDialogs._onloading_start);
122    this._onloading_finish = (typeof onloading_finish == 'function') ? onloading_finish : (onloading_finish === false ? false : fDialogs._onloading_finish);
123    this._onloading_failed = (typeof onloading_failed == 'function') ? onloading_failed : (onloading_failed === false ? false : fDialogs._onloading_failed);
124    this._onloading_system_failed = (typeof onloading_system_failed == 'function') ? onloading_system_failed : null;
125
126    if (typeof module == 'string') {
127        this.module = module;
128    } else if (typeof module == 'number') {
129        this.module = 'object_' + module;
130    }
131
132    this._queue_process = function(){
133        var keys = Object.keys(this.queue);
134        if (this.loading || !keys.length) return true;
135        this.loading = true;
136        var request_id = keys[0];
137        var request = this.queue[request_id];
138        return this._send_async(request);
139    },
140
141    this._send_async = function(request){
142        this.result={};
143        let xhr = new XMLHttpRequest();
144        if (Env._can_parse_json){
145            xhr.responseType = 'json';
146        } else {
147            xhr.onloadstart = function(){ /* ie 11 bug fix */ this.responseType = "json"; }
148        }
149        xhr.open("POST",request.url,true);
150        xhr.context = this;
151        xhr.request_id = request.id;
152        xhr.request_data = request;
153        xhr.onreadystatechange = this._callback_on_response;
154        if (typeof request.onloading_start == 'function'){
155            if (typeof request.context == 'object'){
156                (request.onloading_start).apply(request.context,[request.context_data,request,xhr]);
157            } else {
158                request.onloading_start(request.context_data,request,xhr);
159            }
160        }
161        try {
162            xhr.send(request.data);
163        } catch(err) {
164            console.log('XHR error on sending request ID='+request.id);
165            if (typeof request.onloading_failed == 'function'){
166                if (typeof request.context == 'object'){
167                    return (request.onloading_failed).apply(request.context,[null,context_data,request,xhr,err,connector]);
168                } else {
169                    return (request.onloading_failed).apply(connector,[null,context_data,request,xhr,err,connector]);
170                }
171            } else {
172                if (typeof request.context == 'object'){
173                    return (request.callback).apply(request.context,[null,context_data,request,xhr,err,connector]);
174                } else {
175                    request.callback(null,context_data,request,xhr,err,connector);
176                }
177            }
178            return false;
179        }
180        return false;
181    },
182
183    this._send_sync = function(request){
184        var xhr = new XMLHttpRequest();
185        xhr.open("POST",request.url,false);
186        if (typeof request.onloading_start == 'function'){
187            if (typeof request.context == 'object'){
188                (request.onloading_start).apply(request.context,[request.context_data,request,xhr]);
189            } else {
190                request.onloading_start(request.context_data,request,xhr);
191            }
192        }
193        var result = null;
194        var error=false;
195        this.result = {};
196        try {
197            xhr.send(request.data);
198            if (xhr.readyState  !== XMLHttpRequest.DONE){
199                console.log('Request failed. Status != DONE');
200                error=true;
201            }
202            if (xhr.status != 200){
203                error = xhr.status;
204            }
205            // getting result
206            if (!error){
207                result = JSON.parse(xhr.responseText);
208            } else {
209                if (xhr.responseText){
210                    result = JSON.parse(xhr.responseText);
211                    if (typeof result != 'object'){
212                        result = {'passed':0,'error':-9999,'error_message':xhr.responseText,'http_status':xhr.status}
213                    }
214                }
215            }
216            if (typeof result == 'undefined' || result === null || result === false){
217                error = true;
218            } else{
219                if(typeof request.context_data == 'object' && request.context_data !== null){
220                    result['_context']=request.context_data;
221                }
222            }
223            this.result = result;
224            if (typeof request.onloading_finish == 'function'){
225                if (typeof request.context == 'object'){
226                    (request.onloading_finish).apply(request.context,[result,request.context_data,request,this]);
227                } else {
228                    request.onloading_finish(result,request.context_data,request,this);
229                }
230            }
231            if (!error){
232                if (typeof result == 'undefined' || result === null){
233                    // bad output format or php errors. json not parsed
234                    error=true;
235                    if (typeof request.onloading_failed == 'function'){
236                        // custom function exists
237                        if (typeof request.context == 'object'){
238                            (request.onloading_failed).apply(request.context,[result,request.context_data,null,xhr,'Response parse error',this]);
239                        } else {
240                            request.onloading_failed(result,request.context_data,null,xhr,'Response parse error',this);
241                        }
242                    } else {
243                        // use standart callback
244                        if (typeof request.context == 'object'){
245                            (request.callback).apply(request.context,[result,request.context_data,request,this]);
246                        } else {
247                            request.callback(result,request.context_data,request,this);
248                        }
249                    }
250                }
251            }
252            if (request.cache_index !== null){
253                this.cache_values[request.cache_index]=result;
254            }
255        } catch(err) {
256            console.log('XHR error on sending request ID='+request.id);
257            if (typeof request.onloading_finish == 'function'){
258                if (typeof request.context == 'object'){
259                    (request.onloading_finish).apply(request.context,[result,request.context_data,request,this]);
260                } else {
261                    request.onloading_finish(result,request.context_data,request,this);
262                }
263            } 
264            if (typeof request.onloading_failed == 'function'){
265                if (typeof request.context == 'object'){
266                    (request.onloading_failed).apply(request.context,[null,request.context_data,request,xhr,err,this]);
267                } else {
268                    request.onloading_failed(null,request.context_data,request,xhr,err,this);
269                }
270            }
271        }
272        return result;
273    },
274
275    this._callback_on_response = function(){
276        if (this.readyState !== XMLHttpRequest.DONE) {
277            return true;
278        }
279        let result = this.response;
280        let request = this.request_data;
281        let request_id = request.id;
282        let context_data = request.context_data;
283        let current_connector = request.connector;
284        let is_modal = request.modal;
285        delete current_connector.queue[request_id];
286        current_connector.loading=false;
287        if (is_modal){
288            if (typeof xDialogs.show_modal_loading == 'function'){
289                xDialogs.hide_modal_loading();
290            } else {
291                fDialogs.hide_loading();
292            }
293        }
294        if (typeof request.onloading_finish == 'function'){
295            if (typeof request.context == 'object'){
296                (request.onloading_finish).apply(request.context,[result,context_data,request,this]);
297            } else {
298                (request.onloading_finish).apply(current_connector,[result,context_data,request,this]);
299            }
300        } 
301        if (typeof result == 'undefined' || result === null){
302            // bad output format or php errors. json not parsed
303            if (typeof request.onloading_failed == 'function'){
304                // custom function exists
305                if (typeof request.context == 'object'){
306                    (request.onloading_failed).apply(request.context,[result,context_data,null,this,'Response parse error',current_connector]);
307                } else {
308                    (request.onloading_failed).apply(current_connector,[result,context_data,null,this,'Response parse error',current_connector]);
309                }
310            } else {
311                // use standart callback
312                if (typeof request.context == 'object'){
313                    (request.callback).apply(request.context,[result,context_data,request,this]);
314                } else {
315                    (request.callback).apply(current_connector,[result,context_data,request,this]);
316                }
317            }
318        } else {
319            if (typeof request.context_data == 'object' && request.context_data !== null){
320                result['_context']=request.context_data;
321            }
322            current_connector.result = result;
323            // Check for system error (negative error code)
324            if (typeof result === 'object' && result !== null && typeof result.error === 'number' && result.error < 0) {
325                // Check for fatal error (error code -1000 = Fatal PHP Error)
326                if (result.error === -1000) {
327                    if (typeof current_connector._onloading_failed === 'function') {
328                        current_connector._onloading_failed(result, context_data, null, this, 'Fatal PHP Error', current_connector);
329                    }
330                } else {
331                    // Call system failed for other negative errors
332                    if (typeof current_connector._onloading_system_failed === 'function') {
333                        current_connector._onloading_system_failed(result, context_data, request, this);
334                    }
335                }
336            }
337            if (this.status != 200 ){
338                if (this.status == 401 && typeof result.http_status != 'undefined' && typeof fDialogs.__onloading_unauthorize_response == 'function'){
339                    if (typeof request.context == 'object'){
340                        (fDialogs.__onloading_unauth
340orize_response).apply(request.context,[result.http_status,result.login_url,result,context_data,null,this]);
341                    } else {
342                        (fDialogs.__onloading_unauthorize_response).apply(this,[result.http_status,result.login_url,result,context_data,null,this]);
343                    }
344                }
345            } else {
346                if (typeof request.context == 'object'){
347                    (request.callback).apply(request.context,[result,context_data,request,this]);
348                } else {
349                    (request.callback).apply(current_connector,[result,context_data,request,this]);
350                }
351            }
352        }
353        if (request.cache_index !== null){
354            current_connector.cache_values[request.cache_index]=result;
355        }
356        return current_connector._queue_process();
357    },
358
359    this._parse_request_to_formdata = function(request,try_use_cache){
360
361        let old_browser_formdata_function=false;
362        let params_count=0;
363        let request_formdata = new FormData();
364        let can_cache=true;
365        let cache_key=[];
366        let context_data = null;
367        let funclist = [];
368        let funconly = [];
369
370
371        for (var funcname in request){
372            if (funcname == '_context'){
373                context_data = request[funcname];
374            }
375            var request_cache_key = [];
376            funclist.push(funcname);
377            var funcparams = request[funcname];
378            var t = typeof funcparams;
379            if (t == 'string' && /^#.+$/.test(funcparams)){
380                funcparams = Toolbox.form_export_formdata(funcparams.slice(1));
381                t = typeof funcparams;
382            }
383            var ts = funcparams.toString();
384            if (t == 'object'){
385                if (ts == '[object FormData]'){
386                    // formData object
387                    var tmp = this._parse_formdata_to_object(funcparams);
388                    if (tmp === false){
389                        // false = browser not support iteration by FormData
390                        if (!old_browser_formdata_function && !params_count){
391                            // add only if first and mark its method
392                            request_formdata = funcparams;
393                            old_browser_formdata_function = funcname;
394                            request_formdata.append('_old_browser_method',funcname);
395                            can_cache=false;
396                            cache_key=null;
397                        } else {
398                            var msg = 'Error: old browser not support FormData methods';
399                            console.log(msg);
400                            alert(msg);
401                            return null;
402                        }
403                    } else {
404                        var keys = Object.keys(tmp);
405                        for (var i=0;i<keys.length;i++){
406                            var key = keys[i];
407                            if (typeof tmp[key] == 'object'){
408                                var its_key_is_array = (key.indexOf('[]') == -1) ? false : true;
409                                if (!Array.isArray(tmp[key])){
410                                    // system object
411                                    if (its_key_is_array){
412                                        request_formdata.append(funcname+'___'+key,tmp[key]);
413                                    } else {
414                                        var ttf = tmp[key].toString();
415                                        if (ttf == '[object File]'){
416                                            request_formdata.append(funcname+'___'+key,tmp[key]);
417                                        } else {
418                                            request_formdata.append(funcname+'___'+key+'[]',tmp[key]);
419                                        }
420                                    }
421                                } else {
422                                    // array
423                                    var kk = Object.keys(tmp[key]);
424                                    for (var k in kk){
425                                        if (its_key_is_array){
426                                            request_formdata.append(funcname+'___'+key,tmp[key][k]);
427                                        } else {
428                                            request_formdata.append(funcname+'___'+key+'[]',tmp[key][k]);
429                                        }
430                                    }
431                                }
432                            } else {
433                                request_formdata.append(funcname+'___'+key,tmp[key]);
434                            }
435                            params_count++;
436                            if (try_use_cache && can_cache){
437                                var t = Toolbox.generate_cache_key(tmp[key]);
438                                if (t === null){
439                                    can_cache=false;
440                                    cache_key=null;
441                                } else {
442                                    if (t.length){
443                                        request_cache_key.push(key+'='+t);
444                                    }
445                                }
446                            }
447                        }
448                    }
449                } else {
450                    // ordinary object=array
451                    var keys = Object.keys(funcparams);
452                    for (var i=0;i<keys.length;i++){
453                        var key = keys[i];
454                        // walk by keys
455                        var type_of_val = typeof funcparams[key];
456                        if (type_of_val == 'object'){
457                            if (Array.isArray(funcparams[key])){
458                                var val = '___json___'+JSON.stringify(funcparams[key])
459                                request_formdata.append(funcname+'___'+key,val);
460                            } else {
461                                // object but not array = system dom object
462                                var its_key_is_array = (key.indexOf('[]') == -1) ? false : true;
463                                if (funcparams[key] === null){
464                                    var val = '___json___'+JSON.stringify(funcparams[key])
465                                    request_formdata.append(funcname+'___'+key,val);
466                                } else {
467                                    var tx = funcparams[key].toString();
468                                    if (tx == '[object FileList]'){
469                                        var file_l = funcparams[key].length;
470                                        if (file_l == 1){
471                                            request_formdata.append(funcname+'___'+key,funcparams[key][0]);
472                                        } else {
473                                            if (file_l > 1){
474                                                for (var file_i=0;file_i<file_l;file_i++){
475                                                    var f = funcparams[key][file_i];
476                                                    if (its_key_is_array){
477                                                        request_formdata.append(funcname+'___'+key,f);
478                                                    } else {
479                                                        request_formdata.append(funcname+'___'+key+'[]',f);
480                                                    }
481                                                }
482                                            }
483                                        }
484                                    } else {
485                                        if (tx == '[object File]'){
486                                            //ccnsole.log('single file added!!!');
487                                            request_formdata.append(funcname+'___'+key,funcparams[key]);
488                                        } else {
489                                            if (type_of_val == 'string'){
490                                                request_formdata.append(funcname+'___'+key,funcparams[key]);
491                                            } else {
492                                                var val = '___json___'+JSON.stringify(funcparams[key]);
493                                                request_formdata.append(funcname+'___'+key,val);
494                                            }
495                                        }
496                                    }
497                                }
498                            }
499                        } else {
500                            if (type_of_val == 'string'){
501                                request_formdata.append(funcname+'___'+key,funcparams[key]);
502                            } else {
503                                var val = '___json___'+JSON.stringify(funcparams[key]);
504                                request_formdata.append(funcname+'___'+key,val);
505                            }
506                        }
507
508                        params_count++;
509                        if (try_use_cache && can_cache){
510                            var t = Toolbox.generate_cache_key(funcparams[key]);
511                            if (t === null){
512                                can_cache=false;
513                                cache_key=null;
514                            } else {
515                                if (t.length){
516                                    request_cache_key.push(key+'='+t);
517                                }
518                            }
519                        }
520                    }
521                }
522
523                if (can_cache && request_cache_key && try_use_cache){
524                    cache_key.push(funcname+'=|'+request_cache_key.join('&')+'|')
525                }
526            } else {
527                if (t != 'undefined'){
528                    params_count++;
529                    var val = '___json___'+JSON.stringify(funcparams);
530                    request_formdata.append(funcname+'___params',val);
531                }
532            }
533        }
534        for (var i=0;i<funclist.length;i++){
535            if (funclist[i] != '_context') funconly.push(funclist[i]);
536        }
537        return [request_formdata,can_cache,cache_key,context_data,funclist,funconly];
538    },
539
540    this._parse_formdata_to_object = function(fd ){
541        if (typeof fd.keys == 'undefined'){
542            console.log('Old browser!');
543            return false;
544        }
545        var tmp = {}
546        var keys = fd.keys();
547        var item = keys.next();
548        while (!item.done){
549            var val = fd.getAll(item.value);
550            if (val.length == 1){
551                tmp[item.value]=val[0];
552            } else {
553                if (val.length > 1){
554                    tmp[item.value]=val;
555                } else {
556                    tmp[item.value]=null;
557                }
558            }
559            item = keys.next();
560        }
561        return tmp;
562    }
563    
564    this.execute = function (request, callback, context_object, try_use_cache) {
565        if (typeof request != 'object' || request === null) {
566            console.log('Connector error : empty request');
567            return false;
568        }
569        let time_start = new Date();
570        let [request_data, can_cache, cache_key,context_data,funclist,funconly] = this._parse_request_to_formdata(request,try_use_cache);
571        let cache_index = null;
572        let cache_using = 0;
573        
574        request_data.append('__methods_list', funclist.join(','));
575        request_data.append('__EPC', __EPC);
576
577
578
579        if ((try_use_cache ?? false) && can_cache && cache_key.length) {
580            cache_key = (cache_key.length == 1) ? cache_key[0] : cache_key.join('&');
581            cache_index = this.cache_keys.indexOf(cache_key);
582            if (cache_index === -1) {
583                cache_index = this.cache_keys.length;
584                this.cache_keys[cache_index] = cache_key;
585            } else {
586                if (typeof this.cache_values[cache_index] != 'undefined') {
587                    console.log('Result was founded in cache');
588                    var result = this.cache_values[cache_index];
589                    this.result = result;
590                    if (typeof callback == 'function') {
591                        if (typeof context_object == 'object') {
592                            return (callback).apply(context_object, [result, context_data]);
593                        } else {
594                            return (callback).apply(this, [result, context_data]);
595                        }
596                    } else {
597                        return result;
598                    }
599                }
600            }
601        }
602        var async = ((typeof callback == 'function') ? true : false);
603        var urlparams = [];
604        if (this.module != '_common') {
605            urlparams.push("__modname=" + encodeURIComponent(this.module));
606        }
607        var connector_request = {
608            'comment' : this.module + ' -> '+funconly.join(','),
609            'url': document.location.protocol + '//' + document.location.host + '/_connector/' + (urlparams ? "?" + urlparams.join('&') : ''),
610            'module': this.module,
611            'modal': this.modal,
612            'cache_index': cache_index,
613            'data': request_data,            
614            'connector': this,
615            'context_data': context_data,
616            'context': context_object,
617            'onloading_start': this._onloading_start,
618            'onloading_finish': this._onloading_finish,
619            'onloading_failed': this._onloading_failed            
620        };
621        this.modal = 0;
622        console.log('Connector:: '+(async ? 'async' : 'sync')+(this.admin ? ' admin' : '')+(cache_using ? ' cached' : '') + ' request to: '+connector_request.module+ ' -> '+funconly.join(', '));
623        if (async) {
624            connector_request['callback'] = callback;
625            this.queue_index++;
626            var request_id = this.queue_index;
627            connector_request['id'] = request_id;
628            this.queue[request_id] = connector_request;
629            this._queue_process();
630            return true;
631        } else {
632            return this._send_sync(connector_request);
633        }
634    };
635
636    this.ajax_module = function (modulename,request) {
637        let self = this;
638        modulename = (typeof modulename =='string' && modulename.trim()) ? modulename.trim() : this.module;
639        return new Promise((resolve, reject) => {
640
641            // Prepare FormData
642            
643            let [request_data, can_cache, cache_key,context_data,funclist,funconly] = this._parse_request_to_formdata(request);
644            request_data.append('__methods_list', funclist.join(','));
645            request_data.append('__EPC', __EPC);
646
647            // Create XHR
648            var xhr = new XMLHttpRequest();
649            var urlparams = [];
650            if (typeof modulename == 'string') modulename = modulename.trim();
651
652            if (typeof modulename == 'string' && modulename.length){
653                urlparams.push("__modname=" + encodeURIComponent(modulename.trim()));
654            } else if (self.module !== '_common') {
655                urlparams.push("__modname=" + encodeURIComponent(self.module));
656            }
657
658            var url = document.location.protocol + '//' + document.location.host + '/_connector/' +
659                (urlparams ? "?" + urlparams.join('&') : '');
660
661            xhr.open("POST", url, true);
662
663            xhr.onload = function () {
664
665                let errorObj = {status: xhr.status,response: xhr.response,statusText: xhr.statusText};
666                
667                if (xhr.status === 200) {
668                    var response = xhr.response;
669                    let failed=false;
670                    // JSON response ONLY allowed
671                    if (typeof response === 'string') {
672                        if (!response.length){
673                            failed = true;
674                            c
674onsole.error('Connector:: Error JSON response is empty:', errorObj);
675                        } else {
676                            try {
677                                response = JSON.parse(response);
678                            } catch (e) {
679                                failed = true;
680                                const cleaned = response
681                                    .replace(/<[^>]*>/g, '')   // убираем теги
682                                    .replace(/&quot;/g, '"')   // декодируем частые HTML-сущности
683                                    .replace(/&amp;/g, '&')
684                                    .replace(/&lt;/g, '<')
685                                    .replace(/&gt;/g, '>')
686                                    .trim()
687                                ;
688                                if (cleaned.length > 0 && cleaned.length <= 200) {
689                                    console.error('Connector:: Server returned non-JSON response:\n' + cleaned);
690                                } else {                                    
691                                    console.error('Connector:: Error parsing JSON response:', errorObj);
692                                }
693                                
694                            }
695                        }
696
697                    } else if (typeof response !== 'object' || response === null){
698                        console.error('Connector:: Error with JSON response:', errorObj);
699                        failed = true;
700                    }
701
702                    if (failed){
703                        response = {};
704                        if (typeof self._onloading_finish === 'function') {
705                            self._onloading_finish(response, context_data,xhr,this);
706                        }
707                        if (typeof self._onloading_failed === 'function') {
708                            self._onloading_failed(null, request, xhr, errorObj);
709                        }
710                        resolve(response);
711                        return;
712                    }
713
714                    // Add context to response
715                    if (typeof context_data === 'object' && context_data !== null) {
716                        response._context = context_data;
717                    }
718
719                    self.result = response;
720
721                    if (typeof self._onloading_finish === 'function') {
722                        self._onloading_finish(response, context_data);
723                    }
724
725                    // Check for system error (negative error code)
726                    if (typeof response === 'object' && response !== null && typeof response.error === 'number' && response.error < 0) {
727                        // Check for fatal error (error code -1000 = Fatal PHP Error)
728                        if (response.error === -1000) {
729                            if (typeof self._onloading_failed === 'function') {
730                                self._onloading_failed(response, request, xhr, {status: xhr.status, response: xhr.response, statusText: xhr.statusText, error: 'Fatal PHP Error'});
731                            }
732                        } else {
733                            // Call system failed for other negative errors
734                            if (typeof self._onloading_system_failed === 'function') {
735                                self._onloading_system_failed(response, request, xhr, {status: xhr.status, response: xhr.response, statusText: xhr.statusText});
736                            }
737                        }
738                    }
739
740                    resolve(response);
741                } else {
742                    if (typeof self._onloading_failed === 'function') {
743                        self._onloading_failed(null, request, xhr, errorObj);
744                    }
745                    reject(errorObj);
746                }
747            };
748
749            xhr.onerror = function () {
750                var errorObj = {
751                    status: xhr.status,
752                    error: 'Network error',
753                    xhr: xhr
754                };
755
756                if (typeof self._onloading_failed === 'function') {
757                    self._onloading_failed(null, request, xhr, errorObj);
758                }
759
760                reject(errorObj);
761            };
762
763            xhr.ontimeout = function () {
764                var errorObj = {
765                    error: 'Request timeout',
766                    xhr: xhr
767                };
768
769                if (typeof self._onloading_failed === 'function') {
770                    self._onloading_failed(null, request, xhr, errorObj);
771                }
772
773                reject(errorObj);
774            };
775
776            if (typeof self._onloading_start === 'function') {
777                self._onloading_start(context_data, null, xhr,this);
778            }
779            console.log('Connector:: await request to: '+this.module+ ' -> '+funconly.join(', '));
780            xhr.send(request_data);
781
782        });
783    };
784
785    // custom short methods
786    this.ajax = function (request,modulename) {return this.ajax_module(modulename,request);},
787    this.executex = function (request, callback, context_object) { return this.execute(request, callback, context_object, true); };
788
789};
790
791// admin panel add-on for Connector`s constructor
792function cms_connector_admin(name) {
793    cms_connector_admin.superclass.call(this, name);
794    this.admin=1;
795    this.run_now_cached =false;
796    this.modal=false;
797    this._onloading_start = function(context_data,request,xhr){
798        if (request && typeof request.connector == 'object' && typeof request.connector.modal == 'number' && request.connector.modal){
799            xDialogs.show_modal_loading();
800        } else {
801            xDialogs.show_loading_connector();
802        }
803        
804    }
805    this._onloading_finish = function(result,context_data,request,xhr){
806        if (request && typeof request.connector == 'object' && typeof request.connector.modal == 'number' && request.connector.modal){
807            xDialogs.hide_modal_loading();
808        } else {
809            xDialogs.hide_loading_connector();
810        }
811    }
812
813    this._onloading_failed = function(result, request, xhr, errorObj){
814        xDialogs.stdcheck(result);
815    }
816
817    this.run = function(request,callback,context){
818        this.modal = false;
819        if (this.run_now_cached){
820            this.run_now_cached=false;
821            this.executex(request,callback);
822        } else {
823            this.execute(request,callback,context);
824        }
825        return this.result;
826    };
827
828    this.runx = function(request,callback,raw){
829        this.run_now_cached =true;
830        return this.run(request,callback,raw);
831    };
832
833    this.run_modal = function(request,callback,context){
834        this.modal=true;
835        if (this.run_now_cached){
836            this.run_now_cached=false;
837            this.executex(request,callback,context);
838        } else {
839            this.execute(request,callback,context);
840            if (typeof callback == 'function'){
841                return false;
842            }            
843        }
844        if (typeof callback == 'function'){
845            if (typeof context == 'object'){
846                return (callback).apply(context,[this.result]);
847            } else {
848                callback(this.result);
849            }
850        }
851        return this.result;
852    };
853
854    this.runx_modal = function(request,callback,raw){
855        this.run_now_cached =true;
856        return this.run_modal(request,callback,raw);
857    }
858
859}
860Object.assign(cms_connector_admin.prototype, cms_connector.prototype);
861cms_connector_admin.superclass = cms_connector;
862
863// Detected environment vars of cms
864var Env = {
865
866    is_dev_server: false,
867    is_local_scripts_mode: false,    
868    is_xfront_mode: false,
869    is_print_version: 0,
870
871    current_gmt :null,
872    current_date : null,
873    current_datetime : null,
874    current_daytime : null,
875    _ms_browser_version : null,
876
877    _can_parse_json : true,
878    path_web_lib : null,
879    path_widgets_lib : null,
880    auth_zone : 'front',
881    language_list : __LANG_LIST,
882    language : __LANG,
883    language_alt : __LANG_ALT,
884    lang : null,
885
886    __loaded_scripts_ids : [],
887
888    get_ms_browser_version: function () {
889        if (Env._ms_browser_version !== null) return Env._ms_browser_version;
890        const ua = navigator.userAgent;
891        let v = false;
892        const msie = ua.indexOf('MSIE ');
893        if (msie > 0) {
894            v = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
895        } else {
896            const trident = ua.indexOf('Trident/');
897            if (trident > 0) {
898                const rv = ua.indexOf('rv:');
899                v = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
900            } else {
901                const edge = ua.indexOf('Edge/');
902                if (edge >
902 0) v = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
903            }
904        }
905        Env._ms_browser_version = v;
906        return v;
907    },
908
909    init_dates : function(){
910        var date = new Date();
911        Env.current_gmt = -date.getTimezoneOffset()/60;
912        Env.current_date = Toolbox.format_date('date',date);
913        Env.current_datetime = Toolbox.format_date('datetime',date);
914        Env.current_daytime = Toolbox.format_date('daytime',date);
915    },
916
917    init : function(){
918
919        for (var i=0,c=Env.language_list.length;i<c;i++){
920            var l = Env.language_list[i];
921            l = l.toLowerCase();
922            if (typeof _COMMON_LANGS[l] != 'undefined'){
923                Env.lang = _COMMON_LANGS[l];
924                break;
925            }
926        }
927        let iev = Env.get_ms_browser_version();
928        if (iev && iev < 12){
929            Env._can_parse_json=false;
930        }
931        Env.path_web_lib = __ALP;
932        Env.path_widgets_lib = __WFLP ;
933        Env.auth_zone = AUTH_ZONE;
934        Env.init_dates();
935        if (typeof __JSAD == 'string' && __JSAD){
936            try {
937                let ds = JSON.parse(atob(__JSAD));
938                Env.is_local_scripts_mode = ds.lsm;
939                Env.is_dev_server = ds.ds;
940                Env.is_xfront_mode = ds.xf;
941                Env.is_print_version = ds.pm;
942            } catch (e) {
943                console.error('WARNING: ext. auth info parse error!', e);
944            }
945        } else {
946            Env.is_dev_server = document.location.hostname == 'localhost' || document.location.hostname.endsWith('.local') ? 1 : 0;
947            Env.is_xfront_mode = AUTH_ZONE == 'xfront' ? 1 : 0;
948        }
949    }
950}
951
952var Stack = {
953    
954    _callbacks : {},
955    _callbacks_last_key : 1,
956    _user_callbacks : {},
957    _user_callbacks_last_key : 1,
958
959    push : function(callback_data){
960        var key=this._callbacks_last_key;
961        while (typeof this._callbacks[key] != 'undefined') key++;
962        this._callbacks[key]= (typeof callback_data == 'undefined') ? {} : callback_data;
963        this._callbacks_last_key = key;
964        return key;
965    },
966
967    pop : function(callback_key){
968        if (typeof callback_key == 'undefined'){
969            callback_key = this._callbacks_last_key;
970        }
971        if (typeof this._callbacks[callback_key] != 'undefined'){
972            var back =  this._callbacks[callback_key];
973            delete this._callbacks[callback_key];
974            var keys = Object.keys(this._callbacks);
975            if(keys.length){
976                this._callbacks_last_key = keys[keys.length-1];
977            }
978            return back;
979        }
980    },
981
982    get : function(callback_key,delete_key){
983        if (typeof callback_key == 'undefined'){
984            console.log('Wrong callback key!');
985            return false;
986        }
987        if (typeof this._callbacks[callback_key] != 'undefined'){
988            var t = this._callbacks[callback_key];
989            if (delete_key) delete this._callbacks[callback_key];
990            return t;
991        }
992    },
993
994    custom_push : function(callback_data){
995        var key=this._user_callbacks_last_key;
996        if (key === null){
997            key = 1;
998        } else {
999            while (typeof this._user_callbacks[key] != 'undefined'){
1000                key++;
1001            }
1002        }
1003        if (typeof callback_data == 'undefined'){
1004            this._user_callbacks[key]={};
1005        } else {
1006            this._user_callbacks[key]=callback_data;
1007        }
1008        this._user_callbacks_last_key=key;
1009        return key;
1010    },
1011
1012    custom_pop : function(callback_key){
1013        if (typeof callback_key == 'undefined'){
1014
1015            var last_key = null;
1016            var prev_key = null;
1017            for (var key in this._user_callbacks){
1018                prev_key = last_key;
1019                last_key=key;
1020            }
1021            if (!last_key){
1022                console.log('callback_pop() failed!. array is empty.');
1023                return null;
1024            }
1025            callback_key = last_key;
1026            this._user_callbacks_last_key=prev_key;
1027        }
1028        if (typeof this._user_callbacks[callback_key] != 'undefined'){
1029            var back =  this._user_callbacks[callback_key];
1030            delete this._user_callbacks[callback_key];
1031            return back;
1032        }
1033    },
1034
1035    custom_get : function(callback_key){
1036        if (typeof callback_key == 'undefined'){
1037            var last_key = null;
1038            for (var key in this._user_callbacks){
1039                last_key=key;
1040            }
1041            if (!last_key){
1042                console.log('callback_pop() failed!. array is empty.');
1043                return null;
1044            }
1045            callback_key = last_key;
1046        }
1047        if (typeof this._user_callbacks[callback_key] != 'undefined'){
1048            return this._user_callbacks[callback_key];
1049        }
1050    }
1051};
1052
1053var Toolbox = {
1054
1055    loadScriptOnce : function(url, id, force_load = false) {
1056        if (!force_load && Env.__loaded_scripts_ids.includes(id)) {
1057            return;
1058        }
1059        if (force_load) {
1060            const old = document.getElementById(id);
1061            if (old) old.remove();
1062        }
1063        const s = document.createElement('script');
1064        s.id = id;
1065        s.src = url;
1066        s.type = 'text/javascript';
1067        Env.__loaded_scripts_ids.push(id);
1068        document.head.appendChild(s);
1069    },
1070    
1071    convert_coord_to_array : function(str_coords){
1072        if (typeof str_coords != 'string') return null;
1073        let coords = (str_coords.indexOf('[') != -1) ? str_coords.slice(1,-1) : str_coords;
1074        coords=coords.split(',');
1075        return [parseFloat(coords[0]),parseFloat(coords[1])];
1076    },
1077
1078    copyToClipboard : function(message) {
1079        let textArea = document.createElement("textarea");
1080        textArea.value = message;
1081        textArea.style.opacity = "0"; 
1082        document.body.appendChild(textArea);
1083        textArea.focus();
1084        textArea.select();
1085        try {
1086            let successfull = document.execCommand('copy');
1087            alert(successfull ? Env.lang.clipboard_success : Env.lang.clipboard_failed);
1088        } catch (err) {
1089            alert('Unable to copy value , error : ' + err.message);
1090        }
1091        document.body.removeChild(textArea);
1092    },
1093
1094    get_url_param : function(param){return new URLSearchParams(window.location.search).get(param);},
1095
1096    get_url_all_params : function(url) {
1097        var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
1098        var obj = {};
1099        if (queryString) {
1100            queryString = queryString.split('#')[0];
1101            var arr = queryString.split('&');
1102            for (var i = 0; i < arr.length; i++) {
1103                var a = arr[i].split('=');
1104                var paramName = a[0];
1105                var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];
1106                paramName = paramName.toLowerCase();
1107                if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();
1108                if (paramName.match(/\[(\d+)?\]$/)) {
1109                    var key = paramName.replace(/\[(\d+)?\]/, '');
1110                    if (!obj[key]) obj[key] = [];
1111                    if (paramName.match(/\[\d+\]$/)) {
1112                        var index = /\[(\d+)\]/.exec(paramName)[1];
1113                        obj[key][index] = paramValue;
1114                    } else {
1115                        obj[key].push(paramValue);
1116                    }
1117                } else {
1118                    if (!obj[paramName]) {
1119                        obj[paramName] = paramValue;
1120                    } else if (obj[paramName] && typeof obj[paramName] === 'string') {
1121                        obj[paramName] = [obj[paramName]];
1122                        obj[paramName].push(paramValue);
1123                    } else {
1124                        obj[paramName].push(paramValue);
1125                    }
1126                }
1127            }
1128        }
1129        return obj;
1130    },
1131
1132    get_browser_time : function(server_time,server_event_time,server_next_event_date){
1133        let now_microtime = Date.now();
1134        let t = Math.floor(now_microtime/1000);
1135        let diff = t - server_time;
1136        let hours_diff = Math.round(diff / 3600);
1137        if (hours_diff){
1138            diff = diff - 3600*hours_diff;
1139        }
1140        let new_time = (server_event_time + 3600*hours_diff);
1141        let new_microtime = 1000*new_time;
1142        let sdate = fDialogs.datetime_format(new_time);
1143        return {
1144            'diff' : {'hours':hours_diff,'seconds':diff},
1145            'msec_left' : (new_microtime - now_microtime),
1146            'sec_left' : (new_time - t),
1147            'time':new_time,
1148            'microtime':new_microtime,
1149            'datetime_server' : server_next_event_date,
1150            'datetime_browser' : sdate
1151        }
1152    },
1153
1154    form_export_object : function (form_dom_or_id, remove_prefix, checkbox_to_zero){
1155        let form = null;
1156
1157        if (form_dom_or_id && form_dom_or_id.jquery){
1158            const id = JDOM.generate_new_unique_dom_id(form_dom_or_id);
1159            form = document.getElementById(id);
1160        } else if (typeof form_dom_or_id === 'string'){
1161            form = document.getElementById(form_dom_or_id.trim());
1162        } else if (form_dom_or_id instanceof Element){
1163            form = form_dom_or_id;
1164        } else {
1165            console.log('Invalid parameter: must be ID, DOM element, or jQuery object');
1166            return null;
1167        }
1168
1169        if (!form || !form.elements) return null;
1170
1171        remove_prefix = remove_prefix || '';
1172        checkbox_to_zero = (typeof checkbox_to_zero === 'undefined') ? true : checkbox_to_zero;
1173
1174        const formData = {};
1175
1176        for (let i = 0; i < form.elements.length; i++){
1177            const el = form.elements[i];
1178            if (el.disabled) continue;
1179
1180            const tag = el.tagName.toLowerCase();
1181            let name = el.name || el.id || null;
1182            if (!name) continue;
1183
1184            let val = null;
1185
1186            if (tag === 'input'){
1187                if (['text','email','date','password','button','submit','hidden','tel'].includes(el.type)){
1188                    val = el.value;
1189                } else if (el.type === 'number'){
1190                    val = el.step ? parseFloat(el.value) : parseInt(el.value);
1191                } else if (el.type === 'checkbox'){
1192                    if (el.checked){
1193                        val = (el.value === '1') ? 1 : el.value;
1194                    } else if (checkbox_to_zero){
1195                        val = el.checked;
1196                    }
1197                } else if (el.type === 'radio'){
1198                    if (el.checked){
1199                        val = el.value;
1200                    } else {
1201                        try {
1202                            if (typeof eval('formData.' + name) !== 'undefined') continue;
1203                        } catch(e){ continue; }
1204                    }
1205                } else if (el.type === 'file'){
1206                    val = el.files;
1207                }
1208
1209            } else if (tag === 'select'){
1210                if (el.options.length){
1211                    if (el.multiple){
1212                        name = name.replace(/\[\]$/,'');
1213                        val = [];
1214                        for (let o = 0; o < el.options.length; o++){
1215                            const opt = el.options[o];
1216                            if (el.getAttribute('fullselect')){
1217                                val[opt.value] = opt.text;
1218                            } else if (opt.selected){
1219                                val.push(opt.value);
1220                            }
1221                        }
1222                    } else if (el.selectedIndex >= 0){
1223                        val = el.options[el.selectedIndex].value;
1224                    }
1225                }
1226
1227            } else if (tag === 'textarea'){
1228                val = el.value;
1229            }
1230
1231            try {
1232                if (remove_prefix){
1233                    const len = remove_prefix.length;
1234                    if (name.slice(0,len) === remove_prefix) name = name.slice(len);
1235                }
1236
1237                if (/^(\w+)$/.test(el.name)){
1238                    const tpl = 'formData["' + name + '"]';
1239                    let is_obj = 0;
1240                    eval("if (typeof " + tpl + " == 'object' && " + tpl + " !== null){ is_obj = 1; }");
1241                    if (is_obj){
1242                        eval(tpl + '["__value"] = val;');
1243                    } else {
1244                        eval(tpl + ' = val;');
1245                    }
1246                    continue;
1247                }
1248
1249                if (el.name === "") continue;
1250
1251                if (!/^(\w+)(\[.+\])*$/.test(el.name)){
1252                    if (/^(\w+)\[\]$/.test(el.name)){
1253                        const fname = el.name.match(/^(\w+)\[\]$/)[1];
1254                        if (el.type === 'checkbox' || el.type === 'radio'){
1255                            if (el.checked){
1256                                if (!formData[fname]) formData[fname] = [];
1257                                formData[fname].push(el.value);
1258                            }
1259                        } else {
1260                            if (!formData[fname]) formData[fname] = [];
1261                            formData[fname].push(val);
1262                        }
1263                    }
1264                    continue;
1265                }
1266
1267                const match = el.name.match(/^(\w+)(\[.+\])*$/);
1268                const fname = match[1];
1269                const index_str = match[2];
1270                const index_array = index_str.slice(1,-1).split('][');
1271
1272                if (typeof formData[fname] === 'string'){
1273                    formData[fname] = {'__value': formData[fname]};
1274                }
1275                if (typeof formData[fname] === 'undefined'){
1276                    formData[fname] = {};
1277                }
1278
1279                let tpl = 'formData["' + fname + '"]';
1280                for (let k = 0; k < index_array.length - 1; k++){
1281                    const ind = index_array[k];
1282                    if (!ind) continue;
1283                    tpl += '["' + ind + '"]';
1284                    eval("if (typeof " + tpl + " == 'string'){ " + tpl + "={'__value':" + tpl + "}; }");
1285                    eval("if (typeof " + tpl + " == 'undefined'){ " + tpl + "={}; }");
1286                }
1287
1288                eval(tpl + '["' + index_array[index_array.length - 1] + '"] = val;');
1289
1290            } catch(e){}
1291        }
1292
1293        return formData;
1294    },
1295
1296
1297    form_export_block: function(id, remove_prefix, checkbox_to_zero) {
1298        const block = document.getElementById(id);
1299        if (!block) return null;
1300        if (remove_prefix === undefined) remove_prefix = '';
1301        if (checkbox_to_zero === undefined) checkbox_to_zero = true;
1302        const elems = block.querySelectorAll('select, input, textarea');
1303        const formData = {};
1304        for (let el of elems) {
1305            const tag = el.tagName.toLowerCase();
1306            const type = el.getAttribute('type');
1307            let value;
1308            if (tag === 'input') {
1309                value = el.value;
1310                if (type === 'number') value = parseInt(value);
1311                else if (type === 'checkbox') {
1312                    if (!el.checked) { if (checkbox_to_zero) continue; }
1313                    else if (value === '1') value = 1;
1314                } else if (type === 'radio' && !el.checked) continue;
1315            } else if (tag === 'select') {
1316                const opt = el.querySelector('option:checked');
1317                value = opt ? opt.value : '';
1318            } else {
1319                value = el.value;
1320            }
1321            const name = el.getAttribute('name');
1322            if (!name) continue;
1323            const valint = parseInt(value);
1324            if ((valint + '') === value) value = valint;
1325            if (/^(\w+)$/.test(name)) {
1326                if (typeof formData[name] === 'object' && formData[name] !== null) formData[name]["__value"] = value;
1327                else formData[name] = value;
1328                continue;
1329            }
1330            if (/^(\w+)\[\]$/.test(name)) {
1331                const fname = name.match(/^(\w+)\[\]$/)[1];
1332                if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
1333                if (!Array.isArray(formData[fname])) formData[fname] = [];
1334                formData[fname].push(value);
1335                continue;
1336            }
1337            if (!/^(\w+)(\[.+\])*$/.test(name)) continue;
1338            const match = name.match(/^(\w+)(\[.+\])*$/);
1339            const fname = match[1];
1340            const index_array = match[2].slice(1, -1).split('][');
1341            if (typeof formData[fname] === 'string') formData[fname] = { '__value': formData[fname] };
1342            if (typeof formData[fname] === 'undefined') formData[fname] = {};
1343            let ref = formData[fname];
1344            for (let i = 0; i < index_array.length - 1; i++) {
1345                const key = index_array[i];
1346                if (!key) continue;
1347                if (typeof ref[key] === 'string') ref[key] = { '__value': ref[key] };
1348                if (typeof ref[key] === 'undefined') ref[key] = {};
1349                ref = ref[key];
1350            }
1351            ref[index_array[index_array.length - 1]] = value;
1352        }
1353        return formData;
1354    },
1355
1356    form_export_formdata : function (form_dom_or_id){
1357        let f = null;
1358        if (typeof form_dom_or_id === 'string'){
1359            const id = form_dom_or_id.trim();
1360            if (!id){ console.log('Bad form ID: ' + form_dom_or_id); return null; }
1361            f = document.getElementById(id);
1362            if (!f){ console.log('Form not found. ID: ' + id); return null; }
1363
1364        } else if (form_dom_or_id instanceof Element){
1365            f = form_dom_or_id;
1366        } else if (form_dom_or_id && form_dom_or_id.jquery){
1367            if (form_dom_or_id.length !== 1){ console.log('jQuery object must contain exactly 1 element'); return null; }
1368            f = form_dom_or_id[0];
1369        } else {
1370            console.log('Invalid parameter: must be string, DOM element, or jQuery object');
1371            return null;
1372        }
1373        if (f.tagName.toLowerCase() !== 'form'){ console.log('Element is not a <form>'); return null; }
1374        if (typeof tinymce !== 'undefined' && tinymce.editors.length){
1375            for (let i = 0; i < tinymce.editors.length; i++) tinymce.editors[i].save();
1376        }
1377        return new FormData(f);
1378    },
1379
1380    form_export_as_block_attributes : function (block_or_selector, keys){
1381        let el = null;
1382        if (typeof block_or_selector === 'string'){
1383            el = document.querySelector(block_or_selector);
1384            if (!el){ console.log('Element not found: ' + block_or_selector); return null; }
1385
1386        } else if (block_or_selector instanceof Element){
1387            el = block_or_selector;
1388        } else if (block_or_selector && block_or_selector.jquery){
1389            if (block_or_selector.length !== 1){ console.log('jQuery object must contain exactly 1 element'); return null; }
1390            el = block_or_selector[0];
1391        } else {
1392            console.log('Invalid parameter: must be selector, DOM element, or jQuery object');
1393            return null;
1394        }
1395        const out = {};
1396        for (let i = 0, c = keys.length; i < c; i++){
1397            const key = keys[i];
1398            const val = el.getAttribute(key);
1399
1400            if (val === null){
1401                out[key] = null;
1402            } else if (val === '__true'){
1403                out[key] = true;
1404            } else if (val === '__false'){
1405                out[key] = false;
1406            } else if (/^-?\d*\.\d+$/.test(val)){
1407                out[key] = parseFloat(val);
1408            } else if (/^-?\d+$/.test(val)){
1409                out[key] = parseInt(val);
1410            } else {
1411                out[key] = val;
1412            }
1413        }
1414        return out;
1415    },
1416
1417    form_export_as_block_data_attributes : function (block_or_selector, prefix){
1418        let el = null;
1419        if (typeof block_or_selector === 'string'){
1420            el = document.getElementById(block_or_selector) || document.querySelector(block_or_selector);
1421            if (!el) return {};
1422        } else if (block_or_selector instanceof Element){
1423            el = block_or_selector;
1424        } else if (block_or_selector && block_or_selector.jquery){
1425            if (block_or_selector.length !== 1) return {};
1426            el = block_or_selector[0];
1427        } else {
1428            return {};
1429        }
1430        prefix = prefix || null;
1431        const out = {};
1432        const attrs = el.attributes;
1433        for (let i = 0; i < attrs.length; i++){
1434            const a = attrs[i];
1435            const k = a.name;
1436            const v = a.value;
1437            if (k === 'id' || k === 'class') continue;
1438            if (prefix){
1439                if (k.indexOf(prefix) === 0){
1440                    out[k.slice(prefix.length)] = Toolbox._validated_value(v);
1441                }
1442            } else {
1443                out[k] = Toolbox._validated_value(v);
1444            }
1445        }
1446        return out;
1447    },
1448
1449    form_import_values : function (form_dom_or_id, data){
1450        if (!data || typeof data !== 'object') return false;
1451        let form = null;
1452        if (typeof form_dom_or_id === 'string'){
1453            form = document.getElementById(form_dom_or_id.trim());
1454        } else if (form_dom_or_id instanceof Element){
1455            form = form_dom_or_id;
1456        } else if (form_dom_or_id && form_dom_or_id.jquery){
1457            if (form_dom_or_id.length !== 1) return false;
1458            form = form_dom_or_id[0];
1459        } else {
1460            return false;
1461        }
1462        if (!form) return false;
1463        for (const fname in data){
1464            const items = form.querySelectorAll('[name="' + fname + '"]');
1465            if (!items.length) continue;
1466            const first = items[0];
1467            const tag = first.tagName.toLowerCase();
1468            if (tag === 'input'){
1469                const type = first.type.toLowerCase();
1470                if (['text','hidden','password','submit','reset','number','tel'].includes(type)){
1471                    first.value = data[fname];
1472                } else if (type === 'radio' || type === 'checkbox'){
1473                    items.forEach(el => el.checked = false);
1474                    const val = data[fname];
1475                    const target = Array.from(items).find(el => el.value == val);
1476                    if (target && (val === '1' || val)) target.checked = true;
1477                }
1478            } else if (tag === 'select'){
1479                const sval = (typeof data[fname] === 'string')
1480                    ? data[fname].replace(/\\/g,'\\\\').replace(/"/g,'\\"')
1481                    : data[fname];
1482                first.querySelectorAll('option').forEach(o => o.selected = false);
1483                const opt = first.querySelector('option[value="' + sval + '"]');
1484                if (opt) opt.selected = true;
1485
1486            } else if (tag === 'textarea'){
1487                const v = data[fname];
1488                first.textContent = (v === null || v === false || v === true) ? '' : v;
1489            }
1490        }
1491        return true;
1492    },
1493
1494    form_import_as_block_attributes : function (block_or_selector, data, all_keys){
1495        let el = null;
1496        if (typeof block_or_selector === 'string'){
1497            el = document.querySelector(block_or_selector);
1498            if (!el) return false;
1499        } else if (block_or_selector instanceof Element){
1500            el = block_or_selector;
1501        } else if (block_or_selector && block_or_selector.jquery){
1502            if (block_or_selector.length !== 1) return false;
1503            el = block_or_selector[0];
1504        } else {
1505            return false;
1506        }
1507        const data_keys = Object.keys(data);
1508        all_keys = all_keys || data_keys;
1509        for (let i = 0; i < all_keys.length; i++){
1510            const key = all_keys[i];
1511            if (!data_keys.includes(key)){
1512                el.removeAttribute(key);
1513                continue;
1514            }
1515            const val = data[key];
1516            if (val === null){
1517                el.removeAttribute(key);
1518            } else if (val === true){
1519                el.setAttribute(key, '__true');
1520            } else if (val === false){
1521                el.setAttribute(key, '__false');
1522            } else {
1523                el.setAttribute(key, val);
1524            }
1525        }
1526        return true;
1527    },
1528
1529    parse_file_path : function(filepath){
1530        var pos = filepath.lastIndexOf('/');
1531        var result = {'folder':false,'file':false,'filename':false,'extension':false,'extension_lower':false}
1532        if (pos != -1){
1533            result.file = filepath.slice(pos+1);
1534            result.folder = filepath.slice(0,pos+1);
1535        } else {
1536            result.file = filepath;
1537        }
1538        pos = result.file.lastIndexOf('.');
1539        if (pos != -1){
1540            result.extension = result.file.slice(pos+1);
1541            result.filename = result.file.slice(0,pos);
1542        } else {
1543            result.filename = result.file;
1544        }
1545        if (result.extension) result.extension_lower = result.extension.toLowerCase();
1546        return result;
1547    },
1548
1549    mime_match : function(mime,pattern){
1550        if (!mime || typeof mime !== 'string') return false;
1551        var tmp = pattern.split(',');
1552        for (var i=0,c=tmp.length;i<c;i++){
1553            var pos = tmp[i].indexOf('*');
1554            if ( pos != -1){
1555                if (pos != tmp[i].length -1){
1556                    console.log('bad mime pattern : '+tmp[i]);
1557                    continue;
1558                }
1559                var pat = tmp[i].slice(0,pos);
1560                var smime = mime.slice(0,pos);
1561                if (pat == smime) return true;
1562            } else {
1563                if (tmp[i] == mime) return true;
1564            }
1565        }
1566        return false;
1567    },
1568
1569    calc_time_left: function (time_ms, items_count) {
1570        if (typeof time_ms !== 'number') return '';
1571        let t = Math.ceil((time_ms + ((time_ms % 1000) > 20 ? 1000 : 0)) / 1000);
1572        const days = (t / 86400) | 0; t %= 86400;
1573        const hours = (t / 3600) | 0; t %= 3600;
1574        const minutes = (t / 60) | 0;
1575        const seconds = t % 60;
1576        if (!items_count) return [days, hours, minutes, seconds];
1577        const res = {};
1578        if (days && items_count--) res.days = days;
1579        if (hours && items_count--) res.hours = hours;
1580        if (minutes && items_count--) res.minutes = minutes;
1581        if (seconds && items_count) res.seconds = seconds;
1582        return res;
1583    },
1584
1585    validate_email : function(email) {
1586        var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
1587        return re.test(email);
1588    },
1589
1590    _validated_value : function(value){
1591        var tmp = null;
1592        var t = typeof value;
1593        switch (t){
1594            case 'string':
1595                switch (value){
1596                    case '__true': tmp=true;break;
1597                    case '__false': tmp=false;break;
1598                    case '__null': tmp=null;break;
1599                    default:
1600                        if (Toolbox.validate_float(value)){
1601                            tmp = parseFloat(value);
1602                        } else {
1603                            if (Toolbox.validate_int(value)){
1604                                tmp = parseInt(value);
1605                            } else {
1606                                tmp = value;
1607                            }
1608                        }
1609                }
1610                break;
1611            case 'number':
1612            case 'bool':
1613            case 'boolean':
1614                tmp = value;
1615                break;
1616            default:
1617                tmp = value;
1618        }
1619        return tmp;
1620    },
1621
1622    validate_int : function(value) {return (!isNaN(value) && (parseInt(value) == value.toString())) ? true : false;},
1623    validate_float : function(value) {return (!isNaN(value) && value.toString().indexOf('.') != -1) ? true : false;},
1624
1625    format_date : function(datetype,date_or_ts,gmt){
1626        if (typeof date_or_ts == 'object' && date_or_ts !== null){
1627            // ignore gmt
1628            var d = date_or_ts;
1629        } else {
1630            gmt = (typeof gmt  == 'number') ? gmt : Env.current_gmt;
1631            if (typeof date_or_ts == 'number'){
1632                var d = new Date();
1633                d.setTime(date_or_ts*1000 + (gmt-Env.current_gmt)*1000*60*60);
1634            } else {
1635                var d = new Date(Date.now()  + (gmt-Env.current_gmt)*1000*60*60   );
1636            }
1637        }
1638        var dt = [d.getFullYear(),d.getMonth(),d.getDate(),d.getHours(),d.getMinutes(),d.getSeconds()];
1639        for (var i=0,c=dt.length;i<c;i++){
1640            if (dt[i]<10) dt[i]='0'+dt[i];
1641        }
1642        var res = false;
1643        switch (datetype){
1644            case 'datetime': res = dt[0]+'-'+dt[1]+'-'+dt[2]+' '+dt[3]+':'+dt[4]+':'+dt[5];break;
1645            case 'date': res =  dt[0]+'-'+dt[1]+'-'+dt[2];break;
1646            case 'daytime': res = 100*d.getHours()+d.getMinutes();break;
1647        }
1648        return res;
1649    },
1650
1651    transform_image_url : function(image,sizex,sizey, aspect,color){
1652        if (!image) return 'javascript:void(0);';
1653        var tmp = "/_image/?image="+encodeURIComponent(image);
1654        if (sizex) tmp += "&width="+encodeURIComponent(sizex);
1655        if (sizey) tmp += "&height="+encodeURIComponent(sizey);
1656        if (aspect) tmp += "&cropratio="+encodeURIComponent(aspect);
1657        if (color) tmp += "&color="+encodeURIComponent(color);
1658        return tmp;
1659    },
1660
1661    _tk_cyr2latChars : [['а', 'f'], ['б', ','], ['в', 'd'], ['г', 'u'],['д', 'l'],  ['е', 't'], ['ё', '`'], ['ж', ';'], ['з', 'p'],['и', 'b'], ['й', 'q'], ['к', 'r'], ['л', 'k'],['м', 'v'],  ['н', 'y'], ['о', 'j'], ['п', 'g'],  ['р', 'h'],['с', 'c'], ['т', 'n'], ['у', 'e'], ['ф', 'a'],['х', '['],  ['ц', 'w'], ['ч', 'x'],['ш', 'i'], ['щ', 'o'],['ъ', ']'],  ['ы', 's'], ['ь', 'm'],  ['э', "'"], ['ю', '.'], ['я', 'z'],['А', 'F'], ['Б', ','],  ['В', 'D'], ['Г', 'U'],['Д', 'L'], ['Е', 'T'], ['Ё', '`'],  ['Ж', ';'], ['З', 'P'],['И', 'B'], ['Й', 'Q'],  ['К', 'R'], ['Л', 'K'],['М', 'V'], ['Н', 'Y'], ['О', 'J'],  ['П', 'G'],  ['Р', 'H'],['С', 'C'], ['Т', 'N'],  ['У', 'E'], ['Ф', 'A'],['Х', '['], ['Ц', 'W'], ['Ч', 'X'], ['Ш', 'I'], ['Щ', 'O'],['Ъ', ']'],  ['Ы', 'S'],['Ь', 'M'],['Э', "'"],['Ю', '.'],['Я', 'Z'],['a', 'a'], ['b', 'b'], ['c', 'c'], ['d', 'd'], ['e', 'e'],['f', 'f'], ['g', 'g'], ['h', 'h'], ['i', 'i'], ['j', 'j'],['k', 'k'], ['l', 'l'], ['m', 'm'], ['n', 'n'], ['o', 'o'],['p', 'p'], ['q', 'q'], ['r', 'r'], ['s', 's'], ['t', 't'],['u', 'u'], ['v', 'v'], ['w', 'w'], ['x', 'x'], ['y', 'y'],['z', 'z'],['A', 'A'], ['B', 'B'], ['C', 'C'], ['D', 'D'],['E', 'E'],['F', 'F'],['G', 'G'],['H', 'H'],['I', 'I'],['J', 'J'],['K', 'K'],['L', 'L'], ['M', 'M'], ['N', 'N'], ['O', 'O'],['P', 'P'],['Q', 'Q'],['R', 'R'],['S', 'S'],['T', 'T'],['U', 'U'],['V', 'V'],['W', 'W'], ['X', 'X'], ['Y', 'Y'], ['Z', 'Z'],[' ', '-'],['0', '0'],['1', '1'],['2', '2'],['3', '3'],['4', '4'],['5', '5'],['6', '6'],['7', '7'],['8', '8'],['9', '9'],['-', '-']],
1662    _tk_lat2cyrChars : [['f','а'], [',','б'], ['d','в'], ['u','г'],['l','д'],  ['t','е'], ['`','ё'], [';','ж'], ['p','з'],['b','и'], ['q','й'], ['r','к'], ['k','л'],['v','м'],  ['y','н'], ['j','о'], ['g','п'],  ['h','р'],['c','с'], ['n','т'], ['e','у'], ['a','ф'],['[','х'],  ['w','ц'], ['x','ч'],['i','ш'], ['o','щ'],[']','ъ'],  ['s','ы'], ['m','ь'],  ["'",'э'], ['.','ю'], ['z','я'],['F','А'], [',','Б'],  ['D','В'], ['U','Г'],['L','Д'], ['T','Е'], ['`','Ё'],  [';','Ж'], ['P','З'],['B','И'], ['Q','Й'],  ['R','К'], ['K','Л'],['V','М'], ['Y','Н'], ['J','О'],  ['G','П'],  ['H','Р'],['C','С'], ['N','Т'],  ['E','У'], ['A','Ф'],['[','Х'], ['W','Ц'], ['X','Ч'], ['I','Ш'], ['O','Щ'],[']','Ъ'],  ['S','Ы'],['M','Ь'],["'",'Э'],['.','Ю'],['Z','Я'],['а', 'а'], ['б', 'б'], ['в', 'в'], ['г', 'г'], ['е', 'е'],['ё', 'ё'], ['ж', 'ж'], ['з', 'з'], ['и', 'и'], ['й', 'й'],['к', 'к'], ['л', 'л'], ['м', 'м'], ['н', 'н'], ['о', 'о'],['п', 'п'], ['р', 'р'], ['с', 'с'], ['т', 'т'], ['у', 'у'],['ф', 'ф'], ['х', 'х'], ['ц', 'ц'], ['ч', 'ч'], ['ш', 'ш'],['щ', 'щ'], ['ь', 'ь'], ['ъ', 'ъ'], ['ы', 'ы'], ['э', 'э'],['ю', 'ю'], ['я', 'я'],['А', 'А'], ['Б', 'Б'], ['В', 'В'], ['Г', 'Г'], ['Е', 'Е'],['Ё', 'Ё'], ['Ж', 'Ж'], ['З', 'З'], ['И', 'И'], ['Й', 'Й'],['К', 'К'], ['Л', 'Л'], ['М', 'М'], ['Н', 'Н'], ['О', 'О'],['П', 'П'], ['Р', 'Р'], ['С', 'С'], ['Т', 'Т'], ['У', 'У'],['Ф', 'Ф'], ['Х', 'Х'], ['Ц', 'Ц'], ['Ч', 'Ч'], ['Ш', 'Ш'],['Щ', 'Щ'], ['Ь', 'Ь'], ['Ъ', 'Ъ'], ['Ы', 'Ы'], ['Э', 'Э'],['Ю', 'Ю'], ['Я', 'Я'],[' ', '-'],['0', '0'],['1', '1'],['2', '2'],['3', '3'],['4', '4'],['5', '5'],['6', '6'],['7', '7'],['8', '8'],['9', '9'],['-', '-']],
1663    _ts_cyr2latChars : [['а', 'a'], ['б', 'b'], ['в', 'v'], ['г', 'g'],['д', 'd'],  ['е', 'e'], ['ё', 'yo'], ['ж', 'zh'], ['з', 'z'],['и', 'i'], ['й', 'y'], ['к', 'k'], ['л', 'l'],['м', 'm'],  ['н', 'n'], ['о', 'o'], ['п', 'p'],  ['р', 'r'],['с', 's'], ['т', 't'], ['у', 'u'], ['ф', 'f'],['х', 'h'],  ['ц', 'c'], ['ч', 'ch'],['ш', 'sh'], ['щ', 'shch'],['ъ', ''],  ['ы', 'y'], ['ь', ''],  ['э', 'e'], ['ю', 'yu'], ['я', 'ya'],['А', 'A'], ['Б', 'B'],  ['В', 'V'], ['Г', 'G'],['Д', 'D'], ['Е', 'E'], ['Ё', 'YO'],  ['Ж', 'ZH'], ['З', 'Z'],['И', 'I'], ['Й', 'Y'],  ['К', 'K'], ['Л', 'L'],['М', 'M'], ['Н', 'N'], ['О', 'O'],  ['П', 'P'],  ['Р', 'R'],['С', 'S'], ['Т', 'T'],  ['У', 'U'], ['Ф', 'F'],['Х', 'H'], ['Ц', 'C'], ['Ч', 'CH'], ['Ш', 'SH'], ['Щ', 'SHCH'],['Ъ', ''],  ['Ы', 'Y'],['Ь', ''],['Э', 'E'],['Ю', 'YU'],['Я', 'YA'],['a', 'a'], ['b', 'b'], ['c', 'c'], ['d', 'd'], ['e', 'e'],['f', 'f'], ['g', 'g'], ['h', 'h'], ['i', 'i'], ['j', 'j'],['k', 'k'], ['l', 'l'], ['m', 'm'], ['n', 'n'], ['o', 'o'],['p', 'p'], ['q', 'q'], ['r', 'r'], ['s', 's'], ['t', 't'],['u', 'u'], ['v', 'v'], ['w', 'w'], ['x', 'x'], ['y', 'y'],['z', 'z'],['A', 'A'], ['B', 'B'], ['C', 'C'], ['D', 'D'],['E', 'E'],['F', 'F'],['G', 'G'],['H', 'H'],['I', 'I'],['J', 'J'],['K', 'K'],['L', 'L'], ['M', 'M'], ['N', 'N'], ['O', 'O'],['P', 'P'],['Q', 'Q'],['R', 'R'],['S', 'S'],['T', 'T'],['U', 'U'],['V', 'V'],['W', 'W'], ['X', 'X'], ['Y', 'Y'], ['Z', 'Z'],[' ', '-'],['0', '0'],['1', '1'],['2', '2'],['3', '3'],['4', '4'],['5', '5'],['6', '6'],['7', '7'],['8', '8'],['9', '9'],['-', '-']],
1664
1665    translit_keyboard : function(str,reverse){
1666        let alp;
1667        if (reverse === 1 || reverse === true ){
1668            alp = Toolbox._tk_lat2cyrChars;
1669        } else {
1670            alp = Toolbox._tk_cyr2latChars;
1671        }
1672        let newStr = new String();
1673        for (let i = 0; i < str.length; i++) {
1674            ch = str.charAt(i);
1675            let newCh = '';
1676            for (let j = 0; j < alp.length; j++){
1677                if (ch == alp[j][0]) {
1678                    newCh = alp[j][1];
1679                }
1680            }
1681            newStr += newCh;
1682        }
1683        return newStr.replace(/[_]{2,}/gim, '_').replace(/\n/gim, '');
1684    },
1685
1686    translit_string : function(str) {
1687        var newStr = new String();
1688        for (var i = 0; i < str.length; i++) {
1689            ch = str.charAt(i);
1690            var newCh = '';
1691            for (var j = 0; j < Toolbox._ts_cyr2latChars.length; j++) {
1692                if (ch == Toolbox._ts_cyr2latChars[j][0]) {
1693                    newCh = Toolbox._ts_cyr2latChars[j][1];
1694                }
1695            }
1696            newStr += newCh;
1697        }
1698        return newStr.replace(/[_]{2,}/gim, '_').replace(/\n/gim, '');
1699    },
1700    
1701    hashSHA256 : function(str) {
1702        let hash = 0;
1703        for (let i = 0; i < str.length; i++) {
1704            const char = str.charCodeAt(i);
1705            hash = ((hash << 5) - hash) + char;
1706            hash = hash & hash;
1707        }
1708        return Math.abs(hash).toString(36).substr(0, 16);
1709    },
1710
1711    generate_cache_key : function(tmp, visited) {
1712        if (typeof visited === 'undefined') {
1713            visited = typeof WeakSet !== 'undefined' ? new WeakSet() : null;
1714        }
1715        if (typeof tmp === 'object' && tmp !== null) {
1716            if (visited && visited.has(tmp)) return null;
1717            if (visited) visited.add(tmp);
1718        }
1719        var type = typeof tmp;
1720        if (tmp === null) return '';
1721        switch (type) {
1722            case 'string':
1723            case 'number':
1724            case 'boolean':
1725                return btoa(String(tmp));
1726            case 'object':
1727                var objectType = Object.prototype.toString.call(tmp);
1728                if (objectType === '[object FileList]') {
1729                    return (tmp.length === 0 || (tmp.length === 1 && tmp[0].size === 0)) ? '' : null;
1730                }
1731                if (objectType === '[object Object]' || Array.isArray(tmp)) {
1732                    var keys = Object.keys(tmp).sort();
1733                    var result = [];
1734                    for (var i = 0; i < keys.length; i++) {
1735                        var key = keys[i];
1736                        var value = tmp[key];
1737                        var valueType = typeof value;
1738                        if (valueType === 'object' && value !== null) {
1739                            var cacheKey = Toolbox.generate_cache_key(value, visited);
1740                            if (cacheKey === null) return null;
1741                            result.push(key + '=' + cacheKey);
1742                        } else if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
1743                            result.push(key + '->' + btoa(String(value)));
1744                        } else {
1745                            return null;
1746                        }
1747                    }
1748                    return '|' + result.join('&') + '|';
1749                }
1750                return null;
1751            default:
1752                return null;
1753        }
1754    }
1755}
1756
1757class CMS {
1758
1759    // -------------------------
1760    // Статические свойства
1761    // -------------------------
1762
1763    static connector = null;
1764    static connector_user = null;
1765
1766    static _page_loading = false;
1767    static _upload_queue = [];
1768    static _action_queue = [];
1769    static _current_upload = null;
1770    static _current_action = null;
1771
1772    // -------------------------
1773    // init()
1774    // -------------------------
1775
1776    static init() {
1777        console.log('CMS.init()');
1778        CMS.connector = new cms_connector();
1779        CMS.connector_user = new cms_connector('user');
1780        Env.init();
1781    }
1782
1783    // -------------------------
1784    // widgets
1785    // -------------------------
1786
1787    static widgets = {
1788
1789        connected: {},
1790        loading: {},
1791        timer: 0,
1792
1793        ready_checker: function () {
1794            const dn = Date.now();
1795
1796            for (const widgetname in CMS.widgets.loading) {
1797                const t = CMS.widgets.loading[widgetname];
1798
1799                if (typeof CMS.widgets.connected[widgetname] !== 'undefined') {
1800                    delete CMS.widgets.loading[widgetname];
1801
1802                    if (typeof t.callback_onready === 'function') {
1803                        if (typeof t.context === 'object') {
1804                            t.callback_onready.apply(t.context);
1805                        } else {
1806                            t.callback_onready();
1807                        }
1808                    }
1809                } else {
1810                    if ((dn - t.started) > 20 * 1000) {
1811                        delete CMS.widgets.loading[widgetname];
1812                        console.log('widgetConnect : stopped loading ' + widgetname + ' by time limit');
1813                    }
1814                }
1815            }
1816
1817            if (Object.keys(CMS.widgets.loading).length && CMS.widgets.timer){
1818                clearInterval(CMS.widgets.timer);
1819                CMS.widgets.timer = 0;
1820            }
1821        },
1822
1823        connect: function (widgetname, callback_onready, context, admin_css) {
1824            if (typeof widgetname !== 'string' || !widgetname) {
1825                console.log('widgetConnect : invalid name key');
1826                return false;
1827            }
1828
1829            if (typeof CMS.widgets.connected[widgetname] !== 'undefined') {
1830                if (typeof callback_onready === 'function') {
1831                    if (typeof context === 'object') callback_onready.apply(context);
1832                    else callback_onready();
1833                }
1834                return true;
1835            }
1836
1837            if (typeof CMS.widgets.loading[widgetname] !== 'undefined') {
1838                console.log('widgetConnect : already connecting ' + widgetname);
1839                return false;
1840            }
1841
1842            console.log('Connecting widget: ' + widgetname);
1843
1844            CMS.widgets.loading[widgetname] = {callback_onready,context,started: Date.now()};
1845            let script = document.createElement('script');
1846            script.type = 'text/javascript';
1847            script.src = Env.path_widgets_lib + widgetname + '/' + widgetname + '.js';
1848            document.body.appendChild(script);
1849
1850            let html = '<link rel="stylesheet" type="text/css" media="screen" href="'+Env.path_widgets_lib + widgetname + '/' + widgetname + '.css" />';
1851            if (Env.auth_zone == 'backend' && admin_css) {
1852                html += '<link rel="stylesheet" type="text/css" media="screen" href="'+Env.path_widgets_lib + widgetname + '/' + widgetname + '_be.css" />';
1853            }
1854
1855            document.body.insertAdjacentHTML('beforeend', html);
1856
1857            if (!CMS.widgets.timer) {
1858                CMS.widgets.timer = setTimeout(CMS.widgets.ready_checker, 50);
1859            }
1860
1861            return false;
1862        }
1863    };
1864
1865    // -------------------------
1866    // page_add_to_history()
1867    // -------------------------
1868
1869    static page_add_to_history(url, title, state, meta, do_add_state) {
1870        let protocol = '';
1871
1872        if (url.indexOf('https://') === 0) protocol = 'https:';
1873        else if (url.indexOf('http://') === 0) protocol = 'http:';
1874
1875        if (!protocol) {
1876            console.error('Bad url : unknown protocol');
1877            return false;
1878        }
1879
1880        if (document.location.protocol !== protocol && protocol === 'http:') {
1881            url = url.replace('http://', 'https://');
1882        }
1883
1884        do_add_state = (typeof do_add_state === 'undefined') ? true : !!do_add_state;
1885
1886        if (do_add_state) history.pushState(state, title, url);
1887
1888        if (title || do_add_state) {
1889            const titleEl = document.querySelector('head title');
1890            if (titleEl) titleEl.textContent = title;
1891        }
1892
1893        if (typeof meta !== 'object') return true;
1894
1895        if (typeof meta.keywords !== 'undefined') {
1896            if (do_add_state || meta.keywords) {
1897                const metaKeywords = document.querySelector('head meta[name="Keywords"]');
1898                if (metaKeywords) metaKeywords.setAttribute('content', meta.keywords);
1899            }
1900        }
1901
1902        if (typeof meta.description !== 'undefined') {
1903            if (do_add_state || meta.description) {
1904                const metaDescription = document.querySelector('head meta[name="Description"]');
1905                if (metaDescription) metaDescription.setAttribute('content', meta.description);
1906            }
1907        }
1908
1909        return true;
1910    }
1911
1912    // -------------------------
1913    // page_load wrappers
1914    // -------------------------
1915
1916    static page_load_by_page(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish) {
1917        return CMS.page_load(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish, true);
1918    }
1919
1920    static page_refresh(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish, by_page) {
1921        return CMS.page_load(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish, by_page, true);
1922    }
1923
1924    static page_refresh_by_page(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish) {
1925        return CMS.page_load(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish, true, true);
1926    }
1927
1928    // -------------------------
1929    // page_load()
1930    // -------------------------
1931
1932    static page_load(url, callback, context, callback_failed, context_data, onloading_start, onloading_finish, by_page, mode_refresh) {
1933        if (CMS._page_loading) {
1934            console.warn('page already loading. cancelled..');
1935            return false;
1936        }
1937
1938        if (typeof callback !== 'function') {
1939            console.error('Callback is required for load page function!');
1940            return false;
1941        }
1942
1943        console.log('fDialogs.page_' + (mode_refresh ? 'refresh' : 'load') + '() ' + url);
1944
1945        mode_refresh = (typeof mode_refresh === 'undefined') ? false : !!mode_refresh;
1946
1947        let go_url =
1948            document.location.protocol +
1949            '//' +
1950            document.location.hostname +
1951            '/_ajax_page' +
1952            (mode_refresh ? '_refresh' : '') +
1953            '/?url=' +
1954            encodeURIComponent(url);
1955
1956     
1956   let request = {
1957            url_original: url,
1958            url: go_url,
1959            onloading_start: (typeof onloading_start === 'function' ? onloading_start : fDialogs._onloading_start),
1960            onloading_finish: (typeof onloading_finish === 'function' ? onloading_finish : fDialogs._onloading_finish),
1961            callback: callback,
1962            onloading_failed: (typeof callback_failed === 'function' ? callback_failed : callback),
1963            context_data: context_data,
1964            context: context,
1965            by_page: ((typeof by_page !== undefined) ? !!by_page : false),
1966            mode_refresh: mode_refresh
1967        };
1968
1969        let xhr = new XMLHttpRequest();
1970
1971        if (Env._can_parse_json) {
1972            xhr.responseType = 'json';
1973        } else {
1974            xhr.onloadstart = function () { this.responseType = "json"; };
1975        }
1976
1977        xhr.open("GET", request.url, true);
1978        xhr.request = request;
1979
1980        xhr.onreadystatechange = CMS._page_load_response;
1981
1982        CMS._page_loading = true;
1983
1984        if (typeof request.onloading_start === 'function') {
1985            if (typeof request.context === 'object') {
1986                request.onloading_start.apply(request.context, [request.context_data, request, xhr]);
1987            } else {
1988                request.onloading_start(request.context_data, request, xhr);
1989            }
1990        }
1991
1992        try {
1993            xhr.send();
1994        } catch (err) {
1995            console.log('XHR error on loading page');
1996
1997            if (typeof request.onloading_failed === 'function') {
1998                if (typeof request.context === 'object') {
1999                    return request.onloading_failed.apply(request.context, [null, context_data, request, xhr, err]);
2000                } else {
2001                    return request.onloading_failed(null, context_data, request, xhr, err);
2002                }
2003            } else {
2004                if (typeof request.context === 'object') {
2005                    return request.callback.apply(request.context, [null, context_data, request, xhr, err]);
2006                } else {
2007                    request.callback(null, context_data, request, xhr, err);
2008                }
2009            }
2010
2011            return false;
2012        }
2013
2014        return true;
2015    }
2016
2017    // -------------------------
2018    // _page_load_response()
2019    // -------------------------
2020
2021    static _page_load_response(event) {
2022
2023        if (this.readyState !== XMLHttpRequest.DONE) return true;
2024
2025        let result = this.response;
2026        let request = this.request;
2027        let context_data = request.context_data;
2028
2029        CMS._page_loading = false;
2030
2031        if (typeof request.onloading_finish === 'function') {
2032            if (typeof request.context === 'object') {
2033                request.onloading_finish.apply(request.context, [result, context_data, request, this]);
2034            } else {
2035                request.onloading_finish.apply(this, [result, context_data, request, this]);
2036            }
2037        }
2038
2039        if (typeof result === 'undefined' || result === null) {
2040
2041            if (typeof request.onloading_failed === 'function') {
2042                if (typeof request.context === 'object') {
2043                    request.onloading_failed.apply(request.context, [result, context_data, null, this, 'Response parse error']);
2044                } else {
2045                    request.onloading_failed(result, context_data, null, this, 'Response parse error');
2046                }
2047            } else {
2048                if (typeof request.context === 'object') {
2049                    request.callback.apply(request.context, [result, context_data, request, this]);
2050                } else {
2051                    request.callback(result, context_data, request);
2052                }
2053            }
2054
2055        } else {
2056
2057            if (typeof request.context_data === 'object' && request.context_data !== null) {
2058                result['_context'] = request.context_data;
2059            }
2060
2061            result['_by_page'] = request.by_page;
2062
2063            if (result.http_status === 401 && result.redirect_url) {
2064
2065                if (typeof fDialogs.__onloading_unauthorize_response === 'function') {
2066                    if (typeof request.context === 'object') {
2067                        fDialogs.__onloading_unauthorize_response.apply(request.context, [
2068                            result.http_status,
2069                            result.redirect_url,
2070                            result,
2071                            context_data,
2072                            request,
2073                            this
2074                        ]);
2075                    } else {
2076                        fDialogs.__onloading_unauthorize_response.apply(this, [
2077                            result.http_status,
2078                            result.redirect_url,
2079                            result,
2080                            context_data,
2081                            request,
2082                            this
2083                        ]);
2084                    }
2085                } else {
2086                    document.location.href = result.redirect_url;
2087                }
2088
2089                return false;
2090
2091            } else {
2092
2093                if (typeof request.context === 'object') {
2094                    request.callback.apply(request.context, [result, context_data, request, this]);
2095                } else {
2096                    request.callback(result, context_data, request);
2097                }
2098            }
2099        }
2100
2101        let state = { url: request.url_original, handler: false };
2102
2103        if (typeof request.context === 'object') {
2104            if (typeof request.context.get_state === 'function') {
2105                state = request.context.get_state();
2106            } else if (typeof request.context.last_search_form !== 'undefined') {
2107                state = request.context.last_search_form;
2108            } else if (typeof request.context.state === 'object') {
2109                state = request.context.state;
2110            }
2111        }
2112
2113        let url = request.url_original;
2114
2115        if (url.indexOf('://') === -1) {
2116            url = document.location.protocol + '//' + document.location.hostname + request.url_original;
2117        }
2118
2119        CMS.page_add_to_history(url, result.title, state, result.meta, !request.mode_refresh);
2120    }
2121
2122    // -------------------------
2123    // call_action_* wrappers
2124    // -------------------------
2125
2126    static call_action_logout(callback, onloading_start, onloading_finish, data) {
2127        const request_data = new FormData();
2128        request_data.append('encoded_page_context', __EPC);
2129        return CMS._call_action(request_data, data, callback, onloading_start, onloading_finish, 'logout');
2130    }
2131
2132    static call_action_check(object_id, record_id, status, data, callback, onloading_start, onloading_finish) {
2133        const request_data = new FormData();
2134        request_data.append('Object_ID', object_id);
2135        request_data.append('Record_ID', record_id);
2136        request_data.append('status', status ? 1 : 0);
2137        request_data.append('encoded_page_context', __EPC);
2138
2139        console.log('Calling CHECK action. Record: ' + record_id + ". Set FLAG: " + (status ? 1 : 0));
2140
2141        return CMS._call_action(request_data, data, callback, onloading_start, onloading_finish, 'check');
2142    }
2143
2144    static call_action_add(formdata_or_id, data, callback, onloading_start, onloading_finish) {
2145        return CMS._call_action(formdata_or_id, data, callback, onloading_start, onloading_finish, 'add');
2146    }
2147
2148    static call_action_edit(formdata_or_id, data, callback, onloading_start, onloading_finish) {
2149        return CMS._call_action(formdata_or_id, data, callback, onloading_start, onloading_finish, 'edit');
2150    }
2151
2152    static call_action_delete(object_id, record_id, data, callback, onloading_start, onloading_finish) {
2153        if (!object_id || !record_id) {
2154            const msg = 'delete error. bad input params';
2155            console.log(msg);
2156            fDialogs.alert(msg);
2157            return false;
2158        }
2159
2160        const request_data = new FormData();
2161        request_data.append('Object_ID', object_id);
2162        request_data.append('Record_ID', record_id);
2163        request_data.append('encoded_page_context', __EPC);
2164
2165        return CMS._call_action(request_data, data, callback, onloading_start, onloading_finish, 'delete');
2166    }
2167
2168    // -------------------------
2169    // _call_action()
2170    // -------------------------
2171
2172    static _call_action(formdata_or_id, data, callback, onloading_start, onloading_finish, action) {
2173
2174        if (!formdata_or_id || !action || !callback || typeof callback !== 'function') {
2175            console.log('Bad input data for action');
2176            return false;
2177        }
2178
2179        let formData = null;
2180
2181        if (typeof formdata_or_id === 'string' && formdata_or_id.length) {
2182            formData = Toolbox.form_export_formdata(formdata_or_id);
2183            if (!formData) {
2184                console.error('Error exporting form!');
2185                return false;
2186            }
2187        } else if (formdata_or_id instanceof FormData) {
2188            formData = formdata_or_id;
2189        } else {
2190            console.log('Invalid form data type');
2191            return false;
2192        }
2193
2194        const go_url =
2195            document.location.protocol +
2196            '//' +
2197            document.location.hostname +
2198            '/~a/' +
2199            action +
2200            '/';
2201
2202        const request = {
2203            url: go_url,
2204            onloading_start: (typeof onloading_start === 'function' ? onloading_start : fDialogs._onloading_start),
2205            onloading_finish: (typeof onloading_finish === 'function' ? onloading_finish : fDialogs._onloading_finish),
2206            callback: callback,
2207            formData: formData,
2208            data: data
2209        };
2210
2211        CMS._action_queue.push(request);
2212        return CMS.__call_action_process();
2213    }
2214
2215    // -------------------------
2216    // __call_action_process()
2217    // -------------------------
2218
2219    static __call_action_process() {
2220
2221        if (!CMS._action_queue.length) return false;
2222
2223        CMS._current_action = CMS._action_queue.shift();
2224        const request = CMS._current_action;
2225
2226        const xhr = new XMLHttpRequest();
2227        xhr.responseType = 'json';
2228        xhr.open("POST", request.url, true);
2229
2230        xhr.onreadystatechange = function () {
2231
2232            if (this.readyState !== XMLHttpRequest.DONE) return true;
2233
2234            const result = this.response;
2235            const request = this.request;
2236
2237            if (typeof request.onloading_finish === 'function') {
2238                request.onloading_finish();
2239            }
2240
2241            request.callback(result, request.data, this);
2242
2243            CMS._current_action = null;
2244
2245            return CMS.__call_action_process();
2246        };
2247
2248        if (typeof request.onloading_start === 'function') {
2249            request.onloading_start();
2250        }
2251
2252        xhr.request = request;
2253
2254        try {
2255            xhr.send(request.formData);
2256        } catch (err) {
2257
2258            if (request.onloading_finish) request.onloading_finish();
2259
2260            request.callback(
2261                { passed: 0, error: -400, error_message: 'XHR problem', request },
2262                request.data,
2263                xhr
2264            );
2265
2266
2267            CMS._current_action = null;
2268            return false;
2269        }
2270
2271        return false;
2272    }
2273
2274    // ---------------------------------------------------------
2275    // upload_file_to_record_field()
2276    // ---------------------------------------------------------
2277
2278    /**
2279     * Uploads one or multiple files to a specific record field.
2280     *
2281     * @param {Object} params
2282     * @returns {Boolean}
2283     */
2284    static upload_file_to_record_field(params) {
2285
2286        if (typeof params === 'undefined' || typeof params.upload_input === 'undefined') {
2287            console.error('bad upload params');
2288            return null;
2289        }
2290
2291        const elem = JDOM.get_dom_element(params.upload_input, false);
2292
2293        if (!elem) {
2294            console.error('bad upload params or no valid file input element');
2295            return null;
2296        }
2297
2298        const inp_id = JDOM.generate_new_unique_dom_id(elem);
2299
2300        let ttf = typeof params.target_field;
2301        if (!(ttf === 'string' || ttf === 'number')) {
2302            console.error('bad target field!');
2303            return null;
2304        }
2305
2306        if (typeof params.record_id === 'string') {
2307            params.record_id = parseInt(params.record_id);
2308        }
2309
2310        if (typeof params.record_id !== 'number' || !params.record_id) {
2311            console.log('bad target record id!');
2312            return null;
2313        }
2314
2315        console.log('CMS.upload_file_to_record_field() ' + Math.random());
2316
2317        let mime_pattern = (typeof params.mime === 'string') ? params.mime : null;
2318
2319        if (typeof elem === 'undefined' || typeof elem.files === 'undefined') {
2320            console.log('File field upload error. Bad type of field');
2321            return null;
2322        }
2323
2324        if (!elem.files.length) {
2325            console.log('no files selected');
2326            return null;
2327        }
2328
2329        let max = (typeof params.max === 'number') ? params.max : 0;
2330        let files = elem.files;
2331        let context = (typeof params.context === 'object') ? params.context : null;
2332
2333        let arr = [];
2334
2335        for (let i = 0, c = files.length; i < c; i++) {
2336
2337            if (max && i >= max) break;
2338
2339            if (mime_pattern) {
2340                if (!Toolbox.mime_match(files[i].type, mime_pattern)) {
2341                    console.log('file ' + files[i].name + ' type mismatch');
2342                    if (typeof params.mime_onfailed === 'function') {
2343                        params.mime_onfailed(files[i].type, files[i].name, mime_pattern);
2344                    }
2345                    continue;
2346                }
2347            }
2348
2349            let tmp = {
2350                file: files[i],
2351                mime_pattern: mime_pattern,
2352                onsuccess: (typeof params.onsuccess === 'function') ? params.onsuccess : null,
2353                onfailed: (typeof params.onfailed === 'function') ? params.onfailed : null,
2354                connector_pre: (typeof params.connector_pre === 'string') ? params.connector_pre : null,
2355                connector_post: (typeof params.connector_post === 'string') ? params.connector_post : null,
2356                onprogress: (typeof params.onprogress === 'function') ? params.onprogress : null,
2357                context: context,
2358                onupload_start: (typeof params.onupload_start === 'function') ? params.onupload_start : null,
2359                onupload_end: (typeof params.onupload_end === 'function') ? params.onupload_end : null,
2360                target_field: params.target_field,
2361                last: false,
2362                record_id: params.record_id
2363            };
2364
2365            console.log('add file upload : ' + files[i].name);
2366            arr.push(tmp);
2367        }
2368
2369        if (!arr.length) {
2370            console.log('upload queue is empty. skipping upload');
2371            return null;
2372        }
2373
2374        if (typeof params.onupload_queue_start === 'function') {
2375            if (context) params.onupload_queue_start.apply(context, [params]);
2376            else params.onupload_queue_start(params);
2377        }
2378
2379        arr[arr.length - 1].last = true;
2380
2381        if (typeof params.onupload_queue_end === 'function') {
2382            arr[arr.length - 1].onupload_queue_end = params.onupload_queue_end;
2383        }
2384
2385        for (let i = arr.length - 1; i >= 0; i--) {
2386            CMS._upload_queue.push(arr[i]);
2387        }
2388
2389        CMS.__upload_file_to_record_field_start();
2390        return true;
2391    }
2392
2393    // ---------------------------------------------------------
2394    // __upload_file_to_record_field_start()
2395    // ---------------------------------------------------------
2396
2397    static __upload_file_to_record_field_start() {
2398
2399        if (!CMS._upload_queue.length) return true;
2400
2401        if (CMS._current_upload) {
2402            console.log('already uploading!');
2403            return false;
2404        }
2405
2406        let upload = CMS._upload_queue.pop();
2407        CMS._current_upload = upload;
2408
2409        let formData = new FormData();
2410        formData.append("fileToUpload", upload.file);
2411        formData.append("mime_pattern", upload.mime_pattern);
2412
2413        let url =
2414            document.location.protocol +
2415            '//' +
2416            document.location.hostname +
2417            '/_upload/?action=update_record_field' +
2418            '&target_field=' + encodeURIComponent(upload.target_field) +
2419            '&record_id=' + encodeURIComponent(upload.record_id);
2420
2421        if (upload.connector_pre) url += '&connector_pre=' + encodeURIComponent(upload.connector_pre);
2422        if (upload.connector_post) url += '&connector_post=' + encodeURIComponent(upload.connector_post);
2423        if (upload.last) url += '&last=1';
2424
2425        let xhr = new XMLHttpRequest();
2426        xhr.responseType = 'json';
2427        xhr.open("POST", url, true);
2428
2429        xhr.onreadystatechange = function () {
2430
2431            if (this.readyState !== 4) return true;
2432
2433            let upload = CMS._current_upload;
2434            let result = (typeof this.response !== 'undefined') ? this.response : null;
2435
2436            if (upload.onupload_end) {
2437                if (upload.context) upload.onupload_end.apply(upload.context, [upload, result]);
2438                else upload.onupload_end(upload, result);
2439            }
2440
2441            if (result === null || typeof result.passed === 'undefined' || !result.passed) {
2442
2443                if (upload.onfailed) {
2444                    if (upload.context) upload.onfailed.apply(upload.context, [result, this]);
2445                    else upload.onfailed(result, xhr);
2446                } else {
2447                    if (result !== null && typeof result === 'object') stdcheck(result);
2448                    else alert('Error while uploading file');
2449                }
2450
2451            } else {
2452                if (upload.context) upload.onsuccess.apply(upload.context, [result, this]);
2453                else upload.onsuccess(result, this);
2454            }
2455
2456            if (CMS._upload_queue.length) {
2457                delete CMS._current_upload;
2458                CMS.__upload_file_to_record_field_start();
2459            } else {
2460
2461                if (typeof upload.onupload_queue_end === 'function') {
2462                    if (upload.context) upload.onupload_queue_end.apply(upload.context, [upload, result]);
2463                    else upload.onupload_queue_end(upload, result);
2464                }
2465
2466                delete CMS._current_upload;
2467            }
2468
2469            return true;
2470        };
2471
2472        if (upload.onupload_start) {
2473            if (upload.context) upload.onupload_start.apply(upload.context, [upload]);
2474            else upload.onupload_start(upload);
2475        }
2476
2477        try {
2478            xhr.send(formData);
2479        } catch (err) {
2480
2481            if (upload.onupload_end) {
2482                if (upload.context) upload.onupload_end.apply(upload.context, [upload, err]);
2483                else upload.onupload_end(upload, err);
2484            }
2485
2486            console.log('XHR error on sending file');
2487
2488            if (typeof upload.onfailed === 'function') {
2489                if (upload.context) return upload.onfailed.apply(upload.context, [xhr]);
2490                else upload.onfailed(xhr);
2491            }
2492
2493            return false;
2494        }
2495
2496        return false;
2497    }
2498
2499    static upload_temp_file(field_or_id,mime,onsuccess,onfailed,connector_function_pre,connector_function_post,connector_data,context){
2500        console.log('CMS.upload_temp_file() '+Math.random());
2501        let file=null,elem=null;
2502        if (field_or_id instanceof File) file=field_or_id; else {
2503            elem=JDOM.get_dom_element(field_or_id,true);
2504            if (!elem) return console.error('bad file'),false;
2505            if (!elem.id) elem.id=JDOM.generate_new_unique_dom_id();
2506        }
2507        let myurl='/_upload/?action=upload_file';
2508        if (connector_function_pre||connector_function_post){
2509            if (connector_function_pre) myurl+='&connector_pre='+encodeURIComponent(connector_function_pre);
2510            if (connector_function_post) myurl+='&connector_post='+encodeURIComponent(connector_function_post);
2511            if (connector_data) for (let k in connector_data){
2512                let v=connector_data[k];
2513                if (typeof v=='string'||typeof v=='number'||typeof v=='boolean') myurl+='&data['+k+']='+encodeURIComponent(v);
2514            }
2515        }
2516        if (mime) myurl+='&mime='+mime;
2517        const fd=new FormData();
2518        if (file) fd.append("fileToUpload",file); else {
2519            const files=elem.files;
2520            if (!files||!files.length){
2521                const err=[{passed:0,error:-1000811,error_message:'file not loaded'}];
2522                if (typeof onfailed=='function') return context?onfailed.apply(context,[err]):onfailed(err),false;
2523                return false;
2524            }
2525            for (const f of files) fd.append("fileToUpload[]",f);
2526        }
2527        const xhr=new XMLHttpRequest();
2528        xhr.responseType='json';
2529        xhr.open("POST",myurl,true);
2530        xhr.callback_success=onsuccess;
2531        xhr.callback_error=onfailed;
2532        xhr.context=context;
2533        xhr.onreadystatechange=function(){
2534            if (this.readyState!==4) return;
2535            const r=this.response;
2536            if (!r||r.passed===undefined){
2537                if (this.callback_error) return this.context?this.callback_error.apply(this.context,[r,this]):this.callback_error(r,this);
2538                return;
2539            }
2540            this.context?this.callback_success.apply(this.context,[r,this]):this.callback_success(r,this);
2541        };
2542        try { xhr.send(fd); }
2543        catch(e){
2544            console.log('XHR error on sending file');
2545            if (typeof onfailed=='function') return context?onfailed.apply(context,[xhr]):onfailed(xhr),false;
2546            return false;
2547        }
2548        return false;
2549    }
2550
2551    // ---------------------------------------------------------
2552    // uploaded_file_remove()
2553    // ---------------------------------------------------------
2554
2555    static uploaded_file_remove(file_id_or_path, callback, context) {
2556
2557        console.log('CMS.uploaded_file_remove() ' + Math.random());
2558
2559        let url = '/_upload.php?action=remove_uploaded_file&file=' + encodeURIComponent(file_id_or_path);
2560
2561        let xhr = new XMLHttpRequest();
2562        xhr.responseType = 'json';
2563        xhr.open("GET", url, true);
2564
2565        xhr.callback = callback;
2566        if (typeof context !== 'undefined') xhr.context = context;
2567
2568        xhr.onreadystatechange = function () {
2569
2570            if (xhr.readyState !== 4) return true;
2571
2572            let result = this.response;
2573
2574            if (typeof this.context !== 'undefined') {
2575                this.callback.apply(this.context, [result, xhr]);
2576            } else {
2577                this.callback(result, xhr);
2578            }
2579
2580            return true;
2581        };
2582
2583        try {
2584            xhr.send();
2585        } catch(err) {
2586            console.log('XHR error on sending file');
2587            if (typeof xhr.context !== 'undefined') {
2588                xhr.callback.apply(xhr.context, [null, xhr]);
2589            } else {
2590                xhr.callback(null, xhr);
2591            }
2592        }
2593
2594
2595        return false;
2596    }
2597}

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.