PageSourceSearch

https://boaghana.com/wp-content/plugins/date-time-picker-field/public/js/avdp-availability-core.js?ver=3.0

js boaghana.com collected 2026-10-07 20:17:13 UTC 18,014 bytes, 471 lines download raw bytes

1/**
2 * Availability Core - Pure slot calculation and date logic for datepicker.
3 * Extracted for testability; used by avdp-public.js.
4 *
5 * @package Availability_Datepicker
6 * @since 2.4.0.13
7 */
8
9(function (global) {
10    'use strict';
11
12    var AVDPAvailabilityCore = {};
13
14    /**
15     * Parse a date string flexibly, handling various formats.
16     *
17     * @param {string}      dateStr  Date string to parse.
18     * @param {string|null} [format] Optional format hint (e.g. 'd/m/Y', 'd-m-Y', 'd.m.Y', 'Y-m-d').
19     * @returns {Date|null}
20     */
21    function parseFlexibleDate(dateStr, format) {
22        if (!dateStr) return null;
23
24        var parsedDate;
25        var parts;
26
27        var isDayFirst = format && /^d[\/\-\.]m[\/\-\.]Y/.test(format);
28
29        if (!isDayFirst) {
30            parsedDate = new Date(dateStr);
31
32            if (isNaN(parsedDate.getTime())) {
33                parsedDate = new Date(dateStr.replace(/\//g, '-'));
34            }
35
36            if (!isNaN(parsedDate.getTime())) {
37                return parsedDate;
38            }
39        }
40
41        parts = dateStr.split(/[-\/.]/);
42        if (parts.length >= 3) {
43            if (isDayFirst) {
44                parsedDate = new Date(
45                    parseInt(parts[2], 10),
46                    parseInt(parts[1], 10) - 1,
47                    parseInt(parts[0], 10)
48                );
49            } else if (parts[0].length === 4) {
50                parsedDate = new Date(
51                    parseInt(parts[0], 10),
52                    parseInt(parts[1], 10) - 1,
53                    parseInt(parts[2], 10)
54                );
55            } else if (parts[2].length === 4) {
56                parsedDate = new Date(
57                    parseInt(parts[2], 10),
58                    parseInt(parts[1], 10) - 1,
59                    parseInt(parts[0], 10)
60                );
61            }
62        }
63
64        return parsedDate || new Date(NaN);
65    }
66
67    /**
68     * Find the first available date based on weekly_hours, disabled dates, etc.
69     */
70    function getFirstAvailableDate(config) {
71        var dayMap = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
72        var weekly = config.weekly_hours || {};
73        var disabledDates = config.disabledDates || [];
74        var exemptedDates = config.exemptedDates || [];
75        var noticeMinutes = parseInt(config.minimum_notice, 10) || 0;
76        var bookingType = config.booking_type || 'fixed';
77
78        var startDate = new Date();
79        startDate.setTime(startDate.getTime() + (noticeMinutes * 60 * 1000));
80
81        for (var i = 0; i < 365; i++) {
82            var checkDate = new Date(startDate);
83            checkDate.setDate(startDate.getDate() + i);
84
85            var dayName = dayMap[checkDate.getDay()];
86            var dateStr = checkDate.getFullYear() + '-' +
87                String(checkDate.getMonth() + 1).padStart(2, '0') + '-' +
88                String(checkDate.getDate()).padStart(2, '0');
89
90            if (exemptedDates.indexOf(dateStr) !== -1) {
91                return checkDate;
92            }
93
94            if (disabledDates.indexOf(dateStr) !== -1) {
95                continue;
96            }
97
98            if (weekly[dayName]) {
99                var dayConfig = weekly[dayName];
100                var isEnabled = dayConfig.enabled === true ||
101                    dayConfig.enabled === 1 ||
102                    dayConfig.enabled === '1' ||
103                    dayConfig.enabled === 'true';
104
105                if (bookingType === 'daily') {
106                    if (isEnabled) {
107                        return checkDate;
108                    }
109                } else {
110                    if (isEnabled && dayConfig.slots && dayConfig.slots.length > 0) {
111                        // Ensure at least one slot survives the earliest possible booking time
112                        // (startDate = now + noticeMinutes). This guards both the case where today's
113                        // slots have passed and the case where minimum_notice pushes startDate into a
114                        // future day whose slots are also entirely past that shifted start time.
115                        var isStartDay = (
116                            checkDate.getFullYear() === startDate.getFullYear() &&
117                            checkDate.getMonth()    === startDate.getMonth()    &&
118                            checkDate.getDate()     === startDate.getDate()
119                        );
120                        if (isStartDay) {
121                            var candidateSlots = calculateSlots(checkDate, config);
122                            var remaining = filterTodaySlots(candidateSlots, 0, startDate);
123                            if (remaining.length === 0) {
124                                continue;
125                            }
126                        }
127                        return checkDate;
128                    }
129                }
130            }
131        }
132
133        return new Date();
134    }
135
136    /**
137     * Calculate slots from a custom time range (for allowed dates override).
138     */
139    function calculateSlotsFromCustomRange(date, start, end, config) {
140        var allowed = [];
141        var step = parseInt(config.step, 10) || 30;
142        var bufferBefore = parseInt(config.buffer_before, 10) || 0;
143        var bufferAfter = parseInt(config.buffer_after, 10) || 0;
144        var totalCycle = bufferBefore + step + bufferAfter;
145
146        var startParts = start.split(':');
147        var endParts = end.split(':');
148        var startMins = (parseInt(startParts[0], 10) * 60) + parseInt(startParts[1], 10);
149        var endMins = (parseInt(endParts[0], 10) * 60) + parseInt(endParts[1], 10);
150
151        var isCross = endMins < startMins;
152        var limit = isCross ? 1440 : endMins;
153
154        for (var slotStart = startMins + bufferBefore; slotStart + step <= limit; slotStart += totalCycle) {
155            var h = Math.floor(slotStart / 60);
156            var m = slotStart % 60;
157            allowed.push(
158                String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0')
159            );
160        }
161
162        if (isCross) {
163            for (var slotStart = bufferBefore; slotStart + step <= endMins; slotStart += totalCycle) {
164                var h = Math.floor(slotStart / 60);
165                var m = slotStart % 60;
166                var timeStr = String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');
167                if (allowed.indexOf(timeStr) === -1) {
168                    allowed.push(timeStr);
169                }
170            }
171        }
172
173        allowed.sort();
174        return allowed;
175    }
176
177    /**
178     * Calculate slots client-side from weekly hours or allowed_dates_with_times
179     */
180    function calculateSlots(date, config) {
181        var y = date.getFullYear();
182        var m = String(date.getMonth() + 1).padStart(2, '0');
183        var d = String(date.getDate()).padStart(2, '0');
184        var dateStr = y + '-' + m + '-' + d;
185
186        var allowedDatesWithTimes = config.allowed_dates_with_times || [];
187        for (var i = 0; i < allowedDatesWithTimes.length; i++) {
188            var allowedDate = allowedDatesWithTimes[i];
189            if (allowedDate.date === dateStr && allowedDate.start && allowedDate.end) {
190                return calculateSlotsFromCustomRange(date, allowedDate.start, allowedDate.end, config);
191            }
192        }
193
194        var dayMap = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
195        var dayName = dayMap[date.getDay()];
196        var weekly = config.weekly_hours;
197
198        if (!weekly || !weekly[dayName]) {
199            return [];
200        }
201
202        var dayConfig = weekly[dayName];
203        var isEnabled = dayConfig.enabled === true ||
204            dayConfig.enabled === 1 ||
205            dayConfig.enabled === '1' ||
206            dayConfig.enabled === 'true';
207
208        if (!isEnabled) {
209            return [];
210        }
211
212        var slotsData = dayConfig.slots;
213        if (!slotsData || slotsData.length === 0) {
214            return [];
215        }
216
217        var allowed = [];
218        var step = parseInt(config.step, 10) || 30;
219        var bufferBefore = parseInt(config.buffer_before, 10) || 0;
220        var bufferAfter = parseInt(config.buffer_after, 10) || 0;
221        var totalCycle = bufferBefore + step + bufferAfter;
222
223        slotsData.forEach(function (slotRange) {
224            var startParts = slotRange.start.split(':');
225            var endParts = slotRange.end.split(':');
226
227            var startMins = (parseInt(startParts[0], 10) * 60) + parseInt(startParts[1], 10);
228            var endMins = (parseInt(endParts[0], 10) * 60) + parseInt(endParts[1], 10);
229
230            var isCross = endMins < startMins;
231            var limit = isCross ? 1440 : endMins;
232
233            for (var slotStart = startMins + bufferBefore; slotStart + step <= limit; slotStart += totalCycle) {
234                var h = Math.floor(slotStart / 60);
235                var m = slotStart % 60;
236                allowed.push(
237                    String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0')
238                );
239            }
240        });
241
242        var yestDate = new Date(date);
243        yestDate.setDate(date.getDate() - 1);
244        var yestDayName = dayMap[yestDate.getDay()];
245
246        if (weekly[yestDayName] && weekly[yestDayName].enabled && weekly[yestDayName].slots) {
247            weekly[yestDayName].slots.forEach(function (slotRange) {
248                var startParts = slotRange.start.split(':');
249                var endParts = slotRange.end.split(':');
250                var startMins = (parseInt(startParts[0], 10) * 60) + parseInt(startParts[1], 10);
251                var endMins = (parseInt(endParts[0], 10) * 60) + parseInt(endParts[1], 10);
252
253                if (endMins < startMins) {
254                    for (var slotStart = bufferBefore; slotStart + step <= endMins; slotStart += totalCycle) {
255                        var h = Math.floor(slotStart / 60);
256                        var m = slotStart % 60;
257                        var timeStr = String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');
258                        if (allowed.indexOf(timeStr) === -1) {
259                            allowed.push(timeStr);
260                        }
261                    }
262                }
263            });
264        }
265
266        allowed.sort();
267
268        return allowed;
269    }
270
271    /**
272     * Check if a start date can begin a booking that spans min_days consecutive
273     * calendar days that are all available.
274     *
275     * A disabled weekday inside the window breaks the consecutive run and returns
276     * false immediately — matching the user expectation that a 5-night stay cannot
277     * start on Wednesday when the property is closed on weekends.
278     * An exempted date overrides both disabled-weekday and blocked-date rules and
279     * counts as an available day in the sequence.
280     */
281    function canStartSequentialBooking(startDate, minDays, config) {
282        if (!minDays || minDays <= 0) {
283            return true;
284        }
285
286        var disabledWeekDays = (config.disabledWeekDays || []).map(function (d) {
287            return parseInt(d, 10);
288        });
289        var blockedDates = config.disabledDates || [];
290        var exemptedDates = config.exemptedDates || [];
291
292        var current = new Date(startDate);
293        current.setHours(0, 0, 0, 0);
294
295        for (var i = 0; i < minDays; i++) {
296            var y = current.getFullYear();
297            var m = String(current.getMonth() + 1).padStart(2, '0');
298            var d = String(current.getDate()).padStart(2, '0');
299            var dateStr = y + '-' + m + '-' + d;
300            var dayOfWeek = current.getDay();
301
302            // Exempted dates are always available — they override every other rule.
303            if (exemptedDates.indexOf(dateStr) !== -1) {
304                current.setDate(current.getDate() + 1);
305                continue;
306            }
307
308            // A disabled weekday in the middle of the required span breaks the sequence.
309            if (disabledWeekDays.indexOf(dayOfWeek) !== -1) {
310                return false;
311            }
312
313            // An explicitly blocked date breaks the sequence.
314            if (blockedDates.indexOf(dateStr) !== -1) {
315                return false;
316            }
317
318            current.setDate(current.getDate() + 1);
319        }
320
321        return true;
322    }
323
324    /**
325     * Validate that the date range has a continuous sequence of available working days
326     */
327    function validateSequence(startDate, endDate, config) {
328        var disabledWeekDays = (config.disabledWeekDays || []).map(function (d) {
329            return parseInt(d, 10);
330        });
331        var blockedDates = config.disabledDates || [];
332        var exemptedDates = config.exemptedDates || [];
333
334        var current = new Date(startDate);
335        current.setHours(0, 0, 0, 0);
336        var end = new Date(endDate);
337        end.setHours(0, 0, 0, 0);
338
339        while (current <= end) {
340            var y = current.getFullYear();
341            var m = String(current.getMonth() + 1).padStart(2, '0');
342            var d = String(current.getDate()).padStart(2, '0');
343            var dateStr = y + '-' + m + '-' + d;
344            var dayOfWeek = current.getDay();
345
346            if (disabledWeekDays.indexOf(dayOfWeek) !== -1) {
347                current.setDate(current.getDate() + 1);
348                continue;
349            }
350
351            if (exemptedDates.indexOf(dateStr) !== -1) {
352                current.setDate(current.getDate() + 1);
353                continue;
354            }
355
356            if (blockedDates.indexOf(dateStr) !== -1) {
357                return {
358                    valid: false,
359                    blockedDate: dateStr
360                };
361            }
362
363            current.setDate(current.getDate() + 1);
364        }
365
366        return { valid: true, blockedDate: null };
367    }
368
369    /**
370     * Count available days between two dates (inclusive), excluding unavailable days.
371     *
372     * Exempted dates override both disabled-weekday and blocked-date rules and are
373     * counted as available. Used by avdp-public.js validateSelection to check
374     * min/max days against only the selectable days in a range.
375     *
376     * Pure function — no side effects, no DOM access.
377     *
378     * @param {Date}   startDate
379     * @param {Date}   endDate
380     * @param {Object} config    Requires disabledDates, disabledWeekDays, exemptedDates.
381     * @returns {number} Number of available days.
382     */
383    function countAvailableDays(startDate, endDate, config) {
384        var disabledDates = config.disabledDates || [];
385        var disabledWeekDays = (config.disabledWeekDays || []).map(function (d) {
386            return parseInt(d, 10);
387        });
388        var exemptedDates = config.exemptedDates || [];
389
390        var count = 0;
391        var current = new Date(startDate);
392        current.setHours(0, 0, 0, 0);
393        var end = new Date(endDate);
394        end.setHours(0, 0, 0, 0);
395
396        while (current <= end) {
397            var y = current.getFullYear();
398            var m = String(current.getMonth() + 1).padStart(2, '0');
399            var d = String(current.getDate()).padStart(2, '0');
400            var dateStr = y + '-' + m + '-' + d;
401            var dayOfWeek = current.getDay();
402
403            var isAvailable;
404            if (exemptedDates.indexOf(dateStr) !== -1) {
405                isAvailable = true;
406            } else if (disabledDates.indexOf(dateStr) !== -1) {
407                isAvailable = false;
408            } else if (disabledWeekDays.indexOf(dayOfWeek) !== -1) {
409                isAvailable = false;
410            } else {
411                isAvailable = true;
412            }
413
414            if (isAvailable) {
415                count++;
416            }
417
418            current.setDate(current.getDate() + 1);
419        }
420
421        return count;
422    }
423
424    /**
425     * Filter time slots for today, removing any that fall before (now + noticeMinutes).
426     *
427     * Pure function — no side effects, no DOM access.
428     * Extracted here for testability; called by avdp-public.js updateAllowTimes.
429     *
430     * @param {string[]} slots          Array of 'HH:MM' strings.
431     * @param {number}   noticeMinutes  Minimum notice in minutes (0 = filter past only).
432     * @param {Date}     now            Current time (defaults to new Date()).
433     * @returns {string[]} Filtered slots.
434     */
435    function filterTodaySlots(slots, noticeMinutes, now) {
436        if (!slots || slots.length === 0) return slots;
437        var reference = now instanceof Date ? now : new Date();
438        var minAvailableTime = new Date(reference.getTime() + ((noticeMinutes || 0) * 60 * 1000));
439
440        // If now + notice crosses midnight, every slot for today is already past.
441        if (minAvailableTime.getDate()     !== reference.getDate()     ||
442            minAvailableTime.getMonth()    !== reference.getMonth()    ||
443            minAvailableTime.getFullYear() !== reference.getFullYear()) {
444            return [];
445        }
446
447        var minMins = (minAvailableTime.getHours() * 60) + minAvailableTime.getMinutes();
448
449        return slots.filter(function (timeStr) {
450            var parts = timeStr.split(':');
451            var slotMins = (parseInt(parts[0], 10) * 60) + parseInt(parts[1], 10);
452            return slotMins >= minMins;
453        });
454    }
455
456    AVDPAvailabilityCore.parseFlexibleDate = parseFlexibleDate;
457    AVDPAvailabilityCore.getFirstAvailableDate = getFirstAvailableDate;
458    AVDPAvailabilityCore.calculateSlots = calculateSlots;
459    AVDPAvailabilityCore.calculateSlotsFromCustomRange = calculateSlotsFromCustomRange;
460    AVDPAvailabilityCore.canStartSequentialBooking = canStartSequentialBooking;
461    AVDPAvailabilityCore.validateSequence = validateSequence;
462    AVDPAvailabilityCore.countAvailableDays = countAvailableDays;
463    AVDPAvailabilityCore.filterTodaySlots = filterTodaySlots;
464
465    if (typeof module !== 'undefined' && module.exports) {
466        module.exports = AVDPAvailabilityCore;
467    } else {
468        global.AVDPAvailabilityCore = AVDPAvailabilityCore;
469    }
470
471})(typeof window !== 'undefined' ? window : this);

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.