1if (!String.format) { 2 // String format function is used to inject variables into lang-file strings 3 // {i} indicators will be replaced by the corresponding variables 4 String.format = function (format) { 5 var args = Array.prototype.slice.call(arguments, 1); 6 return format.replace(/{(\d+)}/g, function (match, number) { 7 return typeof args[number] != 'undefined' ? args[number] : match; 8 }); 9 }; 10} 11 12// various objects and arrays are used for different types of displays which require different structures, 13// however they all contain very similar data, eventData is to be considered the base for all other structures. 14let eventData, eventDataOrder, ticketData, dateData, categoryData = [], ticketIDs, calendarData, listViewDateInfo, 15 reservationEventData, reservationTicketData; 16 17 18// buildAjax creates the API request url to fetch ticket data 19// due to the variety of filters and other settings, this url has to be built dynamically 20function buildAjax(catID = -1, dateFrom = "", dateTo = "", event = "", location = "", price = "") { 21 let baseUrl = "api/frontend/eventSequence?includeLinks&includeEventDates"; 22 if (activateEventView && !activateCalendarView && !activateMonthlyView && !activateListView) { 23 baseUrl += "&excludeEventProducts=1&excludeAvailable=1"; 24 } 25 let url; 26 if (clEvent) url = baseUrl + `&parentCategoryId=${catID}&eventDateFrom=`; 27 else url = baseUrl + "&eventDateFrom="; 28 // if no dateFrom is set the API will default to using the current date 29 30 if (dateFrom == "") { 31 let date = new Date(); 32 date.setDate(date.getDate() - 1); 33 const day = showElapsed ? "01" : date.getDate(); 34 // if showElapsed is enabled the base date for the unfiltered rendering will be the first of the month instead of the current date 35 url += (date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + day) + "T23:59:59Z"; 36 } else url += dateFrom; 37 if (catID != -1 && !clEvent) { 38 url += "&q=eq(categoryId," + catID + ")"; 39 } 40 if (dateTo != "") url += '&eventDateTo=' + dateTo + 'T23:59:00Z'; 41 // event and location both use rql queries to filter and therefore have to be added as one parameter 42 if ((event != "" || location != "") && (catID == -1 || clEvent)) url += '&q='; 43 if (event != "") { 44 event = encodeURIComponent(event); 45 url += 'eq(name,"' + event + '")'; 46 } 47 if (location != "") { 48 location = encodeURIComponent(location); 49 url += 'eq(location,"' + location + '")'; 50 } 51 if (price != "") url += '&includeEventDateMinPrice&eventDateMinPriceTo=' + price; 52 if (editReservationId === -1) { 53 url += !(event != "" || location != "" || price != "") ? ('&eventDateLimit=' + unfilteredLimit) : ('&eventDateLimit=' + filteredLimit); 54 } 55 return url; 56} 57 58 59async function getData(callback, url) { 60 const categoryDataDone = getCategoryData(); 61 await $.ajax({ 62 url: appendSubShopId(url), 63 async: true, 64 success: (data) => { 65 categoryDataDone.finally(() => structureData(data, callback)); 66 }, 67 error: () => { 68 categoryDataDone.finally(() => callback(false)); 69 } 70 }); 71 await categoryDataDone; 72} 73 74 75function structureData(data, callback) { 76 eventData = {}, eventDataOrder = [], ticketData = {}, dateData = [], 77 ticketIDs = [], calendarData = {}, reservationEventData = {}, reservationTicketData = {}; 78 let j = 0; 79 for (let i = 0; i < data.length; i++) { 80 if (!data[i].hasOwnProperty("eventDates")) continue; 81 for (let j = 0; j < data[i].eventDates.length; j++) { 82 let object = data[i].eventDates[j]; 83 84 //all dates have to be reformatted, to avoid errors on iOS and Safari 85 let startArray = ('' + object.start).split(/[- :]/); 86 let endArray = ('' + object.end).split(/[- :]/); 87 let salesEndArray = ('' + object.salesEnd).split(/[- :]/); 88 let salesStartArray = ('' + object.salesStart).split(/[- :]/); 89 let startD = object.start = new Date(startArray[0], startArray[1] - 1, startArray[2], startArray[3], startArray[4], startArray[5]); 90 object.s = new Date(startArray[0], startArray[1] - 1, startArray[2]); 91 let endD = object.end = new Date(endArray[0], endArray[1] - 1, endArray[2], endArray[3], endArray[4], endArray[5]); 92 object.e = new Date(endArray[0], endArray[1] - 1, endArray[2]); 93 let saleEndD = object.salesEnd = new Date(salesEndArray[0], salesEndArray[1] - 1, salesEndArray[2], salesEndArray[3], salesEndArray[4], salesEndArray[5]); 94 let saleStartD = object.salesStart = new Date(salesStartArray[0], salesStartArray[1] - 1, salesStartArray[2], salesStartArray[3], salesStartArray[4], salesStartArray[5]); 95 96 object.startDate = (startD.getDate() < 10 ? '0' : '') + startD.getDate() + '.' + ((startD.getMonth() + 1) < 10 ? '0' : '') + (startD.getMonth() + 1) + '.' + startD.getFullYear();
97 object.endDate = (endD.getDate() < 10 ? '0' : '') + endD.getDate() + '.' + ((endD.getMonth() + 1) < 10 ? '0' : '') + (endD.getMonth() + 1) + '.' + endD.getFullYear(); 98 99 object.salesEndDate = (saleEndD.getDate() < 10 ? '0' : '') + saleEndD.getDate() + '.' + ((saleEndD.getMonth() + 1) < 10 ? '0' : '') + (saleEndD.getMonth() + 1) + '.' + saleEndD.getFullYear(); 100 object.salesStartDate = (saleStartD.getDate() < 10 ? '0' : '') + saleStartD.getDate() + '.' + ((saleStartD.getMonth() + 1) < 10 ? '0' : '') + (saleStartD.getMonth() + 1) + '.' + saleStartD.getFullYear(); 101 102 object.startTime = (startD.getHours() < 10 ? '0' : '') + startD.getHours() + ':' + (startD.getMinutes() < 10 ? '0' : '') + startD.getMinutes(); 103 object.endTime = (endD.getHours() < 10 ? '0' : '') + endD.getHours() + ':' + (endD.getMinutes() < 10 ? '0' : '') + endD.getMinutes(); 104 105 object.salesEndTime = (saleEndD.getHours() < 10 ? '0' : '') + saleEndD.getHours() + ':' + (saleEndD.getMinutes() < 10 ? '0' : '') + saleEndD.getMinutes(); 106 object.salesStartTime = (saleStartD.getHours() < 10 ? '0' : '') + saleStartD.getHours() + ':' + (saleStartD.getMinutes() < 10 ? '0' : '') + saleStartD.getMinutes(); 107 108 109 object.multiDay = object.start.getDate() !== object.end.getDate(); 110 111 object.startString = '' + object.start.toDateString(); 112 113 // attributes converted to boolean 114 object.notSaleableOnline = object.notSaleableOnline === "1"; 115 object.waitingListEnabled = object.waitingListEnabled === "1"; 116 117 //determine state, state is not 100% analogous to BE states 118 if (object.state !== "ELAPSED") { 119 if (object.state === "CANCELED") object.state = "CANCELLED"; 120 else if (object.available === 0 || object.available === "0") object.state = "SOLD_OUT"; 121 else if (object.notSaleableOnline) object.state = "NO_SALE"; 122 else if (object.salesEnd < new Date()) object.state = "SALE_END"; 123 else if (object.salesStart > new Date()) object.state = "PRE_SALE"; 124 else if ((object.quota / 3) > object.available) object.state = "NARROW"; 125 } 126 127 if (typeof calendarDate !== 'undefined') { 128 if (object.state !== "ELAPSED" && calendarDate.toDateString() === new Date().toDateString() || calendarDate > object.start) calendarDate = object.start; 129 } 130 131 // the description and title attributes are required by the calendar 132 object.description = String.format(calendar_item_desc, object.startDate, object.startTime, data[i].location); 133 object.title = data[i].name; 134 delete object.name; 135 136 dateData.push(object); 137 138 if (!calendarData[object.start.toDateString()]) calendarData[object.start.toDateString()] = []; 139 calendarData[object.start.toDateString()].push(object); 140 141 if (object.multiDay) { 142 let date = new Date(object.start); 143 // calculate the day-difference between start & end date 144 const dLimit = Math.floor(Math.abs(object.end - object.start) / (1000 * 60 * 60 * 24)); 145 for (let i = 1; i < dLimit; i++) { 146 date.setDate(date.getDate() + 1); 147 if (!calendarData[date.toDateString()]) calendarData[date.toDateString()] = []; 148 calendarData[date.toDateString()].push(object); 149 } 150 if (!calendarData[object.end.toDateString()]) calendarData[object.end.toDateString()] = []; 151 calendarData[object.end.toDateString()].push(object); 152 } 153 154 object.isParticipantAssignable = false; 155 for (const p in object.eventProducts) { 156 const item = object.eventProducts[p]; 157 //the ticket IDs are filtered and added to a list of IDs, these are required to fetch the required ticket information from the ticket data API 158 ticketIDs.indexOf(item.id) === -1 ? ticketIDs.push(item.id) : null; 159 if (item.isParticipantAssignable) { 160 object.isParticipantAssignable = true; 161 } 162 } 163 164 } 165 // eventProductLink is used for the eventView and sequence display in category-listing 166 const getItem = (ed, state) => ed.find(e => e.state === state);
167 const states = ['ON_SALE', 'NARROW', 'PRE_SALE', 'SALE_END', 'SOLD_OUT', 'CANCELLED', 'NO_SALE']; 168 let result; 169 for (const s of states) { 170 result = getItem(data[i].eventDates, s); 171 if (result) break; 172 } 173 if(result) { 174 for (const t of result.eventProducts) { 175 if (t.available > 0) { 176 result = t.productLink; 177 break; 178 } 179 } 180 data[i].eventProductLink = result; 181 } 182 eventData[data[i].id] = data[i]; 183 eventDataOrder[j] = data[i].id; 184 j++; 185 } 186 reservationEventData = eventData; 187 getTicketData(callback); 188} 189 190 191// fetches generalised ticket data which serves to reduce redundancy within the eventSequence model 192async function getTicketData(callback) { 193 await $.ajax({ 194 url: appendSubShopId("api/frontend/eventProductDescription?includeParticipantConfig" + (ticketIDs.length > 0 ? ("&q=in(id,(" + ticketIDs.join() + "))") : "")), 195 async: true, 196 success: (data) => { 197 for (let i = 0; i < data.length; i++) { 198 //ticket objects are stored as-is 199 ticketData[data[i].id] = data[i]; 200 } 201 reservationTicketData = ticketData; 202 if (activateEventView) evaluateGroupAvailability(callback); 203 else if (activateListView) evaluateListViewDates(callback); 204 else callback(true); 205 }, 206 error: () => { 207 callback(false); 208 } 209 }); 210} 211 212// this function fetches category-related static data and is used exclusively by the clEvent logic 213async function getCategoryData() { 214 categoryData = {}; 215 await $.ajax({ 216 url: appendSubShopId("api/category?parentCategoryId=" + catID), 217 async: true, 218 success: (data) => { 219 for (let i = 0; i < data.length; i++) { 220 categoryData[data[i].id] = data[i]; 221 } 222 } 223 }); 224} 225 226// groupAvailable is an attribute added to eventData objects which indicates the group availability 227// of an eventSequence based on the eventDate objects contained therein 228// the hierarchy of priority between eventDate states is reflected in the loop below 229function evaluateGroupAvailability(callback) { 230 for (let i in eventData) { 231 let groupAvailable = ''; 232 let event = eventData[i]; 233 for (let j in event.eventDates) { 234 let object = event.eventDates[j]; 235 groupAvailable = evaluateState(object, groupAvailable)[0]; 236 } 237 event.groupAvailable = groupAvailable; 238 } 239 if (activateListView) evaluateListViewDates(callback); 240 else callback(true); 241} 242 243// this function is used to evaluate the 244function evaluateState(object, groupState, groupId = -1) { 245 let state = groupState, id = groupId; 246 if (object.state == "ON_SALE" && groupState != 'ON_SALE') { 247 state = object.state; 248 id = object.id; 249 } else if (object.state == "NARROW" && groupState != 'ON_SALE') { 250 state = object.state; 251 id = object.id; 252 } else if (object.state == "PRE_SALE" && groupState != 'ON_SALE' && groupState != 'NARROW') { 253 state = object.state; 254 id = object.id; 255 } else if (object.state == "SALE_END" && groupState != 'ON_SALE' && groupState != 'NARROW' && groupState != 'PRE_SALE') { 256 state = object.state; 257 id = object.id; 258 } else if (object.state == "SOLD_OUT" && groupState != 'ON_SALE' && groupState != 'NARROW' && groupState != 'PRE_SALE' && groupState != 'SALE_END' && groupState != "WAITINGLIST") { 259 state = object.state; 260 id = object.id; 261 if (object.waitingListEnabled) { 262 state = "WAITINGLIST"; 263 id = object.id; 264 } 265 } else if (object.state == "CANCELLED" && groupState != 'ON_SALE' && groupState != 'NARROW' && groupState != 'PRE_SALE' && groupState != 'SALE_END' && groupState != 'SOLD_OUT' && groupState != "WAITINGLIST") { 266 state = object.state; 267 id = object.id; 268 } else if (object.state == "NO_SALE" && groupState != 'ON_SALE' && groupState != 'NARROW' && groupState != 'PRE_SALE' && groupState != 'SALE_END' && groupState != 'SOLD_OUT' && groupState != "WAITINGLIST" && groupState != 'CANCELLED') { 269 state = object.state; 270 id = object.id; 271 } 272 return [state, id]; 273} 274 275 276// this function is used to prepare a date reference model for the listView, 277// this is required to accurately group eventDates by date 278function evaluateListViewDates(callback) { 279 listViewDateInfo = {}; 280 for (let i = 0; i < dateData.length; i++) { 281 let object = dateData[i]; 282 if (object.multiDay) continue; 283 if (listViewDateInfo.hasOwnProperty(object.eventSequenceId)) { 284 if (listViewDateInfo[object.eventSequenceId].hasOwnProperty(object.startDate)) { 285 listViewDateInfo[object.eventSequenceId][object.startDate].ids.push(object.id); 286 let available = evaluateState(object, listViewDateInfo[object.eventSequenceId][object.startDate].state, listViewDateInfo[object.eventSequenceId][object.startDate].first); 287 listViewDateInfo[object.eventSequenceId][object.startDate].state = available[0]; 288 listViewDateInfo[object.eventSequenceId][object.startDate].first = available[1]; 289 } else { 290 listViewDateInfo[object.eventSequenceId][object.startDate] = {}; 291 listViewDateInfo[object.eventSequenceId][object.startDate].ids = []; 292 listViewDateInfo[object.eventSequenceId][object.startDate].ids.push(object.id); 293 listViewDateInfo[object.eventSequenceId][object.startDate].state = object.state; 294 listViewDateInfo[object.eventSequenceId][object.startDate].first = object.id; 295 } 296 } else { 297 listViewDateInfo[object.eventSequenceId] = {}; 298 listViewDateInfo[object.eventSequenceId][object.startDate] = {}; 299 listViewDateInfo[object.eventSequenceId][object.startDate].ids = []; 300 listViewDateInfo[object.eventSequenceId][object.startDate].ids.push(object.id); 301 listViewDateInfo[object.eventSequenceId][object.startDate].state = object.state; 302 listViewDateInfo[object.eventSequenceId][object.startDate].first = object.id; 303 } 304 } 305 // the result of this function will look like something akin to this: 306 // { 307 // 22: { 308 // 01.07.2021: {ids: Array(1), state: "ON_SALE", first: "3088"} 309 // 03.07.2021: {ids: Array(1), state: "ON_SALE", first: "3092"} 310 // 27.06.2021: {ids: Array(1), state: "ON_SALE", first: "3080"} 311 // 28.06.2021: {ids: Array(3), state: "ON_SALE", first: "2374"} 312 // } 313 // 24: { 314 // 11.07.2021: {ids: Array(3), state: "ON_SALE", first: "2710"} 315 // 17.07.2021: {ids: Array(1), state: "ON_SALE", first: "2712"} 316 // 26.06.2021: {ids: Array(4), state: "ON_SALE", first: "2700"} 317 // 27.06.2021: {ids: Array(3), state: "ON_SALE", first: "2702"} 318 // } 319 // } 320 // the object represents the different available times and dates for each eventSequence 321 // the "first" ID is the eventDate that is initially rendered, all other options are listed within the "ids" attribute 322 // the "state" attribute is not actually required by the listView to function however it is useful for debugging 323 callback(true); 324} 325 326 327// a helper function used by all views 328// it returns 2 values: the state of availability as well as a description 329function getAvailable(object, index) { 330 //special criteria such as when an event is sold out will affect how it is rendered 331 let state, desc, disabled = ""; 332 let ticket = object.eventProducts[index]; 333 if (object.state == "SOLD_OUT" || ticket.available == 0) { 334 state = "full"; 335 desc = calendar_legend_full; 336 disabled = "disabled"; 337 if (object.waitingListEnabled) desc = calendar_legend_waitinglist; 338 } else { 339 switch (object.state) { 340 case 'CANCELLED': 341 state = "full"; 342 desc = calendar_legend_cancelled; 343 disabled = "disabled"; 344 break; 345 case 'NO_SALE': 346 state = "nosale"; 347 desc = calendar_legend_nosale; 348 disabled = "disabled"; 349 break;
350 case 'SALE_END': 351 state = "full"; 352 desc = calendar_legend_full; 353 disabled = "disabled"; 354 break; 355 case 'PRE_SALE': 356 state = "presale"; 357 desc = calendar_legend_presale; 358 disabled = "disabled"; 359 break; 360 case 'NARROW': 361 state = "narrow"; 362 desc = calendar_legend_narrow; 363 break; 364 default: 365 let string, available; 366 state = "available"; 367 desc = calendar_legend_available; 368 object.available > ticket.available ? available = ticket.available : available = object.available; 369 available === 1 ? string = ticket_available : string = tickets_available; 370 if (eventListShowAvailable) desc = String.format(string, available); 371 372 } 373 } 374 desc = hideAvailabilityLabels ? '' : desc; 375 return [state, desc, disabled]; 376}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.