PageSourceSearch

https://diecezja.kalisz.pl/js/3w_tools.js

js diecezja.kalisz.pl collected 2026-10-02 18:48:38 UTC 157,400 bytes, 4,623 lines download raw bytes

1var global_counter = 1; // globalny unikalny licznik; może być tylko inkrementowany
2var EXPAND_BUTTONS = {}; // miejsce na dane przycisków zwijania/rozwijania
3var GENSELECT_CALLBACKS = {}; // miejsce na callbacki z funkcji genSelect
4
5function assert(cond, text){
6  if(!cond){
7    alert('ASSERT:\n'+text);
8  }
9}
10
11if(!Object.values){ // polyfill dla Object.values()
12  Object.values = function(obj){
13    return Object.keys(obj).map(function(e) {
14      return obj[e];
15    })
16  }
17}
18
19if(!String.replaceAt){ // polyfill dla String.replaceAt()
20  String.prototype.replaceAt = function(index, str){
21    return this.substr(0, index) + str + this.substr(index + str.length);
22  }
23}
24
25if (typeof Object.assign != 'function') { // polyfill dla Object.assign
26  // Must be writable: true, enumerable: false, configurable: true
27  Object.defineProperty(Object, "assign", {
28    value: function assign(target, varArgs) { // .length of function is 2
29      'use strict';
30      if (target == null) { // TypeError if undefined or null
31        throw new TypeError('Cannot convert undefined or null to object');
32      }
33
34      var to = Object(target);
35
36      for (var index = 1; index < arguments.length; index++) {
37        var nextSource = arguments[index];
38
39        if (nextSource != null) { // Skip over if undefined or null
40          for (var nextKey in nextSource) {
41            // Avoid bugs when hasOwnProperty is shadowed
42            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
43              to[nextKey] = nextSource[nextKey];
44            }
45          }
46        }
47      }
48      return to;
49    },
50    writable: true,
51    configurable: true
52  });
53}
54
55$.fn.exists = function(){ // zwraca true jeśli selektor jQuery zawiera jakieś elementy. Użycie: $('#element').exists()
56  return this.length !== 0;
57}
58
59
60
61// zamienia wszystkie puste anchory na void(0), żeby nie przewijało ekranu po kliknięciu na takim guziorze lub linku
62function replaceEmptyAnchors()
63{
64  $('a[href$="#"]').attr('href', 'javascript:void(0)');
65}
66
67addSeparators = function(nStr, separator){
68  separator = separator || ' ';
69  nStr += '';
70  const x = nStr.split('.');
71  let x1 = x[0];
72  const x2 = x.length > 1 ? '.' + x[1] : '';
73  var rgx = /(\d+)(\d{3})/;
74  while (rgx.test(x1)) {
75    x1 = x1.replace(rgx, '$1' + separator + '$2');
76  }
77  return x1 + x2;
78}
79
80formatCurrency = function(num, precision = 2, separator = ' '){ // konwertuje liczbę na format waluty
81  if(typeof precision != 'number'){
82    precision = 2;
83  }
84  num = isNaN(num) || num === '' || num === null ? 0.00 : num;
85  var str = parseFloat(num).toFixed(precision);
86  if(separator){
87    str = addSeparators(str, separator);
88  }
89  return str;
90}
91waluta = formatCurrency; // alias
92
93isSet = function(v){ // zwraca true jeśli podana zmienna jest zdefiniowana
94  return (typeof v != 'undefined')? true : false;
95}
96
97toBoolean =  function(v,true_value,false_value){ // zwraca wartość true_value dla v != 0, "0", false, null i undefined
98  if(!isSet(v) || v == 0 || v == '0' || v == false || v == null){
99    return isSet(false_value)? false_value : false;
100  }
101
102  return isSet(true_value)? true_value : true;
103}
104
105function padNumber(num, size) { // konwertuje liczbę na string zajmujący size pozycji
106    var s = "000000000" + num;
107    return s.substr(s.length-size);
108}
109
110function getUnixDay(date){ // zwraca JS Unix time of beginning of the 'date' given as Date object
111  return Math.floor(date.getTime() / 86400000)*86400000;
112}
113
114data2dataWizyty = function(data, separator) // konwertuje datę z formatu DD.MM.YYYY na YYYY-MM-DD
115{
116  if(data == ""){
117    return "";
118  }
119  separator = separator || '-';
120  return data.substr(6,4) + separator + data.substr(3,2) + separator + data.substr(0,2);
121}
122
123dataWizyty2data = function(data, separator) // konwertuje datę z formatu YYYY-MM-DD na DD.MM.YYYY
124{
125  if(data == ""){
126    return "";
127  }
128  separator = separator || '.';
129  return data.substr(8,2) + separator + data.substr(5,2) + separator + data.substr(0,4);
130}
131
132
133// Próbuje wyciągnąć godzinę i minutę z podanego stringa; string musi mieć postać [[h]h:][m]m
134// Funkcja przyjmuje zakres czasu od 00:00 do 23:59
135// Pominięcie godziny powoduje przyjęcie wartości 0. Jeśli nie da się wyciągnąć czasu, funkcja zwraca false.
136function validateTime(t, allow_seconds = false){
137  if(!t){
138    return false;
139  }
140
141  t = t.trim();
142
143  if(allow_seconds){
144    let match = t.match(/^([0-9]|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$|^([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$|^([0-9]|[0-5][0-9])$/i);
145    if(match){
146      if(match[1] && match[2] && match[3]){ // hh:mm:ss
147        return padNumber(match[1], 2) + ':' + padNumber(match[2], 2) +  ':' + padNumber(match[3], 2);
148      }
149      else if(match[4] && match[5]){ // mm:ss
150        return '00:' + padNumber(match[4], 2) +  ':' + padNumber(match[5], 2);
151      }
152      else if(match[6]){ // ss
153        return '00:00:' + padNumber(match[6], 2);
154      }
155      return '00:00:00';
156    }
157  }
158  else {
159    let match = t.match(/^([0-9]|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9])$|^([0-9]|[0-5][0-9])$/i);
160    if(match){
161      if(match[1] && match[2]){ // hh:mm
162        return padNumber(match[1], 2) + ':' + padNumber(match[2], 2);
163      }
164      if(match[3]){ // tylko minuty
165        return '00:' + padNumber(match[3], 2);
166      }
167    }
168  }
169  return false;
170}
171
172function validateAmount(numStr){
173  var numNum = +numStr;
174  return !isNaN(numNum);
175}
176
177// zwraca nazwę miesiąca podaną jako numer miesiąca z obiektu Date(), czyli styczeń to 0
178function getMonthName(mt, short_name = false, dopelniacz = false){
179
180  if(dopelniacz){
181    switch(+mt){
182    case  1 : return 'lutego';
183    case  2 : return 'marca';
184    case  3 : return 'kwietnia';
185    case  4 : return 'maja';
186    case  5 : return 'czerwca';
187    case  6 : return 'lipca';
188    case  7 : return 'sierpnia';
189    case  8 : return 'września';
190    case  9 : return 'października';
191    case 10 : return 'listopada';
192    case 11 : return 'grudnia';
193    default :
194      return 'stycznia';
195    }
196  }
197  else {
198    switch(+mt){
199    case  1 : return short_name? 'Lut' : 'Luty';
200    case  2 : return short_name? 'Mar' : 'Marzec';
201    case  3 : return short_name? 'Kwi' : 'Kwiecień';
202    case  4 : return short_name? 'Maj' : 'Maj';
203    case  5 : return short_name? 'Cze' : 'Czerwiec';
204    case  6 : return short_name? 'Lip' : 'Lipiec';
205    case  7 : return short_name? 'Sie' : 'Sierpień';
206    case  8 : return short_name? 'Wrz' : 'Wrzesień';
207    case  9 : return short_name? 'Paź' : 'Październik';
208    case 10 : return short_name? 'Lis' : 'Listopad';
209    case 11 : return short_name? 'Gru' : 'Grudzień';
210    default :
211      return short_name? 'Sty' : 'Styczeń';
212    }
213  }
214}
215
216// zwraca nazwę dnia tygodnia podaną jako numer z dat terminarza, czyli niedziela to 0, sobota to 6
217function getWeekdayName(wd, short_name = false, very_short_name = false, very_very_short_name = false){
218  switch((+wd) % 7){
219  case 0 : return short_name? (very_short_name? (very_very_short_name? 'N' : 'Nd') : 'Nie') : 'Niedziela';
220  case 1 : return short_name? (very_short_name? (very_very_short_name? 'P' : 'Po') : 'Pon') : 'Poniedziałek';
221  case 2 : return short_name? (very_short_name? (very_very_short_name? 'W' : 'Wt') : 'Wto') : 'Wtorek';
222  case 3 : return short_name? (very_short_name? (very_very_short_name? 'Ś' : 'Śr') : 'Śro') : 'Środa';
223  case 4 : return short_name? (very_short_name? (very_very_short_name? 'C' : 'Cz') : 'Czw') : 'Czwartek';
224  case 5 : return short_name? (very_short_name? (very_very_short_name? 'P' : 'Pt') : 'Pią') : 'Piątek';
225  case 6 : return short_name? (very_short_name? (very_very_short_name? 'S' : 'So') : 'Sob') : 'Sobota';
226  }
227  return '?weekday?';
228}
229
230// konwertuje dużą wartość na string celem uniknięcia scientific notation
231function toFixed(x){
232  if (Math.abs(x) < 1.0) {
233    var e = parseInt(x.toString().split('e-')[1],10);
234    if (e) {
235        x *= Math.pow(10,e-1);
236        x = '0.' + (new Array(e)).join('0') + x.toString().substring(2);
237    }
238  } else {
239    var e = parseInt(x.toString().split('+')[1],10);
240    if (e > 20) {
241        e -= 20;
242        x /= Math.pow(10,e);
243        x += (new Array(e+1)).join('0');
244    }
245  }
246  return x;
247}
248/*
249// podobnie jak $.center(), ale zawsze na środku ekranu, bez względu na przewinięcie
250jQuery.fn.center2 = function () {
251    this.css("position","absolute");
252    this.css("top", Math.max(0, (($(window).height() - $(this).outerHeight()) / 2) +
253                                                $(window).scrollTop()) + "px");
254    this.css("left", Math.max(0, (($(window).width() - $(this).outerWidth()) / 2) +
255                                                $(window).scrollLeft()) + "px");
256    return this;
257}
258*/
259
260$.fn.extend({
261    center: function () {
262        return this.each(function() {
263            var top = ($(window).height() - $(this).outerHeight()) / 2;
264            var left = ($(window).width() - $(this).outerWidth()) / 2;
265            $(this).css({position:'absolute', margin:0, top: (top > 0 ? top : 0)+'px', left: (left > 0 ? left : 0)+'px'});
266        });
267    },
268    center2: function () { // podobnie jak $.center(), ale zawsze na środku ekranu, bez względu na scroll dokumentu
269      var wh = $(window).height();
270      var ww = $(window).width();
271      var ws = $(window).scrollTop();
272      var wl = $(window).scrollLeft();
273
274      return this.each(function() {
275        $(this).css("position","absolute");
276        $(this).css("top", Math.max(0, ((wh - $(this).outerHeight()) / 2) + ws) + "px");
277        $(this).css("left", Math.max(0, ((ww - $(this).outerWidth()) / 2) + wl) + "px");
278      });
279    },
280    shake : function (ampl,number) {
281        return this.each(function() {
282            ampl = ampl || 10;
283            number = number || 4;
284            $(this).css({ "position": "relative" });
285            for (var x = 0; x < number; x++) {
286              $(this).animate({ left: -ampl }, 10).animate({ left: 0 }, 50).animate({ left: ampl }, 10).animate({ left: 0 }, 50);
287            }
288        });
289    },
290    setChecked : function (checked) {
291        if(typeof checked == 'string'){
292          checked = +checked;
293        }
294        return this.each(function() {
295          $(this).prop('checked', checked? 1 : 0);
296        });
297    },
298    getChecked : function () {
299      return this.prop('checked')? 1 : 0;
300    },
301    setRadioVal : function (value) {
302      return this.each(function(){
303        $(this).prop('checked', $(this).attr('value') == value? 1 : 0);
304      });
305    },
306    getRadioVal : function () {
307      return this.filter(":checked").val();
308    },
309    // \param my      Punkt odniesienia dla umieszczanego elementu; np. 'center top' lub 'left'. Jak nie ma drugiej współrzędnej, przyjmujemy 'center'. Obowiązuje dokładnie jedna spacja pomiędzy.
310    // \param at      Punkt odniesienia w docelowym elemencie. (podobnie jak parametr 'my')
311    // \param of      Obiekt jQuery docelowego elementu.
312    // \param dx      Dodatkowy offset [w pikselach] w poziomie; default: 0
313    // \param dy      Dodatkowy offset [w pikselach] w pionie; default: 0
314    // Przykład: $('#umieszczany_element').align('left top', 'right center', '#do_tego_wyrównujemy', 10, 20)
315    // To nam umieści element tak, że jego lewy górny róg znajdzie się na środku prawej krawędzi docelowego elementu
316    align : function(my, at, $of, dx, dy){ // my,at: <horizontal> [<vertical>]; of: jQuery selektor elementu, do którego wyrównujemy
317      return this.each(function() {
318        dx = +dx || 0;
319        dy = +dy || 0;
320
321        var myi = $(this);
322
323        var my_w = myi.width();
324        var my_h = myi.height();
325
326        my_w += (parseInt(myi.css('padding-left') || '0',10) + parseInt(myi.css('padding-right') || '0',10));
327        my_h += (parseInt(myi.css('padding-top') || '0',10)  + parseInt(myi.css('padding-bottom') || '0',10));
328
329        my_w += (parseInt(myi.css('border-left') || '0',10) + parseInt(myi.css('border-right') || '0',10));
330        my_h += (parseInt(myi.css('border-top') || '0',10)  + parseInt(myi.css('border-bottom') || '0',10));
331
332//        $of = $($of);
333        var of_w = $of.width() + (parseInt($of.css('padding-left') || '0',10) + parseInt($of.css('padding-right') || '0',10));
334        var of_h = $of.height() + (parseInt($of.css('padding-top') || '0',10) + parseInt($of.css('padding-bottom') || '0',10));
335        of_w += (parseInt($of.css('border-left') || '0',10) + parseInt($of.css('border-right') || '0',10));
336        of_h += (parseInt($of.css('border-top') || '0',10) + parseInt($of.css('border-bottom') || '0',10));
337
338        var of_left = $of.offset().left;
339        var of_top = $of.offset().top;
340
341        my = my.split(' ');
342        at = at.split(' ');
343
344        var my_left = my[0];
345        var my_top = my[1];
346        var at_left = at[0];
347        var at_top = at[1];
348
349        // i jeszcze wyjątki dla top i bottom
350        if(my[0] == 'top'){
351          my_left = 'center';
352          my_top = 'top';
353        }
354        else if(my[0] == 'bottom'){
355          my_left = 'center';
356          my_top = 'bottom';
357        }
358        if(at[0] == 'top'){
359          at_left = 'center';
360          at_top = 'top';
361        }
362        else if(at[0] == 'bottom'){
363          at_left = 'center';
364          at_top = 'bottom';
365        }
366
367        var left = of_left;
368        var top = of_top;
369
370        // defaultowo left,top odnosi się do lewego górnego rogu elementu; na podstawie parametrów przesuńmy te pozycje
371        if(my_left == 'left'){
372          ; // jest ok
373        }
374        else if(my_left == 'right'){
375          left -= my_w;
376        }
377        else {
378          left -= my_w/2;
379        }
380
381        if(my_top == 'top'){
382          ; // jest ok
383        }
384        else if(my_top == 'bottom'){
385          top -= my_h;
386        }
387        else {
388          top -= my_h/2;
389        }
390
391
392        if(at_left == 'left'){
393          ; // jest ok
394        }
395        else if(at_left == 'right'){
396          left += of_w;
397        }
398        else {
399          left += of_w/2;
400        }
401
402        if(at_top == 'top'){
403          ; // jest ok
404        }
405        else if(at_top == 'bottom'){
406          top += of_h;
407        }
408        else {
409          top += of_h/2;
410        }
411
412        var left_px = +left + +dx;
413        var top_px = +top + +dy;
414        myi.css('left', left_px + 'px');
415        myi.css('top', top_px + 'px');
416
417      });
418
419    },
420    GoogleTranslate : function (callback) {
421      var str = $($(this).attr('from')).val();
422      var $to = $($(this).attr('to'));
423      var lang_from = $(this).attr('lang_from');
424      var lang_to = $(this).attr('lang_to');
425
426      GoogleTranslate(lang_from, lang_to, str, function(localized){
427        CResultFader3('OK', 1000, 'ok');
428        if(typeof callback == 'function'){
429          callback(localized);
430        }
431        else {
432          $to.val(localized);
433        }
434      });
435
436      return this.prop('checked')? 1 : 0;
437    },
438
439});
440
441function GoogleTranslate(lang_from, lang_to, str, callback){
442
443  var spinner = new CWaitSpinner();
444
445  var url = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=' + lang_from + '&tl=' + lang_to + '&hl=' + lang_to + '&dt=t&dt=bd&dj=1&source=icon&tk=467103.467103&q=' +  encodeURIComponent(str);
446
447  $.ajax({
448    url: url,
449    type: "GET",
450//      data: fd,
451    enctype: 'application/json',
452    dataType: "json",
453    processData: false,  // tell jQuery not to process the data
454    contentType: false   // tell jQuery not to set contentType
455  }).done(function( resp ) {
456    spinner.close();
457
458    var localized = '';
459    if(resp.sentences){
460      resp.sentences.forEach(function(sentence){
461        localized += sentence.trans;
462      });
463    }
464    else {
465      CResultFader3('Nie znaleziono tłumaczenia.', 2000, 'error');
466      return;
467    }
468
469    if(typeof callback === 'function'){
470      if(localized.length && str.length && str[0].toUpperCase() === str[0]){ //
471        localized = localized.replaceAt(0, localized[0].toUpperCase());
472      }
473      callback(localized);
474    }
475
476
477  }).fail(function( jqXHR, textStatus ){
478    spinner.close();
479    alert('Pobranie tłumaczenia nie powiodło się. Odśwież przeglądarkę i spróbuj ponownie.\nBłąd: ' + textStatus);
480  });
481}
482
483
484(function($) {
485        $.addslashes = function (str) {
486                if(str.replace){
487                  str = str.replace(/\'/g,'\\\'');
488                  str = str.replace(/\"/g,'\\"');
489                  str = str.replace(/\\/g,'\\\\');
490                  str = str.replace(/\0/g,'\\0');
491                }
492                return str;
493        };
494})(jQuery);
495
496function replaceAll(find, replace, str) {
497  return str.replace(new RegExp(find, 'g'), replace);
498}
499
500deepcopy = function(obj){
501  return jQuery.extend(true, {}, obj);
502}
503
504// wyszukuje i zwraca następny indeks obiektu lub tablicy (licząc od zera)
505findNextIndex = function(obj){
506  var idx = 0;
507  for(var i in obj){
508    if(+i >= idx){
509      idx = +i+1;
510    }
511  }
512  return idx;
513}
514
515// bezpiecznie usuwa wskazany element zarówno z tablicy, jak i z obiektu
516deleteMember = function(object, member){
517  if(object.constructor === Array){
518    object.splice(member,1);
519  }
520  else {
521    delete object[member];
522  }
523}
524
525// zwraca rozmiar zarówno tablicy, jak i obiektu
526getArraySize = function(arr)
527{
528  var i = 0;
529  for(let a in arr){
530    i++;
531  }
532  return i;
533}
534
535/*
536escapeHTML = function(str)
537{
538  if(typeof str === 'undefined'){
539    return '';
540  }
541  try {
542    return(
543      (str+'').replace(/&/g,'&amp;').
544          replace(/>/g,'&gt;').
545          replace(/</g,'&lt;').
546          replace(/'/g, '&#39;').
547          replace(/"/g,'&quot;')
548    );
549  }
550  catch(e){
551    return '';
552  }
553}
554*/
555function _html(str){
556//function escapeHTML(str){
557
558  if(typeof str === 'undefined' || str === null){
559    return '';
560  }
561
562  const entityMap = {
563    '&': '&amp;',
564    '<': '&lt;',
565    '>': '&gt;',
566    '"': '&quot;',
567    "'": '&#39;',
568    '/': '&#x2F;',
569    '\\':'&#x5C;',
570    '`': '&#x60;',
571    '=': '&#x3D;',
572    '|': '&#7C;'
573  };
574
575  return str.toString().replace(/[&<>"'`=\/\\|]/g, s => entityMap[s]);
576}
577
578//const _html = (str) => escapeHTML(str);
579
580
581function htmlEscape(str) {
582    var stringval="";
583    $.each(str, function (i, element) {
584        stringval += element
585            .replace(/&/g, '&amp;')
586            .replace(/"/g, '&quot;')
587            .replace(/'/g, '&#39;')
588            .replace(/</g, '&lt;')
589            .replace(/>/g, '&gt;')
590            .replace(' ', '-')
591            .replace('?', '-')
592            .replace(':', '-')
593            .replace('|', '-')
594            .replace('.', '-');
595    });
596    return String(stringval);
597}
598
599function escapeAngleBraces(str){
600  if(typeof str === 'undefined'){
601    return '';
602  }
603  try {
604    return((str+'').replace(/>/g,'&gt;').replace(/&#39;/g, '\'').replace(/</g,'&lt;'));
605  }
606  catch(e){
607    return '';
608  }
609}
610
611
612todayDate = function(format_wizyty, separator) // zwraca bieżącą datę w formacie zwykłym (DD.MM.YYYY) lub wizyty (YYYY-MM-DD)
613{
614  var today = new Date();
615  var dd = today.getDate();
616  dd = (+dd < 10)? '0'+dd : dd;
617  var mm = today.getMonth()+1; //January is 0!
618  mm = (+mm < 10)? '0'+mm : mm;
619  var yyyy = today.getFullYear();
620  if(format_wizyty){
621    separator = separator || '-';
622    return yyyy+separator+mm+separator+dd;
623  }
624  else {
625    separator = separator || '.';
626    return dd+separator+mm+separator+yyyy;
627  }
628}
629
630function date2string(date, format_wizyty, separator) // zamienia obiekt daty na string YYYY.MM.DD
631{
632  var dd = date.getDate();
633  dd = (+dd < 10)? '0'+dd : dd;
634  var mm = date.getMonth()+1; //January is 0!
635  mm = (+mm < 10)? '0'+mm : mm;
636  var yyyy = date.getFullYear();
637  if(format_wizyty){
638    separator = separator || '-';
639    return yyyy+separator+mm+separator+dd;
640  }
641  else {
642    separator = separator || '.';
643    return dd+separator+mm+separator+yyyy;
644  }
645}
646
647// przyjmuje datę w formacie yyyy.[0]m.[0]d lub [0]d.[0]m.yyyy i sprawdza, czy jest poprawna.
648// Jeśli tak, zwraca datę, jeśli nie, zwraca false. W zwróconej dacie jednocyfrowe dni i miesiące będą zawierały wiodące zera
649// \param date             Data do sprawdzenia
650// \param format_wizyty    Jeśli true, zwraca datę w formacie yyyy.mm.dd; jeśli false, w formacie dd.mm.yyyy
651// \param separator        Separator części daty, defaultowo kropka
652function validateDate(date, format_wizyty = true, separator = '-'){
653
654  separator = separator || '.';
655
656  var value = date+'';
657  var match = value.match(/^(0?[1-9]|[12]?[0-9]|3[01])[\.\-\/_](0?[1-9]|1[012])[\.\-\/_](19[0-9]{2}|20[0-9]{2})$/);
658  if(match){
659    value = match[1] + separator + match[2] + separator + match[3]; // przepiszmy datę z użyciem separatora
660  }
661  else {
662    // może wpisana odwrotnie?
663    match = value.match(/^(19[0-9]{2}|20[0-9]{2})[\.\-\/_](0?[1-9]|1[012])[\.\-\/_](0?[1-9]|[12]?[0-9]|3[01])$/);
664    if(match){ // ok, data była wpisana odwrotnie, więc wpiszmy poprawnie
665      value = match[3] + separator + match[2] + separator + match[1];
666    }
667    else {
668      return false; // nie rozpoznano daty
669    }
670  }
671
672  // zamieńmy datę na postać dd.mm.rrrr
673  var data_ur = value.split(separator);
674  value = padNumber(data_ur[0],2) + separator + padNumber(data_ur[1],2) + separator + data_ur[2];
675
676  if(format_wizyty){
677    value = data2dataWizyty(value,separator);
678  }
679
680  return value;
681}
682
683// ustawia stan checkboxa (id podawane jak selektor jQuery)
684function setChecked(id, checked){
685  $(id).prop('checked', checked? 1 : 0);
686}
687
688// zwraca stan checkboxa (id podawane jak selektor jQuery) jako numeryczne 0 lub 1
689function getChecked(id){
690  return $(id).prop('checked')? 1 : 0;
691}
692
693// zwraca stan radia
694// \param name    Atrybut name inputa radia
695function getRadioVal(name){
696  return $('input:radio[name="'+name+'"]:checked').val();
697}
698
699// zwraca wartość kontrolki (id podawane jak dla selektora jQuery). Na życzenie konwertuje do liczby
700function getVal(id, as_numeric){
701  return as_numeric? +($(id).val()) : $(id).val();
702}
703
704// helper do sortowania i filtrowania obiektu.
705// \param obj             Obiekt lub tablica z elemntami do sortowania i filtrowania
706// \param sort_fn         Standardowa funkcja przekazywana do array.sort(a, b). Opcjonalna. Jeśli parametr jest typu 'string',
707//                        to jest używana standardowa funkcja generic_compare_desc(a[<parametr>], b[<parametr>], 0, 0).
708//                        Jeśli string zaczyna się od minusa (-), to elementy są sortowane w kolejności odwrotnej
709//                        Jeśli string zaczyna się od plusa (+), to elementy są sortowane numerycznie wg kolejności rosnącej
710//                        Jesli parametr jest pustym stringiem, to elementy są porównywane bezpośrednio: generic_compare_desc(a, b, 0, 0)
711// \param filter_fn       Jeśli jest, to jest wołana jako filter_fn(obj) i powinna zwr
711ócić true lub false. Defaultowa funkcja zwraca true.
712// \param loop_fn         Jeśli jest, to jest wołana jako loop_fn(arr[i], i, arr) dla każdego elementu w wynikowej (posortowanej) tablicy. Jeśli zwróci === false, iterowanie się zakończy.
713// \return                Zwraca posortowaną i odfiltrowaną tablicę ([])
714function filterAndSortArray(obj, sort_fn, filter_fn, loop_fn){
715  filter_fn = filter_fn || function(obj, idx){ return true;};
716
717  if(typeof obj == 'undefined' || obj === null){
718    return [];
719  }
720  let arr = Object.values(obj).filter(filter_fn);
721  if(typeof(sort_fn) == 'function'){
722    arr.sort(sort_fn);
723  }
724  else if(sort_fn === ''){
725    arr.sort(function(a, b){
726/*
727        if(typeof a[sort_fn] == 'undefined'){
728          throw Error('Nie ma ID', sort_fn, a);
729        }
730        if(typeof b[sort_fn] == 'undefined'){
731          throw Error('Nie ma ID', sort_fn, b);
732        }
733*/
734      return generic_compare_desc(a.toString(), b.toString(), 0, 0);
735    });
736  }
737  else if(typeof(sort_fn) == 'string'){
738    if(sort_fn[0] == '-' && sort_fn[1] == '+'){
739      const sort_fld = sort_fn.substring(2);
740      arr.sort(function(a, b){
741/*
742        if(typeof a[sort_fn] == 'undefined'){
743          throw Error('Nie ma ID', sort_fn, a);
744        }
745        if(typeof b[sort_fn] == 'undefined'){
746          throw Error('Nie ma ID', sort_fn, b);
747        }
748*/
749        return generic_compare(+a[sort_fld], +b[sort_fld], 0, 0, true);
750      });
751    }
752    else if(sort_fn[0] == '-'){
753      const sort_fld = sort_fn.substring(1);
754      arr.sort(function(a, b){
755/*
756        if(typeof a[sort_fn] == 'undefined'){
757          throw Error('Nie ma ID', sort_fn, a);
758        }
759        if(typeof b[sort_fn] == 'undefined'){
760          throw Error('Nie ma ID', sort_fn, b);
761        }
762*/
763        return generic_compare(a[sort_fld].toString(), b[sort_fld].toString(), 0, 0);
764      });
765    }
766    else if(sort_fn[0] == '+'){
767      const sort_fld = sort_fn.substring(1);
768      arr.sort(function(a, b){
769/*
770        if(typeof a[sort_fn] == 'undefined'){
771          throw Error('Nie ma ID', sort_fn, a);
772        }
773        if(typeof b[sort_fn] == 'undefined'){
774          throw Error('Nie ma ID', sort_fn, b);
775        }
776*/
777        return generic_compare_desc(+a[sort_fld], +b[sort_fld], 0, 0, true);
778      });
779    }
780    else {
781      const sort_fld = sort_fn;
782      arr.sort(function(a, b){
783/*
784        if(typeof a[sort_fn] == 'undefined'){
785          throw Error('Nie ma ID', sort_fn, a);
786        }
787        if(typeof b[sort_fn] == 'undefined'){
788          throw Error('Nie ma ID', sort_fn, b);
789        }
790*/
791        return generic_compare_desc(a[sort_fld].toString(), b[sort_fld].toString(), 0, 0);
792      });
793    }
794  }
795
796  if(typeof(loop_fn) == 'function'){
797    for(var i = 0; i < arr.length; ++i){
798      if(loop_fn(arr[i], i, arr) === false){
799        break;
800      }
801    }
802  }
803  return arr;
804}
805
806// Klasa do wyświetlania modalnego okienka dialogowego. Okienko defaultowo ma przycisk do zamykania
807// oraz ewentualny czarny background na cały dokument. Daje możliwość wypełnienia contentem zapodanym
808// przez użytkownika.
809// \param options  Obiekt z opcjami dla okienka
810//   Najważniejsze opcje to:
811//     $container = null     Jeśli jest, zostanie użyte jako kanwas dla dialogu.
812//     id                    ID dialogu; na tej podstawie zostanie wygenerowane także ID ciemnego tła pod spodem
813//     add_classes             Dodatkowe klasy, które zostaną dodane do diva z dialogiem
814//     z_index = 5           z-index dla dialogu
815//     no_bkg = false        Gdy true, to pod okienkiem nie będzie ciemnego tła na cały dokuemt
816//     dont_exit_on_bkg = 1  TRUE oznacza, że kliknięcie na ciemnym tle nie powoduje wyjścia z okienka
817//     dont_show_at_once = false   Gdy true, konstruktor nie wywołuje automatycznie metody showDlg()
818//     width = 'auto'        Szerokość okienka jest dopasowywana do zawartości i może być korygowana w css (wartość 'auto'), ale można ją też określić na sztywno jako wartość atrybutu width w css (wartość inna niż auto jest brana dosłownie jako width:<wartość>)
819//     height = 'auto'       Wysokość okienka jest dopasowywana do zawartości i może być korygowana w css (wartość 'auto'), ale można ją też określić na sztywno jako wartość atrybutu height w css (wartość inna niż auto jest brana dosłownie jako height:<wartość>)
820//     opacity = 0.8         Przezroczystość tła
821//     dlg_color = 'inherit' Kolor okna dialogu. Może być podany kolor lub css - wtedy tło jest brane z css-a
822//     dlg_class = 'modal_dlg_bkg' Klasa dialogu
823//     no_reposition = false TRUE oznacza, że okno po wyświetleniu nie będzie repozycjonowane
824//     skip_despace = false  TRUE oznacza, że z tekstu html dla okienka nie będą wycinane whitespacje (bo pewnie już są wycięte)
825//     parent = 'body'       Parent, do którego zostanie dołączone okienko
826//     before_close_callback = false Jeśli jest, to zostanie wywołana tuż przed zamknięciem okienka na ekranie. Parametrem funkcji jest this do CModalDlg2
827//     ctrl_s_callback = null  Jeśli jest, to zostanie wywołane po wciśnięciu kombinacji Ctrl+S (zamiast zapisywania html-a do pliku)
828//     on_exit_fn = null     Jeśli jest, to zostanie wywołana przed anulowaniem okienka (kliknięcie na bkg, zamknięcie przyciskiem, klawisz Esc)
829// \param content_fn         Jeśli jest, to powinien być funkcją zwracającą tekst do wypełnienia contentem. Parametrem funkcji jest this do CModalDlg2
830// \param after_show_fn      Jeśli jest, to zostanie wywołana po wyświetleniu okienka na ekranie (jeszcze przed wycentrowaniem). Parametrem funkcji jest this do CModalDlg2
831CModalDlg2 = function(options, content_fn, after_show_fn){
832  if(!options.id){
833    console.log('CModalDlg2: nie podano ID dialogu');
834    return;
835  }
836  var self = this;
837
838  var o = options;
839  o.$container ??= null;
840  o.z_index = o.z_index || 5;
841  o.width = o.width || 'auto';
842  o.height = o.height || 'auto';
843  o.left = (o.left != undefined)? o.left : 'center';
844  o.right = o.right || 0;
845  o.top = (o.top != undefined)? o.top : 'center';
846  o.bottom = o.bottom || 0;
847  o.margin = o.margin || 0;
848  o.dont_show_at_once = o.dont_show_at_once || false;
849  o.opacity = o.opacity || 0.8;
850  o.dlg_color = o.dlg_color || ''; // '#D0E6E6';
851  o.dlg_class = o.dlg_class || 'modal_dlg_bkg';
852  o.no_reposition = o.no_reposition || false;
853  o.parent = o.parent || 'body';
854  self.content_fn = content_fn || function(){return '';};
855  self.after_show_fn = after_show_fn || function(){};
856  o.dont_exit_on_bkg = typeof o.dont_exit_on_bkg != 'undefined'? o.dont_exit_on_bkg : true;
857  o.no_close_btn = typeof o.no_close_btn != 'undefined'? o.no_close_btn : false;
858  o.no_center_after_tab_select = typeof o.no_center_after_tab_select != 'undefined'? o.no_center_after_tab_select : false;
859  o.skip_despace = o.skip_despace || false;
860
861  if(o.dlg_color == 'from_class'){
862    o.dlg_color = '';
863  }
864
865  this.options = o;
866
867  let $dlg = o.$container;
868
869  if(!o.dont_show_at_once){
870    self.showDlg();
871  }
872
873  self.close = self.closeDlg; // alternatywna nazwa funkcji do zamykania dialogu, bo się myli
874
875  this.getDlg = function(){ // zwraca obiekt jQuery dialogu jako takiego
876    return $dlg;
877  }
878
879  this.closeDlg = function(call_callback){
880
881    if(self.already_closed || o.$container){
882      return;
883    }
884
885    self.already_closed = true; // dzięki temu ewentualne kolejne zamknięcie spełznie na niczym
886
887    var curr_dlg = self.dlg_stack[self.dlg_stack.length-1];
888    if(call_callback && typeof(curr_dlg.options.before_close_callback) == 'function'){
889      curr_dlg.options.before_close_callback(curr_dlg);
890    }
891
892    self.dlg_stack.pop();
893
894    if(typeof o.ctrl_s_callback == 'function'){ // jesli dialog obsługiwał Ctrl+S, to odbindujmy to zdarzenie
895      if(!self.dlg_stack.length){
896        $(window).off('keydown.modal_dlg');
897      }
898    }
899
900    if(!o.no_bkg || o.$container){
901      $('.'+o.id+'_dark_bkg').remove();
902    }
903
904    if($dlg && !o.$container){
905      $dlg.remove();
906    }
907  //  delete this;
908  }
909
910  this.center = function(){
911    if(!o.$container){
912      $dlg.center2();
913    }
914  }
915
916  this.getStyle = function(){
917    var st = $(window).scrollTop();
918    var sl = $(window).scrollLeft();
919
920    var txt = 'padding:5px;position:absolute;left:'+sl+'px;top:'+st+'px;z-index:'+(o.z_index+1);
921    if(o.dlg_color != 'css'){
922      txt += ';background:'+o.dlg_color+';';
923    }
924    return txt;
925  }
926
927  this.showDlg = function(){
928//    self.closeDlg(0);
929
930    var txt = '';
931
932    var bkg_width = $(document).width();
933    var bkg_height = $(document).height();
934
935    let $dlg_int;
936
937    if(!o.$container){
938      if(!o.no_bkg){
939        txt += '<div class="modal_dlg_dark_bkg '+o.id+'_dark_bkg" style="z-index:'+o.z_index+';position:fixed;background:black;opacity:'+o.opacity+';width:'+(bkg_width-1)+'px;height:'+(bkg_height-1)+'px;left:0;top:0;"></div>';
940      }
941
942      txt += '<div class="' + o.dlg_class;
943      if(o.add_classes){
944        txt += ' ' + o.add_classes;
945      }
946      txt += '" style="' + self.getStyle() + '" id="'+o.id+'">';
947
948        txt += '<div class="modal_dlg_int" id="'+o.id+'_int">';
949          var tt = self.content_fn(self);
950
951          if(typeof(tt) != 'string'){
952            tt = '<span class="color_red">Funkcja powinna zwracać string z HTML-em dialogu</span>';
953          }
954
955          if(!o.skip_despace){
956            tt = tt.despace();
957          }
958
959          txt += tt;
960        txt += '</div>';
961        if(!o.no_close_btn){
962          txt += '<div class="dlg_close_btn jeden" id="'+o.id+'_close_btn_jeden" title="Anuluj i wyjdź"></div>';
963          txt += '<div class="dlg_close_btn dwa" id="'+o.id+'_close_btn_dwa" title="Anuluj i wyjdź"></div>';
964        }
965        txt += '<div style="clear:both"></div>';
966      txt += '</div>';
967
968      $(o.parent).append(txt);
969
970      if(o.add_classes){
971        let classes = o.add_classes.split(' ').join('.');
972        $dlg = $('.'+classes+'#'+o.id);
973      }
974      else {
975        $dlg = $('#'+o.id);
976      }
977
978      $dlg_int = $dlg.find('#'+o.id+'_int');
979
980      if(o.width == 'auto'){
981        $dlg_int.css('width', 'auto');
982      }
983      else if(o.width == 'css'){
984        // wartość ustawiana w css
985      }
986      else {
987        const w = o.width[o.width.length-1] == ';'? o.width.substring(0, o.width.length-1)  : o.width;
988        $dlg_int.css('width', w);
989        $dlg_int.css('overflow', 'auto');
990      }
991
992      if(o.height == 'auto'){
993        $dlg_int.css('height', 'auto');
994      }
995      else if(o.height == 'css'){
996        // wartość ustawiana w css
997      }
998      else if(o.height){
999        const h = o.height[o.height.length-1] == ';'? o.height.substring(0, o.height.length-1)  : o.height;
1000        $dlg_int.css('height', h);
1001        $dlg_int.css('overflow', 'auto');
1002      }
1003
1004
1005  //    $dlg_int.css('width', o.width+'px');
1006
1007      $dlg.find('.dlg_close_btn').click(function(){
1008        if(typeof o.on_exit_fn == 'function'){
1009          o.on_exit_fn();
1010        }
1011        self.closeDlg(1);
1012      });
1013
1014      if(!o.dont_exit_on_bkg){
1015        $('.'+o.id+'_dark_bkg').click(function(){ // tupnięcie na tle wychodzi z dialogu
1016          if(typeof o.on_exit_fn == 'function'){
1017            o.on_exit_fn();
1018          }
1019          self.closeDlg(1);
1020        });
1021      }
1022    }
1023    else { // stwórzmy bardzo prostą ramkę
1024      const $dlg = $('<div/>').addClass('modal_dlg_bkg').attr('id', o.id);
1025
1026      if(o.add_classes){
1027        $dlg.addClass(o.add_classes);
1028      }
1029
1030      let txt = '<div class="modal_dlg_int" id="'+o.id+'_int">';
1031        var tt = self.content_fn(self);
1032
1033        if(typeof(tt) != 'string'){
1034          tt = '<span class="color_red">Funkcja powinna zwracać string z HTML-em dialogu</span>';
1035        }
1036
1037        if(!o.skip_despace){
1038          tt = tt.despace();
1039        }
1040
1041        txt += tt;
1042      txt += '</div>';
1043
1044      $dlg_int = $(txt);
1045
1046      $dlg_int.appendTo($dlg);
1047      $dlg.appendTo(o.$container);
1048    }
1049
1050    self.after_show_fn(self);
1051
1052    if(typeof o.ctrl_s_callback == 'function'){ // jesli dialog chce obsłużyć Ctrl+S, to zbindujmy to zdarzenie
1053//      if(!self.dlg_stack.length){
1054        $(window).off('keydown.modal_dlg').on('keydown.modal_dlg', function(evt) {
1055          var key = String.fromCharCode(evt.keyCode);
1056
1057          if(key.toLowerCase() === "s" && (evt.ctrlKey || evt.metaKey)){ //ctrl+S
1058            var curr_dlg = self.dlg_stack[self.dlg_stack.length-1];
1059            if(typeof curr_dlg.options.ctrl_s_callback == 'function'){
1060              curr_dlg.options.ctrl_s_callback(curr_dlg);
1061            }
1062            evt.preventDefault(true);
1063            event.stopImmediatePropagation();
1064            return false;
1065          }
1066          return true;
1067        });
1068//      }
1069    }
1070
1071    if(!o.no_Esc_close){ // jesli dialog nie blokuje zamykania skrótem Esc, to zbindujmy to zdarzenie
1072      if(!self.dlg_stack.length){
1073        $(window).on('keydown.modal_dlg', function(evt) {
1074          var key = String.fromCharCode(evt.keyCode);
1075
1076//          if(key.toLowerCase() === "x" && evt.ctrlKey){ // Ctrl+X
1077          if(evt.key === "Escape"){ // Esc
1078            evt.preventDefault(true);
1079
1080            if(typeof o.on_exit_fn == 'function'){
1081              o.on_exit_fn();
1082            }
1083
1084            if(self.dlg_stack.length){
1085              var curr_dlg = self.dlg_stack[self.dlg_stack.length-1];
1086
1087              curr_dlg.closeDlg(true);
1088            }
1089
1090            return false;
1091          }
1092          return true;
1093        });
1094      }
1095    }
1096
1097    self.dlg_stack.push(self);
1098
1099    $dlg_int.find('.focus').focus(); // wybierzmy pole focusa na w całym dialogu
1100
1101    var $dialog_header = $dlg_int.find('.dialog_header');
1102    if($dialog_header.length && $dialog_header.find('.tab_header').length > 1){ // obsługujemy zakładki tylko jeśli jakieś są
1103      var tab;
1104      if(!$dialog_header.find('.tab_header.selected').length >= 1){ // jeśli nie ma aktywnej zakładki, to uaktywnijmy pierwszą z brzegu
1105        var th = $dialog_header.find('.tab_header');
1106        tab = th.first().attr('tab');
1107
1108        $dialog_header.find('.tab_header[tab="'+tab+'"]').addClass('selected');
1109      }
1110      else {
1111        tab = $dialog_header.find('.tab_header.selected').first().attr('tab');
1112      }
1113
1114      $dlg_int.find('.dialog_tab[tab="'+tab+'"]').addClass('selected');
1115      $dlg_int.find('.dialog_tab[tab="'+tab+'"] .focus').focus(); // wybierzmy pole focusa na domyślnej zakładce
1116
1117      if(!o.no_center_after_tab_select){ // centrujemy obiekt tylko wtedy, gdy to nie jest zakazane
1118        self.center();
1119      }
1120
1121      if(typeof self.set_tab_callback == 'function'){
1122        self.set_tab_callback(tab);
1123      }
1124
1125      $dialog_header.find('.tab_header').click(function(){
1126        var tab =  $(this).attr('tab');
1127
1128        $dialog_header.find('.tab_header').removeClass('selected');
1129        $dlg_int.find('.dialog_tab').removeClass('selected');
1130        $dialog_header.find('.tab_header[tab="'+tab+'"]').addClass('selected');
1131        $dlg_int.find('.dialog_tab[tab="'+tab+'"]').addClass('selected');
1132        $dlg_int.find('.dialog_tab[tab="'+tab+'"] .focus').focus(); // wybierzmy pole focusa na wybranej zakładce
1133
1134        if(!o.no_center_after_tab_select){ // centrujemy obiekt tylko wtedy, gdy to nie jest zakazane
1135          self.center();
1136        }
1137
1138        if(typeof self.set_tab_callback == 'function'){
1139          self.set_tab_callback(tab);
1140        }
1141      });
1142
1143      przygotujDymki($dlg);
1144
1145
1146    }
1147
1148    if(!o.no_reposition){
1149      self.reposition();
1150    }
1151  }
1152
1153  this.reposition = function(){
1154    self.center();
1155
1156    if(o.left && o.left != 'center'){
1157      $dlg.css('left', o.left + (typeof o.left == 'number'? 'px' : ''));
1158    }
1159    if(o.top && o.top != 'center'){
1160      $dlg.css('top', o.top + (typeof o.top == 'number'? 'px' : ''));
1161    }
1162    if(o.right && o.right != 'center'){
1163      $dlg.css('left', 'inherit');
1164      $dlg.css('right', o.right + (typeof o.right == 'number'? 'px' : ''));
1165    }
1166    if(o.bottom && o.bottom != 'center'){
1167      $dlg.css('top', 'inherit');
1168      $dlg.css('bottom', o.bottom + (typeof o.bottom == 'number'? 'px' : ''));
1169    }
1170  }
1171
1172  this.selectTab = function(tab){
1173    var $dlg_int = $dlg.find('#'+o.id+'_int');
1174    var $dialog_header = $dlg_int.find('.dialog_header');
1175    if($dialog_header.length && $dialog_header.find('.tab_header').length > 1){ // obsługujemy zakładki tylko jeśli jakieś są
1176      $dialog_header.find('.tab_header').removeClass('selected');
1177      $dlg_int.find('.dialog_tab').removeClass('selected');
1178      $dialog_header.find('.tab_header[tab="'+tab+'"]').addClass('selected');
1179      $dlg_int.find('.dialog_tab[tab="'+tab+'"]').addClass('selected');
1180      $dlg_int.find('.dialog_tab[tab="'+tab+'"] .focus').focus(); // wybierzmy pole focusa na wybranej zakładce
1181
1182      if(!o.no_center_after_tab_select){ // centrujemy obiekt tylko wtedy, gdy to nie jest zakazane
1183        self.center();
1184      }
1185    }
1186  }
1187}
1188
1189CModalDlg2.prototype.dlg_stack = [];
1190
1191// W podanym parencie (obiekcie jQuery) wyszukuje elementy z atrybutem "dymek" i dopisuje do nich html-e z treściami dymków
1192function przygotujDymki($parent){
1193
1194  $parent.find('.dymek_cont').remove(); // usuńmy poprzednie dymki
1195/*
1196  $parent.find('*[dymek]').each(function(){
1197    var lewy = $(this).attr('lewy');
1198    var dym_id = $(this).attr('dymek');
1199
1200    const head = $(this).attr('dymek_head');
1201    const light = $(this).attr('dymek_light');
1202
1203    var txt = dymek(dym_id, lewy, null, head, light);
1204    $(this).append(txt);
1205  });
1206*/
1207  $parent.find('*[dymek]').each(function(){
1208    var lewy = $(this).attr('lewy');
1209    const dymek = $(this).attr('dymek');
1210
1211    const head = $(this).attr('dymek_head');
1212    const light = $(this).attr('dymek_light');
1213
1214    var txt = dymek2(dymek, lewy, $(this).attr('dymek_id'), head, light);
1215    $(this).append(txt);
1216  });
1217
1218}
1219
1220
1221// Klasa do wyciągania informacji z PESELA
1222// Po utworzeniu konstruktora możemy z obiektu wyciągnąć:
1223// error: string z błędem lub null jak poprawny
1224// rok, miesiec, dzien: skladowe daty
1225// plec: płeć ('M' lub 'K')
1226// \param s  PESEL do sprawdzenia
1227CPesel = function(s){
1228  this.error = null;
1229
1230  if (s.length != 11){ // Sprawdźmy długość, musi być 11 znaków
1231    this.error = 'musi mieć 11 cyfr';
1232    return;
1233  }
1234
1235  // Sprawdźmy, czy wszystkie znaki to cyfry
1236  var aInt = new Array();
1237  for(var i = 0; i < 11; i++){
1238    aInt[i] = parseInt(s.substring(i,i+1),10);
1239    if(isNaN(aInt[i])){
1240      this.error = 'musi zawierać wyłącznie cyfry';
1241      return;
1242    }
1243  }
1244
1245  // Sprawdźmy sumę kontrolną
1246  var wagi = [1,3,7,9,1,3,7,9,1,3,1];
1247  var sum=0;
1248  for (var i = 0; i < 11; i++){
1249    sum += wagi[i] * aInt[i];
1250  }
1251
1252  if((sum % 10) != 0){
1253    this.error = 'Niewłaściwy PESEL';
1254    return;
1255  }
1256
1257  // Policzmy rok z uwzględnieniem XIX, XXI, XXII i XXIII wieku
1258  this.rok = 1900 + aInt[0] *10 + aInt[1];
1259  if(aInt[2] >= 2 && aInt[2] < 8){
1260    this.rok += Math.floor(aInt[2]/2)*100;
1261  }
1262  if(aInt[2]>=8){
1263    this.rok -= 100;
1264  }
1265
1266  this.miesiac = ('0' + ((aInt[2] % 2) * 10 + aInt[3])).slice(-2); // zapewnijmy wiodące zero
1267  this.dzien = ('0' + (aInt[4] * 10 + aInt[5])).slice(-2);
1268
1269  //Sprawdźmy poprawność daty urodzenia
1270  if(!checkDate(this.dzien,this.miesiac,this.rok)){
1271    this.error = 'Niepoprawna data urodzenia!';
1272    return;
1273  }
1274  this.plec = ((aInt[9] % 2) == 1)? "M" : "K";
1275
1276  function checkDate(d,m,y){
1277    var dt = new Date(+y, +m-1, +d);
1278    return dt.getDate() == d &&
1279           dt.getMonth() == m-1 &&
1280           dt.getFullYear() == y;
1281  }
1282}
1283
1284// Klasa do walidacji NIP.
1285// Po utworzeniu konstruktora możemy z obiektu wyciągnąć:
1286// error: string z błędem lub null jak poprawny
1287function CNIP(nip){
1288  this.error = null;
1289  nip += '';
1290//  nip = nip.replace(/[^0-9]+/g, '');
1291  nip = nip.replace(/[-]+/g, '');
1292
1293  if(nip.length != 10){
1294    this.error = 'NIP musi zawierać 10 cyfr';
1295    return;
1296  }
1297
1298  var controlSum = 0;
1299  controlSum += parseInt(nip.charAt(0),10) * 6;
1300  controlSum += parseInt(nip.charAt(1),10) * 5;
1301  controlSum += parseInt(nip.charAt(2),10) * 7;
1302  controlSum += parseInt(nip.charAt(3),10) * 2;
1303  controlSum += parseInt(nip.charAt(4),10) * 3;
1304  controlSum += parseInt(nip.charAt(5),10) * 4;
1305  controlSum += parseInt(nip.charAt(6),10) * 5;
1306  controlSum += parseInt(nip.charAt(7),10) * 6;
1307  controlSum += parseInt(nip.charAt(8),10) * 7;
1308
1309  if((controlSum % 11) != parseInt(nip.charAt(9),10)) {
1310    this.error = 'Błędny numer NIP';
1311    return;
1312  }
1313}
1314
1315// ajaxem zwraca wartość parametru param_name z admina
1316function getParam(param_name, def_val){
1317  try {
1318    if(PARAMETRY_AUTOMATYCZNE[param_name]){
1319      if(+PARAMETRY_AUTOMATYCZNE[param_name].poufny){ // param jest poufny, więc go nie ma w pliku parametry.js
1320        return htmlDataSync('ajax_req.php?aaction=ajax_get_param', 'param_name='+param_name); // ciągniemy z bazy
1321      }
1322      return PARAMETRY_AUTOMATYCZNE[param_name].val;
1323    }
1324    else { // nieznany parametr
1325      return def_val;
1326    }
1327  }
1328  catch(e){
1329    // to chamski hack, ale to chyba najlepszy sposób na wyłapanie nieistniejącej zmiennej globalnej PARAMETRY_AUTOMATYCZNE
1330  }
1331
1332  return htmlDataSync('ajax_req.php?aaction=ajax_get_param', 'param_name='+param_name);
1333}
1334
1335// ajaxem zwraca wartość parametru param_name z admina, ale uwaga: ASYNCHRONICZNIE
1336function getParam2(param_name, def_val, callback){
1337
1338  try {
1339    if(PARAMETRY_AUTOMATYCZNE[param_name]){
1340      if(+PARAMETRY_AUTOMATYCZNE[param_name].poufny){ // param jest poufny, więc go nie ma w pliku parametry.js
1341        return htmlDataSync('ajax_req.php?aaction=ajax_get_param', 'param_name='+param_name); // ciągniemy z bazy
1342      }
1343      return PARAMETRY_AUTOMATYCZNE[param_name].val;
1344    }
1345    else { // nieznany parametr
1346      return def_val;
1347    }
1348  }
1349  catch(e){
1350    // to chamski hack, ale to chyba najlepszy sposób na wyłapanie nieistniejącej zmiennej globalnej PARAMETRY_AUTOMATYCZNE
1351  }
1352
1353  JSONAsyncRequest3('ajax_get_param', 'action=get_param&param_name='+param_name).then(dane => {
1354    if(dane?.result == 'OK'){
1355      if(callback){
1356        callback(dane.param);
1357      }
1358    }
1359    else {
1360      console.error('Nie powiodło się pobranie parametru \''+param_name+'\'. Błąd: ' + dane?.message);
1361      if(callback){
1362        callback(def_val);
1363      }
1364    }
1365  });
1366}
1367
1368// zapewnia, że podany element (jako selektor jQuery) znajdzie się poniżej górnej krawędzi i powyżej dolnej krawędzi okna przeglądarki
1369function checkDivVisibility(elem){
1370  var docViewTop = $(window).scrollTop();
1371  var docViewBottom = docViewTop + $(window).height();
1372
1373  var elemTop = $(elem).offset().top;
1374  var elemBottom = elemTop + $(elem).height();
1375
1376  if(elemTop < docViewTop){ // przesuńmy go tak, żeby się pojawił pod dolną krawędzią
1377    $(elem).css("top", docViewTop + 20);
1378  }
1379}
1380
1381// sprawdza, czy podany elemnt (jako selektor jQuery) przy aktualnym przewinięciu jest widoczny na ekranie
1382// \param top_only   TRUE sprawia, że sprawdzamy tylko, czy górna krawędź elementu jest widoczna
1383function isScrolledIntoView(elem, top_only){
1384    var docViewTop = $(window).scrollTop();
1385    var docViewBottom = docViewTop + $(window).height();
1386
1387    var elemTop = $(elem).offset().top;
1388
1389    if(top_only){ // sprawdzamy tylko widoczność górnej krawędzi
1390      return ((elemTop < docViewBottom) && (elemTop >= docViewTop));
1391    }
1392    else { // sprawdzamy, czy cały element jest widoczny
1393      var elemBottom = elemTop + $(elem).height();
1394      return ((elemBottom < docViewBottom) && (elemTop >= docViewTop));
1395    }
1396}
1397
1398// Helper funkcja wywoływana co period sekund żeby sesja nie wygasła
1399function refreshSession() {
1400  setTimeout(refreshSession, 2*60*1000); // co dwie minuty
1401
1402  // zapytanie o parametr jest tanie, a odświeża sesję
1403  htmlData('ajax_req.php?aaction=ajax_get_param&param_name=PLAY_REFRESH_SOUND',function(response){
1404    if(response == '1' || response == 'true'){
1405      var soundHandle = document.getElementById('audiotag1');
1406        soundHandle.src = 'money.wav';
1407        soundHandle.play();
1408    }
1409  });
1410}
1411
1412// uzupełnienie obiektu Date o możliwość dodawania miesięcy
1413// Użycie:
1414//var myDate = new Date("01/31/2012");
1415//var result1 = myDate.addMonths(1);
1416//var myDate2 = new Date("01/31/2011");
1417//var result2 = myDate2.addMonths(1);
1418
1419Date.isLeapYear = function (year) {
1420    return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
1421};
1422
1423Date.getDaysInMonth = function (year, month) {
1424    return [31, (Date.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
1425};
1426
1427Date.prototype.isLeapYear = function () {
1428    var y = this.getFullYear();
1429    return (((y % 4 === 0) && (y % 100 !== 0)) || (y % 400 === 0));
1430};
1431
1432Date.prototype.getDaysInMonth = function () {
1433    return Date.getDaysInMonth(this.getFullYear(), this.getMonth());
1434};
1435
1436Date.prototype.addMonths = function (value) {
1437    var n = this.getDate();
1438    this.setDate(1);
1439    this.setMonth(this.getMonth() + value);
1440    this.setDate(Math.min(n, this.getDaysInMonth()));
1441    return this;
1442};
1443
1444// converts string yyyy.mm.dd into Date object
1445dateFromString = function(date, separator) {
1446  separator = separator || '.';
1447  var from = date.split(separator);
1448  return new Date(from[0], from[1] - 1, from[2]);
1449}
1450
1451// zamienia czas hh:mm[:ss] na dzisiejszy obiekt Date z podanym czasem
1452timeFromString = function(time, separator){
1453  separator = separator || ':';
1454  var spl = time.split(separator);
1455
1456  var d = new Date();
1457  d.setHours(spl[0]);
1458  d.setMinutes(spl[1]);
1459  if(spl.length > 2){
1460    d.setSeconds(spl[2]);
1461  }
1462  else {
1463    d.setSeconds(0);
1464  }
1465  return d;
1466}
1467
1468// Zwraca tekst z wygenerowanym elementem <select>. Element otrzyma podane id, wartość i opcje (jako pary {val : txt})
1469// Opcjonalnie można podać wartości dla atrybutów value, name, class oraz style
1470// Jeśli parametr process_fn jest funkcją, to zostanie ona wywołana jako process_fn(i, opcje) dla każdego elementu obiektu opcje.
1471// Ta funkcja dla otrzymanego elementu powinna zwrócić obiekt {'txt' : <tekst do taga option>, 'val' : <value do taga option>}.
1472//   Jeśli zwróci także atrybut 'sel' = 1, to dany element zostanie zaznaczony na liście.
1473// Po wybraniu elementu wywołuje callback select_fn(item) (o ile istnieje).
1474genSelect = function(id, value, opcje, readonly, name, klasa, styl, process_fn, select_fn)
1475{
1476  id = _html(id);
1477  value = _html(value);
1478
1479  var txt = '<select id="'+id;
1480
1481  if(value){
1482    txt += '" value="'+value+'"';
1483  }
1484  if(name){
1485    txt += ' name="'+_html(name)+'"';
1486  }
1487  if(klasa){
1488    txt += ' class="'+_html(klasa)+'"';
1489  }
1490  if(styl){
1491    txt += ' style="'+_html(styl)+'"';
1492  }
1493  if(readonly){
1494    txt += ' disabled';
1495  }
1496  if(select_fn){
1497    // przygotujmy callbacka na wybór elementu z listy
1498    GENSELECT_CALLBACKS[id] = function(){
1499      var val = $('select#'+id).val();
1500      select_fn(opcje[val]);
1501    };
1502
1503    txt += ' onchange="GENSELECT_CALLBACKS[\''+id+'\']()"';
1504  }
1505  txt += '>';
1506
1507  process_fn = process_fn || function(i,o){
1508    return {
1509      txt : o[i],
1510      val : i
1511    };
1512  }
1513
1514  for(var i in opcje){
1515    var dat = process_fn(i, opcje);
1516    var val = _html(dat.val);
1517    var opis = _html(dat.txt);
1518    var sel = dat.sel;
1519    txt += '<option value="'+val+'" '+((i==value || sel)? 'selected' : '')+'>'+opis+'</option>';
1520  }
1521  txt += '</select>';
1522
1523  return txt;
1524}
1525
1526// Helper do wybierania elementów listy.
1527// Zwraca tablicę z ofiltrowanymi i posortowany elementami.
1528// Do filtrowania elementów używa callbacka filter_fn(idx, items). Ta funkcja powinna zwrócić obiekt {'o' : <element>, 'i' : <indeks>} przeznaczony do wstawienia do tablicy lub null w przypadku pominięcia elementu.
1529// Do sortowania wybranych elementów używa callbacka sort_fn(item_a, item_b).
1530function filterSortArray(items, filter_fn, sort_fn){
1531
1532  filter_fn = filter_fn || function(idx, items){
1533    return items[idx]; // defaultowo pokazuje wszystkie elementy z listy
1534  }
1535
1536  var arr = [];
1537  for(var i in items){
1538    var filtered = filter_fn(i, items);
1539    if(filtered){
1540      arr.push(filtered);
1541    }
1542  }
1543
1544  if(sort_fn){
1545    arr.sort(sort_fn);
1546  }
1547
1548  return arr;
1549}
1550
1551
1552// odpowiednik funkcji PHP date(). Timestamp podawany jako obiekt Date lub Unixowy timestamp (doba ma 86400 sekund)
1553function php_date(format, timestamp)
1554{
1555  var that = this;
1556  var jsdate, f;
1557  // Keep this here (works, but for code commented-out below for file size reasons)
1558  // var tal= [];
1559  var txt_words = [
1560    'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur',
1561    'January', 'February', 'March', 'April', 'May', 'June',
1562    'July', 'August', 'September', 'October', 'November', 'December'
1563  ];
1564  // trailing backslash -> (dropped)
1565  // a backslash followed by any character (including backslash) -> the character
1566  // empty string -> empty string
1567  var formatChr = /\\?(.?)/gi;
1568  var formatChrCb = function (t, s) {
1569    return f[t] ? f[t]() : s;
1570  };
1571  var _pad = function (n, c) {
1572    n = String(n);
1573    while (n.length < c) {
1574      n = '0' + n;
1575    }
1576    return n;
1577  };
1578  f = {
1579    // Day
1580    d: function () {
1581      // Day of month w/leading 0; 01..31
1582      return _pad(f.j(), 2);
1583    },
1584    D: function () {
1585      // Shorthand day name; Mon...Sun
1586      return f.l()
1587        .slice(0, 3);
1588    },
1589    j: function () {
1590      // Day of month; 1..31
1591      return jsdate.getDate();
1592    },
1593    l: function () {
1594      // Full day name; Monday...Sunday
1595      return txt_words[f.w()] + 'day';
1596    },
1597    N: function () {
1598      // ISO-8601 day of week; 1[Mon]..7[Sun]
1599      return f.w() || 7;
1600    },
1601    S: function () {
1602      // Ordinal suffix for day of month; st, nd, rd, th
1603      var j = f.j();
1604      var i = j % 10;
1605      if (i <= 3 && parseInt((j % 100) / 10, 10) == 1) {
1606        i = 0;
1607      }
1608      return ['st', 'nd', 'rd'][i - 1] || 'th';
1609    },
1610    w: function () {
1611      // Day of week; 0[Sun]..6[Sat]
1612      return jsdate.getDay();
1613    },
1614    z: function () {
1615      // Day of year; 0..365
1616      var a = new Date(f.Y(), f.n() - 1, f.j());
1617      var b = new Date(f.Y(), 0, 1);
1618      return Math.round((a - b) / 864e5);
1619    },
1620
1621    // Week
1622    W: function () {
1623      // ISO-8601 week number
1624      var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3);
1625      var b = new Date(a.getFullYear(), 0, 4);
1626      return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
1627    },
1628
1629    // Month
1630    F: function () {
1631      // Full month name; January...December
1632      return txt_words[6 + f.n()];
1633    },
1634    m: function () {
1635      // Month w/leading 0; 01...12
1636      return _pad(f.n(), 2);
1637    },
1638    M: function () {
1639      // Shorthand month name; Jan...Dec
1640      return f.F()
1641        .slice(0, 3);
1642    },
1643    n: function () {
1644      // Month; 1...12
1645      return jsdate.getMonth() + 1;
1646    },
1647    t: function () {
1648      // Days in month; 28...31
1649      return (new Date(f.Y(), f.n(), 0))
1650        .getDate();
1651    },
1652
1653    // Year
1654    L: function () {
1655      // Is leap year?; 0 or 1
1656      var j = f.Y();
1657      return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0;
1658    },
1659    o: function () {
1660      // ISO-8601 year
1661      var n = f.n();
1662      var W = f.W();
1663      var Y = f.Y();
1664      return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0);
1665    },
1666    Y: function () {
1667      // Full year; e.g. 1980...2010
1668      return jsdate.getFullYear();
1669    },
1670    y: function () {
1671      // Last two digits of year; 00...99
1672      return f.Y()
1673        .toString()
1674        .slice(-2);
1675    },
1676
1677    // Time
1678    a: function () {
1679      // am or pm
1680      return jsdate.getHours() > 11 ? 'pm' : 'am';
1681    },
1682    A: function () {
1683      // AM or PM
1684      return f.a()
1685        .toUpperCase();
1686    },
1687    B: function () {
1688      // Swatch Internet time; 000..999
1689      var H = jsdate.getUTCHours() * 36e2;
1690      // Hours
1691      var i = jsdate.getUTCMinutes() * 60;
1692      // Minutes
1693      // Seconds
1694      var s = jsdate.getUTCSeconds();
1695      return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
1696    },
1697    g: function () {
1698      // 12-Hours; 1..12
1699      return f.G() % 12 || 12;
1700    },
1701    G: function () {
1702      // 24-Hours; 0..23
1703      return jsdate.getHours();
1704    },
1705    h: function () {
1706      // 12-Hours w/leading 0; 01..12
1707      return _pad(f.g(), 2);
1708    },
1709    H: function () {
1710      // 24-Hours w/leading 0; 00..23
1711      return _pad(f.G(), 2);
1712    },
1713    i: function () {
1714      // Minutes w/leading 0; 00..59
1715      return _pad(jsdate.getMinutes(), 2);
1716    },
1717    s: function () {
1718      // Seconds w/leading 0; 00..59
1719      return _pad(jsdate.getSeconds(), 2);
1720    },
1721    u: function () {
1722      // Microseconds; 000000-999000
1723      return _pad(jsdate.getMilliseconds() * 1000, 6);
1724    },
1725
1726    // Timezone
1727    e: function () {
1728      // Timezone identifier; e.g. Atlantic/Azores, ...
1729      // The following works, but requires inclusion of the very large
1730      // timezone_abbreviations_list() function.
1731      /*              return that.date_default_timezone_get();
1732       */
1733      throw 'Not supported (see source code of date() for timezone on how to add support)';
1734    },
1735    I: function () {
1736      // DST observed?; 0 or 1
1737      // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
1738      // If they are not equal, then DST is observed.
1739      var a = new Date(f.Y(), 0);
1740      // Jan 1
1741      var c = Date.UTC(f.Y(), 0);
1742      // Jan 1 UTC
1743      var b = new Date(f.Y(), 6);
1744      // Jul 1
1745      // Jul 1 UTC
1746      var d = Date.UTC(f.Y(), 6);
1747      return ((a - c) !== (b - d)) ? 1 : 0;
1748    },
1749    O: function () {
1750      // Difference to GMT in hour format; e.g. +0200
1751      var tzo = jsdate.getTimezoneOffset();
1752      var a = Math.abs(tzo);
1753      return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4);
1754    },
1755    P: function () {
1756      // Difference to GMT w/colon; e.g. +02:00
1757      var O = f.O();
1758      return (O.substr(0, 3) + ':' + O.substr(3, 2));
1759    },
1760    T: function () {
1761      // Timezone abbreviation; e.g. EST, MDT, ...
1762      // The following works, but requires inclusion of the very
1763      // large timezone_abbreviations_list() function.
1764      /*              var abbr, i, os, _default;
1765      if (!tal.length) {
1766        tal = that.timezone_abbreviations_list();
1767      }
1768      if (that.php_js && that.php_js.default_timezone) {
1769        _default = that.php_js.default_timezone;
1770        for (abbr in tal) {
1771          for (i = 0; i < tal[abbr].length; i++) {
1772            if (tal[abbr][i].timezone_id === _default) {
1773              return abbr.toUpperCase();
1774            }
1775          }
1776        }
1777      }
1778      for (abbr in tal) {
1779        for (i = 0; i < tal[abbr].length; i++) {
1780          os = -jsdate.getTimezoneOffset() * 60;
1781          if (tal[abbr][i].offset === os) {
1782            return abbr.toUpperCase();
1783          }
1784        }
1785      }
1786      */
1787      return 'UTC';
1788    },
1789    Z: function () {
1790      // Timezone offset in seconds (-43200...50400)
1791      return -jsdate.getTimezoneOffset() * 60;
1792    },
1793
1794    // Full Date/Time
1795    c: function () {
1796      // ISO-8601 date.
1797      return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb);
1798    },
1799    r: function () {
1800      // RFC 2822
1801      return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
1802    },
1803    U: function () {
1804      // Seconds since UNIX epoch
1805      return jsdate / 1000 | 0;
1806    }
1807  };
1808  this.date = function (format, timestamp) {
1809    that = this;
1810    jsdate = (timestamp === undefined ? new Date() : // Not provided
1811      (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
1812      new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
1813    );
1814    return format.replace(formatChr, formatChrCb);
1815  };
1816  return this.date(format, timestamp);
1817}
1818
1819
1820
1821/**
1822 * A class to parse color values
1823 * @author Stoyan Stefanov <[email protected]>
1824 * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
1825 * @license Use it if you like it
1826 */
1827 /* Example use:
1828var color = new RGBColor('darkblue');
1829if (color.ok) { // 'ok' is true when the parsing was a success
1830    // alert channels
1831    alert(color.r + ', ' + color.g + ', ' + color.b);
1832    // alert HEX and RGB
1833    alert(color.toHex());
1834    alert(color.toRGB());
1835}
1836*/
1837function RGBColor(color_string)
1838{
1839  this.ok = false;
1840
1841  if(!color_string || color_string == ''){
1842    color_string = 'black';
1843  }
1844
1845  // strip any leading #
1846  if (color_string.charAt(0) == '#') { // remove # if any
1847    color_string = color_string.substr(1,6);
1848  }
1849
1850  color_string = color_string.replace(/ /g,'');
1851  color_string = color_string.toLowerCase();
1852
1853  // before getting into regexps, try simple matches
1854  // and overwrite the input
1855  var simple_colors = {
1856    aliceblue: 'f0f8ff',
1857    antiquewhite: 'faebd7',
1858    aqua: '00ffff',
1859    aquamarine: '7fffd4',
1860    azure: 'f0ffff',
1861    beige: 'f5f5dc',
1862    bisque: 'ffe4c4',
1863    black: '000000',
1864    blanchedalmond: 'ffebcd',
1865    blue: '0000ff',
1866    blueviolet: '8a2be2',
1867    brown: 'a52a2a',
1868    burlywood: 'deb887',
1869    cadetblue: '5f9ea0',
1870    chartreuse: '7fff00',
1871    chocolate: 'd2691e',
1872    coral: 'ff7f50',
1873    cornflowerblue: '6495ed',
1874    cornsilk: 'fff8dc',
1875    crimson: 'dc143c',
1876    cyan: '00ffff',
1877    darkblue: '00008b',
1878    darkcyan: '008b8b',
1879    darkgoldenrod: 'b8860b',
1880    darkgray: 'a9a9a9',
1881    darkgreen: '006400',
1882    darkkhaki: 'bdb76b',
1883    darkmagenta: '8b008b',
1884    darkolivegreen: '556b2f',
1885    darkorange: 'ff8c00',
1886    darkorchid: '9932cc',
1887    darkred: '8b0000',
1888    darksalmon: 'e9967a',
1889    darkseagreen: '8fbc8f',
1890    darkslateblue: '483d8b',
1891    darkslategray: '2f4f4f',
1892    darkturquoise: '00ced1',
1893    darkviolet: '9400d3',
1894    deeppink: 'ff1493',
1895    deepskyblue: '00bfff',
1896    dimgray: '696969',
1897    dodgerblue: '1e90ff',
1898    feldspar: 'd19275',
1899    firebrick: 'b22222',
1900    floralwhite: 'fffaf0',
1901    forestgreen: '228b22',
1902    fuchsia: 'ff00ff',
1903    gainsboro: 'dcdcdc',
1904    ghostwhite: 'f8f8ff',
1905    gold: 'ffd700',
1906    goldenrod: 'daa520',
1907    gray: '808080',
1908    green: '008000',
1909    greenyellow: 'adff2f',
1910    honeydew: 'f0fff0',
1911    hotpink: 'ff69b4',
1912    indianred : 'cd5c5c',
1913    indigo : '4b0082',
1914    ivory: 'fffff0',
1915    khaki: 'f0e68c',
1916    lavender: 'e6e6fa',
1917    lavenderblush: 'fff0f5',
1918    lawngreen: '7cfc00',
1919    lemonchiffon: 'fffacd',
1920    lightblue: 'add8e6',
1921    lightcoral: 'f08080',
1922    lightcyan: 'e0ffff',
1923    lightgoldenrodyellow: 'fafad2',
1924    lightgrey: 'd3d3d3',
1925    lightgreen: '90ee90',
1926    lightpink: 'ffb6c1',
1927    lightsalmon: 'ffa07a',
1928    lightseagreen: '20b2aa',
1929    lightskyblue: '87cefa',
1930    lightslateblue: '8470ff',
1931    lightslategray: '778899',
1932    lightsteelblue: 'b0c4de',
1933    lightyellow: 'ffffe0',
1934    lime: '00ff00',
1935    limegreen: '32cd32',
1936    linen: 'faf0e6',
1937    magenta: 'ff00ff',
1938    maroon: '800000',
1939    mediumaquamarine: '66cdaa',
1940    mediumblue: '0000cd',
1941    mediumorchid: 'ba55d3',
1942    mediumpurple: '9370d8',
1943    mediumseagreen: '3cb371',
1944    mediumslateblue: '7b68ee',
1945    mediumspringgreen: '00fa9a',
1946    mediumturquoise: '48d1cc',
1947    mediumvioletred: 'c71585',
1948    midnightblue: '191970',
1949    mintcream: 'f5fffa',
1950    mistyrose: 'ffe4e1',
1951    moccasin: 'ffe4b5',
1952    navajowhite: 'ffdead',
1953    navy: '000080',
1954    oldlace: 'fdf5e6',
1955    olive: '808000',
1956    olivedrab: '6b8e23',
1957    orange: 'ffa500',
1958    orangered: 'ff4500',
1959    orchid: 'da70d6',
1960    palegoldenrod: 'eee8aa',
1961    palegreen: '98fb98',
1962    paleturquoise: 'afeeee',
1963    palevioletred: 'd87093',
1964    papayawhip: 'ffefd5',
1965    peachpuff: 'ffdab9',
1966    peru: 'cd853f',
1967    pink: 'ffc0cb',
1968    plum: 'dda0dd',
1969    powderblue: 'b0e0e6',
1970    purple: '800080',
1971    red: 'ff0000',
1972    rosybrown: 'bc8f8f',
1973    royalblue: '4169e1',
1974    saddlebrown: '8b4513',
1975    salmon: 'fa8072',
1976    sandybrown: 'f4a460',
1977    seagreen: '2e8b57',
1978    seashell: 'fff5ee',
1979    sienna: 'a0522d',
1980    silver: 'c0c0c0',
1981    skyblue: '87ceeb',
1982    slateblue: '6a5acd',
1983    slategray: '708090',
1984    snow: 'fffafa',
1985    springgreen: '00ff7f',
1986    steelblue: '4682b4',
1987    tan: 'd2b48c',
1988    teal: '008080',
1989    thistle: 'd8bfd8',
1990    tomato: 'ff6347',
1991    turquoise: '40e0d0',
1992    violet: 'ee82ee',
1993    violetred: 'd02090',
1994    wheat: 'f5deb3',
1995    white: 'ffffff',
1996    whitesmoke: 'f5f5f5',
1997    yellow: 'ffff00',
1998    yellowgreen: '9acd32'
1999  };
2000  for (var key in simple_colors) {
2001    if (color_string == key) {
2002      color_string = simple_colors[key];
2003    }
2004  }
2005  // emd of simple type-in colors
2006
2007  // array of color definition objects
2008  var color_defs = [
2009    {
2010      re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
2011      example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
2012      process: function (bits){
2013        return [
2014          parseInt(bits[1],10),
2015          parseInt(bits[2],10),
2016          parseInt(bits[3],10)
2017        ];
2018      }
2019    },
2020    {
2021      re: /^(\w{2})(\w{2})(\w{2})$/,
2022      example: ['#00ff00', '336699'],
2023      process: function (bits){
2024        return [
2025          parseInt(bits[1], 16),
2026          parseInt(bits[2], 16),
2027          parseInt(bits[3], 16)
2028        ];
2029      }
2030    },
2031    {
2032      re: /^(\w{1})(\w{1})(\w{1})$/,
2033      example: ['#fb0', 'f0f'],
2034      process: function (bits){
2035        return [
2036          parseInt(bits[1] + bits[1], 16),
2037          parseInt(bits[2] + bits[2], 16),
2038          parseInt(bits[3] + bits[3], 16)
2039        ];
2040      }
2041    }
2042  ];
2043
2044  // search through the definitions to find a match
2045  for (var i = 0; i < color_defs.length; i++) {
2046    var re = color_defs[i].re;
2047    var processor = color_defs[i].process;
2048    var bits = re.exec(color_string);
2049    if (bits) {
2050      channels = processor(bits);
2051      this.r = channels[0];
2052      this.g = channels[1];
2053      this.b = channels[2];
2054      this.ok = true;
2055    }
2056  }
2057
2058  // validate/cleanup values
2059  this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
2060  this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
2061  this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
2062
2063  // some getters
2064  this.toRGB = function () {
2065    return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
2066  }
2067  this.toHex = function (skip_hex) {
2068    var r = this.r.toString(16);
2069    var g = this.g.toString(16);
2070    var b = this.b.toString(16);
2071    if (r.length == 1) r = '0' + r;
2072    if (g.length == 1) g = '0' + g;
2073    if (b.length == 1) b = '0' + b;
2074    if(skip_hex){
2075      return r + g + b;
2076    }
2077    else {
2078      return '#' + r + g + b;
2079    }
2080  }
2081
2082  // help
2083  this.getHelpXML = function () {
2084
2085    var examples = new Array();
2086    // add regexps
2087    for (var i = 0; i < color_defs.length; i++) {
2088      var example = color_defs[i].example;
2089      for (var j = 0; j < example.length; j++) {
2090        examples[examples.length] = example[j];
2091      }
2092    }
2093    // add type-in colors
2094    for (var sc in simple_colors) {
2095      examples[examples.length] = sc;
2096    }
2097
2098    var xml = document.createElement('ul');
2099    xml.setAttribute('id', 'rgbcolor-examples');
2100    for (var i = 0; i < examples.length; i++) {
2101      try {
2102        var list_item = document.createElement('li');
2103        var list_color = new RGBColor(examples[i]);
2104        var example_div = document.createElement('div');
2105        example_div.style.cssText =
2106                'margin: 3px; '
2107                + 'border: 1px solid black; '
2108                + 'background:' + list_color.toHex() + '; '
2109                + 'color:' + list_color.toHex();
2110
2111        example_div.appendChild(document.createTextNode('test'));
2112        var list_item_value = document.createTextNode(' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex());
2113        list_item.appendChild(example_div);
2114        list_item.appendChild(list_item_value);
2115        xml.appendChild(list_item);
2116
2117      } catch(e){}
2118    }
2119    return xml;
2120
2121  }
2122}
2123
2124function setCookie(cname, cvalue, exseconds, domain, path){
2125  exseconds = exseconds || 86400;
2126  var d = new Date();
2127  d.setTime(d.getTime()+(exseconds*1000));
2128  var expires = ";expires="+d.toGMTString();
2129  if(domain){
2130    domain = ';domain='+domain;
2131  }
2132  else {
2133    domain = '';
2134  }
2135  if(path){
2136    path = ';path='+path;
2137  }
2138  else {
2139    path = '';
2140  }
2141  var val = cname + "=" + cvalue + expires + domain + path;
2142  document.cookie = val;
2143}
2144
2145function getCookie(cname, default_val){
2146
2147  if(typeof default_val === 'undefined'){
2148    default_val = '';
2149  }
2150
2151  var name = cname + "=";
2152  var ca = document.cookie.split(';');
2153  for(var i = 0; i < ca.length; i++){
2154    var c = ca[i].trim();
2155    if(c.indexOf(name) == 0){
2156      return c.substring(name.length, c.length);
2157    }
2158  }
2159  return default_val;
2160}
2161
2162function deleteCookie(name){
2163  document.cookie = name+'=; Max-Age=-99999999;';
2164}
2165
2166
2167// Pomocniczy obiekt używany do wizualnego przedstawiania wyników zapytania ajax-a
2168// Zależnie od rezultatu wyświetla DIV-a z odpowiednim komunikatem i potem go stopniowo faduje.
2169// Jak całkowicie zniknie, usuwa się z pamięci
2170CResultFader3 = function(result, duration, type, x, y){
2171  x = (+x) || 0;
2172  y = (+y) || 0;
2173
2174  var klasa = ' ok';
2175  var color;
2176
2177  if(type == 'ok'){
2178    klasa = ' ok';
2179  }
2180  else if(type == 'warning'){
2181    klasa = ' warning';
2182  }
2183  else if(type == 'error'){
2184    klasa = ' error';
2185  }
2186  else {
2187    color = ' style="color:' + color + '"';
2188  }
2189
2190  duration = duration || 1200;
2191
2192
2193  const $fader = $('<div class="result_fader2'+klasa+'"'+color+'>'+ result+'</div>');
2194
2195  $('body').append($fader);
2196
2197  if(x || y){
2198    $fader.css("top", x + "px");
2199    $fader.css("left", y + "px");
2200  }
2201  else {
2202    $fader.center2();
2203  }
2204
2205  $fader.fadeOut(duration, function(){
2206    $(this).remove();
2207  });
2208}
2209
2210// Pomocniczy obiekt używany do wyświetlania spinnera podczas oczekiwania na system
2211CWaitSpinner = function(){
2212  this.id = 'wait_spinner_'+Math.floor(Math.random()*1e6);
2213
2214  var txt = '<div class="wait_spinner" id="'+this.id+'"></div>';
2215
2216  $('body').append(txt);
2217
2218  $('#'+this.id+'.wait_spinner').center2();
2219}
2220
2221CWaitSpinner.prototype.close = function(){
2222  $('#'+this.id+'.wait_spinner').remove();
2223}
2224
2225
2226
2227// serializuje pola podanego formularza (elementu DOM) do lini zapytania do umieszczenia w URL-u (w GET-cie lub w POST-cie)
2228function serializeForm(form) {
2229//  var kvpairs = [];
2230  var uri = '';
2231  var first = true;
2232  for ( var i = 0; i < form.elements.length; i++ ) {
2233    var e = form.elements[i];
2234//    if(!e.name || !e.value){
2235    if(!e.name){
2236      continue; // Shortcut, may not be suitable for values = 0 (considered as false)
2237    }
2238
2239    switch (e.type){
2240    case 'text':
2241    case 'textarea':
2242    case 'password':
2243    case 'hidden':
2244    case 'select-one':
2245//        kvpairs.push(encodeURIComponent(e.name) + "=" + encodeURIComponent(e.value));
2246        if(first){
2247          first = false;
2248        }
2249        else {
2250          uri += '&';
2251        }
2252
2253        uri += encodeURIComponent(e.name) + "=" + encodeURIComponent(e.value);
2254        break;
2255    case 'radio':
2256    case 'checkbox':
2257        if (e.checked){
2258          if(first){
2259            first = false;
2260          }
2261          else {
2262            uri += '&';
2263          }
2264
2265//          kvpairs.push(encodeURIComponent(e.name) + "=" + encodeURIComponent(e.value));
2266          uri += encodeURIComponent(e.name) + "=" + encodeURIComponent(e.value);
2267        }
2268        break;
2269    /*  To be implemented if needed:
2270    case 'select-one':
2271        ... document.forms[x].elements[y].options[0].selected ...
2272        break;
2273    case 'select-multiple':
2274        for (z = 0; z < document.forms[x].elements[y].options.length; z++) {
2275            ... document.forms[x].elements[y].options[z].selected ...
2276        } */
2277        break;
2278    }
2279  }
2280  return uri;
2281//  return kvpairs;
2282}
2283
2284// helper do wstawiania w HTML przycisku do rozwijania/zwijania wskazanego div-a.
2285// \param el_id         Selektor elementu do rozwinięcia/zwinięcia [w formacie selektora jQuery]
2286// \param collapsed     Flaga, czy element jest początkowo zwinięty
2287// \param options       Ewentualne opcje zwijania/rozwijania
2288function expandBtn(el_id, collapsed, options){
2289
2290  function calcHeight(el_id){
2291    var height = 0;
2292    var top =  0;
2293    var bottom =  0;
2294    $(el_id+' > *').each(function(){
2295      var pos = $(this).offset();
2296      pos.bottom = pos.top + $(this).height();
2297
2298      if(!top || pos.top < top){
2299        top = pos.top;
2300      }
2301      if(!bottom || pos.bottom > bottom){
2302        bottom = pos.bottom;
2303      }
2304    })
2305    height = Math.abs(top-bottom+1);
2306    return height + options.margin;
2307  }
2308
2309  options = options || {};
2310  options.height     = options.height || -1;             // mniejsze lub równe zeru oznacza, że wysokość zostanie obliczona automatycznie. Jeśli to jest funkcja, to po wywołaniu powinna zwrócić wysokość. Wartość numeryczna oznacza ustawioną na sztywno wysokość w pikselach
2311  options.callback   = options.callback || function(){}; // wywoływane po zwinięciu i rozwinięciu
2312  options.margin     = options.margin || 0;             // domyślny dolny margines
2313  options.clas       = options.clas || '';               // klasa w tworzonym divie lub atrybutach
2314  options.min_height = options.min_height || 0;          // wysokość zwiniętego diva
2315  options.payload    = options.payload || 0;             // przekazywane do callbacka zwijania/rozwijania
2316  options.from_class = options.from_class || 0;          // gdy true, stan przycisku jest brany z aktualnej klasy div-a
2317
2318  var id = options.id || 'exp_btn'+Math.floor(Math.random()*1000000);
2319
2320  if(typeof(options.height) != 'function'){
2321    if(+options.height >= 0){ // wartość ustawiona na sztywno [w pikselach]
2322      var height = options.height;
2323      options.height = function(el_id){return height;}
2324    }
2325    else { // za każdym razem przy rozwijaniu spróbujemy obliczyć docelową wysokość div-a
2326      options.height = calcHeight;
2327    }
2328  }
2329
2330/*
2331  if(EXPAND_BUTTONS[el_id]){
2332    console.log('Powtórzony identyfikator w funkcji expandBtn(): '+el_id);
2333  }
2334*/
2335
2336  if(options.from_class){
2337    collapsed = $(el_id).hasClass('collapsed');
2338  }
2339
2340  var txt;
2341  if(options.attrs_only){ // nie generujemy diva a jedynie wymagane atrybuty
2342    txt = ' id="'+id+'" class="'+(collapsed? 'EB_collapsed' : 'EB_expanded')+' '+options.clas+'" onclick="expandBtn_callback(\''+el_id+'\')" ';
2343  }
2344  else { // geenrujemy całego diva przycisku do zwijania/rozwijania
2345    txt = '<div id="'+id+'" class="'+(collapsed? 'EB_collapsed' : 'EB_expanded')+' '+options.clas+'" onclick="expandBtn_callback(\''+el_id+'\')"></div>';
2346  }
2347
2348  EXPAND_BUTTONS[el_id] = {
2349    id : id,
2350    options : options,
2351    collapsed : collapsed
2352  }
2353/*
2354  // i jeszcze ustawmy odpowiednią klasę dla kontenera
2355  if(collapsed){
2356    $(el_id).removeClass('expanded').addClass('collapsed');
2357  }
2358  else {
2359    $(el_id).removeClass('collapsed').addClass('expanded');
2360  }
2361*/
2362  return txt;
2363}
2364
2365// aktualizuje wysokość elementu do nowej wysokości (jeśli jest obliczana automatycznie)
2366function expandBtnUpdate(el_id){
2367  expandBtn_callback(el_id, true);
2368}
2369
2370
2371// callback z funkcji expandBtn()
2372function expandBtn_callback(el_id, force_expand){
2373  var btn = EXPAND_BUTTONS[el_id];
2374  var id = '#'+btn.id;
2375  if(btn.collapsed || force_expand){ // rozwijamy
2376    var height = btn.options.height(el_id);
2377    $(el_id).animate({height : height }, 300, null, function(){btn.options.callback(btn.options.payload, true)});
2378    $(el_id).removeClass('collapsed');
2379    $(id).removeClass('EB_collapsed').addClass('EB_expanded');
2380    btn.collapsed = false;
2381  }
2382  else {
2383//    btn.options.height = $(el_id).height(); // zapamiętajmy aktualną wysokość div-a
2384    $(el_id).animate({height : btn.options.min_height }, 300, null, function(){btn.options.callback(btn.options.payload, false)});
2385    $(el_id).addClass('collapsed');
2386    $(id).removeClass('EB_expanded').addClass('EB_collapsed');
2387    btn.collapsed = true;
2388  }
2389
2390
2391}
2392
2393// zamienia liczbę na rzymską
2394function romanize(num){
2395  if(!+num){
2396    return 0;
2397  }
2398  var digits = String(+num).split(""),
2399      key = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM",
2400             "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC",
2401             "","I","II","III","IV","V","VI","VII","VIII","IX"],
2402      roman = "",
2403      i = 3;
2404  while (i--){
2405    roman = (key[+digits.pop() + (i * 10)] || "") + roman;
2406  }
2407  return Array(+digits.join("") + 1).join("M") + roman;
2408}
2409
2410var NUMERY_KIERUNKOWE = [12,13,14,15,16,17,18,22,23,24,
2411                         25,29,32,33,34,41,42,43,44,46,
2412                         48,52,54,55,56,58,59,61,62,63,
2413                         65,67,68,71,74,75,76,77,81,82,
2414                         83,84,85,86,87,89,91,94,95];
2415
2416// helper do sprawdzania, czy mamy do czynienia z poprawnym numerem komórkowym
2417// Jeśli numer jest niepopawny, zwraca false
2418// Jeśli numer jest stacjonarny, zwraca 0
2419function validateMobilePhone(tel){
2420  tel = tel.replace(/[^0-9]/g,''); // pozbądźmy się znaków innych niż cyfry
2421
2422  tel = tel.substr(-9);
2423
2424  var res = tel.match(/^\d\d\d\d\d\d\d\d\d$/); // dokładnie 9 cyfr
2425
2426  if(!res){
2427    return false; // błędny numer
2428  }
2429  else {
2430    if(NUMERY_KIERUNKOWE.indexOf(+tel.substring(0,2)) >= 0){
2431      return 0; // to jest numer stacjonarny
2432    }
2433  }
2434
2435  return tel;
2436}
2437
2438function displayPhone(tel){ // helper do ładnego wyświetlania numeru
2439  tel = tel.replace(/^00/,'+'); // zamieńmy początkowe 00 na +
2440  tel = tel.replace(/[^0-9+]/g,''); // pozbądźmy się znaków innych niż cyfry i plus
2441
2442  if(tel.length == 11 && tel.substring(0,2) == '48'){ // tel. kom. lub stacjonarny z kodem kraju ale bez wiodącego +
2443    tel = '+' + tel;
2444  }
2445
2446  if(tel.length == 12 && tel.substring(1,3) == '48'){ // dla polskich numerów sprawdźmy, czy to numer stacjonarny
2447
2448    if(NUMERY_KIERUNKOWE.indexOf(+tel.substring(3,5)) >= 0){
2449      // mamy numer stacjonarny
2450      tel = '+48 ' + tel.substring(3,5) + ' ' + tel.substring(5,8) + ' ' + tel.substring(8,10) + ' ' + tel.substring(10,12);
2451    }
2452    else { // komórkowy albo specjalny
2453      tel = '+48 ' + tel.substring(3,6) + ' ' + tel.substring(6,9) + ' ' + tel.substring(9,12);
2454    }
2455  }
2456  else if(tel.length == 9){ // tel komórkowy lub stacjonarny bez kodu kraju
2457    if(NUMERY_KIERUNKOWE.indexOf(+tel.substring(0,2)) >= 0){
2458      // mamy numer stacjonarny
2459      tel = tel.substring(0,2) + ' ' + tel.substring(2,5) + ' ' + tel.substring(5,7) + ' ' + tel.substring(7,9);
2460    }
2461    else { // komórkowy albo specjalny
2462      tel = tel.substring(0,3) + ' ' + tel.substring(3,6) + ' ' + tel.substring(6,9);
2463    }
2464  }
2465  // w przypadku innych numerów zwracamy je jak leci
2466  return tel;
2467}
2468
2469
2470
2471function generic_compare(sa, sb, ia, ib, numeric){
2472  if(numeric){
2473    if(+sa == +sb){
2474      return ia - ib;
2475    }
2476    return (+sb) - (+sa);
2477  }
2478  else {
2479    try {
2480      var n = sa.localeCompare(sb);
2481    }
2482    catch(e){
2483      var n = 0;
2484    }
2485    if(!n){
2486      return ia - ib;
2487    }
2488    return -n;
2489  }
2490}
2491
2492function generic_compare_desc(sa,sb,ia,ib, numeric){
2493  if(numeric){
2494    if(+sa == +sb){
2495      return ia - ib;
2496    }
2497    return (+sa) - (+sb);
2498  }
2499  else {
2500    try {
2501      var n = sa.localeCompare(sb);
2502    }
2503    catch(e){
2504      var n = 0;
2505    }
2506    if(!n){
2507      return ia - ib;
2508    }
2509    return n;
2510  }
2511}
2512
2513function LOC(param, par1, par2, par3){
2514
2515  if(param == 'ID'){
2516    var a = 1;
2517  }
2518
2519  try {
2520//    var string = txt_translations[param];
2521    var string = param; // w tym przypadku nie ma lokalizacji, zatem przekazujemy string 1:1
2522
2523    if(typeof string === 'undefined') {
2524      string = 'Brak tłumaczenia JS ('+lang+') dla \'<span style="color:red">' + param + '\'</span>';
2525    }
2526
2527    if(typeof par1 !== 'undefined'){
2528      string = string.replace(/<<param1>>/g, par1);
2529    }
2530    if(typeof par2 !== 'undefined'){
2531      string = string.replace(/<<param2>>/g, par2);
2532    }
2533    if(typeof par3 !== 'undefined'){
2534      string = string.replace(/<<param3>>/g, par3);
2535    }
2536
2537    return string;
2538  }
2539  catch(e){
2540    return '<span style="color:red">BRAK PLIKÓW LOKALIZACJI</span>';
2541  }
2542}
2543
2544function LOCX(param, par1, par2, par3){ // ta wersja funkcji LOC powinna być używana dla tyc
2544h lokalizacji, które nie mają automatycznie trafiać do bazy
2545  return LOC(param, par1, par2, par3);
2546}
2547
2548// przygotowuje HTML kontrolki tłumaczącej
2549// \param    from      Selektor css kontrolki zawierającej tekst źródłowy (input lub textarea)
2550// \param    to        Selektor css kontrolki docelowej (input lub textarea)
2551// \param    lang_to   Dwuznakowe ID języka docelowego (np. en, ru, sk itd.)
2552// \param    lang_from Jeśli jest, to zawiera dwuznakowe ID języka źródłowego. Jak nie zostanie podane, to język źródłowy jest brany
2553//                     z lang_base z conf-a. Trzeba zdefiniować w javascripcie w jakimś headerze najlepiej.
2554function GTRANS(from, to, lang_to, lang_from, callback){
2555
2556  lang_from = lang_from || (window.lang_base? window.lang_base : 'pl'); // defaultowo tłumaczymy z lang_base (a jak nie ma, to z pl)
2557
2558  callback = callback? callback : '';
2559
2560  return '<div class="google_translate_ctrl" from="'+from+'" to="'+to+'" lang_from="'+lang_from+'" lang_to="'+lang_to+'" onclick="$(this).GoogleTranslate('+callback+')" title="Automatyczne tłumaczenie Google Translate z '+lang_from+' na '+lang_to+'></div>';
2561}
2562
2563//var dymki_first_time = true;
2564
2565// helper do wypisywania ikonki z dymkiem.
2566// \param id     ID parametru dymka (zawsze w grupie DYMKI)
2567// \param lewy   Dymek zostanie wyświetlony z lewej strony
2568function dymek(id, lewy, div_id, head = '', light = false){
2569/*
2570  if(dymki_first_time && typeof(DYMKI) == 'undefined'){
2571    dymki_first_time = false;
2572    alert('Zdefiniuj DYMKI!');
2573
2574    return;
2575  }
2576
2577  var dymek = DYMKI[id]? (DYMKI[id].wartosc) : 'Nie ustawiono parametru <b>'+_html(id)+'</b><br />w grupie <b>DYMKI</b>';
2578*/
2579
2580  if(typeof JSLOC == "object"){ // być może LOC jest zdefiniowane lokalnie w plikach JS
2581    var dymek = JSLOC[id];
2582  }
2583  else if(typeof LOC == 'function'){
2584    var dymek = LOC(id);
2585  }
2586
2587  return dymek2(dymek, lewy, div_id, head, light);
2588}
2589
2590// helper do wypisywania ikonki z dymkiem.
2591// \param dymek  treść dymka
2592// \param lewy   Dymek zostanie wyświetlony z lewej strony
2593function dymek2(dymek, lewy, div_id, head = '', light = false){
2594  var txt = `
2595<div class="dymek_cont">
2596  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="${light? 'light' : ''}">
2597      <path fill-rule="evenodd"
2598          d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
2599          clip-rule="evenodd" />
2600  </svg>
2601
2602  <div class="dymek_inner${lewy? ' lewy' : ''}">
2603    <div class="dymek_inner_txt"${div_id? ' id="'+div_id+'"' : ''}>${head? `<div class="dymek_head">${head}</div>${dymek}` : dymek}</div>
2604  </div>
2605</div>`.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3');
2606  return txt;
2607}
2608
2609
2610// Na podstawie otrzymanej tablicy menu itemów generuje i zwraca html z menu
2611function generateMenu(items, demo){
2612
2613  items = items || {};
2614
2615  var txt = '';
2616  var cnt = 0;
2617
2618  // zbudujmy strukturę drzewka menu, którą potem przeparsujemy tworząc div-y na ekranie
2619  var menu_items = {};
2620
2621  for(var mniid in items){
2622    var it = items[mniid];
2623
2624    menu_items[mniid] = menu_items[mniid] || {
2625      items : {}
2626    };
2627    menu_items[mniid].it = it;
2628
2629    var parent = it.parent;
2630    if(!menu_items[parent]){
2631      menu_items[parent] = {
2632        items : {}
2633      };
2634    }
2635    menu_items[parent].items[mniid] = menu_items[parent].items[mniid] || {
2636      items : {}
2637    };
2638    menu_items[parent].items[mniid].it = it;
2639  }
2640
2641  function createMenu(mniid, depth, active, position){
2642    var txt = '';
2643    position = position || 1;
2644
2645    var mi = menu_items[mniid];
2646    var aktywne = (mi.it.status == 'aktywny');
2647
2648    if(+mniid == 0){ // dla roota nie generujemy nic poza rekurencyjnie wywoływanymi menu itemami głównego poziomu
2649      // posortujmy itemy zgodnie z priorytetem
2650      var arr = [];
2651      for(var sub_mniid in mi.items){
2652        arr.push(mi.items[sub_mniid]);
2653      }
2654      arr.sort(function(a, b){
2655        return +b.it.priorytet - +a.it.priorytet;
2656      });
2657      for(var i = 0; i < arr.length; i++){
2658        var sub_mniid = arr[i].it.mniid;
2659        txt += createMenu(sub_mniid, depth+1, aktywne);
2660      }
2661      return txt;
2662    }
2663
2664    if(!aktywne){
2665      return ''; // pomijamy nieaktywne pozycje menu
2666    }
2667
2668    var items_no = getArraySize(mi.items);
2669    if(!items_no){
2670      if(depth > 1){
2671        txt += '<li>';
2672        var cls = (position == 1? 'first' : '');
2673        cls += (position > 1? ' next' : '');
2674        cls += (position == items_no? ' last' : '');
2675
2676        if(mi.it.link != ''){
2677          txt += '<a '+(demo? 'target="_blank"' : '')+' class="" data-position="'+position+'"   href="'+_html(mi.it.link)+'"><div class="'+cls+'" >'+_html(mi.it.tytul)+'</div></a>';
2678        }
2679        else {
2680          txt += '<a '+(demo? 'target="_blank"' : '')+' class="" data-position="'+position+'"   href="javascript:void(0)"><div class="'+cls+'" >'+_html(mi.it.tytul)+'</div></a>';
2681        }
2682        txt += '</li>';
2683      }
2684      else {
2685        txt += '<li class="position'+position+'">';
2686          if(mi.it.link != ''){
2687            txt += '<a '+(demo? 'target="_blank"' : '')+' href="'+_html(mi.it.link)+'" style="cursor:pointer;">';
2688          }
2689          else {
2690            txt += '<a '+(demo? 'target="_blank"' : '')+' href="javascript:void(0)" style="cursor:pointer;">';
2691          }
2692            txt += '<div>';
2693              txt += '<span class="menu-mid">'+_html(mi.it.tytul)+'</span>';
2694            txt += '</div>';
2695          txt += '</a>';
2696        txt += '</li>';
2697      }
2698    }
2699    else {
2700      txt += '<li class="has-dropdown position'+position+'">';
2701        txt += '<a '+(demo? 'target="_blank"' : '')+' style="cursor:default;">';
2702          txt += '<div>';
2703            txt += '<span class="menu-mid"  >'+_html(mi.it.tytul)+'</span>';
2704          txt += '</div>';
2705        txt += '</a>';
2706        txt += '<ul class="dropdown">';
2707          // posortujmy itemy zgodnie z priorytetem
2708          var arr = [];
2709          for(var sub_mniid in mi.items){
2710            arr.push(mi.items[sub_mniid]);
2711          }
2712          arr.sort(function(a, b){
2713            return +b.it.priorytet - +a.it.priorytet;
2714          });
2715          var pos = 1;
2716          for(var i = 0; i < arr.length; i++){
2717            if(arr[i].it.status == 'aktywny'){
2718              var sub_mniid = arr[i].it.mniid;
2719              txt += createMenu(sub_mniid, depth+1, aktywne, pos++);
2720            }
2721          }
2722        txt += '</ul>';
2723      txt += '</li>';
2724    }
2725
2726    return txt;
2727  }
2728
2729
2730  txt += '<div class="toggleMenuDiv">'; // przycisk do zwijania menu dla responsywnego
2731    txt += '<a class="toggleMenu" href="#">Menu</a>';
2732    txt += '<div style="clear:both"></div>';
2733  txt += '</div>';
2734
2735  txt += '<ul class="nav">';
2736    txt += createMenu(0, 0, 1, 1); // a teraz rekurencyjnie stwórzmy hierarchię divów dla poszczególnych itemów
2737  txt += '</ul>';
2738
2739  return txt;
2740}
2741
2742formatFileSize = function(bytes){
2743  bytes = +bytes || 0;
2744
2745  if(bytes >= 1000000000){
2746    return (bytes / 1000000000).toFixed(2) + ' GB';
2747  }
2748  if(bytes >= 1000000){
2749    return (bytes / 1000000).toFixed(2) + ' MB';
2750  }
2751  return (bytes / 1000).toFixed(2) + ' KB';
2752}
2753
2754// zwraca obiekt, w którym pola obiektu dst są zaktualizowane polami obiektu src. Dodatkowe pola obiektu src są także dopisywane do wyniku
2755merge_objects = function(dst, src){
2756  return $.extend({}, dst, src);
2757}
2758
2759function saveTextAsFileDownload(text, filename, mime)
2760{
2761  if(!text){
2762    return;
2763  }
2764
2765  filename = filename || 'plik.txt';
2766  mime = mime || 'text/plain';
2767
2768  var textFileAsBlob = new Blob([text], {type: mime});
2769  var downloadLink = document.createElement("a");
2770  downloadLink.download = filename;
2771  downloadLink.innerHTML = "Download File";
2772
2773  downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
2774  downloadLink.style.display = "none";
2775  document.body.appendChild(downloadLink);
2776  downloadLink.click();
2777  document.body.removeChild(downloadLink);
2778}
2779
2780
2781// konwertuje php.ini values (8K, 4G itd) na liczby
2782function return_bytes(val){
2783
2784  val = val.trim();
2785  var last = val[val.length-1].toLowerCase();
2786  val = parseInt(val,10);
2787
2788  switch(last) {
2789    // The 'G' modifier is available since PHP 5.1.0
2790    case 'g':
2791      val *= 1024;
2792    case 'm':
2793      val *= 1024;
2794    case 'k':
2795      val *= 1024;
2796  }
2797
2798  return val;
2799}
2800
2801function page_handler_example(type, page, of_pages, current_page){
2802
2803  page = +page || 0;
2804  of_pages = +of_pages || 0;
2805  current_page = +current_page || 0;
2806
2807  var curr = (page == current_page)? ' current' : '';
2808
2809  var spacer = '<div class="spacer"></div>';
2810  var spacer_div =  spacer + '</div>';
2811
2812  if(type == '...'){
2813    return '<div class="page ellipsis">...' + spacer_div;
2814  }
2815  else if(type == '|<'){
2816    return '<div class="page first' + curr + '" page="' + page + '">|&lt;&lt;' + spacer_div;
2817  }
2818  else if(type == '<'){
2819    return '<div class="page prev' + curr + '" page="' + page + '">&lt;&lt;' + spacer_div;
2820  }
2821  else if(type == '>'){
2822    return '<div class="page next' + curr + '" page="' + page + '">&gt;&gt;' + spacer_div;
2823  }
2824  else if(type == '>'){
2825    return '<div class="page next' + curr + '" page="' + page + '">&gt;&gt;' + spacer_div;
2826  }
2827  else if(type == '>|'){
2828    return '<div class="page last' + curr + '" page="' + page + '">&gt;&gt;|</div>';
2829  }
2830
2831  // dla zwykłych stron (type == '#')
2832  return '<div class="page nr' + curr + '" page="' + page + '">' + (page+1) +  spacer_div;
2833}
2834
2835
2836// helper do tworzenia pagera
2837// \param page_handler Opcjonalna funkcja do tworzenia linka, wywoływana jako page_handler(type, page, of_pages, current_page). page i curent_page liczony od 0
2838//                     Parametr type: '#' oznacza numer strony
2839//                                    '...' oznacza przerwę w numeracji
2840//                                    '|<' oznacza powrót na początek
2841//                                    '<' oznacza przejście do poprzedniej strony
2842//                                    '>' oznacza przejście do następnej strony
2843//                                    '>|' oznacza przejście do ostatniej strony
2844function pager(items_no, page, options, page_handler){
2845
2846  this.items_no = +items_no || 0;
2847
2848  if(!this.items_no){
2849    return ''; // nie ma sensu dawać pagera przy braku stron
2850  }
2851
2852  options = options || {};
2853
2854  if(!options.per_page){
2855    console.log('W opcjach pagera należy podać ilość itemów na stronę. Przyjęto domyślną wartość 20 itemów na stronę.');
2856    options.per_page = 20;
2857  }
2858
2859  this.page = +page || 0;
2860  this.o = options;
2861  this.o.per_page = +options.per_page;
2862  this.o.max_before = +options.max_before || 3;
2862 // domyślnie 3 przed bieżącą i 3 po bieżącej
2863  this.o.max_after = +options.max_after || 3; // domyślnie 3 przed bieżącą i 3 po bieżącej
2864  this.o.show_first = options.show_first || true; // czy pokazywać powrót do początku
2865  this.o.show_last = options.show_last || true;  // czy pokazywać przejście na koniec
2866  this.o.show_prev = options.show_prev || true;  // czy pokazywać przejście o jedną wstecz
2867  this.o.show_next = options.show_next || true;  // czy pokazywać przejście o jedną do przodu
2868  this.o.show_with_one_page = options.show_with_one_page || false; // czy pokazywać pager w przypadku jednej strony
2869  this.o.show_select = options.show_select || false; // czy pokazywać selecta ze stronami do wybowu
2870  this.page_handler = page_handler || page_handler_example;
2871
2872  this.pages_no = Math.floor(this.items_no / this.o.per_page) + ((this.items_no % this.o.per_page)? 1 : 0);
2873
2874  if(this.pages_no < 2 && !this.o.show_with_one_page){
2875    return ''; // nie pokazujemy pagera dla jednej strony
2876  }
2877
2878  var txt = '';
2879  if(this.pages_no > 2 && this.page > 0){ // first
2880    if(this.o.show_first){
2881      txt += this.page_handler('|<', 0, this.pages_no, this.page);
2882    }
2883  }
2884
2885  if(this.o.show_prev && this.page > 0){ // prev
2886    txt += this.page_handler('<', this.page-1, this.pages_no, this.page);
2887  }
2888
2889  if(this.page-this.o.max_before > 0){ // początkowa przerwa w numeracji
2890    txt += this.page_handler('...', 0, this.pages_no, this.page);
2891  }
2892
2893  for(var p = this.page-this.o.max_before; p < this.page; ++p){ // strony przed bieżącą
2894    if(p >= 0){
2895      txt += this.page_handler('#', p, this.pages_no, this.page);
2896    }
2897  }
2898
2899  txt += this.page_handler('#', this.page, this.pages_no, this.page);
2900
2901  for(var p = this.page+1; p <= this.page+this.o.max_after; ++p){ // strony po bieżącej
2902    if(p < this.pages_no){
2903      txt += this.page_handler('#', p, this.pages_no, this.page);
2904    }
2905  }
2906
2907  if(this.page+this.o.max_before < this.pages_no-1){ // końcowa przerwa w numeracji
2908    txt += this.page_handler('...', 0, this.pages_no, this.page);
2909  }
2910
2911  if(this.o.show_next && this.page < this.pages_no-1){ // next
2912    txt += this.page_handler('>', this.page+1, this.pages_no, this.page);
2913  }
2914
2915  if(this.pages_no > 2 && this.page < this.pages_no-1){ // last
2916    if(this.o.show_last){
2917      txt += this.page_handler('>|', this.pages_no-1, this.pages_no, this.page);
2918    }
2919  }
2920
2921  if(this.pages_no > 2 && this.o.show_select){
2922    txt += '<select>';
2923      txt += '<option value="-1">---</option>';
2924      for(var p = 0; p < this.pages_no; ++p){
2925        txt += '<option value="' + p + '">' + (p+1) + '</option>';
2926//        txt += this.page_handler('#', p, this.pages_no, this.page);
2927      }
2928    txt += '</select>';
2929  }
2930
2931  return txt;
2932}
2933
2934
2935
2936
2937
2938
2939// Widget do pobierania odpowiedzi tak/nie. Wyrażenie await Confirm(...) zwraca true lub false zależnie od tego, czy klinknięto na tak, czy na nie
2940// \param o           Obiekt z opcjami; dostępne opcje to:
2941//   dlg_class = ''   Dodatkowa klasa okienka dialogu
2942//   dlg_style = ''   Dodatkowe style okienka dialogu
2943//   z_index = null   Jeśli jest ustawiony, wymusza z-index dla dialogu [int]
2944//   txt_yes = '&nbsp;&nbsp;&nbsp;TAK&nbsp;&nbsp;&nbsp;'  Tekst na przycisku YES
2945//   txt_no = '&nbsp;&nbsp;&nbsp;NIE&nbsp;&nbsp;&nbsp;'   Tekst na przycisku NO
2946//   no_outside_click = false       TRUE powoduje, że nie reaguje na kliknięcie poza dialogiem
2947//   single_btn = false             TRUE powoduje, że nie ma przycicisku NIE
2948Confirm = function(title, alert, o){
2949
2950  return new Promise((resolve, reject) => {
2951
2952    title = title || null;
2953    alert = alert || null;
2954    o = o || {};
2955    o.dlg_class = o.dlg_class || '';
2956    o.dlg_style = o.dlg_style || '';
2957    o.z_index = +o.z_index || null;
2958    o.txt_yes = o.txt_yes || ((typeof callback_no == 'function')? '&nbsp;&nbsp;&nbsp;TAK&nbsp;&nbsp;&nbsp;' : '&nbsp;&nbsp;&nbsp;OK&nbsp;&nbsp;&nbsp;');
2959    o.txt_no = o.txt_no || '&nbsp;&nbsp;&nbsp;NIE&nbsp;&nbsp;&nbsp;';
2960    o.no_outside_click = o.no_outside_click || false;
2961    o.single_btn = o.single_btn || false;
2962
2963    var id = 'confirmdlg_' + Math.floor(Math.random()*1e6);
2964
2965
2966    var txt = '';
2967
2968    var bkg_width = $(document).width()-1;
2969    var bkg_height = $(document).height()-1;
2970
2971    txt += '<div class="confirmdlg_dark_bkg" id="'+id+'_dark_bkg" style="position:absolute;width:'+bkg_width+'px;height:'+bkg_height+'px;left:0;top:0;' + (+o.z_index? `z-index:${o.z_index};` : '') +'"></div>';
2972
2973    txt += '<div class="confirmdlg '+o.dlg_class+'" id="'+id+'" style="position:absolute;' + (+o.z_index? `z-index:${o.z_index + 1};` : '') + o.dlg_style + '" >';
2974
2975      if(title){
2976        txt += '<div class="confirmdlg_title">' + title + '</div>';
2977      }
2978      if(alert){
2979        txt += '<div class="confirmdlg_alert">
2979' + alert + '</div>';
2980      }
2981      txt += '<div class="confirmdlg_btns">';
2982        txt += '<form onsubmit="return false">';
2983          txt += '<div class="confirmdlg_btn" id="btn_yes">' + o.txt_yes + '</div>';
2984          if(!o.single_btn){
2985            txt += '<div class="confirmdlg_btn" id="btn_no">' + o.txt_no + '</div>';
2986          }
2987          txt += '<input type="text" id="confirmdlg_target" style="width:0;height:0;outline:none !important;border:none !important;" />';
2988        txt += '</form>';
2989      txt += '</div>';
2990
2991    txt += '</div>';
2992
2993    $('body').append(txt);
2994
2995    $('.confirmdlg#'+id).center2();
2996
2997    $('.confirmdlg#'+id + ' #btn_yes').click(function(){
2998      closeDlg();
2999      resolve(true);
3000    });
3001
3002    $('.confirmdlg#'+id + ' #btn_no').click(function(){
3003      closeDlg();
3004      resolve(false);
3005    });
3006
3007
3008    if(!o.no_outside_click){ // kliknięcie poza dialogiem oznacza 'NIE'
3009      $('.confirmdlg_dark_bkg#'+id+'_dark_bkg').click(function(){
3010        closeDlg();
3011        resolve(false);
3012      });
3013    }
3014
3015    $('.confirmdlg#'+id+' #confirmdlg_target').focus();
3016    $('.confirmdlg#'+id+' #confirmdlg_target').keyup(function(e){
3017      if(e.which == 13 || e.which == 32){ // enter i spacja
3018        closeDlg();
3019        resolve(true);
3020      }
3021      else if(e.which == 27){ // escape
3022        closeDlg();
3023        resolve(false);
3024      }
3025    });
3026
3027    var closeDlg = function(){
3028      $('.confirmdlg_dark_bkg#'+id+'_dark_bkg').remove();
3029      $('.confirmdlg#'+id).remove();
3030      $('body').off('keyup.ConfirmDlg');
3031    }
3032  });
3033}
3034/*
3035Confirm = function(title, alert, callback_yes, callback_no, o){
3036
3037  callback_yes = callback_yes || function(){};
3038  callback_no = callback_no || function(){};
3039
3040  title = title || null;
3041  alert = alert || null;
3042  o = o || {};
3043  o.dlg_class = o.dlg_class || '';
3044  o.txt_yes = o.txt_yes || ((typeof callback_no == 'function')? '&nbsp;&nbsp;&nbsp;TAK&nbsp;&nbsp;&nbsp;' : '&nbsp;&nbsp;&nbsp;OK&nbsp;&nbsp;&nbsp;');
3045  o.txt_no = o.txt_no || '&nbsp;&nbsp;&nbsp;NIE&nbsp;&nbsp;&nbsp;';
3046  o.no_outside_click = o.no_outside_click || false;
3047  o.single_btn = o.single_btn || false;
3048
3049  var id = 'confirmdlg_' + Math.floor(Math.random()*1e6);
3050
3051
3052  var txt = '';
3053
3054  var bkg_width = $(document).width()-1;
3055  var bkg_height = $(document).height()-1;
3056
3057  txt += '<div class="confirmdlg_dark_bkg '+o.dlg_class+'" id="'+id+'_dark_bkg" style="position:absolute;width:'+bkg_width+'px;height:'+bkg_height+'px;left:0;top:0;"></div>';
3058
3059  txt += '<div class="confirmdlg" id="'+id+'" style="position:absolute;" >';
3060
3061    if(title){
3062      txt += '<div class="confirmdlg_title">' + title + '</div>';
3063    }
3064    if(alert){
3065      txt += '<div class="confirmdlg_alert">' + alert + '</div>';
3066    }
3067    txt += '<div class="confirmdlg_btns">';
3068      txt += '<form onsubmit="return false">';
3069        txt += '<div class="confirmdlg_btn" id="btn_yes">' + o.txt_yes + '</div>';
3070        if(!o.single_btn){
3071          txt += '<div class="confirmdlg_btn" id="btn_no">' + o.txt_no + '</div>';
3072        }
3073        txt += '<input type="text" id="confirmdlg_target" style="width:0;height:0;outline:none !important;border:none !important;" />';
3074      txt += '</form>';
3075    txt += '</div>';
3076
3077  txt += '</div>';
3078
3079  $('body').append(txt);
3080
3081  $('.confirmdlg#'+id).center2();
3082
3083  $('.confirmdlg#'+id + ' #btn_yes').click(function(){
3084    closeDlg();
3085    setTimeout(callback_yes, 20);
3086  });
3087
3088  $('.confirmdlg#'+id + ' #btn_no').click(function(){
3089    closeDlg();
3090    setTimeout(callback_no, 20);
3091  });
3092
3093
3094  if(!o.no_outside_click){ // kliknięcie poza dialogiem oznacza 'NIE'
3095    $('.confirmdlg_dark_bkg#'+id+'_dark_bkg').click(function(){
3096      closeDlg();
3097      setTimeout(callback_no, 20);
3098    });
3099  }
3100
3101  $('.confirmdlg#'+id+' #confirmdlg_target').focus();
3102  $('.confirmdlg#'+id+' #confirmdlg_target').keyup(function(e){
3103    if(e.which == 13 || e.which == 32){ // enter i spacja
3104      closeDlg();
3105      setTimeout(callback_yes, 20);
3106    }
3107    else if(e.which == 27){ // escape
3108      closeDlg();
3109      setTimeout(callback_no, 20);
3110    }
3111  });
3112
3113  var closeDlg = function(){
3114    $('.confirmdlg_dark_bkg#'+id+'_dark_bkg').remove();
3115    $('.confirmdlg#'+id).remove();
3116    $('body').off('keyup.ConfirmDlg');
3117  }
3118}
3119*/
3120// Klasa do wyświetlania selecta z kategoriami i itemami w tych kategoriach
3121// \param input_id           Selektor elementu, do którego odnosi się picker, podawany jako selektor jQuery. To zwykle powinien być input type="text" żeby wybrać początkową wartość, ale może być cokolwiek
3122// \param options  Obiekt z opcjami dla okienka
3123//   Najważniejsze opcje to:
3124//     added_class           Dodatkowa klasa dodawana do głównego diva selecta
3125//     z_index = 500         z-index dla listy selecta
3126//     opacity = 0.1         Przezroczystość tła
3127//     position = 'left top,left bottom'         Położenie okienka względem elementu input_id; '' oznacza wyśrodkowanie na ekranie
3128//                           Parametr składa się z czterech (lub sześciu) części (spacja to dokładnie jedna spacja a przecinek musi być bez spacji):
3129//                             <left selecta> <top selecta>,<left inputa> <top inputa>,dx,dy
3130//                           Select zostanie ułożony tak, żeby jego <left selecta> <top selecta> znalazł się w <left inputa> <top inputa>
3131//                           Parametry dx,dy są opcjonalne i stanowią dodatkowy offset w poziomie i w pionie
3132//                           Parmetry dostępne dla left to left, center, right.
3133//                           Parmetry dostępne dla top to top, center, bottom.
3134//     parent = 'body'       Parent, do którego zostanie dołączone okienko
3135//     empty_text = '---'    Tekst wypisywany w przypadku, gdy lista selecta jest pusta
3136//     max_height = 0        Maksymalna długość selecta [px]. Zero oznacza, że maksymalną długością będzie dolna krawędź ekranu
3137//     width = 0             Szerokość selecta [px]. Zero oznacza, że select będzie szerokości elementu inputa
3138// \param elements           Tablica/obiekt zawierający obiekty do wyświetlenia lub funkcja elements(input_id, <wartość atrybutu 'picker' inputa>) zwracająca obiekt/tablicę z elementami selecta
3139//                           Na pierwszym poziomie jest lista kategorii, które zawierają itemy.
3140//                           Jeśli kategoria nie ma nazwy (pola 'txt') lub jest ono puste, to tytuł tej kategorii nie jest wyświetlany.
3141//                           Lista itemów w kategorii może być pusta
3142//                           elements = [
3143//                             { txt : '<nazwa kategorii na liście>',
3144//                               items : [
3145//                                         { txt : '<nazwa subitema na liście'>, item : <obiekt subitema>, id : <opcjonalny unikalny id itema>, val : <opcjonalna wartość dla selecta> },
3146//                                         { txt : '<nazwa subitema na liście'>, item : <obiekt subitema>, id : <opcjonalny unikalny id itema>, val : <opcjonalna wartość dla selecta> },
3147//                                         ...
3148//                                       ],
3149//                               kat   : <obiekt kategorii>,
3150//                               id    : <opcjonalnie unikalny id kategorii - jeśli mamy mieć możliwość manipulowania zawartością selecta>,
3151//                               val   : <opcjonalna wartość dla selecta>
3152//                             },
3153//                             { txt : '<nazwa kategorii na liście>',
3154//                               items : [
3155//                                         { txt : '<nazwa subitema na liście'>, item : <obiekt subitema>, id : <opcjonalny unikalny id itema>, val : <opcjonalna wartość dla selecta> },
3156//                                         { txt : '<nazwa subitema na liście'>, item : <obiekt subitema>, id : <opcjonalny unikalny id itema>, val : <opcjonalna wartość dla selecta> },
3157//                                         ...
3158//                                       ],
3159//                               kat   : <obiekt kategorii>,
3160//                               id    : <opcjonalnie unikalny id kategorii - jeśli mamy mieć możliwość manipulowania zawartością selecta>
3161//                               val   : <opcjonalna wartość dla selecta>
3162//                             },
3163//                             ...
3164//                           ]
3165// \param sort_fn            Jeśli jest, to powinien być funkcją wywoływaną jako sort(a, b, rodzaj). Parametrami a,b tej funkcji są porównywane obiekty kategorii lub itemów a parametr 'rodzaj' to 0 dla itema lub 1 dla kategorii.
3166// \param on_select_fn       Jeśli jest, to powinien być funkcją wywoływaną jako on_select_fn(<tekst wybranego itema/kategorii>, input_id, <wartość atrybutu 'picker' inputa>, <obiekt wybranego elementu - itema lub kategorii>, <opcjonalny unikalny ID itema lub kategorii>, <rodzaj: 0 dla itema lub 1 dla kategorii>)
3167CSelectPicker = function(input_id, options, elements, sort_fn, on_select_fn){
3168  if(!input_id){
3169    alert('Nie wskazano elementu reagującego na selectpickera');
3170    return;
3171  }
3172
3173  sort_fn = sort_fn || null; // defaultowo bez sortowania
3174
3175  var oryg_elements = elements || [];
3176
3177  on_select_fn = on_select_fn || function(){};
3178
3179  var o = deepcopy(options || {});
3180  o.id = 'selectpicker_' + Math.floor(Math.random()*1e6);
3181  o.z_index = o.z_index || 500;
3182  o.position = o.position || 'left top,left bottom';
3183  o.parent = o.parent || 'body';
3184  o.opacity = o.opacity || 0.1;
3185  o.max_height = +o.max_height || 0;
3186
3187  o.added_class = o.added_class || '';
3188  o.empty_text = o.empty_text || '---';
3189
3190  var sorted_arr = null; // tu będą przechowywane skeszowane elementy
3191  var selected_idx = -1; // indeks wybranego elementu
3192
3193  var cnt = $(input_id).length;
3194  $(input_id).each(function(){
3195    var input = $(this);
3196
3197    var val = input.val();
3198
3199    // przykryjmy input naszym div-em
3200    input.css('visibility','hidden');
3201    var io = input.offset();
3202    var txt = '<div class="selectpicker_inp '+o.id+'" id="'+o.id+this.id+'_inp" style="position:absolute;
3202left:'+io.left+'px;top:'+io.top+'px;"></div>';
3203    $(o.parent).append(txt);
3204
3205    var inp_target = $('.selectpicker_inp.'+o.id+'#'+o.id+this.id+'_inp');
3206
3207    inp_target.outerWidth(input.outerWidth());
3208    inp_target.outerHeight(input.outerHeight());
3209
3210    // sprawdźmy, czy wartość w input pasuje do którejś z wartości selecta
3211    var inp_sel;
3212    for(var k in elements){
3213      if(elements[k].val == val){
3214        inp_sel = elements[k].txt;
3215        break;
3216      }
3217      if(elements[k].items){
3218        for(var i in elements[k].items){
3219          if(elements[k].items[i].val == val){
3220            inp_sel = elements[k].items[i].txt;
3221            break;
3222          }
3223        }
3224      }
3225    }
3226    if(inp_sel){
3227      inp_target.append(inp_sel);
3228    }
3229
3230
3231    inp_target.off('click.SelectPicker').on('click.SelectPicker', function(){
3232
3233      var inp_id = this.id;
3234
3235      var showPicker = function(){
3236        closePicker();
3237
3238        if(typeof(oryg_elements) == 'function'){ // pobierzmy dynamicznie te elementy
3239          elements = oryg_elements(inp_id, input.attr('picker'));
3240        }
3241        else {
3242          elements = oryg_elements;
3243        }
3244
3245        var txt = '';
3246
3247        var bkg_width = $(document).width();
3248        var bkg_height = $(document).height();
3249
3250        txt += '<div class="selectpicker_dark_bkg" id="'+o.id+'_dark_bkg" style="z-index:'+o.z_index+';position:fixed;background:black;opacity:'+o.opacity+';width:'+bkg_width+'px;height:'+bkg_height+'px;left:0;top:0;"></div>';
3251
3252        txt += '<div class="selectpicker' + (o.added_class? (' ' + o.added_class) : '') + '" id="'+o.id+'" style="position:absolute;z-index:'+(o.z_index+1)+';" >';
3253            txt += createPicker(inp_id);
3254          txt += '<div style="clear:both"></div>';
3255        txt += '</div>';
3256
3257        $(o.parent).append(txt);
3258        var my = $('#'+o.id);
3259
3260        $('#'+o.id+'_dark_bkg').click(function(){ // kliknięcie poza selectem oznacza anulowanie
3261          closePicker();
3262        });
3263
3264        $('#'+o.id+' .selectpicker_item').click(function(){ // wybranie itema
3265          var idx = +this.id.substring(21); // prefix to selectpicker_item_nr_
3266
3267          closePicker();
3268
3269          inp_target.html(sorted_arr[idx].txt);
3270          val = sorted_arr[idx].val; // zapamiętajmy wybraną wartość
3271          input.val(val); // ustawmy także wartość naszego ukrytego inputa (np. jeśli ma jechać wraz z formularzem)
3272          selected_idx = idx;
3273
3274          if(on_select_fn){
3275            on_select_fn(sorted_arr[idx].txt, inp_id, input.attr('picker'), sorted_arr[idx].obj, sorted_arr[idx].id, 0);
3276          }
3277        });
3278
3279        $('#'+o.id+' .selectpicker_kat_title').click(function(){ // wybranie kategorii
3280          var idx = +this.id.substring(20); // prefix to selectpicker_kat_nr_
3281
3282          closePicker();
3283
3284          inp_target.html(sorted_arr[idx].txt);
3285          val = sorted_arr[idx].val; // zapamiętajmy wybraną wartość
3286          input.val(val); // ustawmy także wartość naszego ukrytego inputa (np. jeśli ma jechać wraz z formularzem)
3287          selected_idx = idx;
3288
3289          if(on_select_fn){
3290            on_select_fn(sorted_arr[idx].txt, inp_id, input.attr('picker'), sorted_arr[idx].obj, sorted_arr[idx].id, 1);
3291          }
3292        });
3293
3294        // ułóżmy picker względem inputa
3295        if(o.position == ''){
3296          my.center2();
3297        }
3298        else {
3299          var parts = o.position.split(',');
3300          var myc = parts[0] || '';
3301          var at = parts[1] || '';
3302          var dx = parts[2] || 0;
3303          var dy = parts[3] || 0;
3304          my.align(myc, at, input, dx, dy);
3305        }
3306
3307        if(o.max_height){
3308          my.css('max-height', o.max_height + 'px');
3309        }
3310        else { // nie możemy przekroczyć rozmiaru ekranu
3311          var wh = $(window).height();
3312          my.css('max-height', (wh-input.outerHeight()-20) + 'px');
3313        }
3314
3315        if(o.width){
3316          my.css('width', o.width + 'px');
3317        }
3318        else {
3319//          my.css('width', input.outerWidth() + 'px');
3320          my.outerWidth(input.outerWidth());
3321        }
3322
3323      }
3324
3325      showPicker();
3326    });
3327
3328
3329    var createPicker = function(inp_id){
3330
3331      if(!sorted_arr){ // zobaczmy, czy mamy skeszowane
3332        if(!getArraySize(elements)){
3333          return o.empty_text;
3334        }
3335
3336        sorted_arr = [];
3337        var kat_arr = [];
3338        for(var k in elements){
3339          var kat = {kat : elements[k], items : []};
3340          if(elements[k].items){
3341            for(var i in elements[k].items){
3342              kat.items.push(elements[k].items[i]);
3343            }
3344            kat.items.sort(function(a, b){
3345              return sort_fn(a.item, b.item, 0);
3346            });
3347          }
3348          kat_arr.push(kat);
3349        }
3350        kat_arr.sort(function(a,b){
3351          return sort_fn(a.kat.kat, b.kat.kat, 1);
3352        });
3353
3354        var idx;
3355        // skeszujmy to wszystko do jednej tablicy
3356        for(var k = 0; k < kat_arr.length; ++k){
3357          if(val == kat_arr[k].kat.val){
3358            selected_idx = sorted_arr.length;
3359          }
3360          sorted_arr.push({rodzaj : 1, txt : kat_arr[k].kat.txt, obj : kat_arr[k].kat.kat, id : kat_arr[k].kat.id, val : kat_arr[k].kat.val});
3361          if(kat_arr[k].items){
3362            for(var i = 0; i < kat_arr[k].items.length; ++i){
3363              if(val == kat_arr[k].items[i].val){
3364                selected_idx = sorted_arr.length;
3365              }
3366              sorted_arr.push({rodzaj : 0, txt : kat_arr[k].items[i].txt, obj : kat_arr[k].items[i].item, id : kat_arr[k].items[i].id, val : kat_arr[k].items[i].val});
3367            }
3368          }
3369          sorted_arr.push({rodzaj : 2}); // rodzaj == 2 to koniec kategorii
3370        }
3371      }
3372
3373      txt = '';
3374      var idx;
3375      for(var idx = 0; idx < sorted_arr.length; ++idx){
3376        var ob = sorted_arr[idx];
3377        switch(ob.rodzaj){
3378          case 1 : // początek kategorii
3379            txt += '<div class="selectpicker_kat" >';
3380              if(ob.txt){
3381                txt += '<div class="selectpicker_kat_title clickable';
3382                  if(idx == selected_idx){
3383                    txt += ' selected';
3384                  }
3385                txt += '" id="selectpicker_kat_nr_' + idx + '"';
3386                  if(isSet(ob.val)){
3387                    txt += ' val="'+_html(ob.val)+'"';
3388                  }
3389                txt += '>' + _html(ob.txt) + '</div>';
3390              }
3391            break;
3392          case 2 : // koniec kategorii
3393            txt += '</div>';
3394            break;
3395          case 0 : // item
3396            txt += '<div class="selectpicker_item clickable';
3397              if(idx == selected_idx){
3398                txt += ' selected';
3399              }
3400            txt += '" id="selectpicker_item_nr_' + idx + '"';
3401              if(isSet(ob.val)){
3402                txt += ' val="'+_html(ob.val)+'"';
3403              }
3404            txt += '>' + _html(ob.txt) + '</div>';
3405            break;
3406        }
3407      }
3408      return txt;
3409    }
3410
3411    var closePicker = function(){
3412      if(!o.no_bkg){
3413        $('#'+o.id+'_dark_bkg').remove();
3414      }
3415      $('#'+o.id).remove();
3416    }
3417
3418  });
3419
3420  return {
3421    disablePicker : function(item_id){ // wywołany z selektorem elementu, wyłącza SelectPicker dla tego elementu. Wywołany bez parametru wyłącza cały picker
3422      var inp = item_id? '.selectpicker_inp.'+o.id+'#'+o.id+item_id+'_inp' : '.selectpicker_inp.'+o.id;
3423      $(inp).remove(); // nie wiadomo, czy to działa
3424    }
3425  }
3426
3427}
3428
3429
3430
3431// W podanym HTML-u zamienia adresy względne na bezwzględne. Zmienia atrybuty href i src, w razie potrzeby uzupełniając je przedrostkiem url_base
3432function replaceIndirectURL(url, url_base){
3433
3434  return url.replace(/((href|src)(="?)(.*?)("|>))/gi, function(match, p1, p2, p3, p4, p5){
3435    if(p4 && p4.substring(0, 4) == 'http'){ // mamy adres bezwzględny, więc zwracamy jak leci
3436      return p1;
3437    }
3438    else { // mamy adres względny
3439      if(url_base[url_base.length-1] == '/' && p4 && p4[0] == '/'){ // pozbądźmy się nadmiarowego ukośnika
3440        p4 = p4.substring(1);
3441      }
3442      var attr = p2 + p3 + url_base + p4 + p5;
3443      return attr;
3444    }
3445  });
3446}
3447
3448function copyTextToClipboard(text){
3449  var textArea = document.createElement("textarea");
3450  textArea.style.position = 'fixed';
3451  textArea.style.top = 0;
3452  textArea.style.left = 0;
3453  textArea.style.width = '2em';
3454  textArea.style.height = '2em';
3455  textArea.style.padding = 0;
3456  textArea.style.border = 'none';
3457  textArea.style.outline = 'none';
3458  textArea.style.boxShadow = 'none';
3459  textArea.style.background = 'transparent';
3460  textArea.value = text;
3461  document.body.appendChild(textArea);
3462  textArea.select();
3463  try {
3464    var successful = document.execCommand('copy');
3465  }
3466  catch (err) {
3467  }
3468  document.body.removeChild(textArea);
3469}
3470
3471// helper do parsowania zlokalizowanych nazw otrzymanych od bazy danych
3472function parseLocalizations(arr, json_field_name, field_name){
3473  for(var id in arr){
3474    var item = arr[id];
3475    var loc_arr = JSON.parse(item[json_field_name]) || {};
3476    delete item[json_field_name];
3477    item[field_name] = {};
3478    for(var i = 0; i < langs_arr.length; ++i){
3479      var lng = langs_arr[i];
3480      item[field_name][lng] = loc_arr[lng] || {strid : 0, lang : lng, object_column : field_name, text : ''};
3481    }
3482  }
3483}
3484
3485// helper do inicjalizowania zlokalizowanego pola przed odpaleniem dialogu do edycji obiektu
3486function defaultLocaliztions(locs, field_name){
3487  locs = locs || {};
3488  for(var i = 0; i < langs_arr.length; ++i){
3489    var lng = langs_arr[i];
3490    locs[lng] = locs[lng] || {strid : 0, lang : lng, object_column : field_name, text : ''};
3491  }
3492  return locs;
3493}
3494
3495// Zwraca true gdy wskazany element (stringowy selektor albo obiekt jQwery) jest widoczny przy bieżącym przewinięciu strony
3496function isScrolledIntoView(elem){
3497  var docViewTop = $(window).scrollTop();
3498  var docViewBottom = docViewTop + $(window).height();
3499
3500  var $elem = (typeof elem == 'string')? $(elem) : elem;
3501
3502  try {
3503
3504    var elemTop = $elem.offset().top;
3505    var elemBottom = elemTop + $elem.height();
3506
3507  //  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); // true jak jest cały widoczny
3508  return ((elemBottom >= docViewTop && elemBottom < docViewBottom) || (elemTop >= docViewTop && elemTop < docViewBottom)); // true jak jest choć częściowo na ekranie
3509  } catch(e){
3510    console.error('3w_tools.js:3694: isScrolledIntoView: element not found\n'+e.message);
3511  }
3512  return false;
3513}
3514
3515function getScrollBarSizes(){ // helper do odczytania grubości scrollbarów
3516  var inner = document.createElement('p');
3517  inner.style.width = "100px";
3518  inner.style.height = "100px";
3519
3520  var outer = document.createElement('div');
3521  outer.style.position = "absolute";
3522  outer.style.top = "0px";
3523  outer.style.left = "0px";
3524  outer.style.visibility = "hidden";
3525  outer.style.width = "100px";
3526  outer.style.height = "100px";
3527  outer.style.overflow = "hidden";
3528  outer.appendChild (inner);
3529
3530  document.body.appendChild (outer);
3531
3532
3533  var w1 = inner.offsetWidth;
3534  var h1 = inner.offsetHeight;
3535  outer.style.overflow = 'scroll';
3536  var w2 = inner.offsetWidth;
3537  var h2 = inner.offsetHeight;
3538
3539  if(w1 == w2){
3540    w2 = outer.clientWidth;
3541  }
3542  if(h1 == h2){
3543    h2 = outer.clientHeight;
3544  }
3545
3546  document.body.removeChild (outer);
3547
3548  return {width : (w1 - w2), height : (h1 - h2)};
3549};
3550
3551// helper do zatwierdzania zlokalizowanych pól w dialogu edycji obiektu
3552// \locs       Obiekt z lokalizacjami danego pola
3553// \field_id   Selektor (jak CSS) kontrolek formularza. W formularzu na stronie te kontrolki mają jeszcze przyrostek języka, np. '_pl'
3554function zatwierdzLokalizacje(locs, field_id){
3555
3556  for(var i = 0; i < langs_arr.length; ++i){
3557    var lng = langs_arr[i];
3558    locs[lng].text = $(field_id + '_' + lng).val();
3559  }
3560}
3561
3562// klasa do keszowania obrazków
3563CCachedImage = function(url, nazwa){
3564  var loaded = false;
3565  var img = null;
3566  var callbacks = [];
3567  this.nazwa = nazwa;
3568
3569  img = new Image();
3570
3571  img.onload = function() {
3572    img.onload = null;
3573    loaded = true;
3574    callbacks.forEach(function(v){
3575      v(img);
3576    });
3577  }
3578
3579  img.src = url;
3580
3581  this.getImage = function(callback){
3582    if(typeof(callback) == 'function'){
3583      if(!loaded){
3584        callbacks.push(callback);
3585      }
3586      else {
3587        callback(img);
3588      }
3589    }
3590  }
3591
3592  this.isLoaded = function(){
3593    return loaded;
3594  }
3595
3596  this.getWidth = function(){
3597    if(loaded){
3598      return img.width;
3599    }
3600    return 0;
3601  }
3602  this.getHeight = function(){
3603    if(loaded){
3604      return img.height;
3605    }
3606    return 0;
3607  }
3608}
3609/*
3610// \param outer_html    TRUE oznacza, że funkcja zwraca HTML łącznie ze wskazanym elementem; FALSE powoduje zwrócenie innerHTML
3611function getDOMTemplate(template_selector, outer_html){ // zwraca tekst reprezentujący DOM zawarty we wskazanym elemencie
3612  var template = document.querySelector(template_selector);
3613  if(!template){
3614    console.error('Nie znaleziono szablonu: ' + template_selector);
3615    return null;
3616  }
3617
3618  if(outer_html){
3619    return template.outerHTML.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3'); // pozbądźmy się wszystkich separatorów między tagami
3620  }
3621  else {
3622    return template.innerHTML.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3'); // pozbądźmy się wszystkich separatorów między tagami
3623  }
3624}
3625*/
3626// \param outer_html    TRUE oznacza, że funkcja zwraca HTML łącznie ze wskazanym elementem; FALSE powoduje zwrócenie innerHTML
3627function getDOMTemplate(template_selector, outer_html){ // zwraca tekst reprezentujący DOM zawarty we wskazanym elemencie
3628  var $template = $(template_selector);
3629  if(!$template){
3630    console.error('Nie znaleziono szablonu: ' + template_selector);
3631    return null;
3632  }
3633
3634  if(outer_html){
3635    return $template[0].outerHTML.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3'); // pozbądźmy się wszystkich separatorów między tagami
3636  }
3637  else {
3638    return $template[0].innerHTML.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3'); // pozbądźmy się wszystkich separatorów między tagami
3639  }
3640}
3641
3642// \param $wydruk                 Element jQuery przeznaczony do wydruku
3643// \param before_print_callback   Jeśli jest funkcją, zostanie wywołane tuż przed wydrukiem, jako callback($head, $body); $head i $body to elementy iframe do wydruku.
3644//                                W tej funkcji można np, ustawić dodatkowe arkusze css, np. $head.append('<link rel="stylesheet" type="text/css" href="css/admin.css">');
3645// \param tylko_podglad           Jeśli jest ustawione, to fukcja nie drukuje, tylko wyświetla wynikową iframe z treścią wydruku
3646function drukujHTML($wydruk, tylko_podglad, callback){
3647//  tylko_podglad = true;
3648
3649  var spinner = new CWaitSpinner();
3650
3651  var self = this;
3652
3653  function closePrint () {
3654    $iframe.remove();
3655  }
3656
3657  var $iframe = $('<iframe id="wydruk" name="iframka"></iframe>');
3658  $iframe.css({'position' : 'fixed', 'right' : 0, 'bottom' : 0});
3659
3660  $('body').append($iframe);
3661
3662  if(tylko_podglad){
3663    $iframe.css({'height': '100%', 'background': 'white', 'width': '100%', 'z-index' : 10000});
3664    $iframe.show();
3665  }
3666  else {
3667    $iframe.hide();
3668  }
3669
3670  $iframe.focus(); // podobno wymaga tego IE
3671
3672  function waitForIFrame(){
3673    setTimeout(function(){ // musimy poczekać co najmniej moment, bo w niektórych przeglądarkach nie idzie od razu
3674      var $body = $iframe.contents().find('body');
3675      var $head = $iframe.contents().find('head');
3676      if($body.length && $head.length){
3677
3678        if($wydruk){
3679          $body.append($wydruk);
3680        }
3681        if(typeof callback == 'function'){
3682          setTimeout(function(){ // poczkajamy na załadowanie się doma
3683            callback($head, $body);
3684
3685            waitForImages(function(){
3686              setTimeout(function(){ // poczekajmy na załadowanie się arkuszy stylów i wyrenderowanie obrazków
3687                spinner.close();
3688                if(!tylko_podglad){
3689                  $iframe[0].contentWindow.onbeforeunload = closePrint;
3690                  $iframe[0].contentWindow.onafterprint = closePrint;
3691                  $iframe[0].contentWindow.print();
3692
3693                }
3694              }, 500);
3695            });
3696
3697          }, 500);
3698        }
3699
3700        var wait_start_time = microtime(true);
3701        // poczekajmy, aż załadują się wszystkie obrazki
3702        function waitForImages(callback2){
3703          setTimeout(function(){
3704            var all = true;
3705
3706            var completed = 0;
3707            var not_completed = 0;
3708
3709            $body.find('img').each(function(){
3710              var img =  this;
3711              if(!this.complete || +this.naturalWidth == 0){
3712                all = false;
3713console.log('waiting for: ' + this.src);
3714                return false;
3715              }
3716              else {
3717//                console.log('complete: ' + $(this).prop('complete') + ', ' + this.src);
3718              }
3719            });
3720
3721            if(all){
3722              callback2();
3723            }
3724            else {
3725              var now_time = microtime(true);
3726              var wait_time = now_time - wait_start_time;
3727console.log('img wait (' + wait_time + 's)');
3728
3729              if(wait_time > 10){ // hmm, czekamy już ponad 10s, więc drukujemy tak czy tak
3730console.log('wait too long');
3731                callback2();
3732              }
3733              else {
3734                waitForImages(callback2);
3735              }
3736            }
3737          }, 200);
3738        }
3739
3740
3741      }
3742      else {
3743console.log('iframe wait');
3744        waitForIFrame();
3745      }
3746    }, 200);
3747  }
3748  waitForIFrame();
3749}
3750
3751// helper do zmiany kolejności sortowania tabelki
3752
3753// UWAGA: ta funkcja jest do dorobienia, bo nie działa
3754
3755// \param old_sort        Dotychczasowa kolejność sortowania
3756// \param new_sort        Nowa kolejność sortowania
3757// \param $table          String zawierający obiekt jQuery tabeli
3758// \param update_function Jeśli jest funkcją to zostanie wywołane przed ustawieniem nowych klas nagłówkowi tabeli
3759function changeSortOrder(old_sort, new_sort, $table, update_function){
3760  var desc;
3761  var sort;
3762  if(old_sort == new_sort){ // jak nie desc, to desc
3763    sort = old_sort + ' desc';
3764    desc = true;
3765  }
3766  else {
3767    sort = new_sort;
3768    desc = false;
3769  }
3770
3771  if(typeof(update_function) == 'function'){
3772    update_function();
3773  }
3774
3775  $table.find('th').removeClass('sort');
3776  $table.find('th').removeClass('sort_desc');
3777
3778  if(desc){
3779    $table.find('th[sort="' + ord + '"]').addClass('sort_desc');
3780  }
3781  else {
3782    $table.find('th[sort="' + ord + '"]').addClass('sort');
3783  }
3784
3785}
3786
3787// helper do drukowania wskazanego tekstu z użyciem wskazanego pliku css-a
3788function printHelper(txt, id = 'print_wnd', css = '/back/css/print.css', head_html = null){
3789  $('iframe#'+id).remove();
3790  var ifr = document.createElement('iframe');
3791  var head;
3792
3793  if(!head_html){
3794    head = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3795<html lang="pl">
3796<head>
3797<title>Diecezja</title>
3798<meta http-equiv="content-type" content="text/html; charset=utf-8" />
3799<link href="${url_base}${css}"  rel="stylesheet" type="text/css" />
3800</head>`;
3801  }
3802  else {
3803    head = head_html;
3804  }
3805
3806  ifr.src = '';
3807
3808  ifr.id = id;
3809  ifr.onload = function () {
3810    var doc = document.getElementById(id);
3811    doc.contentDocument.head.innerHTML = head;
3812    doc.contentDocument.body.innerHTML = txt;
3813    setTimeout(function () {
3814      $('iframe#'+id).css('display', 'none');
3815      ifr.contentWindow.print();
3816//      setTimeout(function () { ifr.style.display = 'none' }, 1000);
3817    }, 50);
3818  }
3819  document.body.appendChild(ifr);
3820}
3821
3822// helper do wysyłania wskazanego tekstu z użyciem wskazanego pliku css-a pod wskazany adres email
3823async function sendHelper(subject, body, email, css = null, with_debug_log = false, params = null){
3824  if(!email){
3825    throw Error('Nie podano adresu email');
3826  }
3827
3828  var spinner = new CWaitSpinner();
3829
3830  let css_text = '';
3831  if(css){// pobierzmy wskazany css
3832    const response = await fetch(url_base + css);
3833    css_text = await response.text();
3834
3835    css_text = `<style type="text/css">${css_text}</style>`;
3836  }
3837
3838  spinner.close();
3839
3840
3841
3842
3843    head = `<!DOCTYPE html>
3844<html lang="pl">
3845<head>
3846<title>Email</title>
3847<meta http-equiv="content-type" content="text/html; charset=utf-8" />
3848${css_text}
3849</head>`;
3850
3851  const payload = {
3852    subject               : subject,
3853    body                  : body,
3854    email                 : email,
3855    head                  : head,
3856    params                : params,
3857    kontakt_email         : params.kontakt_email,
3858    default_mail_from     : params.default_mail_from,
3859
3860
3861    mail_host             : params.mail_host,
3862    mail_port             : params.mail_port,
3863    mail_auth             : params.mail_auth,
3864    mail_security         : params.mail_security,
3865    mail_ignore_bad_certs : params.mail_ignore_bad_certs,
3866    mail_user             : params.mail_user,
3867    mail_pass             : params.mail_pass,
3868    mail_timeout          : params.mail_timeout,
3869
3870  };
3871
3872  const res = await JSONAsyncRequest3('ajax_3w_front_sql','action=send_email' + (with_debug_log? '&with_debug_log=1' : ''), payload);
3873
3874  return res;
3875}
3876
3877
3878CSlownik3w = function(){
3879  var self = this;
3880
3881  let dane_zaladowane = false;
3882
3883  this.id = 0;
3884  this.txt_id = '';
3885  this.nazwa = 'not loaded';
3886  this.opis = 'not loaded';
3887
3888  this.pozycje = {};
3889
3890  this.getName = function(id, no_question_marks){ // zwraca nazwę wskazanej pozycji słownika
3891    return self.pozycje[id]? self.pozycje[id].label : (no_question_marks? '' : '???');
3892  }
3893
3894  this.getPozycjaByID = function(id, def_val = null){
3895    for(let _id in self.pozycje){
3896      if(+_id == +id){
3897        return self.pozycje[id];
3898      }
3899    }
3900    return def_val;
3901  }
3902
3903  this.getLabelByID = function(id, default_val = null){
3904    for(let _id in self.pozycje){
3905      if(+_id == +id){
3906        return self.pozycje[id].label;
3907      }
3908    }
3909    return default_val;
3910  }
3911
3912  // Ładuje dane obiektów
3913  // Po zakończeniu wywołuje opcjonalny handler.
3914  this.ladujDane = async function(txt_id){
3915
3916    if(dane_zaladowane){ // ok, dane już mamy załadowane
3917      self.pozycje;
3918    }
3919
3920//    console.log('Waiting for grupy funkcji');
3921    let T0 = microtime(true);
3922
3923    const dane = await JSONAsyncRequest3('ajax_3w_slowniki_sql','action=load_slownik&id='+encodeURIComponent(txt_id));
3924    if(dane?.result == 'OK'){
3925      self.txt_id = txt_id;
3926      dane_zaladowane = true;
3927      let T1 = microtime(true);
3928      console.log('Loaded slownik ' + txt_id + ', ' + (T1 - T0));
3929      self.id = +dane.slownik.id;
3930      self.txt_id = dane.slownik.txt_id;
3931      self.nazwa = dane.slownik.nazwa;
3932      self.opis = dane.slownik.opis;
3933      self.pozycje = dane.slownik.pozycje;
3934
3935      self.pozycje;
3936    }
3937    else if(dane?.result == 'SLOWNIK NOT FOUND'){
3938      console.error('Nie znaleziono słownika ' + txt_id);
3939      throw Error('Nie znaleziono słownika ' + txt_id);
3940    }
3941    else {
3942      let T1 = microtime(true);
3943      console.log('Rejected slowniki ' + txt_id +', ' + (T1 - T0));
3944      self.obiekty = {};
3945
3946      alert('Nie powiodło się pobranie danych. Odśwież przeglądarkę i spróbuj ponownie.\nBłąd: ' + dane?.message);
3947      throw Error('Nie powiodło się pobranie danych. Odśwież przeglądarkę i spróbuj ponownie.\nBłąd: ' + dane?.message);
3948    }
3949  }
3950
3951  this.afterAllLoaded = function(){
3952  }
3953
3954  // wskazany select wyełnia pozycjami słownika.
3955  // \param $select         Element <select>, wskazany jako selektor css lub obiekt jQuery
3956  // \param options         Dostępne opcje:
3957  //                          - empty_value   Wartość inna niż null powoduje, że na początku listy zostanie umieszczony element o takiej wartości
3958  //                          - empty_label   Etykieta dla empty_value
3959  //                          - by_id         TRUE powoduje, że pozycje są wybierane wg. id pozycji a nie wg. pola val pozycji
3960  // \param pn_select_fn    Jeśli jest funkcją, zostanie wywołane jako on_select_fn(<selected_val>, this)
3961  this.createCtrl = function($select, value, options = null, on_select_fn = null){
3962    options = options || {empty_value : null, empty_label : '', by_id : false};
3963    $select = $($select);
3964
3965    $select.empty();
3966
3967    let has_empty = false;
3968
3969    if(options && options.empty_value !== null && options.empty_value !== undefined){
3970      $('<option />').val(options.empty_value).html(options.empty_label).appendTo($select);
3971      has_empty = true;
3972    }
3973
3974    filterAndSortArray(self.pozycje, (a,b) => {
3975      if(+a.prio == +b.prio){
3976        return generic_compare_desc(a.label, b.label, +a.id, +b.id, false);
3977      }
3978      else {
3979        return b.prio - a.prio;
3980      }
3981    }, null, (v) => {
3982      $('<option />').val(options.by_id? v.id : v.label).html(v.label).appendTo($select);
3983    });
3984
3985    if((value != null && value != undefined && +value !== 0) || has_empty){
3986      $select.val(value);
3987    }
3988    else {
3989      $select.val($select.find('option:first').val()); // zapodajmy value w selekcie
3990    }
3991
3992    if(typeof on_select_fn == 'function'){
3993      $select.change(function(){
3994        on_select_fn($(this).val(), self);
3995      });
3996    }
3997  }
3998}
3999
4000CFeature3W = function(){
4001  var self = this;
4002
4003  let dane_zaladowane = false;
4004
4005  this.id = 0;
4006  this.txt_id = '';
4007  this.nazwa = 'not loaded';
4008  this.opis = 'not loaded';
4009
4010  this.opcje = {};
4011
4012  this.getName = function(id, no_question_marks){ // zwraca nazwę wskazanej pozycji słownika
4013    return self.opcje[id]? self.opcje[id].label : (no_question_marks? '' : '???');
4014  }
4015
4016  this.getOpcjaByID = function(id, def_val = null){
4017    for(let _id in self.opcje){
4018      if(+_id == +id){
4019        return self.opcje[id];
4020      }
4021    }
4022    return def_val;
4023  }
4024
4025  this.getLabelByID = function(id, default_val = null){
4026    for(let _id in self.opcje){
4027      if(+_id == +id){
4028        return self.opcje[id].label;
4029      }
4030    }
4031    return default_val;
4032  }
4033
4034  // Ładuje dane obiektów
4035  // Po zakończeniu wywołuje opcjonalny handler.
4036  this.ladujDane = async function(txt_id){
4037
4038    if(dane_zaladowane){ // ok, dane już mamy załadowane
4039      self.opcje;
4040    }
4041
4042//    console.log('Waiting for grupy funkcji');
4043    let T0 = microtime(true);
4044
4045    const dane = await JSONAsyncRequest3('ajax_3w_features_sql','action=load_feature&id='+encodeURIComponent(txt_id));
4046    if(dane?.result == 'OK'){
4047      self.txt_id = txt_id;
4048      dane_zaladowane = true;
4049      let T1 = microtime(true);
4050      console.log('Loaded features ' + txt_id + ', ' + (T1 - T0));
4051      self.id = +dane.feature.id;
4052      self.txt_id = dane.feature.txt_id;
4053      self.nazwa = dane.feature.nazwa;
4054      self.opis = dane.feature.opis;
4055      self.opcje = dane.feature.opcje;
4056
4057      self.opcje;
4058    }
4059    else if(dane?.result == 'FEATURE NOT FOUND'){
4060      console.error('Nie znaleziono kategorii właściwości ' + txt_id);
4061      throw Error('Nie znaleziono kategorii właściwości ' + txt_id);
4062    }
4063    else {
4064      let T1 = microtime(true);
4065      console.log('Rejected feature ' + txt_id +', ' + (T1 - T0));
4066      self.obiekty = {};
4067
4068      alert('Nie powiodło się pobranie danych. Odśwież przeglądarkę i spróbuj ponownie.\nBłąd: ' + dane?.message);
4069      throw Error('Nie powiodło się pobranie danych. Odśwież przeglądarkę i spróbuj ponownie.\nBłąd: ' + dane?.message);
4070    }
4071  }
4072
4073  this.afterAllLoaded = function(){
4074  }
4075
4076  // wskazany select wyełnia pozycjami słownika.
4077  // \param $select         Element <select>, wskazany jako selektor css lub obiekt jQuery
4078  // \param options         Dostępne opcje:
4079  //                          - empty_value   Wartość inna niż null powoduje, że na początku listy zostanie umieszczony element o takiej wartości
4080  //                          - empty_label   Etykieta dla empty_value
4081  //                          - by_id         TRUE powoduje, że pozycje są wybierane wg. id pozycji a nie wg. pola val pozycji
4082  // \param pn_select_fn    Jeśli jest funkcją, zostanie wywołane jako on_select_fn(<selected_val>, this)
4083  this.createCtrl = function($select, value, options = null, on_select_fn = null){
4084    options = options || {empty_value : null, empty_label : '', by_id : false};
4085    $select = $($select);
4086
4087    $select.empty();
4088
4089    let has_empty = false;
4090
4091    if(options && options.empty_value !== null && options.empty_value !== undefined){
4092      $('<option />').val(options.empty_value).html(options.empty_label).appendTo($select);
4093      has_empty = true;
4094    }
4095
4096    filterAndSortArray(self.opcje, (a,b) => {
4097      if(+a.prio == +b.prio){
4098        return generic_compare_desc(a.label, b.label, +a.id, +b.id, false);
4099      }
4100      else {
4101        return b.prio - a.prio;
4102      }
4103    }, null, (v) => {
4104      $('<option />').val(options.by_id? v.id : v.label).html(v.label).appendTo($select);
4105    });
4106
4107    if((value != null && value != undefined && +value !== 0) || has_empty){
4108      $select.val(value);
4109    }
4110    else {
4111      $select.val($select.find('option:first').val()); // zapodajmy value w selekcie
4112    }
4113
4114    if(typeof on_select_fn == 'function'){
4115      $select.change(function(){
4116        on_select_fn($(this).val(), self);
4117      });
4118    }
4119  }
4120}
4121
4122function slownie(val){
4123// pobrane z algorytm.org
4124
4125  let liczba = parseInt(val);
4126
4127  let jednosci   = ["", " jeden", " dwa", " trzy", " cztery", " pięć", " sześć", " siedem", " osiem", " dziewięć"];
4128  let nastki     = ["", " jedenaście", " dwanaście", " trzynaście", " czternaście", " piętnaście", " szesnaście", " siedemnaście", " osiemnaście", " dziewiętnaście"];
4129  let dziesiatki = ["", " dziesięć", " dwadzieścia", " trzydzieści", " czterdzieści", " pięćdziesiąt", " sześćdziesiąt", " siedemdziesiąt", " osiemdziesiąt", " dziewięćdziesiąt"];
4130  let setki      = ["", " sto", " dwieście", " trzysta", " czterysta", " pięćset", " sześćset", " siedemset", " osiemset", " dziewięćset"];
4131  let grupy      = [["" ,"" ,""],
4132                    [" tysiąc" ," tysiące" ," tysięcy"],
4133                    [" milion" ," miliony" ," milionów"],
4134                    [" miliard"," miliardy"," miliardów"],
4135                    [" bilion" ," biliony" ," bilionów"],
4136                    [" biliard"," biliardy"," biliardów"],
4137                    [" trylion"," tryliony"," trylionów"]];
4138
4139  if(+liczba == +val){ // dla poprawnej wartości
4140
4141    let wynik = '';
4142    let znak = '';
4143    if(!liczba){
4144      wynik = "zero";
4145    }
4146
4147    if(liczba < 0) {
4148      znak = "minus";
4149      liczba = -liczba;
4150    }
4151
4152    let g = 0;
4153    while(liczba > 0){
4154      let s = Math.floor((liczba % 1000) / 100);
4155      let n = 0;
4156      let d = Math.floor((liczba % 100) / 10);
4157      let j = Math.floor(liczba % 10);
4158
4159      if(d == 1 && j > 0){
4160        n = j;
4161        d = 0;
4162        j = 0;
4163      }
4164
4165      let k = 2;
4166      if(j == 1 && s+d+n == 0){
4167        k = 0;
4168      }
4169      if(j == 2 || j == 3 || j == 4){
4170        k = 1;
4171      }
4172      if(s + d + n + j > 0){
4173        wynik = setki[s]+dziesiatki[d]+nastki[n]+jednosci[j]+grupy[g][k]+wynik;
4174      }
4175
4176      g++;
4177      liczba = Math.floor(liczba/1000);
4178    }
4179    return znak + wynik;
4180  }
4181
4182
4183  // nieprawidłowa wartość
4184  return '---';
4185}
4186
4187function roundToPlaces(value, places){ // potrafi zaokrąglać do 10, 100 itd. (places < 0) zł, a nie tylko groszy i dziesiątek groszy (places > 0)
4188  if(places >= 0){
4189    return +((+value).toFixed(places));
4190  }
4191  let divider = parseInt(Math.pow(10, -places));
4192  return Math.round(value/divider)*divider;
4193}
4194
4195Date.prototype.adjust = function(parts, amounts, negative = false){ // pozwala na dodawanie i odejmowanie czasu do/od daty
4196
4197
4198  var map = {
4199        years: 'FullYear', months: 'Month', weeks: 'Hours', days: 'Hours', hours: 'Hours',
4200        minutes: 'Minutes', seconds: 'Seconds', milliseconds: 'Milliseconds',
4201        utcyears: 'UTCFullYear', utcmonths: 'UTCMonth', weeks: 'UTCHours', utcdays: 'UTCHours',
4202        utchours: 'UTCHours', utcminutes: 'UTCMinutes', utcseconds: 'UTCSeconds', utcmilliseconds: 'UTCMilliseconds'
4203      };
4204
4205  if(typeof(parts) == 'string'){
4206    parts = [parts];
4207  }
4208  if(typeof(amounts) == 'string' || typeof(amounts) == 'number'){
4209    amounts = [amounts];
4210  }
4211
4212  var self = this, i = 0;
4213
4214  parts.forEach(function(part){
4215    part = part.toLowerCase();
4216
4217    var amount = +amounts[i++];
4218
4219    var mapPart = map[part];
4220
4221    if(negative){
4222      amount *= -1;
4223    }
4224
4225    if(part == 'weeks' || part == 'utcweeks'){
4226      amount *= 168;
4227    }
4228    if(part == 'days' || part == 'utcdays'){
4229      amount *= 24;
4230    }
4231
4232    self['set'+ mapPart]( self['get'+ mapPart]() + amount );
4233  });
4234
4235  return this;
4236}
4237
4238Date.prototype.diff = function(date2, parts){ // zwraca różnicę pomiędzy obiektem a wskazaną datą
4239  var d1 = new Date(this.getTime()),
4240    d2 = new Date(date2.getTime()),
4241    pm = d1 <= d2? 1 : -1,
4242    result = { },
4243    factors = { weeks: (1000*60*60*24*7), days: (1000*60*60*24), hours: (1000*60*60), minutes: (1000*60), seconds: 1000, milliseconds: 1 };
4244
4245  if(parts === undefined)
4246    parts = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'];
4247  else if(typeof(parts) == "string")
4248    parts = [parts];
4249
4250  for(var i=0, l=parts.length; i<l; i++){
4251    var k = parts[i];
4252    result[k] = 0;
4253
4254    if(factors[k] === undefined){
4255      inaWhile: while( true ){
4256        d2.adjust(k, -1*pm);
4257        if( (pm === 1 && d1 > d2) || (pm === -1 && d1 < d2)){
4258          d2.adjust(k, 1*pm);
4259          break inaWhile;
4260        }
4261        result[k]++;
4262      }
4263    }
4264    else{
4265      var tmpDiff = Math.abs(d2.getTime() - d1.getTime());
4266      result[k] = Math.floor(tmpDiff / factors[k]);
4267      d2.adjust(k, result[k]*-1*pm);
4268    }
4269    result[k] *= pm;
4270  }
4271
4272  if(parts.length == 1){
4273    return result[parts[0]];
4274  }
4275  return result;
4276}
4277
4278// Iterator do dat pomiędzy obiektem a zadaną datą; można iterować po zadanej części daty z określonym krokiem.
4279// Gdy callback zwroci false, iteracja jest od razu kończona
4280Date.prototype.each = function(endDate, parts, step, callback, bind){
4281  var fromDate = new Date(this.getTime()),
4282    toDate = new Date(endDate.getTime()),
4283    pm = fromDate <= toDate? false:true,
4284    i = 0;
4285
4286  while( (pm === false && fromDate <= toDate) || (pm === true && fromDate >= toDate) ){
4287    if(bind){
4288      if(callback.call(bind, fromDate, i, this) === false){
4289        break;
4290      }
4291    }
4292    else {
4293      if(callback(fromDate, i, this) === false){
4294        break;
4295      }
4296    }
4297    i += step;
4298    fromDate.adjust(parts, step, pm);
4299  }
4300  return this;
4301}
4302
4303function displayPDF(link){
4304  new CModalDlg2({
4305      id : 'display_pdf_dlg',
4306      add_classes : 'display_pdf',
4307      z_index : 131,
4308      width: 'auto',
4309      dont_show_at_once : true,
4310      before_close_callback : null,
4311      opacity : 0.11,
4312      no_Esc_close : 0,
4313    },
4314    function(dlg){
4315      var txt = `
4316<iframe src="${link}"></iframe>
4317`;
4318      txt = txt.trim().replace(/(>)([ \t\r\n]*)(<)/g,'$1$3');
4319      return txt;
4320    },
4321    function(dlg){
4322      let $dlg = dlg.getDlg();
4323
4324      $dlg.find('iframe').width($(window).width()-50).height($(window).height()-100);
4325    }
4326  ).showDlg();
4327}
4328
4329function getGUID(separator = '-'){
4330  var d = new Date().getTime();
4331  var d2 = (performance && performance.now && (performance.now() * 1000)) || 0; // time in microseconds since page-load (or 0 if unsupported)
4332  return `xxxxxxxx${separator}xxxx${separator}4xxx${separator}yxxx${separator}xxxxxxxxxxxx`.replace(/[xy]/g, c => {
4333    var r = Math.random() * 16; // random number between 0 and 16
4334    if(d > 0){ // use timestamp until depleted
4335      r = (d + r) % 16 | 0;
4336      d = Math.floor(d/16);
4337    }
4338    else { // use microseconds since page-load (if supported)
4339      r = (d2 + r) % 16 | 0;
4340      d2 = Math.floor(d2 / 16);
4341    }
4342    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
4343  });
4344}
4345
4346function numFormatter(num, base = 1000) {
4347  if(base == 1000){
4348    if(num > 999 && num < 1e6){
4349      return (num/1000).toFixed(1) + 'k'; // convert to k for number from > 1000 - 1e6
4350    }
4351    else if(num >= 1e6 && num < 1e9){
4352      return (num/1e6).toFixed(1) + 'M'; // convert to M for number from > 1e6 - 1e9
4353    }
4354    else if(num >= 1e9){
4355      return (num/1e9).toFixed(1) + 'G'; // convert to G for number > 1e9
4356    }
4357    else if(num < 900){
4358      return num; // if value < 1000, nothing to do
4359    }
4360  }
4361  else { // base 1024
4362    if(num > 1023 && num < 1024*1024){
4363      return (num/1024).toFixed(1) + 'K';
4364    }
4365    else if(num >= 1024*1024 && num < 1024*1024*1024){
4366      return (num/(1024*1024)).toFixed(1) + 'M';
4367    }
4368    else if(num >= 1024*1024*1024){
4369      return (num/(1024*1024*1024)).toFixed(1) + 'G';
4370    }
4371    else if(num < 900){
4372      return num; // if value < 1000, nothing to do
4373    }
4374  }
4375}
4376
4377if(!Math.imul){
4378  Math.imul = (a, b) => {let aHi = (a >>> 16) & 0xffff;let aLo = a & 0xffff;let bHi = (b >>> 16) & 0xffff;let bLo = b & 0xffff;return ((aLo * bLo) + (((aHi * bLo + aLo * bHi) << 16) >>> 0) | 0);};
4379}
4380
4381iHashFromString = s => {for(var i=0,h=9;i<s.length;
4381)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9};
4382
4383/**
4384 * sends a request to the specified url from a form. this will change the window location.
4385 * @param {string} path the path to send the post request to
4386 * @param {object} params the parameters to add to the url
4387 * @param {string} [method=post] the method to use on the form
4388 */
4389function gotoPage(path, params, method = 'post'){
4390
4391  // The rest of this code assumes you are not using a library.
4392  // It can be made less verbose if you use one.
4393  const form = document.createElement('form');
4394  form.method = method;
4395  form.action = path;
4396
4397  for (const key in params) {
4398    if (params.hasOwnProperty(key)) {
4399      const hiddenField = document.createElement('input');
4400      hiddenField.type = 'hidden';
4401      hiddenField.name = key;
4402      hiddenField.value = params[key];
4403
4404      form.appendChild(hiddenField);
4405    }
4406  }
4407
4408  document.body.appendChild(form);
4409  form.submit();
4410  return false;
4411}
4412
4413function csvToArray(str, _headers = 'first row', delimiter = ";", new_line = '\r\n'){
4414
4415  let headers, rows;
4416
4417  if(_headers == 'first row'){ // nagłówki brane z pierwszego wiersza
4418    // slice from start of text to the first \n index
4419    // use split to create an array from string by delimiter
4420    headers = str.slice(0, str.indexOf(new_line)).split(delimiter).map(h => h.trim());
4421
4422    // slice from \n index + 1 to the end of the text
4423    // use split to create an array of each csv value row
4424    rows = str.slice(str.indexOf(new_line) + 1).split(new_line);
4425  }
4426  else { // nagłówki zapodane w parametrze _headers
4427    headers = _headers.map(h => h.trim());
4428    rows = str.slice(0).split(new_line);
4429  }
4430
4431  // Map the rows
4432  // split values from each row into an array
4433  // use headers.reduce to create an object
4434  // object properties derived from headers:values
4435  // the object passed as an element of the array
4436  const arr = rows.map(function (row) {
4437    const values = row.split(delimiter);
4438    const el = headers.reduce(function (object, header, index) {
4439      object[header] = values[index].trim();
4440      return object;
4441    }, {});
4442    return el;
4443  });
4444
4445  // return the array
4446  return arr;
4447}
4448
4449var MD5 = function(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;
4449r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
4450
4451String.prototype.despace = function(){
4452  return this.trim?.().replace?.(/(>)([ \t\r\n]*)(<)/g,'$1$3') ?? '';
4453}
4454
4455String.prototype.nl2br = function(){
4456  return this.trim?.().replace?.(/\n/g,'<br />') ?? '';
4457}
4458
4459RegExp.escape = function(str){
4460  return str.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
4461}
4462
4463// funkcja "podświetla" szukany fragment tekstu wstawiajac w jego miejsce <span> podanej klasy
4464function highliteFragment(txt, fragment, klasa){
4465  txt = txt?.toString() ?? '';
4466  if(!fragment){
4467    return txt;
4468  }
4469  const re = new RegExp('('+RegExp.escape(fragment)+')',"gi");
4470  txt = txt.replace(re,'<span class="'+klasa+'">$1</span>');
4471  return txt;
4472}
4473
4474const applyErrors = ($container, errors) => {
4475  if(errors.length){ // wyświetlmy błędy
4476    var txt = '';
4477    for(var i in errors){
4478      txt += errors[i].txt + '<br />';
4479      $container.find(errors[i].id).addClass('error');
4480    }
4481    CResultFader3(txt, 1000, 'error');
4482    return errors.length;
4483  }
4484
4485  return false;
4486}
4487
4488
4489/*
4490// Helper do obsługi multifiltra
4491class CMultiFilter {
4492
4493  // Multifilter "rozbija" podany filtr na składowe i zależnie od stanu modyfikatorów tyc
4493h składowych interpretuje wynik porównania każdej składowej ze wszystkimi polami wskazanymi w konstruktorze.
4494
4495  defs = [];
4496
4497  // \param  fields   Obiekt ze spisem sprawdzanych pól obiektu. Każda składowa jest parą o postaci <nazwa pola> : <definicja reguły jako funkcja lub wartość_logiczna>.
4498  //                  Sprawdzanie odbywa się dla wszystkich pól dla wszystkich składowych filtra (ale może zakończyć się wcześniej, jeśli pełne sprawdzanie nie jest konieczne).
4499  //                  Dla definicji typu wartość logiczna:
4500  //                    W tym przypadku znalezione elementy filtra są porównywane jako ciągi znaków. Jeśli wartość logiczna definicji ewaluuje do false, wielkość liter nie ma znaczenia. W przeciwnym razie wielkość liter ma znaczenie.
4501  //                  Dla reguł typu function:
4502  //                    Dla każdego pola i każdej składowej filtra jest wywoływana funkcja dla tego pola (jako (<subfilter>, <sprawdzany obiekt>) => {}). Funkcja powinna zwrócić 0 gdy szukany filtr występuje na początku wartości danego pola, > 0 gdy występuje dalej niż na poczatku i < 0 gdy nie występuje w wartości tego pola.
4503  constructor(fields = {}){
4504
4505    const findIgnoreCase = (needle, haystack) => {
4506      return haystack.toLowerCase().indexOf(needle.toLowerCase());
4507    }
4508
4509    const findCaseMatters = (needle, haystack) => {
4510      return haystack.indexOf(needle);
4511    }
4512
4513    for(let field in fields){
4514      if(typeof fields[field] == 'function'){
4515        this.defs.push({field : field, func : fields[field], custom : true});
4516      }
4517      else {
4518        this.defs.push({field : field, fields[field]? findCaseMatters : findIgnoreCase});
4519      }
4520    }
4521  }
4522
4523  // \param  filter   Wartość filtra. Ciągi znaków rozdzielone spacją są traktowane jako AND. W celu uzyskania OR ciągi znaków należy oddzielić pionową pałką (|).
4524  //                  Nadmiarowe spacje są ignorowane.
4525  //                  Ciąg poprzedzony wykrzyknikiem (!) oznacza negację.
4526  //                  Ciąg poprzedzony caret (^) oznacza, że dopasowanie musi wystąpić na początku
4527  // \param obj       Sprawdzany obiekt
4528  // \return          Zwraca true jeśli obiekt ma zostać przepuszczony przez sito i false w przeciwnym przypadku.
4529  filter(filter, obj){
4530    const f1 = filter.replace(
4531  }
4532}
4533*/
4534
4535class CKEditorUploadAdapter {
4536  constructor(loader, params, check_fn){
4537    this.loader = loader; // The file loader instance to use during the upload.
4538    this.params = params;
4539    this.check_fn = check_fn;
4540  }
4541
4542  // Initializes the XMLHttpRequest object using the URL passed to the constructor.
4543  _initRequest() {
4544      const xhr = this.xhr = new XMLHttpRequest();
4545
4546      // Note that your request may look different. It is up to you and your editor
4547      // integration to choose the right communication channel. This example uses
4548      // a POST request with JSON as a data structure but your configuration
4549      // could be different.
4550
4551      xhr.open( 'POST', url_base + '/ajax_req.php?aaction=' + this.params + '&sid=' + Math.random(), true );
4552      xhr.responseType = 'json';
4553  }
4554
4555  // Initializes XMLHttpRequest listeners.
4556  _initListeners( resolve, reject, file ) {
4557    var error = this.check_fn();
4558    if(error){
4559      return reject();
4560    }
4561
4562    const xhr = this.xhr;
4563    const loader = this.loader;
4564    const genericErrorText = `Nie udało się wysłać pliku: ${ file.name }.`;
4565
4566    xhr.addEventListener( 'error', () => reject( genericErrorText ) );
4567    xhr.addEventListener( 'abort', () => reject() );
4568    xhr.addEventListener( 'load', () => {
4569        const response = xhr.response;
4570
4571        if( !response || +response.error_code){ // The reject() function must be called when the upload fails.
4572          return reject( response.message);
4573        }
4574
4575        // If the upload is successful, resolve the upload promise with an object c
4575ontaining
4576        // at least the "default" URL, pointing to the image on the server.
4577        // This URL will be used to display the image in the content. Learn more in the
4578        // UploadAdapter#upload documentation.
4579        resolve( {
4580            default: response.url
4581        } );
4582    } );
4583
4584    // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
4585    // properties which are used e.g. to display the upload progress bar in the editor user interface.
4586    if(xhr.upload){
4587      xhr.upload.addEventListener('progress', evt => {
4588        if(evt.lengthComputable){
4589          loader.uploadTotal = evt.total;
4590          loader.uploaded = evt.loaded;
4591        }
4592      });
4593    }
4594  }
4595
4596  _sendRequest(file){ // Prepares the data and sends the request.
4597      const data = new FormData(); // Prepare the form data.
4598
4599      data.append('upload', file);
4600
4601      // Important note: This is the right place to implement security mechanisms
4602      // like authentication and CSRF protection. For instance, you can use
4603      // XMLHttpRequest.setRequestHeader() to set the request headers containing
4604      // the CSRF token generated earlier by your application.
4605      this.xhr.send(data); // Send the request.
4606  }
4607
4608  upload(){ // Starts the upload process.
4609    return this.loader.file
4610      .then(file => new Promise((resolve, reject) => {
4611        this._initRequest();
4612        this._initListeners(resolve, reject, file);
4613        this._sendRequest(file);
4614      }));
4615  }
4616
4617  // Aborts the upload process.
4618  abort(){
4619    if(this.xhr){
4620      this.xhr.abort();
4621    }
4622  }
4623}

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.