PluginProbe ʕ •ᴥ•ʔ
Event Tickets with Ticket Scanner / 2.8.4
Event Tickets with Ticket Scanner v2.8.4
3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.4 trunk 2.6.0 2.7.0 2.7.1 2.7.10 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 2.9.8 2.9.9 3.0.0 3.0.1 3.0.2 3.0.3
event-tickets-with-ticket-scanner / backend.js
event-tickets-with-ticket-scanner Last commit date
3rd 5 months ago css 5 months ago img 5 months ago includes 5 months ago js 5 months ago languages 5 months ago ticket 5 months ago vendors 5 months ago .gitignore 5 months ago SASO_EVENTTICKETS.php 5 months ago backend.js 5 months ago changelog.txt 5 months ago db.php 5 months ago index.php 5 months ago init_file.php 5 months ago order_details.js 5 months ago readme.txt 5 months ago saso-eventtickets-validator.js 5 months ago sasoEventtickets_AdminSettings.php 5 months ago sasoEventtickets_Authtoken.php 5 months ago sasoEventtickets_Base.php 5 months ago sasoEventtickets_Core.php 5 months ago sasoEventtickets_Frontend.php 5 months ago sasoEventtickets_Messenger.php 5 months ago sasoEventtickets_Options.php 5 months ago sasoEventtickets_PDF.php 5 months ago sasoEventtickets_Seating.php 5 months ago sasoEventtickets_Ticket.php 5 months ago sasoEventtickets_TicketBadge.php 5 months ago sasoEventtickets_TicketDesigner.php 5 months ago sasoEventtickets_TicketQR.php 5 months ago ticket_events.js 5 months ago ticket_scanner.js 5 months ago validator.js 5 months ago wc_backend.js 5 months ago wc_frontend.js 5 months ago woocommerce-hooks.php 5 months ago
backend.js
4044 lines
1 function sasoEventtickets(_myAjaxVar, doNotInit) {
2 const { __, _x, _n, sprintf } = wp.i18n;
3 let myAjax = _myAjaxVar;
4 let self = this;
5 let PREMIUM = null;
6 var $ = jQuery;
7 var PARAS = basics_ermittelURLParameter();
8 var DATA = {
9 /*action: '',*/
10 nonce: myAjax.nonce,
11 last_nonce_check: 0
12 };
13
14 var system = {is_debug:false, DYNJS:{}, DYNJS_CACHE:{}};
15 var FATAL_ERROR = false;
16 var DIV = null;
17 var LAYOUT = null;
18 var DATA_LISTS = null;
19 var DATA_AUTHTOKENS = null;
20 var OPTIONS = {
21 list:[], mapKeys:{},
22 versions:{mapKeys:{}},
23 meta_tags_keys:{list:[], mapKeys:{}},
24 infos:{},
25 tickets_for_testing:[],
26 options_special:{}
27 };
28
29 var STATE = null;
30
31 if (_myAjaxVar._doNotInit) doNotInit = true;
32
33 function time() {
34 return new Date().getTime();
35 }
36
37 function destroy_tags(t) {
38 if (t != null) {
39 t = t.replace("<", "").replace(">","");
40 }
41 return t;
42 }
43
44 function _requestURL(action, myData) {
45 let paras = '?action='+myAjax._action+'&a_sngmbh='+action+'&nonce='+ DATA.nonce+'&t='+time();
46 if (myData) {
47 for(let key in myData) paras += '&data['+key+']='+encodeURIComponent(myData[key]);
48 }
49 for(let key in DATA) paras += '&'+key+'='+encodeURIComponent(DATA[key]);
50 return myAjax.url + paras;
51 }
52
53 function _makePost(action, myData, cbf, ecbf, pcbf) {
54 if (FATAL_ERROR) return;
55 let _data = Object.assign({}, DATA);
56 _data.action = myAjax._action;
57 _data.a_sngmbh = action;
58 _data.t = new Date().getTime();
59 _data.nonce = DATA.nonce;
60 pcbf && pcbf();
61 for(var key in myData) _data['data['+key+']'] = myData[key];
62 $.post( myAjax.url, _data, function( response ) {
63 if (response && response.data && response.data.nonce) {
64 DATA.last_nonce_check = new Date().getTime();
65 DATA.nonce = response.data.nonce;
66 }
67 if (!response.success) {
68 if (ecbf) ecbf(response);
69 else LAYOUT.renderFatalError(response.data);
70 } else {
71 cbf && cbf(response.data);
72 }
73 });
74 }
75
76 function _makeGet(action, myData, cbf, ecbf, pcbf) {
77 if (FATAL_ERROR) return;
78 let _data = Object.assign({}, DATA);
79 _data.action = myAjax._action;
80 _data.a_sngmbh = action;
81 _data.t = new Date().getTime();
82 _data.nonce = DATA.nonce;
83 pcbf && pcbf();
84 for(var key in myData) _data['data['+key+']'] = myData[key];
85 $.get( myAjax.url, _data, function( response ) {
86 if (response && response.data && response.data.nonce) {
87 DATA.last_nonce_check = new Date().getTime();
88 DATA.nonce = response.data.nonce;
89 }
90 if (!response.success) {
91 if (ecbf) ecbf(response);
92 else LAYOUT.renderFatalError(response.data);
93 } else {
94 cbf && cbf(response.data);
95 }
96 });
97 }
98
99 function getOptionsFromServer(cbf, ecbf, pcbf) {
100 _makeGet('getOptions', {}, options=>{
101 _setOptions(options);
102 cbf && cbf(options);
103 }, ecbf, pcbf);
104 }
105
106 function _downloadFile(action, myData, filenameToStore, cbf, ecbf, pcbf) {
107 let _data = Object.assign({}, DATA);
108 _data.action = myAjax._action;
109 _data.a_sngmbh = action;
110 _data.t = new Date().getTime();
111 _data.nonce = DATA.nonce;
112 pcbf && pcbf();
113 for(var key in myData) _data['data['+key+']'] = myData[key];
114 let params = "";
115 for(var key in _data) params += key+"="+_data[key]+"&";
116 let url = myAjax.url+'?'+params;
117 let window_name = myData.code ? myData.code : '_blank';
118 let new_window = window.open(url, window_name);
119 //window.location.href = url;
120 //ajax_downloadFile(url, filenameToStore, cbf);
121 }
122 function ajax_downloadFile(urlToSend, fileName, cbf) {
123 var req = new XMLHttpRequest();
124 req.open("GET", urlToSend, true);
125 req.responseType = "blob";
126 req.onload = function (event) {
127 var blob = req.response;
128 //var fileName = req.getResponseHeader("X-fileName") //if you have the fileName header available
129 var link=document.createElement('a');
130 link.href=window.URL.createObjectURL(blob);
131 link.download=fileName;
132 link.click();
133 cbf && cbf();
134 };
135
136 req.send();
137 }
138
139 function speakOutLoud(v, display) {
140 if ('speechSynthesis' in window) {
141 var t = typeof v === 'object' ? 'Value is an object.' : v;
142 if (t.trim() == "") t = 'Value is empty';
143 var msg = new SpeechSynthesisUtterance(t);
144 msg.lang = "en-US";
145 window.speechSynthesis.speak(msg);
146 if (display) console.log("Speak:", v);
147 } else {
148 console.log(v);
149 }
150 }
151 function _saveOptionValue(key, value, cbf, pcbf) {
152 _makePost('changeOption', {'key':key, 'value':value},
153 ()=>{
154 cbf && cbf();
155 if (key == "wcTicketDesignerTemplateTest") {
156 $("#wcTicketDesignerTemplateTest_button_PDF").prop("disabled", false).text(__('Preview Test Template Code as PDF', 'event-tickets-with-ticket-scanner'));
157 }
158 }, null,
159 ()=>{
160 pcbf && pcbf();
161 if (key == "wcTicketDesignerTemplateTest") {
162 $("#wcTicketDesignerTemplateTest_button_PDF").prop("disabled", true).text(__('saving...', 'event-tickets-with-ticket-scanner'));
163 }
164 });
165
166 }
167
168 function _setOptions(optionData) {
169 OPTIONS.list = optionData.options;
170 for (let a=0;a<OPTIONS.list.length;a++) {
171 let item = OPTIONS.list[a];
172 OPTIONS.mapKeys[item.key] = item;
173 OPTIONS.mapKeys[item.key].getValue = function(key) {
174 return function() {return _getOptions_getValByKey(key);};
175 }(item.key);
176 }
177 if (optionData.versions) {
178 if (!optionData.versions.IS_PRETTY_PERMALINK_ACTIVATED) {
179 LAYOUT.renderInfoBox(__("Warning", 'event-tickets-with-ticket-scanner'), __("In order to make the ticket detail view and the ticket scanner work, you need to set a permalink structure within the settings.<br>Please go to the settings->permalinks and choose a permalink structure, that is not 'plain'.", 'event-tickets-with-ticket-scanner'));
180 }
181 OPTIONS.versions.mapKeys = optionData.versions;
182 }
183 system.is_debug = typeof optionData.versions.is_debug != "undefined" && optionData.versions.is_debug == 1 ? true : false;
184 if (optionData.meta_tags_keys) {
185 OPTIONS.meta_tags_keys.list = optionData.meta_tags_keys;
186 OPTIONS.meta_tags_keys.mapKeys = {};
187 for (let a=0;a<OPTIONS.meta_tags_keys.list.length;a++) {
188 let item = OPTIONS.meta_tags_keys.list[a];
189 OPTIONS.meta_tags_keys.mapKeys[item.key] = item;
190 OPTIONS.meta_tags_keys.mapKeys[item.key].getValue = function(key) {
191 return function() {return _getOptions_Meta_getValByKey(key);};
192 }(item.key);
193 }
194 }
195 if (optionData.infos) {
196 OPTIONS.infos = optionData.infos;
197 }
198 if (optionData.tickets_for_testing) {
199 OPTIONS.tickets_for_testing = optionData.tickets_for_testing;
200 }
201 if (optionData.options_special) {
202 OPTIONS.options_special = optionData.options_special;
203 }
204
205 if (isPremium()) {
206 let serial = _getOptions_getValByKey('serial');
207 if (serial == '') {
208 if (STATE != "options") {
209 let errortext = __("You are using the premium version. Many thanks, please enter your serial key within the options", 'event-tickets-with-ticket-scanner');
210 let i = confirm(errortext);
211 if (i) {
212 _displayOptionsArea();
213 }
214 }
215 }
216 if (serial != "" && typeof OPTIONS.infos.premium_expiration !== "undefined") {
217 let expiration = OPTIONS.infos.premium_expiration;
218 if (expiration.last_run != 0 && expiration.timestamp > 0) {
219 let expirationDate = new Date(expiration.timestamp * 1000);
220 let toCheck = new Date();
221 toCheck.setDate(toCheck.getDate() + 21);
222 let today = new Date();
223 if (expirationDate <= today || toCheck >= expirationDate) {
224 let msg = typeof expiration.message !== "undefined" && expiration.message != "" ? '<br>'+expiration.message : '';
225 let info_box = $('<div style="background-color:red;color:white;padding:10px;">').html("Your premium license expires soon, at the "+expiration.expiration_date+ ' '+expiration.timezone+'<br>It will work, but no updates are possible for the premium plugin after the expiration date.<br>'+msg+'You can <a target="_blank" style="color:white;font-weight:bold;" href="https://vollstart.com/event-tickets-with-ticket-scanner/">renew your premium license here</a>.');
226 $('body').find('div[data-id="plugin_info_area"').html(info_box);
227 }
228 }
229 }
230 }
231 }
232
233 function _getOptions_getByKey(key) {
234 if (OPTIONS.mapKeys[key]) return OPTIONS.mapKeys[key];
235 return null;
236 }
237 function _getOptions_Meta_getByKey(key) {
238 if (OPTIONS.meta_tags_keys.mapKeys[key]) return OPTIONS.meta_tags_keys.mapKeys[key];
239 return null;
240 }
241 function _getOptions_Versions_getByKey(key) {
242 if (OPTIONS.versions.mapKeys[key]) return OPTIONS.versions.mapKeys[key];
243 return null;
244 }
245 function _getOptions_Infos_getByKey(key) {
246 if (OPTIONS.infos[key]) return OPTIONS.infos[key];
247 return null;
248 }
249 function _getOptions_isActivatedByKey(key) {
250 let po = _getOptions_getByKey(key);
251 if (po == null) return false;
252 return po.value == 1;
253 }
254 function _getOptions_Versions_isActivatedByKey(key) {
255 let po = _getOptions_Versions_getByKey(key);
256 if (po == null) return false;
257 return po == 1;
258 }
259 function _getOptions_getLabelByKey(key) {
260 let po = _getOptions_getByKey(key);
261 if (po == null) return "";
262 return po.label;
263 }
264 function _getOptions_Meta_getLabelByKey(key) {
265 let po = _getOptions_Meta_getByKey(key);
266 if (po == null) return "";
267 return po.label;
268 }
269 function _getOptions_getValByKey(key) {
270 let po = _getOptions_getByKey(key);
271 if (po == null) return "";
272 return po.value == "" ? po['default'] : po.value;
273 }
274 function _getOptions_Versions_getValByKey(key) {
275 let po = _getOptions_Versions_getByKey(key);
276 if (po == null) return "";
277 return po;
278 }
279
280 function basics_ermittelURLParameter() {
281 var parawerte = {};
282 var teile;
283 if (window.location.search !== "") {
284 teile = window.location.search.substring(1).split("&");
285 for (var a=0;a<teile.length;a++)
286 {
287 var pos = teile[a].indexOf("=");
288 if (pos < 0) {
289 parawerte[teile[a]] = true;
290 } else {
291 var key = teile[a].substring(0,pos);
292 parawerte[key] = decodeURIComponent(teile[a].substring(pos+1));
293 }
294 }
295 }
296 return parawerte;
297 }
298
299 function intval(v) {
300 let retv = parseInt(v,10);
301 if (isNaN(retv)) retv = 0;
302 return retv;
303 }
304
305 function getDefaultDateFormat() {
306 return (OPTIONS?.options_special?.format_date) ? OPTIONS.options_special.format_date : "d.m.Y";
307 }
308 function getDefaultDateTimeFormat() {
309 return OPTIONS.options_special.format_datetime ? OPTIONS.options_special.format_datetime : "d.m.Y H:i";
310 }
311 function DateTime2Text(millisek) {
312 return Date2Text(millisek, getDefaultDateTimeFormat());
313 }
314 /*
315 function Date2Text(millisek, format, timezone_id) {
316 if (!timezone_id) timezone_id = _getOptions_Versions_getByKey("date_WP_timezone");
317 if (!millisek)
318 millisek = time(timezone_id);
319 var d = new Date(millisek);
320 if (!format)
321 //format = system.format_date ? system.format_date : "%d.%m.%Y";
322 format = getDefaultDateFormat();
323 //format = "%d.%m.%Y %H:%i";
324 var tage = [
325 _x('Sun', 'cal', 'event-tickets-with-ticket-scanner'),
326 _x('Mon', 'cal', 'event-tickets-with-ticket-scanner'),
327 _x('Tue', 'cal', 'event-tickets-with-ticket-scanner'),
328 _x('Wed', 'cal', 'event-tickets-with-ticket-scanner'),
329 _x('Thu', 'cal', 'event-tickets-with-ticket-scanner'),
330 _x('Fri', 'cal', 'event-tickets-with-ticket-scanner'),
331 _x('Sat', 'cal', 'event-tickets-with-ticket-scanner')
332 ];
333 var monate = [
334 _x('Jan', 'cal', 'event-tickets-with-ticket-scanner'),
335 _x('Feb', 'cal', 'event-tickets-with-ticket-scanner'),
336 _x('Mar', 'cal', 'event-tickets-with-ticket-scanner'),
337 _x('Apr', 'cal', 'event-tickets-with-ticket-scanner'),
338 _x('May', 'cal', 'event-tickets-with-ticket-scanner'),
339 _x('Jun', 'cal', 'event-tickets-with-ticket-scanner'),
340 _x('Jul', 'cal', 'event-tickets-with-ticket-scanner'),
341 _x('Aug', 'cal', 'event-tickets-with-ticket-scanner'),
342 _x('Sep', 'cal', 'event-tickets-with-ticket-scanner'),
343 _x('Oct', 'cal', 'event-tickets-with-ticket-scanner'),
344 _x('Nov', 'cal', 'event-tickets-with-ticket-scanner'),
345 _x('Dec', 'cal', 'event-tickets-with-ticket-scanner')
346 ];
347 var formate = {'d':d.getDate()<10?'0'+d.getDate():d.getDate(),
348 'j':d.getDate(),'D':tage[d.getDay()],'w':d.getDate(),'m':d.getMonth()+1<10?'0'+(d.getMonth()+1):d.getMonth()+1,'M':monate[d.getMonth()],
349 'n':d.getMonth()+1,'Y':d.getFullYear(),'y':d.getYear()>100?d.getYear().toString().substring(d.getYear().toString().length-2):d.getYear(),
350 'H':d.getHours()<10?'0'+d.getHours():d.getHours(),'h':d.getHours()>12?d.getHours()-12:d.getHours(),
351 'i':d.getMinutes()<10?'0'+d.getMinutes():d.getMinutes(),'s':d.getSeconds()<10?'0'+d.getSeconds():d.getSeconds()
352 };
353 for (var akey in formate) {
354 //var rg = new RegExp('%'+akey, "g");
355 var rg = new RegExp(akey, "g");
356 format = format.replace(rg, formate[akey]);
357 }
358 return format;
359 }
360 */
361
362 function DateFormatStringToDateTimeText(datestring, format, timezone_id) {
363 if (!format) format = getDefaultDateTimeFormat();
364 let millisek = parseToMillis(datestring, timezone_id);
365 return Date2Text(millisek, format, timezone_id);
366 }
367 function DateFormatStringToDateText(datestring, format, timezone_id) {
368 let millisek = parseToMillis(datestring, timezone_id);
369 return Date2Text(millisek, format, timezone_id);
370 }
371
372 function Date2Text(millisek, format, timezone_id) {
373 // 1) Timezone bestimmen (Fallback: UTC)
374 if (!timezone_id) {
375 timezone_id = _getOptions_Versions_getByKey("date_WP_timezone") || "UTC";
376 }
377
378 // 2) Timestamp normalisieren (PHP liefert oft Sekunden; JS braucht Millisekunden)
379 if (typeof millisek === "string") millisek = Number(millisek);
380 if (!millisek) {
381 // Deine bestehende Logik – falls du hier einen Unix-TS in Sekunden bekommst, bitte ggf. *1000 ergänzen
382 millisek = time(timezone_id);
383 }
384 if (String(Math.trunc(millisek)).length === 10) {
385 millisek = millisek * 1000;
386 }
387 const date = new Date(Number(millisek));
388
389 // 3) Defaults für Format
390 if (!format) {
391 format = getDefaultDateFormat();
392 }
393
394 // 4) Lokalisierte Kurzformen (nutzt deine _x-Übersetzungen)
395 const tage = [
396 _x('Sun', 'cal', 'event-tickets-with-ticket-scanner'),
397 _x('Mon', 'cal', 'event-tickets-with-ticket-scanner'),
398 _x('Tue', 'cal', 'event-tickets-with-ticket-scanner'),
399 _x('Wed', 'cal', 'event-tickets-with-ticket-scanner'),
400 _x('Thu', 'cal', 'event-tickets-with-ticket-scanner'),
401 _x('Fri', 'cal', 'event-tickets-with-ticket-scanner'),
402 _x('Sat', 'cal', 'event-tickets-with-ticket-scanner')
403 ];
404 const monate = [
405 _x('Jan', 'cal', 'event-tickets-with-ticket-scanner'),
406 _x('Feb', 'cal', 'event-tickets-with-ticket-scanner'),
407 _x('Mar', 'cal', 'event-tickets-with-ticket-scanner'),
408 _x('Apr', 'cal', 'event-tickets-with-ticket-scanner'),
409 _x('May', 'cal', 'event-tickets-with-ticket-scanner'),
410 _x('Jun', 'cal', 'event-tickets-with-ticket-scanner'),
411 _x('Jul', 'cal', 'event-tickets-with-ticket-scanner'),
412 _x('Aug', 'cal', 'event-tickets-with-ticket-scanner'),
413 _x('Sep', 'cal', 'event-tickets-with-ticket-scanner'),
414 _x('Oct', 'cal', 'event-tickets-with-ticket-scanner'),
415 _x('Nov', 'cal', 'event-tickets-with-ticket-scanner'),
416 _x('Dec', 'cal', 'event-tickets-with-ticket-scanner')
417 ];
418
419 // 5) Teile in gewünschter Timezone extrahieren
420 const dtf = new Intl.DateTimeFormat("de-CH", {
421 timeZone: timezone_id,
422 year: "numeric",
423 month: "2-digit",
424 day: "2-digit",
425 hour: "2-digit",
426 minute: "2-digit",
427 second: "2-digit",
428 weekday: "short",
429 hour12: false
430 });
431 const parts = Object.fromEntries(dtf.formatToParts(date).map(p => [p.type, p.value]));
432
433 const monthNum = Number(parts.month); // 1..12 (als "01".."12")
434 const dayNum = Number(parts.day); // 1..31
435 const hourNum = Number(parts.hour); // 0..23
436
437 // Wochentag-Index (0=Sun..6=Sat) in der angegebenen Timezone
438 const weekdayEn = new Intl.DateTimeFormat("en-US", { timeZone: timezone_id, weekday: "short" }).format(date);
439 const weekdayIndex = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(weekdayEn);
440
441 // 6) Token-Mapping (PHP-ähnlich)
442 const formate = {
443 'd': parts.day, // 01..31
444 'j': String(dayNum), // 1..31
445 'D': tage[weekdayIndex], // So/Mo/... (aus _x oben, hier auf 'Sun'.. gemappt)
446 'w': String(weekdayIndex), // 0..6 (So=0)
447 'm': parts.month, // 01..12
448 'M': monate[monthNum - 1], // Jan..Dec (aus _x oben)
449 'n': String(monthNum), // 1..12
450 'Y': parts.year, // 2025
451 'y': parts.year.slice(-2), // 25
452 'H': parts.hour, // 00..23
453 'h': String(((hourNum % 12) || 12)).padStart(2,'0'), // 01..12
454 'i': parts.minute, // 00..59
455 's': parts.second // 00..59
456 };
457
458 // 7) Token ersetzen (ohne %; entspricht deiner aktuellen Logik)
459 for (const akey in formate) {
460 const rg = new RegExp(akey, "g");
461 format = format.replace(rg, formate[akey]);
462 }
463 return format;
464 }
465
466 // Hilfsfunktion: Offset-Minuten einer IANA-Zeitzone für einen UTC-Instant ermitteln.
467 // Nutzt Intl.DateTimeFormat mit timeZoneName:'shortOffset' (z.B. "GMT+2").
468 function _getTzOffsetMinutes(utcDate, timezone_id) {
469 const fmt = new Intl.DateTimeFormat('en-US', {
470 timeZone: timezone_id,
471 timeZoneName: 'shortOffset',
472 year: 'numeric', month: '2-digit', day: '2-digit',
473 hour: '2-digit', minute: '2-digit', second: '2-digit',
474 hour12: false
475 });
476 const parts = fmt.formatToParts(utcDate);
477 const z = parts.find(p => p.type === 'timeZoneName')?.value || 'GMT+0';
478 // Erwartete Form: "GMT+2" oder "GMT+02:00"
479 const m = z.match(/GMT([+-])(\d{1,2})(?::?(\d{2}))?/i);
480 if (!m) return 0;
481 const sign = m[1] === '-' ? -1 : 1;
482 const hours = parseInt(m[2], 10);
483 const mins = m[3] ? parseInt(m[3], 10) : 0;
484 return sign * (hours * 60 + mins);
485 }
486
487 // Wandelt verschiedenste Eingaben in einen UTC-Millis-Timestamp.
488 // - Zahlen (Sekunden/Millis) -> normalisiert
489 // - ISO-Strings mit Z/±hh:mm -> nativ geparst
490 // - Naive Strings (z.B. "YYYY-MM-DD HH:mm:ss") -> als timezone_id-Wandzeit interpretiert
491 function parseToMillis(input, timezone_id) {
492 timezone_id = timezone_id || (typeof _getOptions_Versions_getByKey === 'function'
493 ? _getOptions_Versions_getByKey("date_WP_timezone") : "UTC");
494
495 // 1) Direkt Number?
496 if (typeof input === 'number') {
497 // 10-stellige Sekunden -> *1000
498 if (String(Math.trunc(input)).length === 10) return input * 1000;
499 return input; // bereits Millisekunden
500 }
501
502 // 2) String -> trim
503 if (typeof input === 'string') {
504 const s = input.trim();
505
506 // 2a) Reine Ziffern -> Sekunden/Millis
507 if (/^\d+$/.test(s)) {
508 const n = Number(s);
509 return (s.length === 10) ? n * 1000 : n;
510 }
511
512 // 2b) ISO mit Z / Offset -> nativ (sicher)
513 if (/T.*(Z|[+-]\d{2}:\d{2})$/.test(s)) {
514 const d = new Date(s);
515 if (!isNaN(d)) return d.getTime();
516 }
517
518 // 2c) Naive Formate: "YYYY-MM-DD HH:mm:ss" | "YYYY/MM/DD HH:mm" | "YYYY-MM-DD"
519 // Wir parsen Komponenten und interpretieren sie als Wandzeit in timezone_id.
520 const m = s.match(
521 /^(\d{4})[-\/](\d{2})[-\/](\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/
522 );
523 if (m) {
524 const Y = parseInt(m[1], 10);
525 const Mo = parseInt(m[2], 10);
526 const D = parseInt(m[3], 10);
527 const H = m[4] ? parseInt(m[4], 10) : 0;
528 const I = m[5] ? parseInt(m[5], 10) : 0;
529 const S = m[6] ? parseInt(m[6], 10) : 0;
530
531 // Instant-Kandidat in UTC aus den "lokalen" Komponenten
532 // Idee: Komponenten als UTC annehmen -> Offset der Ziel-Zeitzone abziehen.
533 const utcGuess = new Date(Date.UTC(Y, Mo - 1, D, H, I, S));
534
535 // Offset der Ziel-Zone zum angegebenen Zeitpunkt holen (inkl. DST)
536 const offMin = _getTzOffsetMinutes(utcGuess, timezone_id);
537
538 // Echte UTC-Millis, wenn Y-M-D H:I:S die Wandzeit in timezone_id ist:
539 return utcGuess.getTime() - offMin * 60 * 1000;
540 }
541
542 // 2d) Fallback: Versuch natives Date (Browser-lokal) – nicht ideal, aber besser als NaN
543 const d = new Date(s.replace(' ', 'T'));
544 if (!isNaN(d)) return d.getTime();
545 }
546
547 // 3) Wenn alles fehlschlägt -> NaN (oder wirf Fehler je nach Policy)
548 return NaN;
549 }
550 function _getMediaData(mediaid, cbf) {
551 _makeGet('getMediaData', {'mediaid':mediaid}, (ret)=>{
552 cbf && cbf(ret);
553 });
554 }
555
556 function getDataLists(cbf) {
557 if (DATA_LISTS !== null) cbf && cbf();
558 _makeGet('getLists', {}, data=>{
559 DATA_LISTS = data;
560 cbf && cbf(DATA_LISTS);
561 });
562 }
563
564 function getCodeObjectMeta(codeObj) {
565 if (codeObj.metaObj) return codeObj.metaObj;
566 try {
567 if (typeof codeObj.meta == "undefined" || codeObj.meta == "") {
568 codeObj.metaObj = null;
569 } else {
570 codeObj.metaObj = JSON.parse(codeObj.meta);
571 }
572 } catch(e) {
573 // new empty tickets have no meta
574 //console.log("Error should not happen. Meta is broken. ", codeObj);
575 codeObj.metaObj = null;
576 }
577 return codeObj.metaObj;
578 }
579
580 function updateCodeObject(codeObj, newCodeObj) {
581 for(var prop in newCodeObj) {
582 codeObj[prop] = newCodeObj[prop];
583 }
584 codeObj.metaObj = null;
585 }
586
587 function closeDialog(dlg) {
588 $(dlg).dialog( "close" );
589 $(dlg).html('');
590 $(dlg).dialog("destroy").remove();
591 $(dlg).empty();
592 $(dlg).remove();
593 $('.ui-dialog-content').dialog('destroy');
594 }
595
596 function getUseFulVideosHTML() {
597 return '<h3>Useful videos</h3><ul><li><span class="dashicons dashicons-external"></span><a href="https://youtu.be/yJcHMV7oAFc" target="_blank">Setup for use case Event Organizer (Youtube)</a></li><li><span class="dashicons dashicons-external"></span><a href="https://www.youtube.com/watch?v=TDMWI0R_HXQ" target="_blank">Setup for use case Club, Spa and Fitness clubs (Youtube)</a></li></ul>';
598 }
599
600 function getAuthtokens(cbf) {
601 if (DATA_AUTHTOKENS !== null) cbf && cbf();
602 _makeGet('getAuthtokens', {}, data=>{
603 DATA_AUTHTOKENS = data;
604 cbf && cbf(DATA_AUTHTOKENS);
605 });
606 }
607
608 function _displayAuthTokensArea() {
609 STATE = 'authtokens';
610 DIV.html('');
611 DIV.append(getBackButtonDiv());
612
613 DIV.append('<h3>'+_x('Auth Token', 'label', 'event-tickets-with-ticket-scanner')+'</h3>');
614 $('<p>').html(__('You can add auth tokens, that can be used to access your ticket scanner. Create an auth token and pass the QR code to the user or let the user scan it from your admin area. The used auth token will bypass any access restricton settings for the ticket scanner that are set in the options.', 'event-tickets-with-ticket-scanner')).appendTo(DIV);
615 $('<p>').html(__('The user scan the QR code for the auth token with the ticket scanner. Just like a normal ticket. The system will store the auth token to the browser.', 'event-tickets-with-ticket-scanner')).appendTo(DIV);
616 let loading = $('<div/>').html(_getSpinnerHTML()).appendTo(DIV);
617 let div2 = $('<div style="background:white;padding:15px;border-radius:15px;">').appendTo(DIV);
618 let tplace = $('<div style="background:white;padding:15px;border-radius:15px;"/>');
619
620 getOptionsFromServer(reply=>{
621 let tabelle_authtokens_datatable;
622 let btn_new = $('<button/>').addClass("button-primary").html(_x('Add', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
623 __showMaskAuthtoken(null);
624 });
625 $('<div/>').css('text-align', 'right').css('margin-bottom','10px').append(btn_new).appendTo(div2);
626 let div_tabelle = $('<div>');
627 loading.html("");
628 tplace.html("").append(div_tabelle).appendTo(div2);
629
630 function __showMaskAuthtoken(editValues) {
631 let _options = {
632 title: editValues !== null ? _x('Edit Auth Token', 'title', 'event-tickets-with-ticket-scanner') : _x('Add Auth Token', 'title', 'event-tickets-with-ticket-scanner'),
633 modal: true,
634 minWidth: 600,
635 minHeight: 400,
636 buttons: [
637 {
638 text: _x('Ok', 'label', 'event-tickets-with-ticket-scanner'),
639 click: function() {
640 ___submitForm();
641 }
642 },
643 {
644 text: _x('Cancel', 'label', 'event-tickets-with-ticket-scanner'),
645 click: function() {
646 closeDialog(this);
647 }
648 }
649 ]
650 };
651 let dlg = $('<div/>').html('<form>'+_x('Name', 'label', 'event-tickets-with-ticket-scanner')+'<br><input name="inputName" type="text" style="width:100%;" required></form>');
652 dlg.dialog(_options);
653
654 dlg.find("form").append('<p>'+_x('Bound to product(s)', 'label', 'event-tickets-with-ticket-scanner')+'<br><input name="inputBoundToProducts" type="text" placeholder="'+_x('all products allowed to be redeemed', 'label', 'event-tickets-with-ticket-scanner')+'" style="width:100%;"><br>'+__('You can add comma seperated "," product ids. This will limit the user to redeem tickets only of products listed here. If left empty, all are allowed.', 'event-tickets-with-ticket-scanner')+'</p>');
655 dlg.dialog(_options);
656
657 dlg.find("form").append($('<p>'+_x('Description', 'label', 'event-tickets-with-ticket-scanner')+'<br><textarea name="desc" style="width:100%;"></textarea></p>'));
658 if (isPremium() && typeof PREMIUM.addAuthtokenMaskEditFields != "undefined") PREMIUM.addAuthtokenMaskEditFields(dlg, editValues);
659 dlg.find("form").append($('<p><input type="checkbox" name="aktiv">'+_x('is active', 'label', 'event-tickets-with-ticket-scanner')+'</p>'));
660
661 let form = dlg.find("form").on("submit", event=>{
662 event.preventDefault();
663 ___submitForm();
664 });
665
666 let metaObj = [];
667 if (editValues && typeof editValues.meta !== "undefined" && editValues.meta != "") {
668 try {
669 metaObj = JSON.parse(editValues.meta);
670 } catch(e) {}
671 }
672
673 if (editValues) {
674 form[0].elements['inputName'].value = editValues.name;
675 form[0].elements['inputName'].select();
676 form[0].elements['inputBoundToProducts'].value = editValues.metaObj.ticketscanner.bound_to_products;
677 form[0].elements['aktiv'].checked = editValues.aktiv == 1 ? true : false;
678 if (typeof metaObj.desc !== "undefined") {
679 form[0].elements['desc'].value = metaObj.desc;
680 }
681 }
682
683 function ___submitForm() {
684 let inputName = form[0].elements['inputName'].value.trim();
685 if (inputName === "") return;
686
687 dlg.html(_getSpinnerHTML());
688 let _data = {"name":inputName};
689 _data['aktiv'] = form[0].elements['aktiv'].checked ? 1 : 0;
690 _data['meta'] = {"desc":"", "ticketscanner":{"bound_to_products":""}};
691 _data['meta']['desc'] = form[0].elements['desc'].value.trim();
692 _data['meta']['ticketscanner']['bound_to_products'] = form[0].elements['inputBoundToProducts'].value.trim();
693 if (isPremium() && typeof PREMIUM.addAuthtokenMaskEditFieldsData != "undefined") PREMIUM.addAuthtokenMaskEditFieldsData(_data, form[0], editValues);
694
695 form[0].reset();
696 if (editValues) {
697 _data.id = editValues.id;
698 _makePost('editAuthtoken', _data, result=>{
699 DATA_AUTHTOKENS = null;
700 __renderTabelleAuthtokens();
701 //tabelle_authtokens_datatable.ajax.reload();
702 setTimeout(function(){closeDialog(dlg);},250);
703 }, ()=>{
704 closeDialog(dlg);
705 });
706 } else {
707 _makePost('addAuthtoken', _data, result=>{
708 DATA_AUTHTOKENS = null;
709 __renderTabelleAuthtokens();
710 closeDialog(dlg);
711 }, response=>{
712 closeDialog(dlg);
713 if (response.data.slice(0,1) === "#") {
714 FATAL_ERROR === false && LAYOUT.renderFatalError(response.data);
715 //FATAL_ERROR = true;
716 }
717 });
718 }
719 }
720 } // end __showMaskAuthtoken
721
722 function __renderTabelleAuthtokens() {
723 div_tabelle.html(_getSpinnerHTML());
724 getAuthtokens(()=>{
725 let table_id = myAjax.divPrefix+'_tabelle_authtokens';
726 let tabelle = $('<table/>').attr("id", table_id);
727 tabelle.html('<thead><tr><th></th><th align="left">'+_x('Name', 'label', 'event-tickets-with-ticket-scanner')+'</th><th align="left">'+_x('Created', 'label', 'event-tickets-with-ticket-scanner')+'</th><th>'+_x('Area', 'label', 'event-tickets-with-ticket-scanner')+'</th><th>'+_x('Status', 'label', 'event-tickets-with-ticket-scanner')+'</th><th></th></tr></thead>');
728 div_tabelle.html(tabelle);
729
730 let table = $('#'+table_id);
731 $(table).DataTable().clear().destroy();
732 tabelle_authtokens_datatable = $(table).DataTable({
733 "responsive": true,
734 "visible": true,
735 "searching": true,
736 "ordering": true,
737 "processing": true,
738 "serverSide": false,
739 "stateSave": true,
740 "data": DATA_AUTHTOKENS,
741 "order": [[ 1, "asc" ]],
742 "columns":[
743 {"data":null,"className":'details-control',"orderable":false,"defaultContent":'', "width":10},
744 {"data":"name", "orderable":true,
745 "render": ( data, type, row )=>{
746 return encodeURIComponent(data);
747 }
748 },
749 {"data":"time", "orderable":true, "width":80,
750 "render":function (data, type, row) {
751 return '<span style="display:none;">'+data+'</span>'+DateFormatStringToDateTimeText(data);
752 }
753 },
754 {"data":"areacode", "orderable":true, "className":"dt-center", "width":80},
755 {"data":"aktiv", "orderable":true, "width":50, "className":"dt-center", "render":(data, type, row)=>{
756 return data == 1 ? 'active' : 'inactive';
757 }},
758 {"data":null,"orderable":false,"defaultContent":'',"className":"buttons dt-right","width":100,
759 "render": ( data, type, row )=>{
760 return '<button class="button-secondary" data-type="edit">'+_x('Edit', 'label', 'event-tickets-with-ticket-scanner')+'</button> <button class="button-secondary" data-type="delete">'+_x('Delete', 'label', 'event-tickets-with-ticket-scanner')+'</button>';
761 }
762 }
763 ]
764 });
765 tabelle.css("width", "100%");
766 table.on('click', 'button[data-type="edit"]', e=>{
767 let data = tabelle_authtokens_datatable.row( $(e.target).parents('tr') ).data();
768 __showMaskAuthtoken(data);
769 });
770 table.on('click', 'button[data-type="delete"]', e=>{
771 let data = tabelle_authtokens_datatable.row( $(e.target).parents('tr') ).data();
772 LAYOUT.renderYesNo(_x('Do you want to delete?', 'title', 'event-tickets-with-ticket-scanner'), __('Are you sure, you want to delete this auth token?', 'event-tickets-with-ticket-scanner')+'<br><p><b>'+data.name+'</b></p>'+__('The user with this auth token will not be able to use the server anymore. The user will need to add a new auth token from you.<p>The effect will be immediately.</p>', 'event-tickets-with-ticket-scanner'), ()=>{
773 let _data = {'id':data.id};
774 div_tabelle.html(_getSpinnerHTML());
775 _makePost('removeAuthtoken', _data, result=>{
776 DATA_AUTHTOKENS = null;
777 __renderTabelleAuthtokens();
778 //tabelle_authtokens_datatable.ajax.reload();
779 });
780 });
781 });
782 $('#'+table_id+' tbody').on('click', 'td.details-control', e=>{
783 function ___format(d) {
784 let metaObj = {};
785 if (d.metaObj) metaObj = d.metaObj;
786 if (d.meta && !d.metaObj) {
787 metaObj = JSON.parse(d.meta);
788 }
789 let id = 'qrcode_'+d.id+'_'+time();
790 let content = JSON.stringify({"type":"auth", "time":d.time, "name":d.name, "code":d.code, "areacode":d.areacode, "url":OPTIONS.infos.site.site_url});
791 let content2 = _getTicketScannerURL()+'&auth='+encodeURIComponent(content);
792
793 let div = $('<div/>');
794 $('<div>').html("<b>Authcode: </b>"+d.code).appendTo(div);
795 let div_wrapper = $('<div style="padding-top:10px;">').appendTo(div);
796
797 $('<div style="width:256px;float:left;text-align:center">').html('<b>Only Auth Token</b><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode(\''+content+'\');</script>').appendTo(div_wrapper);
798 $('<div style="margin-left:20px;width:256px;float:left;text-align:center">').html('<b>With Ticket Scanner URL</b><div id="'+id+'2" style="text-align:center;"></div><script>jQuery("#'+id+'2").qrcode(\''+content2+'\');</script>').appendTo(div_wrapper);
799
800 let div_inner = $('<div style="float:left;padding-left:10px;">').appendTo(div_wrapper);
801 let _desc = metaObj.desc == "" ? "-" : metaObj.desc;
802 $('<div>').html('<b><a href="'+content2+'" target="_blank">Open Ticket Scanner with Auth Token</a></b>').appendTo(div_inner);
803 $('<div>').html('<b>Desc:</b> ').append($('<span>').text(_desc)).appendTo(div_inner);
804
805 let bound_to_products = metaObj.ticketscanner.bound_to_products == "" ? [] : metaObj.ticketscanner.bound_to_products.toString().split(",");
806 $("<div>").html("<b>Bound to product:</b> "+(bound_to_products.length == 0 ? "all products": bound_to_products.join(", "))).appendTo(div_inner);
807
808 return div;
809 }
810
811 var tr = $(e.target).parents('tr');
812 var row = tabelle_authtokens_datatable.row( tr );
813 if ( row.child.isShown() ) {
814 // This row is already open - close it
815 row.child.hide();
816 tr.removeClass('shown');
817 } else {
818 // Open this row
819 row.child( ___format(row.data()) ).show();
820 tr.addClass('shown');
821 }
822
823 });
824 });
825 }
826 __renderTabelleAuthtokens();
827 });
828 }
829
830 function _displayFAQArea() {
831 STATE = 'faq';
832 DIV.html(_getSpinnerHTML());
833
834 let questions = [
835 {
836 "q":'PDF is not rendering - critical error',
837 "t":'<p>The used PDF library cannot handle all the fancy HTML and CSS. Using these in the product description can lead to an error. If the ticket detail page is working, but the PDF not then you can try to remove the HTML tags or use the option to not print the product description to the ticket.<br>Please set the option <b>wcTicketPDFStripHTML</b> to remove the HTML and retry the PDF by reloading the browser or click again.</p><p>If your system is not live yet, you can use the debug mode first to see which HTML tags are used. The basics HTML tags are working well.</p><p>Try the option to remove the not supported HTML tags - this is not always great, because it removes the HTML tags that Wordpress is not supporting and could still lead to PDF issues, but a great start.<br>If this was not helping, then remove please the HTML tags in your product description for a test. You can also just deactivate the option <b>wcTicketDisplayShortDesc</b> to not use the short description of the product for a test.</p>'
838 },
839 {
840 "q":'Receiving 404 error page if calling the ticket view and/or PDF',
841 "t":'<p>Some installations have issues to open the ticket details view and/or the ticket scanner.<br>This could be because of your theme, other plugins or more stricter security settings.</p><p>If you experience to see the "file not found" page (404), then it could help if your activate the compatibility mode in the options.</p><p>For this configure the option <b>wcTicketCompatibilityModeURLPath</b> and/or <b>wcTicketCompatibilityMode</b>.</p><p>If this do not help, then the plugin will not work with your installation for now.</p>'
842 },
843 {
844 "q":'How to ask for a value of your ticket?',
845 "t":'<p>You can setup your product to ask your customer for up to 2 values. Free text and a value chosen from a dropdown.<br>You can checkout how it is done with <a href="https://youtu.be/2vTV39wgWNE" target="_blank">this video</a>.</p>'
846 },
847 {
848 "q":'(Pre)Create order with tickets in the backend',
849 "t":'<p>You can also checkout <a href="https://youtu.be/VxUV-s-SIpA" target="_blank">this video here</a>.<br>This video shows how to create an order from the backend and generate the tickets.<br>This approach is also good for free tickets. So you can create the order and have valid tickets. Do not forget to set the order to a redeemable status. The default is "completed".</p>'
850 },
851 {
852 "q":'How to display meta information of the purchased item?',
853 "t":'You can display the meta information of the item with TWIG.<br>Try TWIG code in the ticket template test designer, to see if this helps. First it is a good idea to check the whole meta values. You can achieve this, by displaying the values as JSON with this code.<p><b>{% for item_id, item in ORDER.get_items %}<br>{{ item.get_meta_data|json_encode() }}<br>{% endfor %}</b></p><p>You will see the key value pairs. Then grab your values. E.g.</p><p><b>{%- for item_id, item in ORDER.get_items -%}<br>{%- if item_id == METAOBJ.woocommerce.item_id -%}<br>&lt;br&gt;Date: {{ item.get_meta("Booked From", true) }} - {{ item.get_meta("Booked To", true) }}<br>{%- endif -%}<br>{%- endfor -%}</b></p>'
854 },
855 {
856 "q":"How to set the order immediately to 'completed' if the order is paid?",
857 "t":"You can activate the option wcTicketSetOrderToCompleteIfAllOrderItemsAreTickets to change the order status to completed if all purchased items in the order are tickets and the order status is processing. With this the order is fine and not paid orders are not automatically set to completed. This prevents frauds."
858 },
859 {
860 "q":"How to use own page with ticket scanner and have the QR code redirect to it?",
861 "t":"<p>You set up a page with the ticket scanner shortcode 'sasoEventTicketsValidator_ticket_scanner'.<br>Then adjust the URL for your tickets (scanner is included). The only option for now is the wcTicketCompatibilityModeURLPath. But this also changes the detail page of the ticket. Basically the system is adding to this URL just the '/scanner/?code='.</p><p>If you do not want this, you can adjust the QR content with the option qrOwnQRContent.</p><p>Set it to have the content:<br>https://domain-and-path/scanner/?code={WC_TICKET__PUBLIC_TICKET_ID}</p>"
862 }
863 ];
864
865 let div = $('<div>');
866 div.append("<h2>FAQ</h2>");
867 let div2 = $('<div style="background:white;padding:15px;border-radius:15px;">').appendTo(div);
868 div2.append(getUseFulVideosHTML()+'<br><br>');
869
870 questions.forEach(v=>{
871 let clicked = false;
872 div2.append($('<h3 style="cursor:pointer;">').html("+ "+v.q).on("click",e=>{
873 f1.css("display", clicked ? "none" : "block");
874 clicked = !clicked;
875 }));
876 let f1 = $('<div style="display:none;padding-bottom:15px;">').html(v.t).appendTo(div2);
877 });
878
879 DIV.html(getBackButtonDiv());
880 DIV.append(div);
881 }
882
883 function _displaySeatingplanArea() {
884 STATE = 'seatingplan';
885 let div = $('<div>').html(_getSpinnerHTML());
886 const version = system.is_debug ? new Date().getTime() : myAjax._plugin_version;
887 const jsFile = 'js/seating_admin.js?v=' + version;
888 const cssFile = 'css/seating_admin';
889
890 addStyleTag(myAjax._plugin_home_url + '/' + cssFile + '.css?v=' + version, 'saso_seating_admin_css');
891
892 // Load JS if not already loaded (or always in debug mode)
893 if (!system.is_debug && system.DYNJS[jsFile]) {
894 sasoEventtickets_js_seating_admin(myAjax, getHelperFunktions()).initAdmin(div);
895 } else {
896 console.log('Loading seating admin JS: ' + jsFile);
897 $.getScript(myAjax._plugin_home_url + '/' + jsFile, (data) => {
898 system.DYNJS[jsFile] = data;
899 eval(data);
900 sasoEventtickets_js_seating_admin(myAjax, getHelperFunktions()).initAdmin(div);
901 });
902 }
903
904 return div;
905 }
906 function _displaySupportInfoArea() {
907 STATE = 'support';
908 DIV.html(_getSpinnerHTML());
909 getOptionsFromServer(reply=>{
910 let newline = '<br>';
911 let div_stats = $('<div/>').html(_getSpinnerHTML());
912
913 _makeGet('getSupportInfos', {}, infos=>{
914 div_stats.html("");
915 div_stats.append('<b>Codes:</b>: '+infos.amount.codes+newline);
916 div_stats.append('<b>Lists:</b>: '+infos.amount.lists+newline);
917 div_stats.append('<b>IPs:</b>: '+infos.amount.ips+newline);
918 });
919
920 let data = reply.options; // options values
921 let versions = reply.versions;
922
923 DIV.html(getBackButtonDiv());
924
925 // zeige support email
926 DIV.append(getUseFulVideosHTML);
927 DIV.append('<h3>BETA Chat bot for fast answers</h3><p>Use our new chat bot to get fast answers. The bot is trained with the FAQ and the documentation. It can help you to find answers faster. The bot is in BETA and we are using ChatGPT for now - you need to login to use it (sorry).</p><p><a class="button button-secondary" href="https://chatgpt.com/g/g-6819d8f68338819193a4be7e7973cce0-event-tickets-support-gpt" target="_blank">Open Chat Bot</a></p>');
928 DIV.append('<h3>Release notes</h3><p>You can find the release notes here: <span class="dashicons dashicons-external"></span><a href="https://vollstart.com/posts/category/eventticketupdates/" target="_blank">Release Notes</a></p>');
929 DIV.append('<h3>Support Email</h3><b>support@vollstart.com</b>');
930 DIV.append('<h3>Support Context Information</h3><p>'+__('Please copy the following information, so that we can support you better and faster. Remove any critical information if needed.', 'event-tickets-with-ticket-scanner')+'</p>');
931 DIV.append('<b>Ticket Counter: </b> '+reply.infos.ticket.counter+newline);
932 DIV.append('<b>Wordpress Version:</b> '+versions.wp+newline);
933 DIV.append('<b>MySQL/Mariadb Version:</b> '+versions.mysql+newline);
934 DIV.append('<b>PHP Version:</b> '+versions.php+newline);
935 DIV.append('<b>Product:</b> Event Tickets with WooCommerce'+newline);
936 DIV.append('<b>Basic Plugin Version:</b> '+versions.basic+newline);
937 DIV.append('<b>Basic DB Version:</b> '+versions.db+newline);
938 if (versions.premium != "") {
939 DIV.append('<b>Premium Serial:</b> '+versions.premium_serial+newline);
940 DIV.append('<b>Premium Plugin Version:</b> '+versions.premium+newline);
941 DIV.append('<b>Premium DB Version:</b> '+versions.premium_db+newline);
942 }
943 DIV.append('<h4 style="margin-bottom:0;">Date</h4>');
944 DIV.append('<b>Your default timezone: </b> '+versions.date_default_timezone+newline);
945 DIV.append('<b>Your WP timezone: </b> '+versions.date_WP_timezone+newline);
946 DIV.append('<b>Your WP timezone full: </b> '+versions.date_WP_timezone_time+newline);
947 DIV.append('<b>Your date: </b> '+versions.date_default_timezone_time+newline);
948 DIV.append('<b>UTC date: </b> '+versions.date_UTC_timezone_time+newline);
949
950 DIV.append('<h4 style="margin-bottom:0;">Stats</h4>');
951 DIV.append(div_stats);
952 DIV.append('<h4 style="margin-bottom:0;">URLs</h4>');
953 DIV.append('<b>Mulitsite: </b> '+reply.infos.site.is_multisite+newline);
954 DIV.append('<b>Home: </b> '+reply.infos.site.home+newline);
955 DIV.append('<b>Network home: </b> '+reply.infos.site.network_home+newline);
956 DIV.append('<b>Site URL: </b> '+reply.infos.site.site_url+newline);
957
958 DIV.append('<h4 style="margin-bottom:0;">Ticket URLs</h4>');
959 //$wcTicketCompatibilityModeURLPath = trim(trim($wcTicketCompatibilityModeURLPath, "/"));
960 DIV.append('<b>Ticket Detail Own URL Path: </b> '+reply.infos.site.home+"/"+_getOptions_getValByKey("wcTicketCompatibilityModeURLPath")+newline);
961 DIV.append('<b>Ticket Scanner Own URL Path: </b> '+reply.infos.site.home+"/"+_getOptions_getValByKey("wcTicketCompatibilityModeURLPath")+'/scanner/'+newline);
962 DIV.append('<b>Ticket Default Plugin Detail URL: </b> '+reply.infos.ticket.ticket_base_url+newline);
963 DIV.append('<b>Ticket Default Plugin Scanner Path: </b> '+reply.infos.ticket.ticket_scanner_path+newline);
964 DIV.append('<b>Ticket Detail Default Plugin Path: </b> '+reply.infos.ticket.ticket_detail_path+newline);
965 DIV.append('<b>Ticket Scanner Default Plugin Path: </b> '+reply.infos.ticket.ticket_detail_path+'scanner/'+newline);
966
967 let tabelle_errorlogs_datatable;
968 DIV.append('<h3 style="margin-bottom:10px;">Error Logs</h3>');
969 $('<div style="text-align:right;margin-bottom:10px;">')
970 .append($('<button>').html(__('Refresh table', 'event-tickets-with-ticket-scanner')).addClass("button-secondary").on("click", ()=>{
971 tabelle_errorlogs_datatable.ajax.reload();
972 }))
973 .append($('<button>').html(__('Empty table', 'event-tickets-with-ticket-scanner')).addClass("sngmbh_btn-delete").on("click", ()=>{
974 LAYOUT.renderYesNo(__('Empty table', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: name of ticket table */__('Do you want to empty the "%s" table? All data will be lost.', 'event-tickets-with-ticket-scanner'), _x("Error Logs", 'title', 'event-tickets-with-ticket-scanner')), ()=>{
975 LAYOUT.renderYesNo(__('Empty table - last chance', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: name of ticket table */__('Are you sure? You will not be able to restore the data, except you have a backup of your database. All data will be lost.', 'event-tickets-with-ticket-scanner'), _x("Error Logs", 'title', 'event-tickets-with-ticket-scanner')), ()=>{
976 _makeGet('emptyTableErrorLogs', null, ()=>{
977 tabelle_errorlogs_datatable.ajax.reload();
978 });
979 });
980 });
981 }))
982 .appendTo(DIV);
983
984 let div_tabelle = $('<div style="margin-bottom:20px;">').appendTo(DIV);
985
986 let label_version = _x('Version', 'label', 'event-tickets-with-ticket-scanner');
987 DIV.append('<h3 style="margin-bottom:10px;">Used Libraries</h3>');
988 DIV.append('<p>'+__('The following libraries are used in the plugin.', 'event-tickets-with-ticket-scanner')+'</p>');
989 DIV.append('<p>'+__('The libraries are used in the frontend and backend.', 'event-tickets-with-ticket-scanner')+'</p>');
990 DIV.append('<ul>');
991 DIV.append('<li><b>jQuery</b> - '+label_version+': '+jQuery.fn.jquery+'</li>');
992 DIV.append('<li><b>jQuery UI</b> - '+label_version+': '+jQuery.ui.version+'</li>');
993 DIV.append('<li><b>jQuery UI CSS</b> - '+label_version+': '+jQuery.ui.version+'</li>');
994 DIV.append('<li><b>PHP TWIG template engine</b> https://twig.symfony.com/ - '+label_version+': 3.22.0</li>');
995 DIV.append('<li><b>PHP QR Code</b> http://sourceforge.net/projects/phpqrcode/ - '+label_version+': 1.1.4</li>');
996 DIV.append('<li><b>FPDI</b> '+label_version+': 2.3.7</li>');
997 DIV.append('<li><b>FPDF</b> '+label_version+': 1.8.5</li>');
998 DIV.append('<li><b>TCPDF</b> http://www.tcpdf.org - '+label_version+': 6.4.4</li>');
999 DIV.append('<li><b>Javascript QR code scanner:</b> https://github.com/nimiq/qr-scanner - '+label_version+': 1.4.2</li>');
1000 DIV.append('<li><b>Javascript Datatable:</b> https://datatables.net/ - '+label_version+': 1.10.21</li>');
1001 DIV.append('<li><b>Javascript Raphael:</b> http://raphaeljs.com/ - '+label_version+': 2.3.0</li>');
1002 DIV.append('<li><b>Javascript Ace Editor</b></li>');
1003 DIV.append('<li><b>html5-qrcode:</b> https://github.com/mebjas/html5-qrcode/ - '+label_version+': 2.3.8</li>');
1004
1005 DIV.append('<h3 style="margin-bottom:10px;">Options</h3>');
1006 // liste alle optionen mit wert auf
1007 data.forEach(v=>{
1008 if (v.type != 'heading' && v.key != "serial") {
1009 if (v.additional && v.additional.doNotRender && v.additional.doNotRender === 1) {}
1010 else {
1011 let value = v.value;
1012 let def = '';
1013 if (value == '') {
1014 def = ' (DEFAULT used)';
1015 value = v.default;
1016 }
1017 text = document.createTextNode(value);
1018 DIV.append(`<b>${v.key}${def}:</b> `).append(text).append(`${newline}`);
1019 }
1020 }
1021 });
1022
1023 /*
1024 DIV.append('<h3 style="margin-bottom:0;">All available Options</h3>');
1025 let list_elem = $('<div>').appendTo(DIV);
1026 data.forEach(v=>{
1027 if (v.type != 'heading' && v.key != "serial" && v.type != "desc") {
1028 if (v.additional && v.additional.doNotRender && v.additional.doNotRender === 1) {}
1029 else {
1030 list_elem.append(v.key);
1031 list_elem.append(' - ');
1032 list_elem.append(v.label);
1033 if (v.desc != "") {
1034 list_elem.append(`${newline}`).append(v.desc);
1035 }
1036 list_elem.append(`${newline}`);
1037 list_elem.append(`${newline}`);
1038 }
1039 } else {
1040 if (v.type == 'heading') {
1041 list_elem.append(`${newline}`);
1042 list_elem.append('== '+v.label+' ==');
1043 if (v.desc != "") {
1044 //list_elem.append(`${newline}`).append(v.desc);
1045 }
1046 list_elem.append(`${newline}`);
1047 }
1048 }
1049 });
1050 */
1051
1052 // helper buttons
1053 $('<button/>').css("margin-top", "30px").addClass("sngmbh_btn-delete").html(_x("Repair tables", 'label', 'event-tickets-with-ticket-scanner')).appendTo(DIV).on("click", ()=>{
1054 LAYOUT.renderYesNo(__('Repair database tables?', 'event-tickets-with-ticket-scanner'), __('Do you realy want to try to repair your database table definitions for the plugin? It should be safe, but only needed in very rare cases. You might see errors messages during the page reload - that is normal. Why not asking support, if you should do it? ;)', 'event-tickets-with-ticket-scanner'), dlg=>{
1055 dlg.html(_getSpinnerHTML());
1056 dlg.dialog({
1057 title:_x('Repaired', 'title', 'event-tickets-with-ticket-scanner'), modal:true, dialogClass: "no-close",
1058 close: function(event, ui){ abort=true; },
1059 buttons: [
1060 {
1061 text: _x('Ok', 'label', 'event-tickets-with-ticket-scanner'),
1062 click: function() {
1063 $( this ).dialog( _x('Close', 'label', 'event-tickets-with-ticket-scanner') );
1064 $( this ).html('');
1065 }
1066 }
1067 ]
1068 });
1069 _makePost('repairTables', {}, result=>{
1070 speakOutLoud(result, true);
1071 dlg.html(result);
1072 });
1073 });
1074 });
1075
1076 function __renderTabelleErrorLogs() {
1077 div_tabelle.html(_getSpinnerHTML());
1078 let table_id = myAjax.divPrefix+'_tabelle_errorlogs';
1079 let tabelle = $('<table/>').attr("id", table_id);
1080 tabelle.html('<thead><tr><th></th><th align="left">'+_x('Created', 'label', 'event-tickets-with-ticket-scanner')+'</th><th align="left">'+_x('Exception', 'label', 'event-tickets-with-ticket-scanner')+'</th><th>'+_x('Function', 'label', 'event-tickets-with-ticket-scanner')+'</th></tr></thead>');
1081 div_tabelle.html(tabelle);
1082
1083 let table = $('#'+table_id);
1084 $(table).DataTable().clear().destroy();
1085 tabelle_errorlogs_datatable = $(table).DataTable({
1086 "responsive": true,
1087 "searching": true,
1088 "ordering": true,
1089 "processing": true,
1090 "serverSide": true,
1091 "stateSave": false,
1092 "pageLength":50,
1093 "ajax": {
1094 url: _requestURL('getErrorLogs'),
1095 type: 'POST',
1096 },
1097 "order": [[ 1, "desc" ]],
1098 "columns":[
1099 {"data":null,"className":'details-control',"orderable":false,"defaultContent":'', "width":10},
1100 {"data":"time", "orderable":true, "width":80},
1101 {"data":"exception_msg", "orderable":true},
1102 {"data":"caller_name", "orderable":true},
1103 ]
1104 });
1105 tabelle.css("width", "100%");
1106 $('#'+table_id+' tbody').on('click', 'td.details-control', e=>{
1107 var tr = $(e.target).parents('tr');
1108 var row = tabelle_errorlogs_datatable.row( tr );
1109 if ( row.child.isShown() ) {
1110 // This row is already open - close it
1111 row.child.hide();
1112 tr.removeClass('shown');
1113 } else {
1114 // Open this row
1115 let d = row.data();
1116 row.child( "#"+d.id+'<br><pre>'+destroy_tags(d.msg)+'</pre>' ).show();
1117 tr.addClass('shown');
1118 }
1119
1120 });
1121 }
1122 __renderTabelleErrorLogs();
1123
1124 });
1125 }
1126
1127 /**
1128 * returns 0 if the versions are the same, 1 if version1 is greater, -1 if version2 is greater
1129 */
1130 function compareVersions(version1, version2) {
1131 const v1 = version1.split('.').map(Number);
1132 const v2 = version2.split('.').map(Number);
1133
1134 for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
1135 const num1 = v1[i] || 0;
1136 const num2 = v2[i] || 0;
1137
1138 if (num1 > num2) return 1;
1139 if (num1 < num2) return -1;
1140 }
1141
1142 /*
1143 // Example usage:
1144 const result = compareVersions('5.8.1', '5.8.2');
1145 if (result > 0) {
1146 console.log('Version 5.8.1 is greater than 5.8.2');
1147 } else if (result < 0) {
1148 console.log('Version 5.8.1 is less than 5.8.2');
1149 } else {
1150 console.log('Both versions are equal');
1151 }
1152 */
1153
1154 return 0;
1155 }
1156
1157 function _displayOptionsArea() {
1158 STATE = 'options';
1159 DIV.html(_getSpinnerHTML());
1160 getOptionsFromServer(reply=>{
1161 let data = reply.options; // options values
1162 let meta_tags_keys = reply.meta_tags_keys;
1163
1164 DIV.html(getBackButtonDiv());
1165
1166 // Create tabs
1167 let tabs = $('<div class="tabs"/>');
1168 let tabOptions = $('<div id="tab-options" class="tab-content"/>');
1169
1170 // Create tab navigation
1171 let tabNav = $('<ul class="tab-nav"/>');
1172 tabNav.append('<li><a href="#tab-options">Options</a></li>');
1173 if (isPremium() && typeof PREMIUM.displayOptionsArea_Templates !== "undefined") {
1174 tabNav.append(PREMIUM.displayOptionsArea_Tab);
1175 }
1176
1177 tabs.append(tabNav);
1178 tabs.append(tabOptions);
1179 if (isPremium() && typeof PREMIUM.displayOptionsArea_Templates !== "undefined") {
1180 tabs.append(PREMIUM.displayOptionsArea_Templates(_getOptions_Versions_getByKey('premium')));
1181 }
1182
1183 // seating plan tab
1184 let tabNavSeatingplan = $('<li><a href="#tab-seatingplan">Seating Plans</a></li>');
1185 tabNavSeatingplan.on("click", ()=>{
1186 tabSeatingplan.html(_displaySeatingplanArea());
1187 });
1188 tabNav.append(tabNavSeatingplan);
1189 let tabSeatingplan = $('<div id="tab-seatingplan" class="tab-content"/>');
1190 tabs.append(tabSeatingplan);
1191
1192 DIV.append(tabs);
1193
1194 // Populate Options tab
1195 let div_options = $('<div/>');
1196 let div_infos = $('<div style="padding-top: 50px;"/>');
1197 let resetOption_div = $('<div class="reset_option_wrap" style="padding-top: 20px;"/>');
1198 tabOptions.append(div_options);
1199 tabOptions.append('<hr>');
1200 tabOptions.append(resetOption_div);
1201 $('<button class="button reset_btn_actn">').html(_x('Reset All Options', 'label', 'event-tickets-with-ticket-scanner'))
1202 .on('click', ()=>{
1203 LAYOUT.renderYesNo(_x('Reset All Options', 'title', 'event-tickets-with-ticket-scanner'), __('Do you really want to reset all the option?', 'event-tickets-with-ticket-scanner'), ()=>{
1204 _makePost('resetOptions','', function(result) {
1205 if(result){
1206 _displayOptionsArea();
1207 }
1208 });
1209 });
1210 }).appendTo(resetOption_div);
1211 tabOptions.append(div_infos);
1212 div_infos.append('<a name="replacementtags"></a><h3>'+_x('Replacement Tags', 'title', 'event-tickets-with-ticket-scanner')+'</h3>').append('<p>'+__('You can use these replacement tags in your text messages and URLs for the meta ticket values', 'event-tickets-with-ticket-scanner')+'</p>');
1213 meta_tags_keys.forEach(v=>{
1214 let t = '<p><b>{'+v.key+'}</b>: '+v.label+'</p>';
1215 div_infos.append(t);
1216 });
1217
1218 //div_options.append('<h3>'+_x('Options', 'title', 'event-tickets-with-ticket-scanner')+'</h3>');
1219 div_options.append('<p><span class="dashicons dashicons-external"></span><a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/" target="_blank">Click here, to visit the documentation.</a></p>');
1220 div_options.append(getUseFulVideosHTML());
1221
1222 let menu_band = $('<div style="padding-top:10px;padding-bottom:15px;">').appendTo(div_options);
1223 let menu_values = [];
1224 data.forEach(v=>{
1225 if (v.type === "heading") {
1226 menu_values.push(v);
1227 }
1228 });
1229 menu_values.sort((a,b)=>{
1230 if(a.label < b.label) { return -1; }
1231 if(a.label > b.label) { return 1; }
1232 return 0;
1233 });
1234 menu_values.forEach(v=>{
1235 $('<a href="#'+v.key+'" style="padding:5px;padding-left:0;margin-right:10px;">').html(v.label).appendTo(menu_band);
1236 });
1237 $('<a href="#topMenu" style="text-decoration:none;position:fixed;bottom:50px;right:10px;background-color:#b225cb;color:white;border-radius:15px;border:1 px solid blue;display:inline-block;padding:10px;">').html('<i class="dashicons dashicons-arrow-up"></i> Top').appendTo(div_options);
1238
1239 // Add jQuery for tab functionality
1240 $('.tab-nav a').on('click', function(e) {
1241 e.preventDefault();
1242 $('.tab-content').hide();
1243 $($(this).attr('href')).show();
1244 $('.tab-nav a').removeClass('active');
1245 $(this).addClass('active');
1246 });
1247
1248 // Show the first tab by default
1249 if (typeof PARAS.subdisplay !== "undefined" && PARAS.subdisplay == 'templates') {
1250 $('.tab-nav a').eq(1).click();
1251 } else {
1252 $('.tab-nav a:first').click();
1253 }
1254
1255 function __createTicketTemplateChooserBox(ticket_template, editor) {
1256 return $('<div style="width:250px;display:inline-block;margin-right:5px;text-align:center;">')
1257 .append('<img style="width:250px;" src="'+myAjax._plugin_home_url+'/img/ticket_templates/'+ticket_template.image_url+'">')
1258 .append("<br>Zero-Padding: "+(ticket_template.wcTicketPDFZeroMarginTest ? "Yes" : "No"))
1259 .append(", Size: ("+ticket_template.wcTicketSizeWidthTest+'x'+ticket_template.wcTicketSizeHeightTest+")")
1260 .append("<br>")
1261 .append($('<button class="button button-primary">').text(__('Load template','event-tickets-with-ticket-scanner')).on("click", ()=>{
1262 LAYOUT.renderYesNo(_x('Load Template Ticket Code', 'title', 'event-tickets-with-ticket-scanner'),
1263 __('Do you want to replace the test ticket template code with this template?', 'event-tickets-with-ticket-scanner')+'<br><p><img style="width:250px;" src="'+myAjax._plugin_home_url+'/img/ticket_templates/'+ticket_template.image_url+'"></p><p>Following values will be changed:'
1264 +'<br><b>wcTicketPDFZeroMarginTest</b>: '+(ticket_template.wcTicketPDFZeroMarginTest ? "Yes" : "No")
1265 +'<br><b>wcTicketPDFisRTLTest</b>: '+(ticket_template.wcTicketPDFisRTLTest ? "Yes" : "No")
1266 +'<br><b>wcTicketSizeWidthTest</b>: '+ticket_template.wcTicketSizeWidthTest
1267 +'<br><b>wcTicketSizeHeightTest</b>: '+ticket_template.wcTicketSizeHeightTest
1268 +'<br><b>wcTicketQRSizeTest</b>: '+ticket_template.wcTicketQRSizeTest
1269 +'</p>'
1270 , ()=>{
1271 editor.wcTicketDesignerTemplateTest_editor.setValue(ticket_template.wcTicketDesignerTemplateTest);
1272 $('input[data-key="wcTicketPDFZeroMarginTest"').prop("checked",ticket_template.wcTicketPDFZeroMarginTest).trigger("change");
1273 $('input[data-key="wcTicketPDFisRTLTest"').prop("checked",ticket_template.wcTicketPDFisRTLTest).trigger("change");
1274 $('input[data-key="wcTicketSizeWidthTest"').val(ticket_template.wcTicketSizeWidthTest).trigger("change");
1275 $('input[data-key="wcTicketSizeHeightTest"').val(ticket_template.wcTicketSizeHeightTest).trigger("change");
1276 $('input[data-key="wcTicketQRSizeTest"').val(ticket_template.wcTicketQRSizeTest).trigger("change");
1277 let value = editor.wcTicketDesignerTemplateTest_editor.getValue().trim();
1278 _saveOptionValue("wcTicketDesignerTemplateTest", value);
1279 editor.wcTicketDesignerTemplateTest_btn.prop("disabled", true);
1280
1281 });
1282 }));
1283 }
1284
1285 // render die input felder
1286 function __getOptionByKey(key) {
1287 for(let a=0;a<data.length;a++) {
1288 if (key == data[a].key) return data[a];
1289 }
1290 return null;
1291 }
1292
1293 let editor = {}; // for ace editor
1294 data.forEach(v=>{
1295 if (typeof v.additional !== "undefined" && v.additional.doNotRender) return;
1296 if (v.type == "heading") {
1297 let desc = v.desc;
1298 if (typeof v._doc_video !== "undefined" && v._doc_video != "") {
1299 desc += ' <span class="dashicons dashicons-external"></span> <a href="'+v._doc_video+'" target="_blank">Video Help</a>';
1300 }
1301 div_options.append('<hr>').append('<h3 id="'+v.key+'" '+(desc !== "" ? ' style="margin-bottom:0;"' : '')+'>'+v.label+'</h3>').append(desc !== "" ? '<div style="margin-bottom:15px;"><i>'+desc+'</i></div>':'');
1302 } else if (v.type =="desc") {
1303 let desc = v.desc+" ";
1304 if (typeof v._do_not_trim !== "undefined" && v._do_not_trim) {
1305 desc += 'To leave this value blank, enter a space. ';
1306 }
1307 if (typeof v._doc_video !== "undefined" && v._doc_video != "") {
1308 desc += '<span class="dashicons dashicons-external"></span> <a href="'+v._doc_video+'" target="_blank">Video Help</a>';
1309 }
1310 div_options.append('<div/>').css({"margin-bottom": "15px","margin-right": "15px"}).append('<b>'+v.label+'</b><br>'+desc+"<br>");
1311 } else {
1312 let elem_div = $('<div/>').css({"margin-bottom": "15px","margin-right": "15px"});
1313 let elem_input = $('<input type="'+v.type+'">');
1314 elem_input.attr("placeholder", v.default);
1315 if (typeof v.additional !== "undefined" && typeof v.additional.disabled !== "undefined") {
1316 elem_input.attr("disabled", true);
1317 }
1318
1319 let cbf = null;
1320 let pcbf = null;
1321 let value = v.value;
1322 if (typeof v._do_not_trim !== "undefined" && v._do_not_trim) {
1323 } else {
1324 value = (""+v.value) !== "" ? (""+v.value).trim() : ""+v.default;
1325 }
1326
1327 v.label = v.label + ' <span style="color:grey;">{'+v.key+'}</span>';
1328 if (typeof v._doc_video !== "undefined" && v._doc_video != "") {
1329 v.label += ' <span class="dashicons dashicons-external"></span> <a href="'+v._doc_video+'" target="_blank">Video Help</a>';
1330 }
1331
1332 switch (v.type) {
1333 case "editor":
1334 elem_input = $('<div id="'+v.key+'_editor" style="height:'+(typeof v.additional !== "undefined" && typeof v.additional.height !== "undefined" ? v.additional.height : '500px')+';">').text(value.trim());
1335 break;
1336 case "textarea":
1337 elem_input = $('<textarea>');
1338 elem_input.attr("placeholder", v.default);
1339 //elem_input.val(value);
1340 elem_input.val(value);
1341 if (typeof v.additional !== "undefined" && typeof v.additional.rows !== "undefined") {
1342 elem_input.attr("rows", v.additional.rows);
1343 }
1344 break;
1345 case "checkbox":
1346 v.value = intval(v.value);
1347 elem_input.prop("checked",v.value === 1 ? true : false);
1348 elem_input.on("change", function(){
1349 _makePost('changeOption', {'key':v.key, 'value':elem_input[0].checked ? 1:0});
1350 });
1351 elem_div.html(elem_input).append(v.label).append(v.desc !== "" ? '<br><i>'+v.desc+'</i>':'');
1352 break;
1353 case "number":
1354 if (typeof v.additional.min !== "undefined") elem_input.attr("min", v.additional.min);
1355 break;
1356 case "dropdown":
1357 elem_input = $('<select>');
1358 if (v.additional.multiple) {
1359 elem_input.prop("multiple", true);
1360 }
1361 v.additional.values.forEach(_v=>{
1362 $('<option>').attr("value", _v.value).html(_v.label).appendTo(elem_input);
1363 });
1364 if (v.additional.multiple) {
1365 if (v.value.length == 0) {
1366 value = v.default;
1367 } else {
1368 value = v.value;
1369 }
1370 } else {
1371 if (value == "") value = 1;
1372 }
1373 elem_input.val(value);
1374 break;
1375 case "media":
1376 let image_info = $('<div>');
1377 let image = $('<image style="display:none;">');
1378 let image_btn_del = $('<button class="sngmbh_btn sngmbh_btn-delete" style="display:none;">').html(_x('Remove file', 'label', 'event-tickets-with-ticket-scanner'));
1379 image_btn_del.on('click', ()=>{
1380 LAYOUT.renderYesNo(_x('Remove file', 'title', 'event-tickets-with-ticket-scanner'), __('Do you really want to remove the file information from this option?', 'event-tickets-with-ticket-scanner'), ()=>{
1381 elem_input.val("");
1382 elem_input.trigger("change");
1383 _renderMedia(0, v, image_info, image, image_btn_del);
1384 });
1385 });
1386 if (typeof v.additional == "undefined") v.additional = {};
1387 if (v.additional.max) {
1388 if (v.additional.max.width) {
1389 image.css("max-width", v.additional.max.width+'px');
1390 }
1391 if (v.additional.max.height) {
1392 image.css("max-height", v.additional.max.height+'px');
1393 }
1394 }
1395 elem_input.attr("type", "hidden");
1396 let image_btn_add = $('<button style="display:block;" />').addClass("button-primary")
1397 .html(v.additional.button)
1398 .on("click", ()=>{
1399 let is_multiple = typeof v.additional.is_multiple != "undefined" ? v.additional.is_multiple : false;
1400 let imgContainer = null;
1401 let type_filter = typeof v.additional.type_filter != "undefined" ? v.additional.type_filter : null;
1402 _openMediaChooser(elem_input, is_multiple, imgContainer, type_filter);
1403 });
1404 $('<div/>').css({"margin-bottom": "15px","margin-right": "15px"})
1405 .html(v.label+'<br>')
1406 .append(image_btn_add)
1407 .append(v.desc !== "" ? '<i>'+v.desc+'</i>':'')
1408 .append(elem_input)
1409 .append(image_info)
1410 .append(image)
1411 .append(image_btn_del)
1412 .appendTo(elem_div);
1413 _renderMedia(value, v, image_info, image, image_btn_del);
1414 pcbf = function() {
1415 image_info.html(_getSpinnerHTML());
1416 image.css('display', 'none');
1417 }
1418 cbf = function () {
1419 let value = elem_input.val();
1420 _renderMedia(value, v, image_info, image, image_btn_del);
1421 }
1422 break;
1423 }
1424
1425 if (v.type != "checkbox") {
1426 if (v.type != "media") {
1427 elem_div.html(v.label+'<br>').append(elem_input);
1428 let desc = v.desc+" ";
1429 if (typeof v._do_not_trim !== "undefined" && v._do_not_trim) {
1430 desc += 'To leave this value blank, enter a space. ';
1431 }
1432 desc = desc.trim();
1433 elem_div.append(desc !== "" ? '<br><i>'+desc+'</i>':'');
1434 }
1435 if (v.type != "number") {
1436 elem_input.css({"width":"90%"});
1437 }
1438 if (v.type != "dropdown" && v.type != "editor") {
1439 elem_input.attr("value",value);
1440 }
1441 if (v.type != "editor") {
1442 elem_input.on("change", ()=>{
1443 let value = elem_input.val();
1444 _saveOptionValue(v.key, value, cbf, pcbf);
1445 });
1446 }
1447 }
1448
1449 elem_input.attr("data-key", v.key);
1450
1451 if (v.key == "wcassignmentUseGlobalSerialFormatter") {
1452 let option = __getOptionByKey('wcassignmentUseGlobalSerialFormatter_values');
1453 let formatterValues = null;
1454 if (option.value != "") {
1455 try {
1456 formatterValues = JSON.parse(option.value);
1457 } catch (e) {
1458 //console.log(e);
1459 }
1460 }
1461 let extra_div = $('<div>').appendTo(elem_div).css("margin-top", "10px").css("margin-left", "50px").css("padding", "10px").css("border", "1px solid black");
1462 // render here den formatter
1463 let serialCodeFormatter = _form_fields_serial_format(extra_div);
1464 serialCodeFormatter.setNoNumberOptions();
1465 serialCodeFormatter.setFormatterValues(formatterValues);
1466 serialCodeFormatter.setCallbackHandle(_formatterValues=>{
1467 // speicher formatterValues
1468 _makePost('changeOption', {'key':'wcassignmentUseGlobalSerialFormatter_values', 'value':JSON.stringify(_formatterValues)});
1469 });
1470 serialCodeFormatter.render();
1471 }
1472
1473 if (v.key == "wcTicketDesignerTemplate") {
1474 $('<button class="button button-primary">').html("Show Default Template").on("click", e=>{
1475 LAYOUT.renderInfoBox(_x('Ticket Default Template', 'title', 'event-tickets-with-ticket-scanner'), $('<textarea style="width:100%;height:400px">').val(v.default));
1476 }).appendTo(div_options);
1477 }
1478
1479 if (v.type == "editor") {
1480 //https://ace.c9.io/#nav=howto
1481 let btn_group = $('<div>').prependTo(elem_div);
1482 editor[v.key+"_editor"] = null; // will be filled later
1483 editor[v.key+"_btn"] = $('<button class="button button-primary">').prop("disabled", true).html(_x('Save Template Code', 'title', 'event-tickets-with-ticket-scanner')).on("click", evt=>{
1484 let value = editor[v.key+"_editor"].getValue().trim();
1485 _saveOptionValue(v.key, value, cbf, pcbf);
1486 editor[v.key+"_btn"].prop("disabled", true);
1487 }).appendTo(btn_group);
1488 $('<button class="button button-danger">').html(_x('Copy Template Code To Live Code', 'title', 'event-tickets-with-ticket-scanner')).on("click", evt=>{
1489 LAYOUT.renderYesNo(_x('Replace Live Template Code', 'title', 'event-tickets-with-ticket-scanner'), __('Do you want to replace the live template code with the template code from the test?', 'event-tickets-with-ticket-scanner'), ()=>{
1490 let value = editor[v.key+"_editor"].getValue().trim();
1491 $('input[data-key="'+v.key.replace("Test", "")+'"').val(value).trigger("change");
1492 if (v.key == "wcTicketDesignerTemplateTest") {
1493 $('input[data-key="wcTicketPDFZeroMargin"').prop("checked",$('input[data-key="wcTicketPDFZeroMarginTest"').is(':checked')).trigger("change");
1494 $('input[data-key="wcTicketPDFisRTL"').prop("checked",$('input[data-key="wcTicketPDFisRTLTest"').is(':checked')).trigger("change");
1495 $('input[data-key="wcTicketSizeWidth"').val($('input[data-key="wcTicketSizeWidthTest"').val()).trigger("change");
1496 $('input[data-key="wcTicketSizeHeight"').val($('input[data-key="wcTicketSizeHeightTest"').val()).trigger("change");
1497 $('input[data-key="wcTicketQRSize"').val($('input[data-key="wcTicketQRSizeTest"').val()).trigger("change");
1498 }
1499 });
1500 }).appendTo(btn_group);
1501
1502 if (v.key == "wcTicketDesignerTemplateTest") {
1503 let ticket_test_chooser = $('<div>');
1504 let ticket_template_chooser = $('<div style="padding-top:5px;padding-bottom:20px;">').html('<b>Templates</b><br>You can choose from the templates below to have a starting point.<br>').appendTo(ticket_test_chooser);
1505 let ticket_test_select = $('<select>').appendTo(ticket_test_chooser);
1506 let ticket_test_direct_input = $('<input type="text" style="width:180px;" placeholder="or enter a public ticket number">');
1507 // display the template thumbnails
1508 for(let a=0;a<reply.ticket_templates.length;a++) {
1509 let ticket_template = reply.ticket_templates[a];
1510 __createTicketTemplateChooserBox(ticket_template, editor).appendTo(ticket_template_chooser);
1511 }
1512
1513 if (OPTIONS.tickets_for_testing.length > 0) {
1514 let option_values = [];
1515 for(let a=0;a<OPTIONS.tickets_for_testing.length;a++) {
1516 let ticket = OPTIONS.tickets_for_testing[a];
1517 let metaObj = null;
1518 try {
1519 metaObj = JSON.parse(ticket.meta);
1520 } catch(e) {}
1521 if (metaObj != null) {
1522 option_values.push({t:ticket, m:metaObj});
1523 }
1524 }
1525 if (option_values.length > 0) {
1526 for(let a=0;a<option_values.length;a++) {
1527 let item = option_values[a];
1528 $('<option value="'+item.m.wc_ticket._public_ticket_id+'">')
1529 .text("Order Id: "+item.t.order_id+" - "+item.m.wc_ticket._public_ticket_id+" - "+item.t._PRODUCT_NAME+" (#"+item.m.woocommerce.product_id+")")
1530 .attr("data-url-pdf", item.m.wc_ticket._url)
1531 .appendTo(ticket_test_select);
1532 }
1533 ticket_test_direct_input.appendTo(ticket_test_chooser);
1534 $('<button class="button button-primary" id="wcTicketDesignerTemplateTest_button_PDF">')
1535 .html(__('Preview Test Template Code as PDF', 'event-tickets-with-ticket-scanner')).
1536 appendTo(ticket_test_chooser).on("click", ()=>{
1537 let ticket_url = ticket_test_select.find(":selected").attr("data-url-pdf");
1538 let v = ticket_test_direct_input.val().trim();
1539 if (v != "") {
1540 ticket_url = reply.infos.ticket.ticket_base_url + v; // myAjax.ticket_base_url
1541 }
1542 iframe.attr("src", ticket_url+'?pdf&testDesigner=1&t='+time()+'&nonce='+DATA.nonce);
1543 iframe
1544 .css("width", "80%")
1545 .css("height", "500px")
1546 .css("margin-top", "10px")
1547 .css("display", "block");
1548 });
1549 let iframe = $('<iframe style="display:none;">').appendTo(ticket_test_chooser);
1550 } else {
1551 $('<option value="">').text(__("ticket cannot be used. Public Ticket Id missing.",'event-tickets-with-ticket-scanner')).appendTo(ticket_test_select);
1552 }
1553 } else {
1554 $('<option value="">').text(__("no ticket for preview available", 'event-tickets-with-ticket-scanner')).appendTo(ticket_test_select);
1555 }
1556 ticket_test_chooser.appendTo(elem_div);
1557 }
1558 }
1559
1560 elem_div.appendTo(div_options);
1561 }
1562 });
1563 if (window.location.hash != "") {
1564 window.setTimeout(()=>{
1565 let h = window.location.hash;
1566 window.location.hash = "";
1567 window.location.hash = h;
1568 }, 250);
1569 }
1570 window.setTimeout(()=>{
1571 for(var k in editor) {
1572 if (k.substring(k.length -7) == "_editor") {
1573 editor[k] = ace.edit(k);
1574 //editor.wcTicketDesignerTemplateTest_editor.setTheme("ace/theme/monokai");
1575 editor[k].session.setMode("ace/mode/twig");
1576 editor[k].setShowPrintMargin(false);
1577 editor[k].commands.addCommand({name:'save', bindKey:{win:'Ctrl-S', mac:'Command-S'}, readOnly:false, exec:myEditor=>{
1578 myEditor.trigger("change");
1579 }});
1580 editor[k].session.on("change", delta=>{
1581 editor[k.replace("_editor", "_btn")].prop("disabled", false);
1582 });
1583 }
1584 }
1585 }, 250)
1586
1587 });
1588 }
1589
1590 function getSuffixFromFilename(filename) {
1591 let extension = filename.slice(filename.lastIndexOf('.') + 1);
1592 return extension;
1593 }
1594 function _renderMedia(mediaId, v, image_info, image, image_btn_del) {
1595 if (mediaId != "" && parseInt(mediaId) != 0) {
1596 _getMediaData(mediaId, data=>{
1597 let suffix = getSuffixFromFilename(data.url.replace(/^.*[\\\/]/,'')).toLowerCase();
1598 let info = suffix != "pdf" ? '('+data.meta.width+'x'+data.meta.height+')' : '';
1599 image_info.html('<b>'+_x('Title', 'title', 'event-tickets-with-ticket-scanner')+':</b> '+data.title+' '+info);
1600 if (v.additional.max && v.additional.msg_error_max) {
1601 if (v.additional.max.width && v.additional.msg_error_max.width && data.meta.width > v.additional.max.width) image_info.append('<div style="color:red;">'+v.additional.msg_error_max.width+'</div>');
1602 if (v.additional.max.height && v.additional.msg_error_max.height && data.meta.height > v.additional.max.height) image_info.append('<div style="color:red;">'+v.additional.msg_error_max.height+'</div>');
1603 }
1604 if (suffix != "pdf") {
1605 image.attr("src", data.url).css("display","block");
1606 }
1607 image_btn_del.css("display", "block");
1608 });
1609 } else {
1610 image_info.html("");
1611 image.css("display", "none");
1612 image_btn_del.css("display", "none");
1613 }
1614 }
1615 function _openMediaChooser(input_elem, multiple, imgContainer, typeFilter) {
1616 var image_frame;
1617 if(image_frame){
1618 image_frame.open();
1619 }
1620 if (!typeFilter) typeFilter = 'image';
1621 multiple ? multiple = true : multiple = false;
1622 // Define image_frame as wp.media object
1623 image_frame = wp.media({
1624 title: _x('Select Media', 'title', 'event-tickets-with-ticket-scanner'),
1625 multiple : multiple,
1626 library : {
1627 type : typeFilter,
1628 }
1629 });
1630
1631 image_frame.on('close',function() {
1632 // On close, get selections and save to the hidden input
1633 // plus other AJAX stuff to refresh the image preview
1634 var selection = image_frame.state().get('selection');
1635
1636 if (imgContainer) { // zeige erstes bild an
1637 var attachment = selection.first().toJSON();
1638 imgContainer.html( '<img src="'+attachment.url+'" style="max-width:100%;"/>' );
1639 }
1640
1641 var gallery_ids = new Array();
1642 var my_index = 0;
1643 selection.each(function(attachment) {
1644 gallery_ids[my_index] = attachment['id'];
1645 my_index++;
1646 });
1647 var ids = gallery_ids.join(",");
1648 input_elem.val(ids);
1649 input_elem.trigger("change");
1650 });
1651
1652 image_frame.on('open',function() {
1653 // On open, get the id from the hidden input
1654 // and select the appropiate images in the media manager
1655 var selection = image_frame.state().get('selection');
1656 var ids = input_elem.val().split(',');
1657 ids.forEach(function(id) {
1658 var attachment = wp.media.attachment(id);
1659 attachment.fetch();
1660 selection.add( attachment ? [ attachment ] : [] );
1661 });
1662 });
1663 image_frame.open();
1664 } // ende openmediachooser
1665
1666 function getBackButtonDiv() {
1667 let div_buttons = $('<div class="event-tickets-with-ticket-scanner-topbar">');
1668 let div = $('<div/>').addClass("event-tickets-with-ticket-scanner-topbar-left").append(
1669 $('<button />')
1670 .addClass("event-tickets-with-ticket-scanner-back-btn")
1671 .html('<span class="event-tickets-with-ticket-scanner-back-icon">&lt;</span> ' + _x('Back', 'label', 'event-tickets-with-ticket-scanner'))
1672 .on("click", ()=>{ LAYOUT.renderAdminPageLayout(); }
1673 )
1674 );
1675 div_buttons.append(div);
1676 div_buttons.append(_displaySettingAreaButton());
1677 return div_buttons;
1678 }
1679
1680 function _getTicketScannerURL() {
1681 let url = _getOptions_Infos_getByKey('ticket').ticket_scanner_path;
1682 let _urlpath = _getOptions_getValByKey("wcTicketCompatibilityModeURLPath");
1683 if (_urlpath != "") {
1684 url = OPTIONS.infos.site.home+"/"+_urlpath+'/scanner/?code=';
1685 } else {
1686 url = OPTIONS.infos.ticket.ticket_scanner_url;
1687 }
1688 return url;
1689 }
1690 function _displaySettingAreaButton() {
1691 let btn_grp = $('<nav id="topMenu"/>')
1692 .addClass("event-tickets-with-ticket-scanner-topmenu")
1693 .attr("aria-label", "Event Tickets navigation");
1694 $('<button/>')
1695 .addClass('event-tickets-with-ticket-scanner-topmenu-item')
1696 .toggleClass('event-tickets-with-ticket-scanner-topmenu-item-active', STATE === 'support')
1697 .html(_x("Support Info", 'label', 'event-tickets-with-ticket-scanner'))
1698 .on("click", () => {
1699 _displaySupportInfoArea();
1700 })
1701 .appendTo(btn_grp);
1702 $('<button/>')
1703 .addClass("event-tickets-with-ticket-scanner-topmenu-item")
1704 .toggleClass('event-tickets-with-ticket-scanner-topmenu-item-active', STATE === 'faq')
1705 .html(_x("FAQ", 'label', 'event-tickets-with-ticket-scanner'))
1706 .on("click", ()=>{
1707 _displayFAQArea();
1708 }).appendTo(btn_grp);
1709 //if (_getOptions_Versions_isActivatedByKey('is_wc_available')) {
1710 $('<button/>').addClass("event-tickets-with-ticket-scanner-topmenu-item").html(_x("Ticket Scanner", 'label', 'event-tickets-with-ticket-scanner'))
1711 .on("click", ()=>{
1712 let url = _getTicketScannerURL();
1713 window.open(url, 'ticketscanner');
1714 })
1715 .appendTo(btn_grp);
1716 //}
1717 $('<button/>')
1718 .addClass("event-tickets-with-ticket-scanner-topmenu-item")
1719 .toggleClass('event-tickets-with-ticket-scanner-topmenu-item-active', STATE === 'authtokens')
1720 .html(_x('Auth Token', 'label', 'event-tickets-with-ticket-scanner'))
1721 .on("click", ()=>{
1722 _displayAuthTokensArea();
1723 }).appendTo(btn_grp);
1724 $('<button/>')
1725 .addClass("event-tickets-with-ticket-scanner-topmenu-item")
1726 .toggleClass('event-tickets-with-ticket-scanner-topmenu-item-active', STATE === 'options')
1727 .html(_x('Options', 'label', 'event-tickets-with-ticket-scanner'))
1728 .on("click", ()=>{
1729 _displayOptionsArea();
1730 }).appendTo(btn_grp);
1731
1732 if (isPremium()) {
1733 btn_grp = PREMIUM.displaySettingAreaButton(btn_grp);
1734 }
1735 return btn_grp;
1736 }
1737
1738 function _form_fields_serial_format(appendToDiv) {
1739 let input_prefix_codes;
1740 let input_type_codes;
1741 let input_amount_letters;
1742 let input_letter_excl;
1743 let input_letter_style;
1744 let input_include_numbers;
1745 let input_serial_delimiter;
1746 let input_serial_delimiter_space;
1747 let input_number_start;
1748 let input_number_offset;
1749
1750 let noNumbersOptions = false;
1751 let cbk = null;
1752 let formatterValues;
1753
1754 function _setNoNumberOptions() {
1755 noNumbersOptions = true;
1756 }
1757 function _setCallbackHandle(_cbk) {
1758 cbk = _cbk;
1759 }
1760 function _callCallbackHandle() {
1761 cbk && cbk(_getFormatterValues());
1762 }
1763 function _setFormatterValues(values) {
1764 formatterValues = values;
1765 }
1766
1767 function __render() {
1768 $('<br>').appendTo(appendToDiv);
1769 // prefix
1770 let div_prefix_codes = _createDivInput(_x("Enter a prefix (optional)", 'label', 'event-tickets-with-ticket-scanner')).appendTo(appendToDiv);
1771 input_prefix_codes = $('<input type="text">').appendTo(div_prefix_codes);
1772 $('<div>').html(__('You can use date placeholder to have the prefix filled with the date of the confirmed purchase.', 'event-tickets-with-ticket-scanner')+'<br>'+__('You can use: {Y} = year, {m} = month, {d} = day, {H} = hour, {i} = minutes, {s} = seconds, {TIMESTAMP} = unix timestamp.', 'event-tickets-with-ticket-scanner')).appendTo(div_prefix_codes);
1773 if (formatterValues && formatterValues['input_prefix_codes'] != null) input_prefix_codes.val(formatterValues['input_prefix_codes']);
1774 input_prefix_codes.on("change", ()=>{
1775 _callCallbackHandle();
1776 });
1777 // type numbers/serials
1778 let div_type_codes = _createDivInput(_x("Choose type of ticket numbers", 'label', 'event-tickets-with-ticket-scanner')).appendTo(appendToDiv);
1779 input_type_codes = $('<select><option value="1" selected>'+_x('Serials', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2">'+_x('Numbers', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select>').appendTo(div_type_codes);
1780 if (formatterValues && formatterValues['input_type_codes'] != null) input_type_codes.val(formatterValues['input_type_codes']);
1781
1782 if (noNumbersOptions) {
1783 input_type_codes.prop("disabled", true);
1784 }
1785 input_type_codes.on("change", function() {
1786 if (input_type_codes.val() === "2") {
1787 div_serials && div_serials.find("input").prop("disabled", true);
1788 div_serials && div_serials.find("select").prop("disabled", true);
1789 div_numbers && div_numbers.find("input").prop("disabled", false);
1790 div_numbers && div_numbers.find("select").prop("disabled", false);
1791 } else {
1792 div_serials && div_serials.find("input").prop("disabled", false);
1793 div_serials && div_serials.find("select").prop("disabled", false);
1794 div_numbers && div_numbers.find("input").prop("disabled", true);
1795 div_numbers && div_numbers.find("select").prop("disabled", true);
1796 }
1797 _callCallbackHandle();
1798 });
1799 // serials options
1800 let div_serials = $('<div>').html('<h4>'+_x('Serials options', 'title', 'event-tickets-with-ticket-scanner')+'</h4>').appendTo(appendToDiv);
1801 // anzahl letters
1802 let div_amount_letters = _createDivInput(_x('Amount of letter needed', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1803 input_amount_letters = $('<input type="number" required value="21" min="1" max="30">').appendTo(div_amount_letters);
1804 if (formatterValues && formatterValues['input_amount_letters'] != null) input_amount_letters.val(formatterValues['input_amount_letters']);
1805 input_amount_letters.on("change", function(){
1806 input_serial_delimiter.trigger("change");
1807 _callCallbackHandle();
1808 });
1809 // select letter exclusion
1810 let div_letter_excl = _createDivInput(_x('Letter exclusion', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1811 input_letter_excl = $('<select><option value="1">'+_x('None', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2" selected>i,l,o,p,q</option></select>').appendTo(div_letter_excl);
1812 if (formatterValues && formatterValues['input_letter_excl'] != null) input_letter_excl.val(formatterValues['input_letter_excl']);
1813 input_letter_excl.on("change", ()=>{
1814 _callCallbackHandle();
1815 });
1816 // radio button text gross/klein/both/none
1817 let div_letter_style = _createDivInput(_x('Letter style', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1818 input_letter_style = $('<select><option value="1" selected>'+_x('Uppercase', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2">'+_x('Lowercase', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="3">'+_x('Both', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select>').appendTo(div_letter_style);
1819 if (formatterValues && formatterValues['input_letter_style'] != null) input_letter_style.val(formatterValues['input_letter_style']);
1820 input_letter_style.on("change", ()=>{
1821 _callCallbackHandle();
1822 });
1823 // radio button numbers/none
1824 let div_include_numbers = _createDivInput(_x('Numbers needed?', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1825 input_include_numbers = $('<select><option value="1">'+_x('No', 'label', 'event-tickets-with-ticket-scanner')+'</option><option value="2" selected>'+_x('Yes', 'label', 'event-tickets-with-ticket-scanner')+'</option><option value="3">'+_x('Only numbers', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select>').appendTo(div_include_numbers);
1826 if (formatterValues && formatterValues['input_include_numbers'] != null) input_include_numbers.val(formatterValues['input_include_numbers']);
1827 input_include_numbers.on("change", ()=>{
1828 _callCallbackHandle();
1829 });
1830 // select delimiter none/-/./space
1831 let div_serial_delimiter = _createDivInput(_x('Delimiter?', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1832 input_serial_delimiter = $('<select><option value="1">'+_x('None', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2" selected>-</option><option value="4">:</option><option value="3">'+_x('Space', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select>').appendTo(div_serial_delimiter);
1833 if (formatterValues && formatterValues['input_serial_delimiter'] != null) input_serial_delimiter.val(formatterValues['input_serial_delimiter']);
1834 function __refreshDelimiterSpace() {
1835 input_serial_delimiter_space.html("");
1836 if (input_serial_delimiter.val() !== "1") {
1837 let anzahl = parseInt(input_amount_letters.val(),10);
1838 if (anzahl > 0) {
1839 for(let a=1;a<anzahl;a++) input_serial_delimiter_space.append($('<option'+(anzahl > 2 && a === 7 ? " selected": "")+'>').attr("value",a).html(a));
1840 }
1841 }
1842 }
1843 input_serial_delimiter.on("change", function(){
1844 __refreshDelimiterSpace();
1845 _callCallbackHandle();
1846 });
1847 // choose delimiter space
1848 let div_serial_delimiter_space = _createDivInput(_x('After how many letters?', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_serials);
1849 input_serial_delimiter_space = $('<select></select>').appendTo(div_serial_delimiter_space);
1850 if (formatterValues && formatterValues['input_serial_delimiter'] != null) {
1851 // setze Werte erstmal ein
1852 __refreshDelimiterSpace();
1853 }
1854 if (formatterValues && formatterValues['input_serial_delimiter_space'] != null) input_serial_delimiter_space.val(formatterValues['input_serial_delimiter_space']);
1855 input_serial_delimiter_space.on("change", ()=>{
1856 _callCallbackHandle();
1857 });
1858 // numbers options
1859 let div_numbers = $('<div>').html('<h4>'+_x('Numbers options', 'title', 'event-tickets-with-ticket-scanner')+'</h4>').appendTo(appendToDiv);
1860 if (noNumbersOptions) div_numbers.css("display","none");
1861 // number start
1862 let div_number_start = _createDivInput(_x('Start number', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_numbers);
1863 input_number_start = $('<input type="number" disabled required value="10000" min="1">').appendTo(div_number_start);
1864 if (formatterValues && formatterValues['input_number_start'] != null) input_number_start.val(formatterValues['input_number_start']);
1865 input_number_start.on("change", ()=>{
1866 _callCallbackHandle();
1867 });
1868 // number offset
1869 let div_number_offset = _createDivInput(_x('Offset for each number', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_numbers);
1870 input_number_offset = $('<input type="number" disabled required value="1" min="1">').appendTo(div_number_offset);
1871 if (formatterValues && formatterValues['input_number_offset'] != null) input_number_offset.val(formatterValues['input_number_offset']);
1872 input_number_offset.on("change", ()=>{
1873 _callCallbackHandle();
1874 });
1875 }
1876
1877 function __generateCode(length, cases, withnumbers, exclusion) {
1878 let charset = 'abcdefghijklmnopqrstuvwxyz';
1879 if (cases === 1) charset = charset.toUpperCase();
1880 if (cases === 3) charset += charset.toUpperCase();
1881 if (withnumbers === 2) charset += '0123456789';
1882 if (withnumbers === 3) charset = '0123456789';
1883 if (typeof exclusion !== "undefined") {
1884 exclusion.forEach(function(v){
1885 let regex = new RegExp(v, 'gi');
1886 charset = charset.replace(regex, "");
1887 });
1888 }
1889 let retVal = "";
1890 for (var i = 0, n = charset.length; i < length; ++i) {
1891 retVal += charset.charAt(Math.floor(Math.random() * n));
1892 }
1893 return retVal;
1894 }
1895 function __insertSeperator(str, serial_delimiter, serial_delimiter_space) {
1896 if (str !== "" && serial_delimiter !== "" && serial_delimiter_space > 0) {
1897 let result = [str[0]];
1898 for(let x=1; x<str.length; x++) {
1899 if (x%serial_delimiter_space === 0) {
1900 result.push(serial_delimiter, str[x]);
1901 } else {
1902 result.push(str[x]);
1903 }
1904 }
1905 return result.join('');
1906 }
1907 return str;
1908 }
1909
1910 function _isTypeNumbers() {
1911 return input_type_codes.val() === "2";
1912 }
1913 function _getPrefix() {
1914 return input_prefix_codes.val().trim();
1915 }
1916 function _getAmountLetters() {
1917 let amount_letters = parseInt(input_amount_letters.val().trim(),10);
1918 if (isNaN(amount_letters) || amount_letters < 1) {
1919 input_amount_letters.select();
1920 return alert(__("Amount of letters has to be higher", 'event-tickets-with-ticket-scanner'));
1921 }
1922 return amount_letters;
1923 }
1924 function _getLetterExclusion() {
1925 return input_letter_excl.val() === "2" ? ['i','l','o','p','q'] : [];
1926 }
1927 function _getLetterStyle() {
1928 return parseInt(input_letter_style.val(),10);
1929 }
1930 function _getIncludeNumbers() {
1931 return parseInt(input_include_numbers.val(),10);
1932 }
1933 function _getSerialDelimiter() {
1934 return ['','-',' ',':'][parseInt(input_serial_delimiter.val(),10)-1];
1935 }
1936 function _getSerialDelimiterSpace() {
1937 let serial_delimiter_space = 0;
1938 try {
1939 serial_delimiter_space = _getSerialDelimiter() !== "" ? parseInt(input_serial_delimiter_space.val(),10) : 0;
1940 } catch (e) {}
1941 return serial_delimiter_space;
1942 }
1943 function _getNumberStart() {
1944 let start_number = parseInt(input_number_start.val().trim(),10);
1945 if (isNaN(start_number) || start_number < 1) {
1946 input_number_start.select();
1947 return alert(__("Your start number is not correct. It has to be an integer bigger than 0", 'event-tickets-with-ticket-scanner'));
1948 }
1949 return start_number;
1950 }
1951 function _getNumberOffset() {
1952 let number_offset = parseInt(input_number_offset.val().trim(),10);
1953 if (isNaN(number_offset) || number_offset < 1) number_offset = 1;
1954 return number_offset;
1955 }
1956 function _generateSerialCode(offsetCounter) {
1957 let code;
1958 let prefix = _getPrefix();
1959 if (_isTypeNumbers()) { // numbers
1960 if (!offsetCounter) offsetCounter = 0;
1961 let number_offset = offsetCounter * _getNumberOffset();
1962 code = _getNumberStart() + number_offset;
1963 if (prefix !== '') code = prefix + code;
1964 } else {
1965 code = __generateCode(_getAmountLetters(), _getLetterStyle(), _getIncludeNumbers(), _getLetterExclusion());
1966 code = __insertSeperator(code, _getSerialDelimiter(), _getSerialDelimiterSpace());
1967 if (prefix !== '') code = prefix + code;
1968 }
1969 return code;
1970 }
1971 function _getFormatterValues() {
1972 return {
1973 input_prefix_codes:_getPrefix().replace('/', '-'),
1974 input_type_codes:input_type_codes.val(),
1975 input_amount_letters:_getAmountLetters(),
1976 input_letter_excl:input_letter_excl.val(),
1977 input_letter_style:_getLetterStyle(),
1978 input_include_numbers:input_include_numbers.val(),
1979 input_serial_delimiter:input_serial_delimiter.val(),
1980 input_serial_delimiter_space:input_serial_delimiter_space.val(),
1981 input_number_start:_getNumberStart(),
1982 input_number_offset:_getNumberOffset()
1983 };
1984 }
1985
1986 return {
1987 render:__render,
1988 getAmountLetters:_getAmountLetters,
1989 getLetterExclusion:_getLetterExclusion,
1990 getLetterStyle:_getLetterStyle,
1991 getIncludeNumbers:_getIncludeNumbers,
1992 getSerialDelimiter:_getSerialDelimiter,
1993 getSerialDelimiterSpace:_getSerialDelimiterSpace,
1994 getNumberStart:_getNumberStart,
1995 getNumberOffset:_getNumberOffset,
1996 isTypeNumbers:_isTypeNumbers,
1997 getPrefix:_getPrefix,
1998 generateSerialCode:_generateSerialCode,
1999 setNoNumberOptions:_setNoNumberOptions,
2000 getFormatterValues:_getFormatterValues,
2001 setCallbackHandle:_setCallbackHandle,
2002 setFormatterValues:_setFormatterValues
2003 };
2004 }
2005
2006 function _createDivInput(label) {
2007 return $('<div/>').css({
2008 "display": "inline-block",
2009 "margin-bottom": "15px",
2010 "margin-right": "15px"
2011 }).html(label+"<br>");
2012 }
2013
2014 function __showFirstSteps() {
2015 let infoBox = null;
2016 // check if option displayFirstStepsHelp is set and active
2017 if (_getOptions_isActivatedByKey("displayFirstStepsHelp")) {
2018 // render info box with the first steps instructions.
2019 infoBox = $('<div style="background:#f9f9f9;border:1px solid #ddd;border:2px solid blue;padding:15px;margin-top:20px;margin-bottom:20px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);border-radius:10px;"/>');
2020 infoBox.append($('<h3/>').html(_x('First Steps', 'title', 'event-tickets-with-ticket-scanner')));
2021 $('<p/>').html(__('The basic steps to sell tickets, no matter which use case you have.', 'event-tickets-with-ticket-scanner')).appendTo(infoBox);
2022 let ul = $('<ol/>').appendTo(infoBox);
2023 $('<li/>').html(__('Create a list of tickets if none exists.<br>You can create different lists for different events or purposes. The ticket list need to be assigned to the product.', 'event-tickets-with-ticket-scanner')).appendTo(ul);
2024 $('<li/>').html(__('Go to the WooCommerce products and add/change a product.', 'event-tickets-with-ticket-scanner')).appendTo(ul);
2025 $('<li/>').html(__('Open the Event Ticket tab and activate the product to be a ticket and assign a ticket list.', 'event-tickets-with-ticket-scanner')).appendTo(ul);
2026 $('<li/>').html(__('Adjust the ticket informations if needed.', 'event-tickets-with-ticket-scanner')).appendTo(ul);
2027 $('<p/>').html(__('If you want to specialize your tickets, checkout our use case videos and also take a look at the options area with more than 200 options.', 'event-tickets-with-ticket-scanner')).appendTo(infoBox);
2028 $('<p/>').html(__('If you need help, please contact us via email. The information is in "Support Info" area - button above.', 'event-tickets-with-ticket-scanner')).appendTo(infoBox);
2029 infoBox.append(getUseFulVideosHTML());
2030 let btn_dont_show = $('<button class="button button-secondary" style="margin-top:10px;"/>').html(_x("Don't show this again", 'label', 'event-tickets-with-ticket-scanner')).appendTo(infoBox);
2031 btn_dont_show.on("click", function(){
2032 _saveOptionValue("displayFirstStepsHelp", "0", ()=>{
2033 infoBox.slideUp(300, function(){
2034 infoBox.remove();
2035 });
2036 });
2037 });
2038 }
2039 return infoBox; // jquery object
2040 }
2041
2042 class Layout {
2043 constructor(){
2044 DIV.addClass("sngmbh_container");
2045 this.div_liste = $('<div style="background:white;padding:15px;border-radius:15px;"/>').html(_getSpinnerHTML());
2046 this.div_codes = $('<div style="background:white;padding:15px;border-radius:15px;"/>').html(_getSpinnerHTML());
2047 this.div_spinner = $('<div style="display: none;position: fixed;z-index: 1031;top: 50%;right: 50%;margin-top: 0.5vh;background-color: white;margin-left: 0.5vw;border: 4px solid #2e74b5;padding: 10px;border-radius:10%;"/>').html(_getSpinnerHTML("loading"));
2048 $("body").append(this.div_spinner);
2049 }
2050 renderMainBody() {
2051 let infoBoxFirstSteps = __showFirstSteps();
2052
2053 // display upgrade to premium link
2054 if (!isPremium()) {
2055 let btn_upgrade = $('<a/>')
2056 .html('<img src="'+myAjax._plugin_home_url+'/img/button_premium_icon.gif" alt="" class="event-tickets-with-ticket-scanner-upgrade-icon">' + _x('Upgrade', 'label', 'event-tickets-with-ticket-scanner'))
2057 .addClass("event-tickets-with-ticket-scanner-upgrade-btn")
2058 .attr("href", getPremiumProductURL())
2059 .attr("target", "_blank");
2060 $('body').find('#event-tickets-with-ticket-scanner-header-actions').html(btn_upgrade);
2061 }
2062
2063 let div_body = $('<div/>');
2064 div_body.append(
2065 $('<div class="event-tickets-with-ticket-scanner-topbar">')
2066 .html($('<div/>').addClass("event-tickets-with-ticket-scanner-topbar-left"))
2067 .append(_displaySettingAreaButton())
2068 );
2069 if (infoBoxFirstSteps) {
2070 div_body.append(infoBoxFirstSteps);
2071 }
2072 div_body.append($('<h3/>').html(_x('List of tickets', 'title', 'event-tickets-with-ticket-scanner')));
2073 div_body.append($('<p/>').html(__("Organize your tickets in lists. You can assign tickets to a list.", 'event-tickets-with-ticket-scanner')));
2074 div_body.append(this.div_liste);
2075 div_body.append($('<hr/>'));
2076 div_body.append($('<h3/>').html(_x("Event Tickets", 'title', 'event-tickets-with-ticket-scanner')));
2077 div_body.append(this.div_codes);
2078 return div_body;
2079 }
2080 renderAddCodes() {
2081 DIV.html(_getSpinnerHTML());
2082 getDataLists(()=>{
2083 function __generateCodes() {
2084 // generate codes and
2085 let amount_codes = parseInt(input_amount_codes.val().trim(),10);
2086 if (isNaN(amount_codes) || amount_codes < 1) {
2087 input_amount_codes.select();
2088 return alert(_x("Enter an amount of how many ticket numbers you need", 'title', 'event-tickets-with-ticket-scanner'));
2089 }
2090 if (amount_codes > _maxCodes) {
2091 input_amount_codes.val(_maxCodes);
2092 amount_codes = _maxCodes;
2093
2094 }
2095 let uniq = {};
2096 let versuche = 0;
2097 if (serialCodeFormatterForm.isTypeNumbers()) { // numbers
2098 for(let a=0; a < amount_codes; a++) {
2099 let code = serialCodeFormatterForm.generateSerialCode( a );
2100 if (typeof uniq[code] !== "undefined") {
2101 continue;
2102 }
2103 uniq[code] = true;
2104 }
2105 versuche = amount_codes;
2106 } else {
2107 // erstmal kein check ob mit dem alphabet und die geforderte Menge an letters, unique codes erstellt werden können
2108 let counter = 0;
2109 let versuche_max = amount_codes * 1.5;
2110 while(counter < amount_codes && versuche < versuche_max) {
2111 versuche++;
2112 let code = serialCodeFormatterForm.generateSerialCode();
2113 if (typeof uniq[code] !== "undefined") {
2114 continue;
2115 }
2116 uniq[code] = true;
2117 counter++;
2118 }
2119 }
2120 return [Object.keys(uniq), versuche];
2121 } // __generateCodes
2122
2123 let div = $('<div>').append(getBackButtonDiv());
2124 // eingabe generator options
2125 let div_generator = $('<div/>').css("padding", "10px").css("border","1px solid black").html('<h3>'+_x('1. Ticket number generator (optional step)', 'title', 'event-tickets-with-ticket-scanner')+'</h3>').appendTo(div);
2126 div_generator.append($('<p>').html(__("You can generate ticket numbers.", 'event-tickets-with-ticket-scanner')));
2127 if (isPremium()) div_generator.append('<p>'+__('Up 100.000 tickets generation per run. The limit is to prevent performance issues.', 'event-tickets-with-ticket-scanner')+'<br>'+__('You can repeat the "store tickets" operations as often as needed.', 'event-tickets-with-ticket-scanner')+'</p>');
2128 // anzahl codes
2129 let div_amount_codes = _createDivInput(_x('Enter amount of needed ticket numbers', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_generator);
2130 let _maxCodes = myAjax._max.codes;
2131 if (!isPremium()) div_amount_codes.append(sprintf(/* translators: 1: amount of possible codes 2: premium info */__('%1$d max. %2$s up to 100.000 for each run', 'event-tickets-with-ticket-scanner'), _maxCodes, getLabelPremiumOnly())+'<br>');
2132 let input_amount_codes = $('<input type="number" required value="100" min="1" max="'+_maxCodes+'">').appendTo(div_amount_codes);
2133
2134 // predefine elements
2135 let serialCodeFormatterForm = _form_fields_serial_format(div_generator);
2136 serialCodeFormatterForm.render();
2137
2138 let elem_clean_codebox = $('<input checked type="checkbox" />');
2139 $('<div/>').css({"margin-bottom": "15px","margin-right": "15px"})
2140 .html(elem_clean_codebox)
2141 .append(_x('Clear the ticket numbers list textarea field below to add fill in the new generated ticket numbers', 'label', 'event-tickets-with-ticket-scanner'))
2142 .appendTo(div_generator);
2143
2144 let elem_create_cvv = $('<input type="checkbox" />');
2145 $('<div/>').css({"margin-bottom": "15px","margin-right": "15px"})
2146 .html(elem_create_cvv)
2147 .append(_x('Generate Code Verification Value (CVV) for each ticket number', 'label', 'event-tickets-with-ticket-scanner'))
2148 .appendTo(div_generator);
2149
2150 // button generate
2151 div_generator.append($('<button/>').addClass("button-secondary").html(_x('Generate ticket numbers', 'label', 'event-tickets-with-ticket-scanner')).on("click", function(){
2152 let time_start = performance.now();
2153 btn_store_codes.prop("disabled", false);
2154 input_textarea.prop("disabled", false);
2155 if (elem_clean_codebox[0].checked) {
2156 input_textarea.html("");
2157 }
2158 input_textarea.prop("disabled", true);
2159 div_textarea_info.css("padding-bottom", "50px").html(_getSpinnerHTML());
2160 setTimeout(function(){
2161 let r = __generateCodes();
2162 let codes = r[0];
2163 let secs = ((performance.now() - time_start) / 1000)+"";
2164 if (elem_create_cvv[0].checked) {
2165 codes = codes.map(v=>{
2166 return v += ';'+(Math.floor(Math.random() * 10000) + 10000).toString().substring(1);
2167 });
2168 }
2169 input_textarea.append(codes.join("\n")).append("\n");
2170 input_textarea.prop("disabled", false);
2171 div_textarea_info.html(sprintf(/* translators: 1: amount of created tickets 2: seconds 3: amount of runs */__('Created %1$d tickets. In %2$s seconds, with %3$d runs to find unique ticket numbers.', 'event-tickets-with-ticket-scanner'), codes.length, secs.slice(0,5), r[1]));
2172 _calcLinesOfCodeTextArea();
2173 },250);
2174 }));
2175
2176 // eingabe maske textarea
2177 function _calcLinesOfCodeTextArea() {
2178 let codesAmount = 0;
2179 input_textarea.val().trim().split('\n').forEach(v=>{
2180 if (v.trim() !== "") codesAmount++;
2181 });
2182 input_textarea_info.html(sprintf(/* translators: %d: amout of ticket numbers */__('contains %d tickets', 'event-tickets-with-ticket-scanner'), codesAmount));
2183 }
2184 let div_textarea = $('<div/>').html('<h3>'+_x('2. Ticket numbers to store on the server', 'title', 'event-tickets-with-ticket-scanner')+'</h3><p>'+__('One number per line and/or comma-separated (,). <br>If you want to add the CVV number then separate your ticket number with (;) and append your CVV number.<br>While storing the numbers to the server, it will check if the ticket number is unique and mark the ones, that are not.', 'event-tickets-with-ticket-scanner')+'</p>').appendTo(div);
2185 let div_textarea_info = $('<div/>').appendTo(div_textarea);
2186 let input_textarea = $('<textarea>').change(_calcLinesOfCodeTextArea).css("height","135px").css("width","100%").appendTo(div_textarea);
2187 let input_textarea_info = $('<div/>').appendTo(div_textarea);
2188 div_textarea.append("<br>");
2189 _calcLinesOfCodeTextArea();
2190 // list auswahl
2191 let div_code_list = _createDivInput(_x('Assign to this ticket list', 'label', 'event-tickets-with-ticket-scanner')).appendTo(div_textarea);
2192 let input_code_list = $('<select><option value="0">'+_x('None', 'option value', 'event-tickets-with-ticket-scanner')+'</select></select>').appendTo(div_code_list);
2193 DATA_LISTS.forEach(v=>{
2194 input_code_list.append('<option value="'+v.id+'">'+v.name+'</option>');
2195 });
2196 div_textarea.append("<br>");
2197
2198 // additional prem fields
2199 if (isPremium() && PREMIUM.addAddCodeFields) {
2200 div_textarea.append(PREMIUM.addAddCodeFields());
2201 }
2202
2203 // button store codes
2204 if (!isPremium()) div_textarea.append('<b>'+sprintf(/* translators: 1: max amout of ticket numbers 2: premium info */__('You can store up to %1$d. %2$s unlimited', 'event-tickets-with-ticket-scanner'), myAjax._max.codes_total, getLabelPremiumOnly())+'<br>');
2205 let btn_store_codes = $('<button/>');
2206 btn_store_codes.addClass("button-primary").html(_x('Store ticket numbers', 'label', 'event-tickets-with-ticket-scanner')).on("click", function(){
2207 // extract codes and
2208 let codes = [];
2209 let codesLines = input_textarea.val().split("\n").map(x=>x.trim());
2210 codesLines.forEach(x=>{
2211 x.split(",").forEach(y=>{
2212 y = y.trim();
2213 y = destroy_tags(y);
2214 if (y != "") codes.push(y);
2215 });
2216 });
2217 if (codes.length === 0) return;
2218
2219 // sperre btn store codes
2220 btn_store_codes.prop("disabled", true);
2221 input_textarea.prop("disabled", true);
2222
2223 div_textarea_info.append($('<div/>').addClass("notice notice-info").html(__("Each entry will turn green (successfull stored) or red (NOT OK - duplicat entry on the server).<br>Scroll down and wait for all to finish.<br>In the textarea below you will find all the successful stored tickets.", 'event-tickets-with-ticket-scanner')));
2224 let _output = $('<ol/>').appendTo(div_textarea_info);
2225 div_textarea_info.append('<h3>'+_x('Successfull stored ticket numbers', 'title', 'event-tickets-with-ticket-scanner')+'</h3>');
2226 let output_textarea_codes_done = $('<textarea disabled style="4px solid green;width:100%;height:150px;"></textarea>').appendTo(div_textarea_info);
2227
2228 let list_id = parseInt(input_code_list.val(),10);
2229
2230 function __addCodesInChunks(chunk_size) {
2231 let dlg = $('<div/>').html(_getSpinnerHTML());
2232 dlg.dialog({title:_x('Importing', 'title', 'event-tickets-with-ticket-scanner'),closeOnEscape: true,modal: true, dialogClass: "no-close", close: function(event, ui){ abort=true; } });
2233
2234 let abort = false;
2235 let counter_ok = 0;
2236 let counter_notok = 0;
2237 let counter_all = codes.length;
2238 const array_chunks = (array, chunk_size) => Array(Math.ceil(array.length / chunk_size)).fill().map((_, index) => index * chunk_size).map(begin => array.slice(begin, begin + chunk_size));
2239 let chunks = array_chunks(codes, chunk_size);
2240 function _addCodeChunk(idx) {
2241 if (abort) return;
2242 if (idx >= chunks.length) {
2243 dlg.append('<p>'+__('Import process finished', 'event-tickets-with-ticket-scanner')+'</p>');
2244 $('<center/>').append($('<button class="button-primary" />').html(_x('Ok', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{ closeDialog(dlg); })).appendTo(dlg);
2245 return;
2246 }
2247 let arr = chunks[idx];
2248 arr.forEach(v=>{
2249 let div_info_entry = $('<li data-id="code_'+v+'"/>').html(v);
2250 _output.append(div_info_entry);
2251 });
2252 let attr = {"codes":arr, "list_id":list_id};
2253 if (isPremium() && PREMIUM.addAddCodeFieldsData) {
2254 attr = PREMIUM.addAddCodeFieldsData(div_textarea, attr);
2255 }
2256
2257 _makePost("addCodes", attr, function(data){
2258 counter_ok += data.ok.length;
2259 counter_notok += data.notok.length;
2260 if (myAjax._max.codes_total > 0 && myAjax._max.codes_total <= parseInt(data.total_size)) {
2261 div_textarea_info.prepend('<h3 style="color:red;">'+sprintf(/* translators: %d: total ticket count */_x('Your Limit of %d tickets is reached. Use the premium version to have unlimited tickets', 'title', 'event-tickets-with-ticket-scanner'),myAjax._max.codes_total)+'</h3>');
2262 }
2263 let per = Math.ceil(((counter_ok+counter_notok)/counter_all)*100);
2264 let info_content = '<div style="width:100%;border:1px solid #efefef;background-color:white;"><div style="text-align:center;height:20px;background-color:#428bca;color:white;width:'+per+'%;">'+per+'%</div></div>';
2265 info_content += '<p style="margin-top:20px;">'+_x('Amount', 'title', 'event-tickets-with-ticket-scanner')+': '+(counter_ok+counter_notok)+'/'+counter_all+'<br>'+_x('Ok', 'label', 'event-tickets-with-ticket-scanner')+': '+counter_ok+'<br>'+_x('Not Ok', 'label', 'event-tickets-with-ticket-scanner')+': '+counter_notok+'</p>';
2266 dlg.html(info_content);
2267 data.ok.forEach(_v=> {
2268 _output.find('li[data-id="code_'+_v+'"]').css("color","green").append(' ('+_x('Ok', 'label', 'event-tickets-with-ticket-scanner')+')');
2269 output_textarea_codes_done.append(_v+"\n");
2270 });
2271 data.notok.forEach(_v=> {
2272 _output.find('li[data-id="code_'+_v+'"]').css("color","red").append(' ('+_x('Not Ok', 'label', 'event-tickets-with-ticket-scanner')+')');
2273 });
2274 setTimeout(()=>{
2275 _addCodeChunk(idx+1);
2276 }, 100);
2277 }, function(response){
2278 if (response.data.slice(0,4) === "#208") {
2279 FATAL_ERROR === false && LAYOUT.renderFatalError(response.data);
2280 FATAL_ERROR = true;
2281 }
2282 });
2283 }
2284
2285 if (chunks.length === 0) {
2286 closeDialog(dlg);
2287 } else {
2288 _addCodeChunk(0);
2289 }
2290 } // __addCodesInChunks
2291 __addCodesInChunks(100);
2292
2293 // zeige ok button, der info area leer macht und den btn store codes wieder aktiviert
2294 div_textarea_info.append($('<button/>').addClass("button-primary").css("margin-bottom", "20px").html(_x('Ok', 'label', 'event-tickets-with-ticket-scanner')).on("click", function(){
2295 div_textarea_info.html("");
2296 btn_store_codes.prop("disabled", false);
2297 input_textarea.prop("disabled", false);
2298 window.scrollTo(0,0);
2299 }));
2300
2301 }).appendTo(div_textarea);
2302 DIV.html(div);
2303 });
2304 }
2305 renderAdminPageLayout(cbf) {
2306 function __showMaskExport(totalRecordCount) {
2307 if (!totalRecordCount) totalRecordCount = 0;
2308 let maxRange = totalRecordCount > 40000 ? 40000 : totalRecordCount;
2309 let _options = {
2310 title: _x('Export tickets', 'title', 'event-tickets-with-ticket-scanner'),
2311 modal: true,
2312 minWidth: 400,
2313 minHeight: 200,
2314 buttons: [
2315 {
2316 text: _x('Export', 'label', 'event-tickets-with-ticket-scanner'),
2317 click: function() {
2318 ___submitForm();
2319 }
2320 },
2321 {
2322 text: _x('Cancel', 'label', 'event-tickets-with-ticket-scanner'),
2323 click: function() {
2324 closeDialog(this);
2325 }
2326 }
2327 ]
2328 };
2329 let formdlg = $('<form/>').html('<b>'+_x('Choose your export settings', 'title', 'event-tickets-with-ticket-scanner')+'</b><p>');
2330 formdlg.append(_x('Choose the delimiter for the column values', 'label', 'event-tickets-with-ticket-scanner')+'<br><select name="delimiter"><option value="1">, ('+_x('Comma', 'option value', 'event-tickets-with-ticket-scanner')+')</option><option value="2">; ('+_x('Semicolon', 'option value', 'event-tickets-with-ticket-scanner')+')</option><option value="3">| ('+_x('Pipe', 'option value', 'event-tickets-with-ticket-scanner')+')</option></select><p>');
2331 formdlg.append(_x('Choose a file suffix', 'label', 'event-tickets-with-ticket-scanner')+'<br><select name="suffix"><option value="1">.csv</option><option value="2">.txt</option></select><p>');
2332
2333 let _listChooser = $('<select name="listchooser"><option value="0">'+_x('All', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select>');
2334 for(let a=0;a<DATA_LISTS.length;a++) {
2335 _listChooser.append('<option value="'+DATA_LISTS[a].id+'">'+DATA_LISTS[a].name+'</option>');
2336 }
2337 formdlg.append(_x('Limit export to ticket list', 'label', 'event-tickets-with-ticket-scanner')+'<br>').append(_listChooser).append('<p>');
2338
2339 formdlg.append(_x('Choose a sorting field', 'label', 'event-tickets-with-ticket-scanner')+'<br><select name="orderby"><option value="1" selected>'+_x('Creation date', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2">'+__('Ticket number', 'event-tickets-with-ticket-scanner')+'</option><option value="3">'+__('Ticket display number', 'event-tickets-with-ticket-scanner')+'</option><option value="4">'+_x('List name', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select><p>');
2340 formdlg.append(_x('Choose a sorting direction', 'label', 'event-tickets-with-ticket-scanner')+'<br><select name="orderbydirection"><option value="1" selected>'+_x('Ascending', 'option value', 'event-tickets-with-ticket-scanner')+'</option><option value="2">'+_x('Descending', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select><p>');
2341 formdlg.append(_x('Set a range', 'label', 'event-tickets-with-ticket-scanner')+'<br><i>'+sprintf(/* translators: %d: total record count */__('You have %d tickets stored.', 'event-tickets-with-ticket-scanner'), totalRecordCount)+'<br>'+__('Some systems are slow and the connection timeout interupts the export, if you have too many tickets. In that case, you can export your tickets in several steps. e.g. 0 and 20000 amount and then 20001 and 20000 amount.', 'event-tickets-with-ticket-scanner')+'</i><br>'+__('Enter your row start (0 = from the first)', 'event-tickets-with-ticket-scanner')+'<br><input type="number" name="rangestart" value="0"><br>'+_x('Enter amount of tickets', 'label', 'event-tickets-with-ticket-scanner')+'<br><input type="number" name="rangeamount" value="'+maxRange+'"><p>');
2342 if (isPremium() && PREMIUM && PREMIUM.addExportTicketsInputFields) {
2343 formdlg.append(PREMIUM.addExportTicketsInputFields());
2344 }
2345 let dlg = $('<div/>').append(formdlg);
2346
2347 dlg.dialog(_options);
2348
2349 let form = dlg.find("form").on("submit", function(event) {
2350 event.preventDefault();
2351 ___submitForm();
2352 });
2353
2354 function ___submitForm() {
2355 let delimiter = dlg.find('select[name="delimiter"]').val();
2356 let filesuffix = dlg.find('select[name="suffix"]').val();
2357 let orderby = dlg.find('select[name="orderby"]').val();
2358 let orderbydirection = dlg.find('select[name="orderbydirection"]').val();
2359 let rangestart = dlg.find('input[name="rangestart"]').val();
2360 let rangeamount = dlg.find('input[name="rangeamount"]').val();
2361 let listchooser = dlg.find('select[name="listchooser"]').val();
2362
2363 let data = {'delimiter':delimiter, 'filesuffix':filesuffix, 'orderby':orderby, 'orderbydirection':orderbydirection, 'rangestart':rangestart, 'rangeamount':rangeamount, 'listchooser':listchooser};
2364 if (isPremium() && PREMIUM && PREMIUM.addExportTicketsInputFieldsData) {
2365 data = PREMIUM.addExportTicketsInputFieldsData(data, dlg);
2366 }
2367
2368 let url = _requestURL('exportTableCodes', data);
2369 closeDialog(dlg);
2370 window.open(url, "_blank");
2371 }
2372 }
2373 function __showMaskList(editValues){
2374 let _options = {
2375 title: editValues !== null ? _x('Edit List', 'title', 'event-tickets-with-ticket-scanner') : _x('Add List', 'title', 'event-tickets-with-ticket-scanner'),
2376 modal: true,
2377 minWidth: 600,
2378 minHeight: 400,
2379 open: function(e) {
2380 //$(e.target).parent().css('background-color','orangered');
2381 },
2382 buttons: [
2383 {
2384 text: _x('Ok', 'label', 'event-tickets-with-ticket-scanner'),
2385 click: function() {
2386 ___submitForm();
2387 }
2388 },
2389 {
2390 text: _x('Cancel', 'label', 'event-tickets-with-ticket-scanner'),
2391 click: function() {
2392 closeDialog(this);
2393 }
2394 }
2395 ]
2396 };
2397 let dlg = $('<div/>').html('<form>'+_x('Name', 'label', 'event-tickets-with-ticket-scanner')+'<br><input name="inputName" type="text" style="width:100%;" required></form>');
2398 dlg.dialog(_options);
2399
2400 dlg.find("form").append($('<p>'+_x('Description', 'label', 'event-tickets-with-ticket-scanner')+'<br><textarea name="desc" style="width:100%;"></textarea></p>'));
2401
2402 if (isPremium()) PREMIUM.addListMaskEditFields(dlg, editValues);
2403 else {
2404 if (_getOptions_isActivatedByKey("oneTimeUseOfRegisterCode")) {
2405 dlg.append($('<p><b>'+sprintf(/* translators: %s: h4 option name */__('Overrule %s per Ticket list', 'event-tickets-with-ticket-scanner'), _getOptions_getLabelByKey("h4"))+'</b> '+getLabelPremiumOnly()+'</p>'));
2406 }
2407 }
2408
2409 let metaObj = [];
2410 if (editValues && typeof editValues.meta !== "undefined" && editValues.meta != "") {
2411 try {
2412 metaObj = JSON.parse(editValues.meta);
2413 } catch(e) {}
2414 }
2415
2416 if (_getOptions_isActivatedByKey("userJSRedirectActiv")) {
2417 dlg.find("form").append($('<p>'+_getOptions_getLabelByKey("userJSRedirectURL")+'<br><input type="text" name="redirecturl" style="width:100%;"></p>'));
2418 }
2419
2420 dlg.find("form").append($('<p><input name="serialformatter" type="checkbox"> '+_x('Overrule the ticket format settings', 'label', 'event-tickets-with-ticket-scanner')+'</p>'));
2421 let extra_div = $('<div>').appendTo(dlg).css("margin-top", "10px").css("margin-left", "24px").css("padding", "10px").css("border", "1px solid black")
2422 .html('<p><b>'+_x('Note', 'label', 'event-tickets-with-ticket-scanner')+':</b> '+__('Will be overriden if you set the ticket number format settings on the product!', 'event-tickets-with-ticket-scanner')+'</p>');
2423 let serialCodeFormatter = _form_fields_serial_format(extra_div);
2424 serialCodeFormatter.setNoNumberOptions();
2425 if (typeof metaObj.formatter !== "undefined" && metaObj.formatter.format != "") {
2426 let formatterValues;
2427 try {
2428 let o = metaObj.formatter.format.replace(new RegExp("\\\\", "g"), "").trim();
2429 formatterValues = JSON.parse(o);
2430 serialCodeFormatter.setFormatterValues(formatterValues);
2431 } catch (e) {}
2432 }
2433 serialCodeFormatter.render();
2434
2435 $('<hr>').appendTo(dlg);
2436 $('<h4>').html(_x('Webhook', 'heading', 'event-tickets-with-ticket-scanner')).appendTo(dlg);
2437 if (!_getOptions_isActivatedByKey("webhooksActiv")) {
2438 $('<div style="color:red">').html(_x('The webhook need to be activated first in the options to be executed, even if the URL is set here.', 'label', 'event-tickets-with-ticket-scanner')).appendTo(dlg);
2439 }
2440 $('<div>').html(_x('URL to your service if the WooCommerce ticket is sold', 'label', 'event-tickets-with-ticket-scanner')).appendTo(dlg);
2441 let meta_webhooks_webhookURLaddwcticketsold = $('<input name="meta_webhooks_webhookURLaddwcticketsold" type="text" style="width:100%;">').appendTo(dlg);
2442
2443 let form = dlg.find("form").on("submit", function(event) {
2444 event.preventDefault();
2445 ___submitForm();
2446 });
2447
2448 if (editValues) {
2449 form[0].elements['inputName'].value = editValues.name;
2450 form[0].elements['inputName'].select();
2451 if (typeof metaObj.desc !== "undefined") {
2452 form[0].elements['desc'].value = metaObj.desc.replace(new RegExp("\\\\", "g"), "").trim();
2453 }
2454 if (typeof metaObj.formatter !== "undefined" && metaObj.formatter.active) {
2455 form[0].elements['serialformatter'].checked = true;
2456 }
2457 if (_getOptions_isActivatedByKey("userJSRedirectActiv") && typeof metaObj.redirect !== "undefined" && metaObj.redirect.url) {
2458 form[0].elements['redirecturl'].value = metaObj.redirect.url.trim();
2459 }
2460 if (typeof metaObj.webhooks != "undefined") {
2461 if (typeof metaObj.webhooks.webhookURLaddwcticketsold != "undefined") {
2462 meta_webhooks_webhookURLaddwcticketsold.val(metaObj.webhooks.webhookURLaddwcticketsold);
2463 }
2464 }
2465 }
2466
2467 function ___submitForm() {
2468 let inputName = form[0].elements['inputName'].value.trim();
2469 if (inputName === "") return;
2470
2471 dlg.html(_getSpinnerHTML());
2472 let _data = {"name":inputName};
2473 _data['meta'] = {"desc":"", "formatter":{}, "webhooks":{}};
2474 _data['meta']['desc'] = form[0].elements['desc'].value.trim();
2475 _data['meta']['formatter']['active'] = form[0].elements['serialformatter'].checked ? 1 : 0;
2476 _data['meta']['formatter']['format'] = JSON.stringify(serialCodeFormatter.getFormatterValues());
2477 if (_getOptions_isActivatedByKey("userJSRedirectActiv")) {
2478 _data['meta']['redirect'] = {"url":form[0].elements['redirecturl'].value.trim()};
2479 }
2480 _data['meta']['webhooks']['webhookURLaddwcticketsold'] = meta_webhooks_webhookURLaddwcticketsold.val().trim();
2481 if (isPremium()) PREMIUM.addListMaskEditFieldsData(_data, form[0], editValues);
2482
2483 form[0].reset();
2484 if (editValues) {
2485 _data.id = editValues.id;
2486 _makePost('editList', _data, result=>{
2487 DATA_LISTS = null;
2488 __renderTabelleListen();
2489 tabelle_codes_datatable.ajax.reload();
2490 setTimeout(function(){closeDialog(dlg);},250);
2491 }, function() {
2492 closeDialog(dlg);
2493 });
2494 } else {
2495 _makePost('addList', _data, result=>{
2496 DATA_LISTS = null;
2497 __renderTabelleListen();
2498 closeDialog(dlg);
2499 }, function(response) {
2500 closeDialog(dlg);
2501 if (response.data.slice(0,1) === "#") {
2502 FATAL_ERROR === false && LAYOUT.renderFatalError(response.data);
2503 FATAL_ERROR = true;
2504 }
2505 });
2506 }
2507 }
2508
2509 } // ende showmaskliste
2510
2511 function __showMaskCode(editValues){
2512 let _options = {
2513 title: editValues !== null ? _x('Edit Ticket', 'title', 'event-tickets-with-ticket-scanner') : _x('Add Ticket', 'title', 'event-tickets-with-ticket-scanner'),
2514 modal: true,
2515 minWidth: 400,
2516 minHeight: 200,
2517 buttons: [
2518 {
2519 text: _x('Ok', 'label', 'event-tickets-with-ticket-scanner'),
2520 click: function() {
2521 ___submitForm();
2522 }
2523 },
2524 {
2525 text: _x('Cancel', 'label', 'event-tickets-with-ticket-scanner'),
2526 click: function() {
2527 $( this ).dialog( "close" );
2528 $( this ).html('');
2529 }
2530 }
2531 ]
2532 };
2533 let dlg = $('<div />').html('<form>'+_x('List', 'label', 'event-tickets-with-ticket-scanner')+'<br><select name="inputListId"><option value="0">'+_x('None', 'option value', 'event-tickets-with-ticket-scanner')+'</option></select></form>');
2534 DATA_LISTS.forEach(v=>{
2535 $(dlg).find('select[name="inputListId"]').append('<option '+(editValues && parseInt(editValues.list_id,10) === parseInt(v.id,10) ? 'selected ':'')+'value="'+v.id+'">'+v.name+'</option>');
2536 });
2537
2538 let elem_cvv = $('<input type="text" size="6" minlength="5" maxlength="4" />');
2539 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
2540 .html(_x('CVV - use 4 digits for best results', 'label', 'event-tickets-with-ticket-scanner')+'<br>')
2541 .append(elem_cvv)
2542 .append('<br><i>'+__('If CVV is set, then your user will be asked to enter also the CVV to check the ticket number.', 'event-tickets-with-ticket-scanner')+'</i>')
2543 .appendTo(dlg.find("form"));
2544
2545 let div_status = $('<div/>');
2546 div_status.append(
2547 $('<select name="inputStatus"/>')
2548 .append('<option '+(editValues.aktiv === "1"?'selected':'')+' value="1">'+_x('is activ', 'option value', 'event-tickets-with-ticket-scanner')+'</option>')
2549 .append('<option '+(editValues.aktiv === "0"?'selected':'')+' '+(!isPremium()?'disabled':'')+' value="0">'+_x('is inactiv', 'option value', 'event-tickets-with-ticket-scanner')+' '+(!isPremium()?getLabelPremiumOnly():'')+'</option>')
2550 .append('<option '+(editValues.aktiv === "2"?'selected':'')+' value="2">'+_x('is stolen', 'label', 'event-tickets-with-ticket-scanner')+'</option>')
2551 )
2552 .appendTo(dlg);
2553
2554 dlg.dialog(_options);
2555
2556 if (editValues) {
2557 if (editValues.cvv) elem_cvv.val(editValues.cvv);
2558 }
2559
2560 if (isPremium()) PREMIUM.addCodeMaskEditFields(dlg, editValues);
2561
2562 let form = dlg.find("form").on("submit", function(event) {
2563 event.preventDefault();
2564 ___submitForm();
2565 });
2566 function ___submitForm() {
2567 let inputListId = parseInt($(dlg).find('select[name="inputListId"]').val(),10);
2568 let inputStatusValue = $(dlg).find('select[name="inputStatus"]').val();
2569 dlg.html(_getSpinnerHTML());
2570 let _data = {"list_id":inputListId, "aktiv":inputStatusValue, "cvv":elem_cvv.val().trim()};
2571 if (isPremium()) PREMIUM.addCodeMaskEditFieldsData(_data, form[0], editValues);
2572 form[0].reset();
2573 if (editValues) {
2574 _data.code = editValues.code;
2575 _makeGet('editCode', _data, ()=>{
2576 tabelle_codes_datatable.ajax.reload();
2577 closeDialog(dlg);
2578 }, function() {
2579 closeDialog(dlg);
2580 });
2581 } else {
2582 alert(__("Use the add option", 'event-tickets-with-ticket-scanner'));
2583 }
2584 }
2585 } // ende __showMaskCode
2586
2587 let id_codes = myAjax.divPrefix+'_tabelle_codes';
2588 let tabelle_liste_datatable;
2589 let tabelle_codes_datatable;
2590 let tabelle_codes = $('<table/>').attr("id", id_codes);
2591 let tplace = $('<div/>');
2592
2593 function __renderTabelleListen() {
2594 getDataLists(()=>{
2595 let id_liste = myAjax.divPrefix+'_tabelle_liste';
2596 let tabelle_liste = $('<table/>').attr("id", id_liste);
2597 tabelle_liste.html('<thead><tr><th align="left">'+_x('Name', 'label', 'event-tickets-with-ticket-scanner')+'</th><th align="left">'+_x('Created', 'label', 'event-tickets-with-ticket-scanner')+'</th><th></th></tr></thead>');
2598 tplace.html(tabelle_liste);
2599
2600 let table = $('#'+id_liste);
2601 $(table).DataTable().clear().destroy();
2602 tabelle_liste_datatable = $(table).DataTable({
2603 language: {
2604 emptyTable: '<b>You need a ticket list to assign it to the products in order to sell tickets.</b>'
2605 },
2606 "responsive": true,
2607 "visible": true,
2608 "searching": true,
2609 "ordering": true,
2610 "processing": true,
2611 "serverSide": false,
2612 "stateSave": true,
2613 "data": DATA_LISTS,
2614 "order": [[ 0, "asc" ]],
2615 "columns":[
2616 {"data":"name", "orderable":true},
2617 {"data":"time", "orderable":true, "width":80,
2618 "render":function (data, type, row) {
2619 return '<span style="display:none;">'+data+'</span>'+DateFormatStringToDateTimeText(data);
2620 }
2621 },
2622 {"data":null,"orderable":false,"defaultContent":'',"className":"buttons dt-right","width":180,
2623 "render": function ( data, type, row ) {
2624 return '<button class="button-secondary" data-type="showCodes">'+_x('Tickets', 'label', 'event-tickets-with-ticket-scanner')+'</button> <button class="button-secondary" data-type="edit">'+_x('Edit', 'label', 'event-tickets-with-ticket-scanner')+'</button> <button class="button-secondary" data-type="deleteAllTickets" style="color:#b32d2e;">'+_x('Delete All Tickets', 'label', 'event-tickets-with-ticket-scanner')+'</button> <button class="button-secondary" data-type="delete">'+_x('Delete', 'label', 'event-tickets-with-ticket-scanner')+'</button>';
2625 }
2626 }
2627 ]
2628 });
2629 tabelle_liste.css("width", "100%");
2630 table.on('click', 'button[data-type="showCodes"]', e=>{
2631 let data = tabelle_liste_datatable.row( $(e.target).parents('tr') ).data();
2632 tabelle_codes_datatable.search("LIST:"+data.id).draw();
2633 });
2634 table.on('click', 'button[data-type="edit"]', e=>{
2635 let data = tabelle_liste_datatable.row( $(e.target).parents('tr') ).data();
2636 __showMaskList(data);
2637 });
2638 table.on('click', 'button[data-type="delete"]', e=>{
2639 let data = tabelle_liste_datatable.row( $(e.target).parents('tr') ).data();
2640 let content = $('<div>');
2641 content.append('<p>' + __('Are you sure, you want to delete this list?', 'event-tickets-with-ticket-scanner') + '</p>');
2642 content.append('<p><b>' + data.name + '</b></p>');
2643 content.append('<p>' + __('No ticket will be deleted. Just the list.', 'event-tickets-with-ticket-scanner') + '</p>');
2644 content.append('<hr style="margin:15px 0;">');
2645 let checkboxId = 'delete-list-check-products-' + data.id;
2646 let checkboxWrapper = $('<label for="' + checkboxId + '" style="display:flex;align-items:center;gap:8px;cursor:pointer;">');
2647 let checkbox = $('<input type="checkbox" id="' + checkboxId + '" checked>');
2648 checkboxWrapper.append(checkbox);
2649 checkboxWrapper.append(__('Check if list is used by products', 'event-tickets-with-ticket-scanner'));
2650 content.append(checkboxWrapper);
2651
2652 LAYOUT.renderYesNo(_x('Do you want to delete?', 'title', 'event-tickets-with-ticket-scanner'), content, ()=>{
2653 let _data = {
2654 'id': data.id,
2655 'skip_product_check': !checkbox.is(':checked')
2656 };
2657 _makePost('removeList', _data, result=>{
2658 if (result && result.error === 'list_in_use' && result.products) {
2659 let errorContent = $('<div>');
2660 errorContent.append('<p style="color:#b32d2e;font-weight:bold;">' + __('This list is still assigned to products:', 'event-tickets-with-ticket-scanner') + '</p>');
2661 let productList = $('<ul style="margin:10px 0;padding-left:20px;">');
2662 result.products.forEach(function(product) {
2663 let li = $('<li style="margin:5px 0;">');
2664 if (product.edit_url) {
2665 li.append('<a href="' + product.edit_url + '" target="_blank">' + product.name + '</a> (ID: ' + product.id + ')');
2666 } else {
2667 li.append(product.name + ' (ID: ' + product.id + ')');
2668 }
2669 productList.append(li);
2670 });
2671 errorContent.append(productList);
2672 errorContent.append('<p>' + __('Please reassign these products first, or uncheck the product check option.', 'event-tickets-with-ticket-scanner') + '</p>');
2673 LAYOUT.renderInfoBox(__('Cannot delete list', 'event-tickets-with-ticket-scanner'), errorContent);
2674 } else {
2675 __renderTabelleListen();
2676 tabelle_codes_datatable.ajax.reload();
2677 }
2678 });
2679 });
2680 });
2681 table.on('click', 'button[data-type="deleteAllTickets"]', e=>{
2682 let data = tabelle_liste_datatable.row( $(e.target).parents('tr') ).data();
2683 LAYOUT.renderYesNo(
2684 _x('Delete all tickets?', 'title', 'event-tickets-with-ticket-scanner'),
2685 sprintf(__('Are you sure you want to delete ALL tickets from the list "%s"?', 'event-tickets-with-ticket-scanner'), '<b>'+data.name+'</b>') + '<br><br><span style="color:#b32d2e;">' + __('This action cannot be undone!', 'event-tickets-with-ticket-scanner') + '</span>',
2686 ()=>{
2687 let content = $('<div>');
2688 content.append('<p>' + __('To confirm deletion, type DELETE in the field below:', 'event-tickets-with-ticket-scanner') + '</p>');
2689 let confirmInput = $('<input type="text" style="width:100%;" placeholder="DELETE">');
2690 content.append(confirmInput);
2691 LAYOUT.renderYesNo(
2692 _x('Final confirmation', 'title', 'event-tickets-with-ticket-scanner'),
2693 content,
2694 ()=>{
2695 if (confirmInput.val().trim().toUpperCase() !== 'DELETE') {
2696 alert(__('You must type DELETE to confirm.', 'event-tickets-with-ticket-scanner'));
2697 return;
2698 }
2699 let btn = $(e.target);
2700 btn.prop('disabled', true).text(__('Deleting...', 'event-tickets-with-ticket-scanner'));
2701 _makePost('removeAllCodesFromList', {'list_id': data.id}, result=>{
2702 btn.prop('disabled', false).text(_x('Delete All Tickets', 'label', 'event-tickets-with-ticket-scanner'));
2703 tabelle_codes_datatable.ajax.reload();
2704 if (result && result.deleted !== undefined) {
2705 alert(sprintf(__('%d tickets have been deleted.', 'event-tickets-with-ticket-scanner'), result.deleted));
2706 }
2707 });
2708 }
2709 );
2710 }
2711 );
2712 });
2713 }); // end of loading lists
2714 } // __renderTabelleListen
2715 tabelle_codes.css("width", "100%");
2716
2717 STATE = 'admin';
2718 DIV.html(_getSpinnerHTML());
2719 getOptionsFromServer(optionData=>{
2720 DIV.html('');
2721 DIV.append(this.renderMainBody());
2722
2723 let btn_liste_empty = $('<button/>').addClass("button-secondary").html(__('Empty table', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2724 LAYOUT.renderYesNo(__('Empty table', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: title list of tickets */__('Do you want to empty the "%s" table? All data will be lost. No ticket will be deleted. Just the lists.', 'event-tickets-with-ticket-scanner'), _x('List of tickets', 'title', 'event-tickets-with-ticket-scanner')), ()=>{
2725 LAYOUT.renderYesNo(__('Empty table - last chance', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: title list of tickets */__('Are you sure? You will not be able to restore the data, except you have a backup of your database. All data will be lost. No ticket will be deleted. Just the lists.', 'event-tickets-with-ticket-scanner'), _x('List of tickets', 'title', 'event-tickets-with-ticket-scanner')), ()=>{
2726 _makeGet('emptyTableLists', null, ()=>{
2727 tabelle_codes_datatable.ajax.reload();
2728 __renderTabelleListen();
2729 });
2730 });
2731 });
2732 });
2733 let btn_liste_new = $('<button/>').addClass("button-primary").html(_x('Add', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2734 __showMaskList(null);
2735 });
2736 this.div_liste.html($('<div/>').css('text-align', 'right').css('margin-bottom','10px').append(btn_liste_empty).append(isPremium()?'':' '+sprintf(/* translators: 1: max possible lists amount 2: link to premium */__('Max. %1$d list. Unlimited with %2$s', 'event-tickets-with-ticket-scanner'), myAjax._max.lists, getLabelPremiumOnly())+' ').append(btn_liste_new));
2737 this.div_liste.append(tplace);
2738
2739 __renderTabelleListen();
2740
2741 let additionalColumn_counter_before_created_field = 0;
2742 let additionalColumn = {customerName:'',customerCompany:'',redeemAmount:'',confirmedCount:''};
2743 if (_getOptions_isActivatedByKey('displayAdminAreaColumnConfirmedCount')) {
2744 additionalColumn.confirmedCount = '<th>'+_x('Confirmed Count', 'label', 'event-tickets-with-ticket-scanner')+'</th>';
2745 }
2746 if (_getOptions_isActivatedByKey('displayAdminAreaColumnBillingName')) {
2747 additionalColumn.customerName = '<th>'+_x('Customer', 'label', 'event-tickets-with-ticket-scanner')+'</th>';
2748 additionalColumn_counter_before_created_field++;
2749 }
2750 if (_getOptions_isActivatedByKey('displayAdminAreaColumnBillingCompany')) {
2751 additionalColumn.customerCompany = '<th>'+_x('Company', 'label', 'event-tickets-with-ticket-scanner')+'</th>';
2752 additionalColumn_counter_before_created_field++;
2753 }
2754 if (_getOptions_isActivatedByKey('displayAdminAreaColumnRedeemedInfo')) {
2755 additionalColumn.redeemAmount = '<th>'+_x('Redeem Amount', 'label', 'event-tickets-with-ticket-scanner')+'</th>';
2756 }
2757
2758 tabelle_codes.html('<thead><tr><th style="text-align:left;padding-left:10px;"><input type="checkbox" data-id="checkAll"></th><th>&nbsp;</th><th align="left">'
2759 +_x('Ticket', 'label', 'event-tickets-with-ticket-scanner')+'</th>'+additionalColumn.customerName+additionalColumn.customerCompany+'<th align="left">'
2760 +_x('List', 'label', 'event-tickets-with-ticket-scanner')+'</th><th align="left">'
2761 +_x('Created', 'label', 'event-tickets-with-ticket-scanner')+'</th>'+additionalColumn.confirmedCount+'<th align="left">'
2762 +_x('Redeemed', 'label', 'event-tickets-with-ticket-scanner')+'</th>'+additionalColumn.redeemAmount+'<th>'
2763 +_x('OrderId', 'label', 'event-tickets-with-ticket-scanner')+'</th><th>CVV</th><th>'
2764 +_x('Status', 'label', 'event-tickets-with-ticket-scanner')+'</th><th></th></tr></thead><tfoot><th colspan="10" style="text-align:left;font-weight:normal;padding-left:0;padding-bottom:0;"></th></tfoot>');
2765 tabelle_codes.find('input[data-id="checkAll"]').on('click', (e)=> {
2766 let isChecked = $(e.currentTarget).prop('checked');
2767 let found = false;
2768 tabelle_codes.find('input[data-type="select-checkbox"]').each((i,v)=>{
2769 $(v).prop('checked', isChecked);
2770 found = true;
2771 });
2772 if (isChecked && found) {
2773 //drop_codes_bulk.prop("disabled", false);
2774 } else {
2775 //drop_codes_bulk.prop("disabled", true);
2776 }
2777 });
2778 let btn_codes_new = $('<button/>').addClass("button-primary").html(_x('Add', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2779 if (tabelle_liste_datatable.page.info().recordsTotal === 0) {
2780 alert(__("You need to create a ticket list first before you can add tickets.", 'event-tickets-with-ticket-scanner'));
2781 } else {
2782 if (!isPremium() && tabelle_codes_datatable.page.info().recordsTotal > myAjax._max.codes_total) {
2783 alert(__("You reached maximum amount of tickets. You need to delete tickets before you can add more new tickets or buy the premium version to have unlimited tickets.", 'event-tickets-with-ticket-scanner'));
2784 } else {
2785 LAYOUT.renderAddCodes();
2786 }
2787 }
2788 });
2789 let btn_codes_empty = $('<button/>').addClass("button-secondary").html(__('Empty table', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2790 LAYOUT.renderYesNo(__('Empty table', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: name of ticket table */__('Do you want to empty the "%s" table? All data will be lost.', 'event-tickets-with-ticket-scanner'), _x("Event Tickets", 'title', 'event-tickets-with-ticket-scanner')), ()=>{
2791 LAYOUT.renderYesNo(__('Empty table - last chance', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: name of ticket table */__('Are you sure? You will not be able to restore the data, except you have a backup of your database. All data will be lost.', 'event-tickets-with-ticket-scanner'), _x("Event Tickets", 'title', 'event-tickets-with-ticket-scanner')), ()=>{
2792 _makeGet('emptyTableCodes', null, ()=>{
2793 tabelle_codes_datatable.ajax.reload();
2794 });
2795 });
2796 });
2797 });
2798 let btn_codes_reload = $('<button/>').addClass("button-secondary").html(__('Refresh table', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2799 LAYOUT.renderSpinnerShow();
2800 tabelle_codes_datatable.ajax.reload();
2801 window.setTimeout(()=>{LAYOUT.renderSpinnerHide();}, 1500);
2802 });
2803 let btn_codes_export = $('<button/>').addClass("button-secondary").html(_x('Export tickets', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
2804 //let url = _requestURL('exportTableCodes', null);
2805 //window.open(url, "_blank");
2806 //console.log(tabelle_codes_datatable.page.info());
2807 __showMaskExport(tabelle_codes_datatable.page.info().recordsTotal);
2808 });
2809 let drop_codes_bulk = $('<select data-id="bulk-code-action" />')
2810 .html('<option value="">'+_x('Bulk Action', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2811 //.append('<option value="delete">'+_x('Delete', 'label', 'event-tickets-with-ticket-scanner')+'</option>');
2812 for (var key in BulkActions.codes) {
2813 let entry = BulkActions.codes[key];
2814 drop_codes_bulk.append('<option value="'+key+'">'+entry.label+'</option>');
2815 }
2816 drop_codes_bulk.on('change', ()=>{
2817 let val = drop_codes_bulk.val();
2818 if (val !== "") {
2819 let selectedElems = [];
2820 tabelle_codes.find('input[data-type="select-checkbox"]').each((i,v)=>{
2821 if ($(v).prop("checked")) selectedElems.push(v);
2822 });
2823 if (selectedElems.length) {
2824 let fkt = null;
2825 if (typeof BulkActions.codes[val] == "function") {
2826 fkt = BulkActions.codes[val];
2827 } else {
2828 fkt = BulkActions.codes[val].fkt;
2829 }
2830 fkt && fkt(selectedElems, tabelle_codes_datatable);
2831 }
2832 }
2833 drop_codes_bulk.val('');
2834 });
2835 let drop_search = $('<select data-id="filter_type" />');
2836 drop_search.append('<option value="">'+_x('Default search filter', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2837 drop_search.append('<option value="LIST:">'+_x('Filter for list id', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2838 drop_search.append('<option value="ORDERID:">'+_x('Filter for order id', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2839 drop_search.append('<option value="CVV:">'+_x('Filter for cvv value', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2840 drop_search.append('<option value="STATUS:">'+_x('Filter for status (1:active, 0:inactive, 2:stolen)', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2841 drop_search.append('<option value="REDEEMED:">'+_x('Filter for redeemed status (0:not redeemed yet, 1:redeemed)', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2842 drop_search.append('<option value="USERID:">'+_x('Filter for registered user id', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2843 drop_search.append('<option value="CUSTOMER:">'+_x('Filter for customer name in billing first and last name', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2844 drop_search.append('<option value="PRODUCTID:">'+_x('Filter for product id', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2845 drop_search.append('<option value="DAYPERTICKET:">'+_x('Filter for choosen date (enter YYYY-MM-DD)', 'option value', 'event-tickets-with-ticket-scanner')+'</option>');
2846 drop_search.on("change", e=>{
2847 let old_search = tabelle_codes_datatable.search().trim();
2848 let search = drop_search.val();
2849 if (old_search && old_search.length > 0) {
2850 search = old_search + " & " + search;
2851 }
2852 tabelle_codes_datatable.search(search);
2853 });
2854 this.div_codes
2855 .html($('<div/>').css('text-align', 'right').css('margin-bottom','10px')
2856 .append(drop_codes_bulk)
2857 .append(drop_search)
2858 .append(btn_codes_export)
2859 .append(btn_codes_empty)
2860 .append(btn_codes_reload)
2861 .append(isPremium()?'':' '+sprintf(/* translators: 1: max amount tickets 2: premium link */__('Max. %1$d tickets. Unlimited with %2$s', 'event-tickets-with-ticket-scanner'), myAjax._max.codes_total, getLabelPremiumOnly())+' ').append(btn_codes_new));
2862 this.div_codes.append(tabelle_codes);
2863
2864 let table_columns = [
2865 {"data":null,"orderable":false,"defaultContent":'', "render":function (data, type, row) {
2866 return '<input type="checkbox" data-type="select-checkbox" data-key="'+data.id+'" data-code="'+data.code+'">';
2867 }},
2868 {"data":null,"className":'details-control',"orderable":false,"defaultContent":''},
2869 {"data":"code_display", "orderable":true, "render":(data,type,row)=>{
2870 return destroy_tags(data);
2871 }},
2872 {"data":"list_name", "orderable":true, "render":(data,type,row)=>{
2873 return destroy_tags(data);
2874 }},
2875 {"data":"time", "className":"dt-center", "orderable":true,
2876 "render":function (data, type, row) {
2877 return '<span style="display:none;">'+data+'</span>'+DateFormatStringToDateTimeText(data);
2878 }
2879 },
2880 {"data":"redeemed", "orderable":true, "className":"dt-center", "render":function(data, type, row) {
2881 if (data == 1) {
2882 return 'yes';
2883 } else {
2884 return '';
2885 }
2886 }},
2887 {"data":"order_id", "className":"dt-right", "orderable":true},
2888 {"data":null, "orderable":false, "className":"dt-center", "render":function(data, type, row){
2889 return data.cvv === "" ? "" : '****';
2890 }},
2891 {"data":null, "orderable":true, "className":"dt-center", "render":function(data, type, row){
2892 let _stat = '';
2893 if (data.meta != "") {
2894 let metaObj = JSON.parse(data.meta);
2895 if (typeof metaObj['used'] !== "undefined") {
2896 if (metaObj.used.reg_request !== "") _stat = '/used';
2897 }
2898 }
2899 if (data.aktiv === "2") return '<span style="color:red;">'+_x('stolen', 'label', 'event-tickets-with-ticket-scanner')+'</span>'+_stat;
2900 return data.aktiv === "1" ? '<span style="color:green;">'+__('active', 'event-tickets-with-ticket-scanner')+'</span>'+_stat : '<span style="color:grey;">'+_x('is inactiv', 'label', 'event-tickets-with-ticket-scanner')+'</span>'+_stat;
2901 }},
2902 {"data":null,"orderable":false,"defaultContent":'',"className":"buttons dt-right",
2903 "render": function ( data, type, row ) {
2904 return '<button class="button-secondary" data-type="edit">'+_x('Edit', 'label', 'event-tickets-with-ticket-scanner')+'</button> <button class="button-secondary" data-type="delete">'+_x('Delete', 'label', 'event-tickets-with-ticket-scanner')+'</button>';
2905 }
2906 }
2907 ];
2908 let addition_column_offset = 0;
2909 if (_getOptions_isActivatedByKey('displayAdminAreaColumnBillingName')) {
2910 addition_column_offset++;
2911 table_columns.splice(3, 0, {
2912 "data":"_customer_name","orderable":false
2913 });
2914 }
2915 if (_getOptions_isActivatedByKey('displayAdminAreaColumnBillingCompany')) {
2916 addition_column_offset++;
2917 table_columns.splice(3, 0, {
2918 "data":"_customer_company","orderable":false
2919 });
2920 }
2921 if (_getOptions_isActivatedByKey('displayAdminAreaColumnConfirmedCount')) {
2922 addition_column_offset++;
2923 table_columns.splice(4+addition_column_offset, 0, {
2924 "data":null,"orderable":false,"defaultContent":'',"className":"dt-center",
2925 "render":function(data,type,row) {
2926 let ret = 0;
2927 let metaObj = getCodeObjectMeta(data);
2928 if(!metaObj) return ret;
2929 if (typeof metaObj.confirmedCount != "undefined") {
2930 ret = metaObj.confirmedCount;
2931 }
2932 return ret;
2933 }
2934 });
2935 }
2936 if (_getOptions_isActivatedByKey('displayAdminAreaColumnRedeemedInfo')) {
2937 addition_column_offset++;
2938 table_columns.splice(5+addition_column_offset, 0, {
2939 "data":null,"orderable":false,"defaultContent":'',"className":"dt-center",
2940 "render":function(data,type,row) {
2941 let ret = '';
2942 if (row._max_redeem_amount > 0) {
2943 ret = row._redeemed_counter+'/'+row._max_redeem_amount;
2944 } else {
2945 ret = row._redeemed_counter+'/unlimited';
2946 }
2947 return ret;
2948 }
2949 });
2950 }
2951
2952 tabelle_codes_datatable = $(this.div_codes).find('#'+id_codes).DataTable({
2953 "language": {
2954 emptyTable: '<div style="text-align:left;"><b>'+__('You have no tickets yet.', 'event-tickets-with-ticket-scanner')+'</b>'
2955 + '<p>Tickets (number) can be added by two ways.</p>'
2956 + '<ol>'
2957 + '<li>Automatically with each sale of a ticket product.<br>Please configure a woocommerce product to be a ticket product - recommended<br><a href="https://vollstart.com/event-tickets-quick-start-video" target="_blank">Check out the quick start video</a></li>'
2958 + '<li>Or add ticket numbers upfront to a ticket list<br>Click on the add button to import ticket numbers.<br>For this activate the option <b>wcassignmentReuseNotusedCodes</b></li></ol>'
2959 + '</div>'
2960 },
2961 "responsive": true,
2962 "search": {
2963 "search": typeof PARAS.code !== "undefined" ? encodeURIComponent(PARAS.code.trim()) : ''
2964 },
2965 footerCallback: function(row, data, start, end, display) {
2966 let data_anser = tabelle_codes_datatable.ajax.json();
2967 let text = sprintf(/* translators: 1: amount tickets 2: total amount tickets */__('Redeemed tickets: %1$d (filtered) of %2$d (total redeemed tickets)', 'event-tickets-with-ticket-scanner'), data_anser.redeemedRecordsFiltered, data_anser.redeemedRecordsTotal);
2968 var api = this.api();
2969 $(api.column(1).footer()).html(text);
2970 //$(api.tables().footer()).html(text);
2971 },
2972 "processing": true,
2973 "serverSide": true,
2974 "stateSave": false,
2975 "ajax": {
2976 "url": _requestURL('getCodes'),
2977 "type": 'GET'
2978 },
2979 "order": [[ 4 + additionalColumn_counter_before_created_field, "desc" ]],
2980 "columns": table_columns,
2981 "initComplete": function () {
2982 LAYOUT.renderSpinnerHide();
2983 },
2984 "autowidth":true
2985 });
2986 tabelle_codes.on('click', 'button[data-type="edit"]', function (e) {
2987 let data = tabelle_codes_datatable.row( $(this).parents('tr') ).data();
2988 __showMaskCode(data);
2989 });
2990 tabelle_codes.on('click', 'button[data-type="delete"]', function (e) {
2991 let data = tabelle_codes_datatable.row( $(this).parents('tr') ).data();
2992 LAYOUT.renderYesNo(_x('Do you want to delete?', 'title', 'event-tickets-with-ticket-scanner'), __('Are you sure, you want to delete this ticket?', 'event-tickets-with-ticket-scanner')+'<br><br><b>'+data.code+'</b>', ()=>{
2993 let _data = {'id':data.id};
2994 _makePost('removeCode', _data, result=>{
2995 tabelle_codes_datatable.ajax.reload();
2996 });
2997 });
2998 });
2999 $('#'+id_codes+' tbody').on('click', 'td.details-control', function () {
3000 function ___format(d) {
3001 let metaObj = [];
3002 if (d.meta) {
3003 metaObj = JSON.parse(d.meta);
3004 }
3005 let div = $('<div/>');
3006
3007 // hole das aktuelle Metaobj
3008 function __getData(_codeObj) {
3009 div.html(_getSpinnerHTML());
3010 _makeGet('getMetaOfCode',{'code':d.code}, dataMeta=>{
3011 if (_codeObj) { // um eine Aktualisierung in das codeObj aufzunehmen
3012 _codeObj.meta = JSON.stringify(dataMeta);
3013 updateCodeObject(d, _codeObj);
3014 metaObj = getCodeObjectMeta(d);
3015 }
3016
3017 div.html("");
3018 d.meta = JSON.stringify(dataMeta);
3019 d.metaObj = dataMeta;
3020
3021 let btn_grp = $('<div/>').addClass("btn-group").appendTo(div);
3022 $('<button>').html(_x('Display QR with ticket number', 'label', 'event-tickets-with-ticket-scanner')).appendTo(btn_grp).on("click", e=>{
3023 let id = 'qrcode_'+d.code+'_'+time();
3024 let content = _x('This QR image contains', 'label', 'event-tickets-with-ticket-scanner')+':<br><b>'+d.code+'</b><br><br><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode("'+d.code+'");</script>';
3025 LAYOUT.renderInfoBox(_x('QR with ticket number', 'title', 'event-tickets-with-ticket-scanner'), content);
3026 });
3027 if (d.metaObj.wc_ticket.is_ticket && typeof d.metaObj.wc_ticket._public_ticket_id !== "undefined" && d.metaObj.wc_ticket._public_ticket_id != "") {
3028 $('<button>').html(_x('Display QR with PUBLIC ticket number', 'label', 'event-tickets-with-ticket-scanner')).appendTo(btn_grp).on("click", e=>{
3029 let id = 'qrcode_'+d.code+'_'+time();
3030 let content = _x('This QR image contains', 'label', 'event-tickets-with-ticket-scanner')+':<br><b>'+d.metaObj.wc_ticket._public_ticket_id+'</b><br>'+_x('Can be used with the ticket scanner', 'label', 'event-tickets-with-ticket-scanner')+'<br><br><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode("'+d.metaObj.wc_ticket._public_ticket_id+'");</script>';
3031 LAYOUT.renderInfoBox(_x('QR with ticket number', 'title', 'event-tickets-with-ticket-scanner'), content);
3032 });
3033 }
3034 if (d.metaObj.wc_ticket.is_ticket && typeof d.metaObj.wc_ticket._qr_content !== "undefined" && d.metaObj.wc_ticket._qr_content != "") {
3035 $('<button>').html(_x('Display QR with your own QR content', 'label', 'event-tickets-with-ticket-scanner')).appendTo(btn_grp).on("click", e=>{
3036 let id = 'qrcode_own_'+d.code+'_'+time();
3037 let content = _x('This QR image contains', 'label', 'event-tickets-with-ticket-scanner')+':<br><b>'+d.metaObj.wc_ticket._qr_content+'</b><br>'+_x('Can be used with the ticket scanner', 'label', 'event-tickets-with-ticket-scanner')+'<br><br><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode("'+d.metaObj.wc_ticket._qr_content+'");</script>';
3038 LAYOUT.renderInfoBox(_x('QR with ticket number', 'title', 'event-tickets-with-ticket-scanner'), content);
3039 });
3040 }
3041 if (typeof d.metaObj._QR != "undefined" && typeof d.metaObj._QR.directURL != "undefined" && d.metaObj._QR.directURL != "") {
3042 $('<button>').html(_x('Display QR with URL', 'label', 'event-tickets-with-ticket-scanner')).appendTo(btn_grp).on("click", e=>{
3043 let id = 'qrcode_url_'+d.code+'_'+time();
3044 let qr_content = d.metaObj._QR.directURL;
3045 let content = _x('This QR image contains', 'label', 'event-tickets-with-ticket-scanner')+':<br><b>'+qr_content+'</b><br><br><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode("'+qr_content+'");</script>';
3046 LAYOUT.renderInfoBox(_x('QR with URL and code', 'title', 'event-tickets-with-ticket-scanner'), content);
3047 });
3048 }
3049 div.append('<div/>');
3050
3051 // male die Inhalte
3052 div.append('#'+d.id+'<br><b>'+_x('Created', 'label', 'event-tickets-with-ticket-scanner')+':</b> '+DateFormatStringToDateTimeText(d.time)+' ('+d.time+')<br><b>'+__('Ticket number', 'event-tickets-with-ticket-scanner')+':</b> '+d.code+'<br><b>'+__('Ticket display number', 'event-tickets-with-ticket-scanner')+':</b> '+d.code_display+'<br><b>'+_x('Code Verification Value (CVV)', 'label', 'event-tickets-with-ticket-scanner')+':</b> '+(d.cvv == "" ? '-' : d.cvv)+'<br><b>'+_x('is active', 'event-tickets-with-ticket-scanner')+':</b> '+(parseInt(d.aktiv,10) === 1?'True':'False'));
3053 div.append(_displayCodeDetails(d, metaObj, tabelle_codes_datatable));
3054
3055 div.append('<h3>'+_x('WooCommerce Order', 'title', 'event-tickets-with-ticket-scanner')+'</h3>');
3056 if (!_getOptions_Versions_isActivatedByKey("is_wc_available")) {
3057 div.append($("<div>").css("color", "red").html(__("WooCommerce not found", 'event-tickets-with-ticket-scanner')));
3058 }
3059 div.append('<b>'+_x('OrderId', 'label', 'event-tickets-with-ticket-scanner')+':</b> ' + (parseInt(d.order_id) === 0 ? '-' : '#'+d.order_id+' <a target="_blank" href="post.php?post='+d.order_id+'&action=edit">'+_x('Show in WooCommerce Orders', 'label', 'event-tickets-with-ticket-scanner')+'</a>'));
3060 if (typeof metaObj['woocommerce'] !== "undefined") {
3061 if (metaObj.woocommerce.order_id !== 0) {
3062 div.append($("<div>").html('<b>'+_x('Order from', 'label', 'event-tickets-with-ticket-scanner')+':</b> ').append($('<span>').text(DateFormatStringToDateTimeText(metaObj.woocommerce.creation_date)+' ('+metaObj.woocommerce.creation_date+')')));
3063 div.append($("<div>").html('<b>'+_x('Product Id', 'label', 'event-tickets-with-ticket-scanner')+':</b> ').append($('<span>').html(metaObj.woocommerce.product_id+' <a target="_blank" href="post.php?post='+encodeURIComponent(metaObj.woocommerce.product_id)+'&action=edit">'+_x('Show Product', 'label', 'event-tickets-with-ticket-scanner')+'</a>')));
3064 }
3065 }
3066 if (typeof metaObj.wc_ticket.subs !== "undefined" && metaObj.wc_ticket.subs.length > 0) {
3067 div.append('<h4>'+__('Related Subscriptions', 'event-tickets-with-ticket-scanner')+'</h4>');
3068 metaObj.wc_ticket.subs.forEach(sub=>{
3069 div.append($("<div>").html('<b>'+_x('Subscription Id', 'label', 'event-tickets-with-ticket-scanner')+':</b> ').append($('<span>').html(sub.order_id+' <a target="_blank" href="post.php?post='+encodeURIComponent(sub.order_id)+'&action=edit">'+_x('Show Subscription', 'label', 'event-tickets-with-ticket-scanner')+'</a> ['+DateTime2Text(sub.date)+']')));
3070 });
3071 }
3072 if (parseInt(d.order_id) > 0) {
3073 div.append($('<div style="margin-top:10px;">').html($('<button>').addClass("button-delete").html(_x('Delete WooCommerce order info for this ticket', 'label', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
3074 LAYOUT.renderYesNo(_x('Remove order', 'title', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: ticket number */__('Do you really want to remove your order information of this ticket "%s"? This will also remove the ticket number from the order! For the PREMIUM PLUGIN: It will only remove it from the position of the order. If you have in one order more than one item with ticket number, then it will only remove the ticket number(s) from this item on the order. For the BASIC PLUGIN, it will remove all tickets from all items on this order. Click OK to proceed the removal.', 'event-tickets-with-ticket-scanner'), d.code_display), ()=>{
3075 _makeGet('removeWoocommerceOrderInfoFromCode', {'code':d.code}, _codeObj=>{
3076 //tabelle_codes_datatable.ajax.reload();
3077 __getData(_codeObj);
3078 });
3079 });
3080 })));
3081 }
3082
3083 div.append('<h4>'+__('WooCommerce ticket sale', 'event-tickets-with-ticket-scanner')+'</h4>');
3084 div.append(_displayWCETicket(d, tabelle_codes_datatable));
3085
3086 div.append('<h3>'+__('WooCommerce Purchase Restriction', 'event-tickets-with-ticket-scanner')+'</h3>');
3087 if (typeof metaObj['wc_rp'] !== "undefined") {
3088 if (metaObj.wc_rp.order_id !== 0) {
3089 div.append($("<div>").html('<b>'+_x('Used for Order ID', 'label', 'event-tickets-with-ticket-scanner')+':</b> ').append($('<span>').html('#'+metaObj.wc_rp.order_id+' <a target="_blank" href="post.php?post='+encodeURIComponent(metaObj.wc_rp.order_id)+'&action=edit">'+_x('Open WooCommerce Order', 'label', 'event-tickets-with-ticket-scanner')+'</a>')));
3090 div.append($("<div>").html('<b>'+_x('Order from', 'label', 'event-tickets-with-ticket-scanner')+':</b> ').append($('<span>').text(metaObj.wc_rp.creation_date)));
3091 div.append($("<div>").html('<b>'+_x('Product Id', 'label', 'event-tickets-with-ticket-scanner')+'s:</b> ').append($('<span>').html(metaObj.wc_rp.product_id+' <a target="_blank" href="post.php?post='+encodeURIComponent(metaObj.wc_rp.product_id)+'&action=edit">'+_x('Show Product', 'label', 'event-tickets-with-ticket-scanner')+'</a>')));
3092 div.append($('<div style="margin-top:10px;">').html($('<button>').addClass("button-delete").html(__('Remove purchase ticket information', 'event-tickets-with-ticket-scanner')).on("click", ()=>{
3093 LAYOUT.renderYesNo(__('Remove purchase ticket information', 'event-tickets-with-ticket-scanner'), sprintf(/* translators: %s: ticket nummer */__('Do you really want to remove the purchase ticket information from the order of this ticket "%s"? This will remove the also the ticket(s) from the order items! This ticket can then be reused for purchases. Click OK to proceed the removal.', 'event-tickets-with-ticket-scanner'), d.code_display), ()=>{
3094 _makeGet('removeWoocommerceRstrPurchaseInfoFromCode', {'code':d.code}, _codeObj=>{
3095 //tabelle_codes_datatable.ajax.reload();
3096 __getData(_codeObj);
3097 });
3098 });
3099 })));
3100 } else {
3101 div.append($("<div>").html('<b>'+_x('Used for Order ID', 'label', 'event-tickets-with-ticket-scanner')+':</b> -'));
3102 }
3103 }
3104
3105 div.append('<h3>'+_x('Registered user', 'title', 'event-tickets-with-ticket-scanner')+'</h3>');
3106 div.append(_displayRegisteredUserForCode(d, metaObj, tabelle_codes_datatable));
3107
3108 div.append('<h3>Redeem operations</h3>');
3109 div.append(_displayRedeemOperationsForCode(d, metaObj));
3110
3111 div.append('<h3>'+_x('IP list checked for this ticket', 'title', 'event-tickets-with-ticket-scanner')+'</h3>');
3112 if (isPremium()) {
3113 div.append(PREMIUM.displayTrackedIPsForCode(d.code));
3114 } else {
3115 div.append(getLabelPremiumOnly());
3116 }
3117
3118 if (isPremium() && PREMIUM.displayCodeDetailsAtEnd) div.append(PREMIUM.displayCodeDetailsAtEnd(d, tabelle_codes_datatable, metaObj));
3119
3120 div.append("<hr>");
3121 });
3122 }
3123 __getData();
3124 return div;
3125 }
3126
3127 var tr = $(this).closest('tr');
3128 var row = tabelle_codes_datatable.row( tr );
3129 if ( row.child.isShown() ) {
3130 // This row is already open - close it
3131 row.child.hide();
3132 tr.removeClass('shown');
3133 } else {
3134 // Open this row
3135 row.child( ___format(row.data()) ).show();
3136 tr.addClass('shown');
3137 }
3138 });
3139 cbf && cbf();
3140 }); // end getOptions
3141 } // render layout
3142
3143 renderInfoBox(title, content, displayPlain) {
3144 let _options = {
3145 title: title,
3146 modal: true,
3147 minWidth: 400,
3148 minHeight: 200,
3149 buttons: [{text:_x('Ok', 'label', 'event-tickets-with-ticket-scanner'),
3150 click: function() {
3151 $(this).dialog("close");
3152 $(this).html("");
3153 }}]
3154 };
3155 let dlg = $('<div/>');
3156 if (displayPlain) {
3157 dlg.text(content);
3158 } else {
3159 dlg.html(content);
3160 }
3161 dlg.dialog(_options);
3162 return dlg;
3163 }
3164 renderSpinnerShow() {
3165 this.div_spinner.css("display", "block");
3166 }
3167 renderSpinnerHide() {
3168 this.div_spinner.css("display", "none");
3169 }
3170 renderFatalError(content) {
3171 return LAYOUT.renderInfoBox(_x('Error', 'title', 'event-tickets-with-ticket-scanner'), content);
3172 }
3173 renderYesNo(title, content, cbfYes, cbfNo) {
3174 let _options = {
3175 title: title,
3176 modal: true,
3177 minWidth: 400,
3178 minHeight: 200,
3179 buttons: [{text:_x('Yes', 'label', 'event-tickets-with-ticket-scanner'), click:function(){
3180 $(this).dialog("close");
3181 $(this).html("");
3182 cbfYes && cbfYes(dlg);
3183 }},{text:_x('No', 'label', 'event-tickets-with-ticket-scanner'), click:function(){
3184 $(this).dialog("close");
3185 $(this).html("");
3186 cbfNo && cbfNo();
3187 }}]
3188 };
3189 let dlg = $('<div/>').html(content);
3190 dlg.dialog(_options);
3191 return dlg;
3192 }
3193 }
3194
3195 function _displayCodeDetails(codeObj, metaObj, tabelle) {
3196 let div = $('<div/>');
3197 function __getData(_codeObj) {
3198 if (_codeObj) { // um eine Aktualisierung in das codeObj aufzunehmen
3199 updateCodeObject(codeObj, _codeObj);
3200 }
3201
3202 div.html("");
3203 if (codeObj.meta !== "") {
3204 let metaObj = getCodeObjectMeta(codeObj);
3205 if (typeof metaObj.confirmedCount !== "undefined") {
3206 div.append($('<div/>').html('<b>Confirmed count:</b> '+metaObj.confirmedCount));
3207 if (metaObj.confirmedCount > 0 && metaObj.validation) {
3208 if (metaObj.validation.first_success != "") {
3209 div.append($('<div/>').html('<b>First successful validation at:</b> '+metaObj.validation.first_success));
3210 div.append($('<div/>').html('<b>First successful validation IP:</b> '+metaObj.validation.first_ip));
3211 }
3212 if (metaObj.validation.last_success != "" && metaObj.validation.last_success != metaObj.validation.first_success) {
3213 div.append($('<div/>').html('<b>Last successful validation at:</b> '+metaObj.validation.last_success));
3214 div.append($('<div/>').html('<b>Last successful validation IP:</b> '+metaObj.validation.last_ip));
3215 }
3216 }
3217 }
3218 let btngrp = $('<div style="margin-top:10px;">');
3219 if (typeof metaObj.used !== "undefined") {
3220 div.append("<h3>Code marked as used</h3>");
3221 if (metaObj.used.reg_request !== "") {
3222 div.append($("<div>").html("<b>Request from:</b> ").append($('<span>').text(DateFormatStringToDateTimeText(metaObj.used.reg_request)+' ('+metaObj.used.reg_request+')')));
3223 div.append($("<div>").html("<b>Request by wordpress user:</b> ").append($('<span>').text(metaObj.used.reg_userid)));
3224 if (metaObj.used._reg_username) div.append($("<div>").html("<b>Request by wordpress user:</b> ").append($('<span>').text(metaObj.used._reg_username)));
3225 div.append($("<div>").html("<b>Request from IP:</b> ").append($('<span>').text(metaObj.used.reg_ip)));
3226
3227 btngrp.append($('<button/>').addClass("button-delete").html('Delete ticket used information').on("click", function(){
3228 LAYOUT.renderYesNo('Remove usage information', 'Do you really want to remove the usage information of this ticket "'+codeObj.code_display+'"? This will also reset the "Confirmed count" to 0.', ()=>{
3229 _makeGet('removeUsedInformationFromCode', {'code':codeObj.code}, _codeObj=>{
3230 //tabelle.ajax.reload();
3231 __getData(_codeObj);
3232 });
3233 });
3234 }));
3235 } else {
3236 div.append("Not used - still available");
3237 }
3238
3239 btngrp.append($('<button/>').addClass("button-edit").html('Edit wordpress user information').on("click", function(){
3240 // display eingabe maske für userid
3241 function __showMask(){
3242 let _options = {
3243 title: 'Edit requested wordpress user',
3244 modal: true,
3245 minWidth: 400,
3246 minHeight: 200,
3247 buttons: [
3248 {
3249 id: 'okBtn',
3250 text: "Ok",
3251 click: function() {
3252 ___submitForm();
3253 }
3254 },
3255 {
3256 text: "Cancel",
3257 click: function() {
3258 $( this ).dialog( "close" );
3259 $( this ).html('');
3260 }
3261 }
3262 ]
3263 };
3264 let dlg = $('<div />');
3265 let form = $('<form />').appendTo(dlg);
3266
3267 let elem_userid = $('<input type="number" min="0" value="'+metaObj.used.reg_userid+'" />');
3268 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3269 .html('Requested wordpress userid<br>')
3270 .append(elem_userid)
3271 .appendTo(form);
3272
3273 dlg.append('<p>Changes will trigger the webhook, if activated.<br>The IP will be updated too. The requested date will only be changed, if it was not set already.</p>');
3274 dlg.dialog(_options);
3275
3276 form.on("submit", function(event) {
3277 event.preventDefault();
3278 ___submitForm();
3279 });
3280 function ___submitForm() {
3281 let reg_userid = intval(elem_userid.val().trim());
3282 dlg.html(_getSpinnerHTML());
3283 let _data = {"reg_userid":reg_userid};
3284 form[0].reset();
3285 _data.code = codeObj.code;
3286 $('#okBtn').remove();
3287 _makeGet('editUseridForUsedInformationFromCode', _data, _codeObj=>{
3288 //tabelle.ajax.reload();
3289 __getData(_codeObj);
3290 closeDialog(dlg);
3291 }, function() {
3292 closeDialog(dlg);
3293 });
3294 }
3295 } // ende __showMask
3296 __showMask();
3297 })); // end button-edit
3298 }
3299 div.append(btngrp);
3300
3301 if (isPremium()) div.append(PREMIUM.displayCodeDetails(codeObj, tabelle, metaObj));
3302 } // endif codeObj.meta !== ""
3303 }
3304 __getData();
3305 return div;
3306 }
3307
3308 function _displayWCETicket(codeObj, tabelle) {
3309 let div = $('<div/>');
3310 function __getData(_codeObj) {
3311 if (_codeObj) { // um eine Aktualisierung in das codeObj aufzunehmen
3312 updateCodeObject(codeObj, _codeObj);
3313 }
3314
3315 div.html("");
3316 let metaObj = getCodeObjectMeta(codeObj);
3317 if(metaObj) {
3318 if (typeof metaObj.wc_ticket != "undefined" && typeof metaObj.wc_ticket.day_per_ticket != "undefined") {
3319 div.append($('<div>').html('<b>Date per Ticket (choosen by customer):</b> '+metaObj.wc_ticket.day_per_ticket +" ").append(
3320 $("<button>").html("Edit").on("click", ()=>{
3321
3322 let _options = {
3323 title: 'Edit Ticket Date',
3324 modal: true,
3325 minWidth: 400,
3326 minHeight: 200,
3327 buttons: [
3328 {
3329 id: 'okBtn',
3330 text: "Ok",
3331 click: function() {
3332 ___submitForm();
3333 }
3334 },
3335 {
3336 text: "Cancel",
3337 click: function() {
3338 $( this ).dialog( "close" );
3339 $( this ).html('');
3340 }
3341 }
3342 ]
3343 };
3344 let dlg = $('<div />');
3345 let form = $('<form />').appendTo(dlg);
3346
3347 let elem_input = $('<input type="date" value="'+metaObj.wc_ticket.day_per_ticket+'" />');
3348 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3349 .html('Date per Ticket (yyyy-mm-dd).<br><b>Very important to not break the date format and syntax, if you want to change the date!</b><br>')
3350 .append(elem_input)
3351 .appendTo(form);
3352
3353 dlg.dialog(_options);
3354
3355 form.on("submit", function(event) {
3356 event.preventDefault();
3357 ___submitForm();
3358 });
3359 function ___submitForm() {
3360 let v = elem_input.val().trim();
3361 dlg.html(_getSpinnerHTML());
3362 let _data = {"value":v, "key":'wc_ticket.day_per_ticket'};
3363 form[0].reset();
3364 _data.code = codeObj.code;
3365 $('#okBtn').remove();
3366 _makeGet('editTicketMetaEntry', _data, _codeObj=>{
3367 //tabelle.ajax.reload();
3368 __getData(_codeObj);
3369 closeDialog(dlg);
3370 }, function() {
3371 closeDialog(dlg);
3372 });
3373 }
3374 })
3375 ));
3376 }
3377 if (typeof metaObj.wc_ticket != "undefined" && typeof metaObj.wc_ticket.name_per_ticket != "undefined") {
3378 div.append($('<div>').html('<b>Name per Ticket (product detail setting):</b> '+metaObj.wc_ticket.name_per_ticket +" ").append(
3379 $("<button>").html("Edit").on("click", ()=>{
3380
3381 let _options = {
3382 title: 'Edit Ticket Name',
3383 modal: true,
3384 minWidth: 400,
3385 minHeight: 200,
3386 buttons: [
3387 {
3388 id: 'okBtn',
3389 text: "Ok",
3390 click: function() {
3391 ___submitForm();
3392 }
3393 },
3394 {
3395 text: "Cancel",
3396 click: function() {
3397 $( this ).dialog( "close" );
3398 $( this ).html('');
3399 }
3400 }
3401 ]
3402 };
3403 let dlg = $('<div />');
3404 let form = $('<form />').appendTo(dlg);
3405
3406 let elem_input = $('<input type="text" value="'+metaObj.wc_ticket.name_per_ticket+'" />');
3407 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3408 .html('Name per Ticket<br>')
3409 .append(elem_input)
3410 .appendTo(form);
3411
3412 dlg.dialog(_options);
3413
3414 form.on("submit", function(event) {
3415 event.preventDefault();
3416 ___submitForm();
3417 });
3418 function ___submitForm() {
3419 let v = elem_input.val().trim();
3420 dlg.html(_getSpinnerHTML());
3421 let _data = {"value":v, "key":'wc_ticket.name_per_ticket'};
3422 form[0].reset();
3423 _data.code = codeObj.code;
3424 $('#okBtn').remove();
3425 _makeGet('editTicketMetaEntry', _data, _codeObj=>{
3426 //tabelle.ajax.reload();
3427 __getData(_codeObj);
3428 closeDialog(dlg);
3429 }, function() {
3430 closeDialog(dlg);
3431 });
3432 }
3433 })
3434 ));
3435 }
3436 if (typeof metaObj.wc_ticket != "undefined" && typeof metaObj.wc_ticket.value_per_ticket != "undefined") {
3437 div.append($('<div>').html('<b>Value per Ticket (product detail setting):</b> '+metaObj.wc_ticket.value_per_ticket +" ").append(
3438 $("<button>").html("Edit").on("click", ()=>{
3439
3440 let _options = {
3441 title: 'Edit Ticket Value',
3442 modal: true,
3443 minWidth: 400,
3444 minHeight: 200,
3445 buttons: [
3446 {
3447 id: 'okBtn',
3448 text: "Ok",
3449 click: function() {
3450 ___submitForm();
3451 }
3452 },
3453 {
3454 text: "Cancel",
3455 click: function() {
3456 $( this ).dialog( "close" );
3457 $( this ).html('');
3458 }
3459 }
3460 ]
3461 };
3462 let dlg = $('<div />');
3463 let form = $('<form />').appendTo(dlg);
3464
3465 let elem_input = $('<input type="text" value="'+metaObj.wc_ticket.value_per_ticket+'" />');
3466 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3467 .html('Value per Ticket<br>')
3468 .append(elem_input)
3469 .appendTo(form);
3470
3471 dlg.dialog(_options);
3472
3473 form.on("submit", function(event) {
3474 event.preventDefault();
3475 ___submitForm();
3476 });
3477 function ___submitForm() {
3478 let v = elem_input.val().trim();
3479 dlg.html(_getSpinnerHTML());
3480 let _data = {"value":v, "key":'wc_ticket.value_per_ticket'};
3481 form[0].reset();
3482 _data.code = codeObj.code;
3483 $('#okBtn').remove();
3484 _makeGet('editTicketMetaEntry', _data, _codeObj=>{
3485 //tabelle.ajax.reload();
3486 __getData(_codeObj);
3487 closeDialog(dlg);
3488 }, function() {
3489 closeDialog(dlg);
3490 });
3491 }
3492 })
3493 ));
3494 }
3495 // Seat information
3496 if (typeof metaObj.wc_ticket != "undefined" && typeof metaObj.wc_ticket.seat_id != "undefined" && metaObj.wc_ticket.seat_id) {
3497 let seatInfo = metaObj.wc_ticket.seat_label || metaObj.wc_ticket.seat_identifier || ('Seat #' + metaObj.wc_ticket.seat_id);
3498 if (metaObj.wc_ticket.seat_category) {
3499 seatInfo += ' (' + metaObj.wc_ticket.seat_category + ')';
3500 }
3501 div.append($('<div>').html('<b>'+__('Seat', 'event-tickets-with-ticket-scanner')+':</b> ' + seatInfo));
3502 }
3503 if (typeof metaObj['woocommerce'] !== "undefined" && metaObj.woocommerce.order_id !== 0 && typeof metaObj.wc_ticket !== "undefined") {
3504 if (metaObj.wc_ticket.set_by_admin > 0) {
3505 div.append($("<div>").html("<b>Ticket set by admin user:</b> ").append($('<span>').text(metaObj.wc_ticket._set_by_admin_username+' ('+metaObj.wc_ticket.set_by_admin+') '+metaObj.wc_ticket.set_by_admin_date)));
3506 }
3507 if (metaObj.wc_ticket.redeemed_date != '') {
3508 div.append($("<div>").html("<b>Redeemed at:</b> ").append($('<span>').text(DateFormatStringToDateTimeText(metaObj.wc_ticket.redeemed_date)+' ('+metaObj.wc_ticket.redeemed_date+')')));
3509 div.append($("<div>").html("<b>Redeemed by wordpress userid:</b> ").append($('<span>').text(metaObj.wc_ticket.userid)));
3510 if (metaObj.wc_ticket._username) div.append($("<div>").html("<b>Redeemed by wordpress user:</b> ").append($('<span>').text(metaObj.wc_ticket._username)));
3511 div.append($("<div>").html("<b>IP while redeemed:</b> ").append($('<span>').text(metaObj.wc_ticket.ip)));
3512 if (metaObj.wc_ticket.redeemed_by_admin > 0) {
3513 div.append($("<div>").html("<b>Redeemed by admin user:</b> ").append($('<span>').text(metaObj.wc_ticket._redeemed_by_admin_username+' ('+metaObj.wc_ticket.redeemed_by_admin+')')));
3514 }
3515 }
3516 if (metaObj.wc_ticket.is_ticket == 1) {
3517 let _max_redeem_amount = typeof metaObj.wc_ticket._max_redeem_amount !== "undefined" ? metaObj.wc_ticket._max_redeem_amount : 1;
3518 $("<div>").html("<b>Ticket number: </b>"+codeObj.code_display).appendTo(div);
3519 $("<div>").html("<b>Public Ticket number: </b>"+metaObj.wc_ticket._public_ticket_id).appendTo(div);
3520 if (typeof metaObj.wc_ticket.stats_redeemed !== "undefined") {
3521 $("<div>").html("<b>Redeem usage: </b>"+metaObj.wc_ticket.stats_redeemed.length + ' of ' + (_max_redeem_amount == 0 ? 'unlimited' : _max_redeem_amount)).appendTo(div);
3522 }
3523 $("<div>").html('<b>Ticket Page:</b> <a target="_blank" href="'+metaObj.wc_ticket._url+'">Open Ticket Detail Page</a>').appendTo(div);
3524 $("<div>").html('<b>Ticket Page Testmode:</b> <a target="_blank" href="'+metaObj.wc_ticket._url+'?testDesigner=1">Open Ticket Detail Page with template test code</a>').appendTo(div);
3525 $("<div>").html('<b>Ticket PDF:</b> <a target="_blank" href="'+metaObj.wc_ticket._url+'?pdf">Open Ticket PDF</a>').appendTo(div);
3526 $("<div>").html('<b>Ticket PDF Testmode:</b> <a target="_blank" href="'+metaObj.wc_ticket._url+'?pdf&testDesigner=1">Open Ticket PDF with template test code</a>').appendTo(div);
3527 $("<div>").html('<b>Ticket Scanner:</b> <a target="_blank" href="'+_getTicketScannerURL()+encodeURIComponent(metaObj.wc_ticket._public_ticket_id)+'">Open Ticket Scanner with ticket</a>').appendTo(div);
3528 $("<div>").html('<b>Order Ticket Page:</b> <a target="_blank" href="'+metaObj.wc_ticket._order_page_url+'">Open Order Ticket Page</a>').appendTo(div);
3529 $("<div>").html('<b>Order PDF:</b> <a target="_blank" href="'+metaObj.wc_ticket._order_url+'">Open Order Ticket PDF</a>').appendTo(div);
3530 }
3531
3532 let btngrp = $('<div style="margin-top:10px;">').appendTo(div);
3533 if (metaObj.wc_ticket.is_ticket == 1) {
3534 $('<button>').html("Download PDF").appendTo(btngrp).on("click", ()=>{
3535 _downloadFile('downloadPDFTicket', {'code':codeObj.code}, "eventticket_"+codeObj.code+".pdf");
3536 return false;
3537 });
3538 $('<button>').html("Download Ticket Badge").appendTo(btngrp).on("click", ()=>{
3539 _downloadFile('downloadPDFTicketBadge', {'code':codeObj.code}, "eventticket_badge_"+codeObj.code+".pdf");
3540 return false;
3541 });
3542 $('<button>').html("Display QR with URL to PDF").appendTo(btngrp).on("click", e=>{
3543 let id = 'qrcode_'+codeObj.code+'_'+time();
3544 let content = 'This QR image contains:<br><b>'+codeObj.code+'</b><br><br><div id="'+id+'" style="text-align:center;"></div><script>jQuery("#'+id+'").qrcode("'+metaObj.wc_ticket._url+'?pdf");</script>';
3545 LAYOUT.renderInfoBox('QR with URL to PDF', content);
3546 });
3547 }
3548 if (metaObj.wc_ticket.is_ticket == 0) {
3549 $('<button>').html("Set as ticket sale").on("click", ()=>{
3550 LAYOUT.renderYesNo('Set as a ticket', 'Do you want to set this purchased ticket number as a ticket sale?', ()=>{
3551 _makeGet('setWoocommerceTicketForCode', {'code':codeObj.code}, _codeObj=>{
3552 __getData(_codeObj);
3553 });
3554 });
3555 }).appendTo(btngrp);
3556 }
3557 let btn_redeem = $('<button>').addClass("button-delete").html('Redeem ticket').on("click", ()=>{
3558 let reg_userid = (metaObj.user && metaObj.user.reg_userid) ? metaObj.user.reg_userid : 0;
3559 LAYOUT.renderYesNo('Redeem ticket', 'Do you really want to redeem the ticket number "'+codeObj.code_display+'"? Click OK to redeem the ticket.', ()=>{
3560 let userid = prompt('Optional. You can enter a userid you redeem the ticket for', reg_userid);
3561 _makeGet('redeemWoocommerceTicketForCode', {'code':codeObj.code, 'userid':userid}, _codeObj=>{
3562 __getData(_codeObj);
3563 });
3564 });
3565 }).appendTo(btngrp);
3566 let _max_redeem_amount = typeof metaObj.wc_ticket._max_redeem_amount !== "undefined" ? metaObj.wc_ticket._max_redeem_amount : 1;
3567 if (metaObj.wc_ticket.is_ticket == 0 || _max_redeem_amount == 0 || metaObj.wc_ticket.stats_redeemed.length >= _max_redeem_amount) {
3568 btn_redeem.attr("disabled", true);
3569 }
3570
3571 let btn_unredeem = $('<button>').addClass("button-delete").html('Delete redeem information').on("click", ()=>{
3572 LAYOUT.renderYesNo('Remove ticket information', 'Do you really want to remove the information that the ticket number "'+codeObj.code_display+'" is redeemed? Click OK to un-redeem the ticket and allow your customer to use the ticket again.', ()=>{
3573 _makeGet('removeRedeemWoocommerceTicketForCode', {'code':codeObj.code}, _codeObj=>{
3574 __getData(_codeObj);
3575 });
3576 });
3577 }).appendTo(btngrp);
3578 if (metaObj.wc_ticket.is_ticket == 0 || metaObj.wc_ticket.redeemed_date == "") {
3579 btn_unredeem.attr("disabled", true);
3580 }
3581 if (metaObj.wc_ticket.is_ticket == 1 && metaObj.wc_ticket.redeemed_date == "") {
3582 $('<button>').addClass("button-delete").html("Unset Ticket").on("click", ()=>{
3583 LAYOUT.renderYesNo('Remove ticket', 'Do you really want to remove the ticket info from this ticket number? The WooCommerce sale will be set and you need to remove it manually.', ()=>{
3584 _makeGet('removeWoocommerceTicketForCode', {'code':codeObj.code}, _codeObj=>{
3585 __getData(_codeObj);
3586 });
3587 });
3588 }).appendTo(btngrp);
3589 }
3590 }
3591 }
3592 }
3593 __getData();
3594 return div;
3595 }
3596
3597 function _displayRedeemOperationsForCode(d, metaObj) {
3598 let div = $('<div/>');
3599 if (typeof metaObj.wc_ticket.stats_redeemed !== "undefined") {
3600 if (metaObj.wc_ticket.stats_redeemed.length > 0) {
3601 let t = $('<table>').appendTo(div);
3602 t.html('<tr><th>#</th><th>Date</th><th>IP</th><th>By admin</th><th>User ID</th></tr>').appendTo(t);
3603 metaObj.wc_ticket.stats_redeemed.forEach((v,idx)=>{
3604 let tr = $('<tr>').appendTo(t);
3605 $('<td>').html('#'+(idx+1)).appendTo(tr);
3606 $('<td>').html(DateFormatStringToDateTimeText(v.redeemed_date)+' ('+v.redeemed_date+')').appendTo(tr);
3607 $('<td>').html(v.ip).appendTo(tr);
3608 $('<td>').html(v.redeemed_by_admin == 1 ? 'Yes' : 'No').appendTo(tr);
3609 $('<td>').html(v.userid).appendTo(tr);
3610 });
3611 } else {
3612 div.html("no redeem operations yet");
3613 }
3614 }
3615 return div;
3616 }
3617
3618 function _displayRegisteredUserForCode(codeObj, metaObj, tabelle) {
3619 let div = $('<div/>');
3620 function __getData(_codeObj) {
3621 if (_codeObj) { // um eine Aktualisierung in das codeObj aufzunehmen
3622 updateCodeObject(codeObj, _codeObj);
3623 }
3624 div.html("");
3625 let btngrp = $('<div style="margin-top:10px;">');
3626 if (typeof codeObj.meta !== "undefined" && codeObj.meta !== "") {
3627 let metaObj = getCodeObjectMeta(codeObj);
3628 if (metaObj.user.reg_request !== "") {
3629 div.append($("<div>").html("<b>Register value:</b> ").append($('<span>').text(metaObj.user.value)));
3630 div.append($("<div>").html("<b>Register by wordpress userid:</b> ").append($('<span>').text(metaObj.user.reg_userid)));
3631 if (metaObj.user._reg_username) div.append($("<div>").html("<b>Register by wordpress user:</b> ").append($('<span>').text(metaObj.user._reg_username)));
3632 div.append($("<div>").html("<b>Request from:</b> ").append($('<span>').text(metaObj.user.reg_request)));
3633 div.append($("<div>").html("<b>Request from IP:</b> ").append($('<span>').text(metaObj.user.reg_ip)));
3634 btngrp.append($('<button/>').addClass("button-delete").html('Delete registered user information').on("click", function(){
3635 LAYOUT.renderYesNo('Remove register user value', 'Do you really want to remove the registered user value of this ticket "'+codeObj.code_display+'"?', ()=>{
3636 // sende delete user from code operation zum server
3637 div.html(_getSpinnerHTML());
3638 _makeGet('removeUserRegistrationFromCode', {'code':codeObj.code}, _codeObj=>{
3639 //tabelle.ajax.reload();
3640 __getData(_codeObj);
3641 });
3642 });
3643 }));
3644 } else {
3645 div.append("No registration to this ticket done");
3646 }
3647
3648 btngrp.append($('<button/>').addClass("button-edit").html('Edit registered user information').on("click", function(){
3649 // display eingabe maske für value und userid
3650 function __showMask(){
3651 let _options = {
3652 title: 'Edit registered user',
3653 modal: true,
3654 minWidth: 400,
3655 minHeight: 200,
3656 buttons: [
3657 {
3658 id: 'okBtn',
3659 text: "Ok",
3660 click: function() {
3661 ___submitForm();
3662 }
3663 },
3664 {
3665 text: "Cancel",
3666 click: function() {
3667 $( this ).dialog( "close" );
3668 $( this ).html('');
3669 }
3670 }
3671 ]
3672 };
3673 let dlg = $('<div />');
3674 let form = $('<form />').appendTo(dlg);
3675
3676 let elem_value = $('<input type="text" value="'+metaObj.user.value+'" />');
3677 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3678 .html('Registered value<br>')
3679 .append(elem_value)
3680 //.append('<br><i>If CVV is set, then your user will be asked to enter also the CVV to check the serial code.</i>')
3681 .appendTo(form);
3682 let elem_userid = $('<input type="number" min="0" value="'+metaObj.user.reg_userid+'" />');
3683 $('<div/>').css({"margin-top":"10px","margin-bottom": "15px","margin-right": "15px"})
3684 .html('Registered wordpress userid<br>')
3685 .append(elem_userid)
3686 .appendTo(form);
3687
3688 dlg.append('<p>Changes will trigger the webhook, if activated.<br>The IP will updated too. The registered date will only be changed, if it was not set already.</p>');
3689 dlg.dialog(_options);
3690
3691 form.on("submit", function(event) {
3692 event.preventDefault();
3693 ___submitForm();
3694 });
3695 function ___submitForm() {
3696 let reg_userid = intval(elem_userid.val().trim());
3697 let reg_value = elem_value.val().trim();
3698 dlg.html(_getSpinnerHTML());
3699 let _data = {"value":reg_value, "reg_userid":reg_userid};
3700 form[0].reset();
3701 _data.code = codeObj.code;
3702 $('#okBtn').remove();
3703 _makeGet('editUseridForUserRegistrationFromCode', _data, _codeObj=>{
3704 //tabelle.ajax.reload();
3705 __getData(_codeObj);
3706 closeDialog(dlg);
3707 }, function() {
3708 closeDialog(dlg);
3709 });
3710 }
3711 } // ende __showMask
3712 __showMask();
3713 })); // end button-edit
3714 div.append(btngrp);
3715 if (isPremium()) div.append(PREMIUM.displayRegisteredUserForCode(codeObj, tabelle, metaObj));
3716 } // endif typeof codeObj.meta !== "undefined" && codeObj.meta !== ""
3717 }
3718 __getData();
3719 return div;
3720 }
3721
3722 function addStyleCode(content) {
3723 let c = document.createElement('style');
3724 c.innerHTML = content;
3725 document.getElementsByTagName("head")[0].appendChild(c);
3726 }
3727 function addStyleTag(url, id, onloadfkt, attrListe, loadLatest) {
3728 var script = document.createElement('link');
3729 script.type = 'text/css';
3730 script.rel = "stylesheet";
3731 let myId = id;
3732 if (!myId) myId = url;
3733 if (document.getElementById(id) && document.getElementById(id).src === url) {
3734 onloadfkt && onloadfkt();
3735 return; // prevent re-adding the same tag
3736 }
3737 script.id = id;
3738 if (attrListe) for(var attr in attrListe) script.setAttribute(attr, attrListe[attr]);
3739 script.href = url;
3740 if (loadLatest) script.href += '?t='+new Date().getTime();
3741 if (typeof onloadfkt !== "undefined") script.onload = onloadfkt;
3742 document.getElementsByTagName("head")[0].appendChild(script);
3743 }
3744 function addScriptCode(content, id) {
3745 if (typeof system.DYNJS_CACHE.scriptCodeElements === "undefined") {
3746 system.DYNJS_CACHE.scriptCodeElements = {};
3747 }
3748 let c;
3749 if (id && typeof system.DYNJS_CACHE.scriptCodeElements[id] !== "undefined") {
3750 c = system.DYNJS_CACHE.scriptCodeElements[id];
3751 document.getElementsByTagName("head")[0].removeChild(c);
3752 } else {
3753 c = document.createElement('script');
3754 }
3755 c.innerHTML = content;
3756 if (id) {
3757 system.DYNJS_CACHE.scriptCodeElements[id] = c;
3758 }
3759 document.getElementsByTagName("head")[0].appendChild(c);
3760 }
3761 function addScriptTag(url, id, onloadfkt, attrListe, loadLatest) {
3762 var head = document.getElementsByTagName("head")[0];
3763 var script = document.createElement('script');
3764 script.type = 'text/javascript';
3765 let myId = id;
3766 if (!myId) myId = url;
3767 if (document.getElementById(id) && document.getElementById(id).src === url) {
3768 onloadfkt && onloadfkt();
3769 return; // prevent re-adding the same tag
3770 }
3771 script.id = id;
3772 if (attrListe) for(var attr in attrListe) script.setAttribute(attr, attrListe[attr]);
3773 script.src = url;
3774 if (loadLatest) script.src += '?t='+new Date().getTime();
3775 if (typeof onloadfkt !== "undefined") script.onload = onloadfkt;
3776 head.appendChild(script);
3777 }
3778
3779 function getPremiumProductURL() {
3780 return 'https://vollstart.com/event-tickets-with-ticket-scanner/?utm_source=etwts_plugin&utm_medium=plugin_link&utm_campaign=etwts_upgrade_to_premium';
3781 }
3782 function getLabelPremiumOnly() {
3783 return '[<a href="'+getPremiumProductURL()+'">PREMIUM ONLY</a>]';
3784 }
3785
3786 function _getSpinnerHTML() {
3787 return '<span class="lds-dual-ring"></span>';
3788 }
3789
3790 function _loadingJSDatatables(cbf) {
3791 let loaded = {};
3792 addStyleCode('table.dataTable tr.shown td.details-control {background: url('+myAjax._plugin_home_url+'/img/details_close.png) no-repeat center center;}td.details-control {background: url('+myAjax._plugin_home_url+'/img/details_open.png) no-repeat center center;cursor: pointer;}');
3793 addStyleTag(myAjax._plugin_home_url+'/3rd/datatables.min.css', 'jquery_dataTables', ()=>{
3794 loaded['1'] = true;
3795 if (loaded['2']) {
3796 cbf && cbf();
3797 }
3798 }, {'crossorigin':"anonymous"});
3799 addScriptTag(myAjax._plugin_home_url+"/3rd/datatables.min.js", 'jquery_dataTables', ()=>{
3800 loaded['2'] = true;
3801 if (loaded['1']) {
3802 cbf && cbf();
3803 }
3804 }, {'crossorigin':"anonymous", "charset":"utf8"});
3805 }
3806
3807 function isPremium() {
3808 return myAjax._isPremium == "1" || myAjax._isPremium === true;
3809 }
3810
3811 var BulkActions = {
3812 'codes': {
3813 'delete': {
3814 "label": _x('Delete', 'label', 'event-tickets-with-ticket-scanner'),
3815 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3816 LAYOUT.renderYesNo('Delete all selected tickets?', 'Are you sure, you want to delete all selected tickets?<br><br>'+selectedElems.length+' tickets will be deleted.', ()=>{
3817 let _data = {'ids':[]};
3818 selectedElems.forEach(v=>{
3819 _data.ids.push($(v).attr("data-key"));
3820 });
3821 _makePost('removeCodes', _data, result=>{
3822 tabelle_codes_datatable.ajax.reload();
3823 });
3824 });
3825 }
3826 },
3827 'remove_marked_used': {
3828 "label": _x("Remove marked as used", 'option', 'event-tickets-with-ticket-scanner'),
3829 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3830 LAYOUT.renderYesNo('Remove marked used?', 'Are you sure, you want to remove the used marked from all selected tickets?<br><br>'+selectedElems.length+' tickets will be changed.', ()=>{
3831 let _data = {'ids':[], 'codes':[]};
3832 selectedElems.forEach(v=>{
3833 _data.ids.push($(v).attr("data-key"));
3834 _data.codes.push($(v).attr("data-code"));
3835 });
3836 _makePost('removeUsedInformationFromCodeBulk', _data, result=>{
3837 tabelle_codes_datatable.ajax.reload();
3838 });
3839 });
3840 }
3841 },
3842 'remove_ticket_redeemed': {
3843 "label": _x("Delete Redeem Information", 'option', 'event-tickets-with-ticket-scanner'),
3844 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3845 LAYOUT.renderYesNo('Delete the redeem information?', 'Are you sure, you want to remove the the information about the redeem operation of the ticket?<br><br>'+selectedElems.length+' tickets will be changed.', ()=>{
3846 let _data = {'ids':[], 'codes':[]};
3847 selectedElems.forEach(v=>{
3848 _data.ids.push($(v).attr("data-key"));
3849 _data.codes.push($(v).attr("data-code"));
3850 });
3851 _makePost('removeRedeemWoocommerceTicketForCodeBulk', _data, result=>{
3852 tabelle_codes_datatable.ajax.reload();
3853 });
3854 });
3855 }
3856 },
3857 'generate_pdf': {
3858 "label": _x("Generate ticket PDF", 'option', 'event-tickets-with-ticket-scanner'),
3859 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3860 LAYOUT.renderYesNo('Generate the ticket PDF?', 'Are you sure, you want to generate the ticket PDFs for the selected tickets? This can take a while an could timeout the server.<br><br>'+selectedElems.length+' tickets will be added in one PDF.', ()=>{
3861 let _data = {'ids':[], 'codes':[]};
3862 selectedElems.forEach(v=>{
3863 _data.ids.push($(v).attr("data-key"));
3864 _data.codes.push($(v).attr("data-code"));
3865 });
3866 _downloadFile('generateOnePDFForTicketsBulk', _data, "tickets_merged.pdf");
3867 });
3868 }
3869 },
3870 'generate_badge': {
3871 "label": _x("Generate badge ticket", 'option', 'event-tickets-with-ticket-scanner'),
3872 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3873 LAYOUT.renderYesNo('Generate the ticket badge PDF?', 'Are you sure, you want to generate the ticket badge PDFs for the selected tickets? This can take a while an could timeout the server.<br><br>'+selectedElems.length+' badges will be added in one PDF.', ()=>{
3874 let _data = {'ids':[], 'codes':[]};
3875 selectedElems.forEach(v=>{
3876 _data.ids.push($(v).attr("data-key"));
3877 _data.codes.push($(v).attr("data-code"));
3878 });
3879 _downloadFile('generateOnePDFForBadgesBulk', _data, "ticketbadges_merged.pdf");
3880 });
3881 }
3882 },
3883 'move_to_list':{
3884 "label": _x("Move to ticket list", 'option', 'event-tickets-with-ticket-scanner'),
3885 "fkt": (selectedElems, tabelle_codes_datatable)=>{
3886 let content = $('<div>');
3887 let div_code_list = _createDivInput(_x('Assign selected tickets to this ticket list', 'label', 'event-tickets-with-ticket-scanner')).appendTo(content);
3888 let input_code_list = $('<select><option value="0">'+_x('None', 'option value', 'event-tickets-with-ticket-scanner')+'</select></select>').appendTo(div_code_list);
3889 DATA_LISTS.forEach(v=>{
3890 input_code_list.append('<option value="'+v.id+'">'+v.name+'</option>');
3891 });
3892 content.append("<br>");
3893 LAYOUT.renderYesNo('Move ticket(s) to ticket list', content, ()=>{
3894 let _data = {'ids':[], 'codes':[], 'list_id':input_code_list.val()};
3895 selectedElems.forEach(v=>{
3896 _data.ids.push($(v).attr("data-key"));
3897 _data.codes.push($(v).attr("data-code"));
3898 });
3899 _makePost('assignTicketListToTicketsBulk', _data, result=>{
3900 tabelle_codes_datatable.ajax.reload();
3901 });
3902 });
3903 }
3904 }
3905 }
3906 }
3907
3908 function addTabCSS() {
3909 $('<style>')
3910 .prop('type', 'text/css')
3911 .html(`
3912 .tabs {
3913 width: 100%;
3914 display: block;
3915 }
3916 .tab-nav {
3917 list-style: none;
3918 padding: 0;
3919 margin: 0;
3920 display: flex;
3921 border-bottom: 1px solid #ccc;
3922 }
3923 .tab-nav li {
3924 margin: 0;
3925 }
3926 .tab-nav a {
3927 display: block;
3928 padding: 10px 20px;
3929 text-decoration: none;
3930 color: #333;
3931 border: 1px solid #ccc;
3932 border-bottom: none;
3933 background: #f9f9f9;
3934 margin-right: 5px;
3935 border-radius: 5px 5px 0 0;
3936 }
3937 .tab-nav a.active {
3938 background: #fff;
3939 border-bottom: 1px solid #fff;
3940 font-weight: bold;
3941 }
3942 .tab-content {
3943 display: none;
3944 padding: 20px;
3945 border: 1px solid #ccc;
3946 border-radius: 0 5px 5px 5px;
3947 background: #fff;
3948 }
3949 `)
3950 .appendTo('head');
3951 }
3952
3953 function getHelperFunktions() {
3954 return {
3955 _getSpinnerHTML:_getSpinnerHTML,
3956 _makePost:_makePost,
3957 _makeGet:_makeGet,
3958 _getMediaData:_getMediaData,
3959 _downloadFile:_downloadFile,
3960 _requestURL:_requestURL,
3961 _getLAYOUT:function(){ return LAYOUT;},
3962 _getDIV:function(){ return DIV;},
3963 _BulkActions:BulkActions,
3964 _closeDialog:closeDialog,
3965 _OPTIONS:function(){ return OPTIONS;},
3966 _getVarSYSTEM:function(){ return system;},
3967 _updateCodeObject:updateCodeObject,
3968 _getCodeObjectMeta:getCodeObjectMeta,
3969 _DateTime2Text:DateTime2Text,
3970 _DateFormatStringToDateTimeText:DateFormatStringToDateTimeText,
3971 _DateFormatStringToDateText:DateFormatStringToDateText,
3972 _compareVersions:compareVersions,
3973 _getBackButtonDiv:getBackButtonDiv,
3974 _addStyleTag:addStyleTag
3975 };
3976 }
3977
3978 function refreshNoncePeriodically() {
3979 // check if the last check of nonce is older than 4 minutes
3980 // do a ping to get the new nonce
3981 setInterval(()=>{
3982 let last_check = DATA.last_nonce_check;
3983 if (last_check == null || last_check == "") {
3984 last_check = 0;
3985 }
3986 let now = new Date().getTime();
3987 if (now - last_check > 240000) {
3988 _makeGet('ping', [], data=>{
3989 });
3990 }
3991 }, 60000);
3992 }
3993
3994 function init() {
3995 addStyleCode('.lds-dual-ring {display:inline-block;width:64px;height:64px;}.lds-dual-ring:after {content:" ";display:block;width:46px;height:46px;margin:1px;border-radius:50%;border:5px solid #fff;border-color:#2e74b5 transparent #2e74b5 transparent;animation:lds-dual-ring 0.6s linear infinite;}@keyframes lds-dual-ring {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}');
3996 addStyleTag(myAjax._plugin_home_url+'/css/styles_backend.css');
3997
3998 addScriptTag(myAjax._plugin_home_url+'/3rd/ace/ace.js');
3999
4000 addTabCSS();
4001
4002 DIV = $('#'+myAjax.divId);
4003 DIV.html(_getSpinnerHTML());
4004 LAYOUT = new Layout();
4005 function _init() {
4006 document.body.style.background = "#ffffff";
4007 _loadingJSDatatables(function() {
4008 if (typeof PARAS.display !== "undefined" && PARAS.display == 'options') {
4009 _displayOptionsArea();
4010 } else if (typeof PARAS.display !== "undefined" && PARAS.display == 'support') {
4011 _displaySupportInfoArea();
4012 } else if (typeof PARAS.display !== "undefined" && PARAS.display == 'authtokens') {
4013 _displayAuthTokensArea();
4014 } else if (typeof PARAS.display !== "undefined" && PARAS.display == 'faq') {
4015 _displayFAQArea();
4016 } else {
4017 LAYOUT.renderAdminPageLayout();
4018 }
4019 });
4020 }
4021
4022 if (isPremium() && myAjax._premJS !== "") {
4023 addScriptTag(myAjax._premJS, null, function() {
4024 PREMIUM = new sasoEventticketsPremium(myAjax, getHelperFunktions());
4025 _init();
4026 });
4027 } else {
4028 _init();
4029 }
4030 $('#wpfooter').css('display', 'none');
4031 refreshNoncePeriodically();
4032 }
4033 if (!doNotInit) init();
4034 return {
4035 init: init,
4036 form_fields_serial_format: _form_fields_serial_format,
4037 makePost: _makePost,
4038 getMediaData: _getMediaData
4039 };
4040
4041 }
4042 if (typeof Ajax_sasoEventtickets !== "undefined") {
4043 window.sasoEventtickets_backend = sasoEventtickets(Ajax_sasoEventtickets);
4044 }