PageSourceSearch

https://kb-allgaeu.de/wp-content/plugins/quiz-master-next/renderer/assets/js/qsm-timer.js?ver=11.2.4

js kb-allgaeu.de collected 2026-10-06 13:43:13 UTC 18,053 bytes, 459 lines download raw bytes

1/**
2 * QSM Timer - Enhanced with LocalStorage, Resume Functionality, and Conditional Popups
3 * @package QSM
4 */
5var qsmTimerInterval = [];
6(function($) {
7    if (typeof qsm_timer_consumed_obj === 'undefined') {
8        window.qsm_timer_consumed_obj = {
9            qmn_count_upward_status : false
10        };
11    }
12    
13    QSMPagination.Timer = {
14        quizObjects: {},
15        intervals: {},
16        originalTitle: document.title,
17        warningThresholds: {
18            ninety: 90,  // 90% time consumed
19            ten: 10      // 10% time remaining
20        },
21
22        init: function() {
23            const self = this;
24            // Look for both new and legacy form selectors
25            $('.qsm-quiz-container').each(function() {
26                const $container = $(this);
27                const $form = $container.find('.qsm-quiz-form');
28                const quizId = $container.data('quiz-id');
29                if (quizId && !self.quizObjects[quizId]) {
30                    self.initTimer(quizId, $form);
31                }
32            });
33            this.bindEvents();
34        },
35
36        initTimer: function(quizId, $form) {
37            const data = this.getQuizData(quizId);
38            
39            if (!data.timer_limit || data.timer_limit <= 0) {
40                return;
41            }
42
43            const $timer = $form.find('.mlw_qmn_timer');
44            const totalTime = data.timer_limit * 60;
45            const consumedTime = Number.parseInt(localStorage.getItem('mlw_time_consumed_quiz' + quizId), 10) || 1;
46            const remainingTime = this.calculateInitialTime(quizId, totalTime, consumedTime);
47            
48            this.quizObjects[quizId] = {
49                form: $form,
50                timer: $timer.length ? $timer : null,
51                totalTime: totalTime,
52                remainingTime: remainingTime,
53                consumedTime: consumedTime,
54                isActive: false,
55                data: data,
56                warnings: {
57                    ninety: false,
58                    ten: false,
59                    expired: false
60                },
61                timerStatus: false,
62                timerInterval: null
63            };
64
65            // Update qmn_quiz_data for legacy compatibility
66            this.updateLegacyQuizData(quizId);
67            $form.find(".hiddentimer").html(remainingTime);
68            
69            if ($timer && $timer.length) {
70                $timer.show();
71            }
72            this.updateDisplay(quizId);
73            
74            // Auto-start conditions matching legacy behavior
75            if (this.shouldAutoStart(quizId, data)) {
76                this.start(quizId);
77            }
78        },
79
80        bindEvents: function() {
81            const self = this;
82            
83            // QSM-11 events
84            $(document).on('qsm_quiz_initialized', function(e, quizId, instance) {
85                if (!self.quizObjects[quizId]) {
86                    self.initTimer(quizId, instance.form);
87                }
88            });
89            
90            $(document).on('qsm_after_page_change', function(e, quizId, pageNumber) {
91                if (pageNumber > 1 && self.quizObjects[quizId] && !self.quizObjects[quizId].isActive) {
92                    self.start(quizId);
93                }
94            });
95            
96            $(document).on('qsm_quiz_started', function(e, quizId) {
97                if (self.quizObjects[quizId] && !self.quizObjects[quizId].isActive) {
98                    self.start(quizId);
99                }
100            });
101            
102            $(document).on('qsm_form_submit', function(e, quizId) {
103                self.endTimer(quizId);
104            });
105            
106            // Legacy events compatibility
107            $(document).on('qsm_activate_time_before qsm_activate_time_after', function(e, quizId, qmn_quiz_data) {
108                if (e.type === 'qsm_activate_time_before') {
109                    self.start(quizId);
110                }
111            });
112        },
113
114        start: function(quizId) {
115            const currentQuiz = this.quizObjects[quizId];
116            if (!currentQuiz || currentQuiz.isActive) return;
117            
118            currentQuiz.isActive = true;
119            currentQuiz.timerStatus = true;
120            
121            // Update legacy data
122            this.updateLegacyQuizData(quizId);
123            
124            // Mark quiz as started in localStorage (legacy keys)
125            localStorage.setItem('mlw_started_quiz' + quizId, 'yes');
126            
127            // Set initial time in localStorage if not set
128            if (!localStorage.getItem('mlw_quiz_start_date' + quizId)) {
129                localStorage.setItem('mlw_quiz_start_date' + quizId, new Date().getTime());
130            }
131            
132            // Start the interval
133            currentQuiz.timerInterval = setInterval(function() {
134                this.tick(quizId);
135            }.bind(this), 1000);
136            
137            this.intervals[quizId] = currentQuiz.timerInterval;
138            
139            // Show stop timer element if available
140            $('.qsm-quiz-container-' + quizId).find('.stoptimer-p').show();
141            
142            // Trigger legacy events
143            $(document).trigger('qsm_activate_time_after', [quizId, qmn_quiz_data]);
144        },
145
146        stop: function(quizId) {
147            const currentQuiz = this.quizObjects[quizId];
148            if (!currentQuiz) return;
149            
150            if (this.intervals[quizId]) {
151                clearInterval(this.intervals[quizId]);
152                delete this.intervals[quizId];
153            }
154            
155            if (currentQuiz.timerInterval) {
156                clearInterval(currentQuiz.timerInterval);
157                currentQuiz.timerInterval = null;
158            }
159            
160            currentQuiz.isActive = false;
161            currentQuiz.timerStatus = false;
162            
163            // Update legacy data
164            this.updateLegacyQuizData(quizId);
165        },
166
167        expire: function(quizId) {
168            const currentQuiz = this.quizObjects[quizId];
169            if (!currentQuiz || currentQuiz.warnings.expired) return;
170            
171            currentQuiz.warnings.expired = true;
172            this.stop(quizId);
173            currentQuiz.remainingTime = 0;
174            this.updateDisplay(quizId);
175            
176            // Add visual indicators
177            currentQuiz.timer?.addClass('qsm-timer-expired qsm-timer--danger');
178            
179            // Disable form inputs (matching legacy behavior)
180            const $quizForm = currentQuiz.form;
181            $quizForm.find('input[type="radio"]').attr('disabled', true);
182            $quizForm.find('input[type="checkbox"]').attr('disabled', true);
183            $quizForm.find('select').attr('disabled', true);
184            $quizForm.find('.mlw_qmn_question_comment').attr('disabled', true);
185            $quizForm.find('.mlw_answer_open_text').attr('disabled', true);
186            $quizForm.find('.mlw_answer_number').attr('readonly', true);
187            
188            // Add timer ended class and error message
189            const $container = $quizForm.closest('.qmn_quiz_container, .qsm-quiz-container');
190            $container.addClass('qsm_timer_ended');
191            
192            // Add error message if not already present
193            if (!$container.find('.qmn_error_message').length) {
194                const errorMsg = currentQuiz.data.quiz_time_over || 'Time is up!';
195                $container.prepend('<p class="qmn_error_message" style="color: red;">' + errorMsg + '</p>');
196            }
197            
198            // Handle auto-submit or show modal
199            if (Number(currentQuiz.data.enable_result_after_timer_end) === 1) {
200                // Auto-submit the form
201                setTimeout(function() {
202                    $container.find('.qsm-submit-btn, .qsm_submit_btn').trigger('click');
203                }, 1000);
204            } else {
205                // Show timer expired modal
206                $('.qsm-quiz-container-' + quizId).find('.stoptimer-p').hide();
207                MicroModal.show('modal-3');
208            }
209            
210            // Trigger events
211            $(document).trigger('qsm_timer_expired', [quizId, currentQuiz]);
212            $(document).trigger('qsm_timer_ended', [quizId, qmn_quiz_data, {
213                qmn_count_upward_status: false
214            }]);
215        },
216
217        tick: function(quizId) {
218            const currentQuiz = this.quizObjects[quizId];
219            if (!currentQuiz || currentQuiz.warnings.expired) return;
220            
221            // Check for scheduled time expiration
222            if (this.checkScheduledTimeExpiration(quizId)) {
223                return; // Exit if quiz expired due to scheduled time
224            }
225            
226            currentQuiz.remainingTime--;
227            currentQuiz.consumedTime++;
228            
229            if (currentQuiz.remainingTime < 0) {
230                currentQuiz.remainingTime = 0;
231            }
232            
233            // Update legacy data
234            this.updateLegacyQuizData(quizId);
235            
236            // Save current state to localStorage (both new and legacy keys)
237            localStorage.setItem('mlw_time_consumed_quiz' + quizId, currentQuiz.consumedTime);
238            localStorage.setItem('mlw_time_quiz' + quizId, currentQuiz.remainingTime);
239            
240            this.updateDisplay(quizId);
241            this.checkWarnings(quizId);
242            
243            // Trigger events
244            $(document).trigger('qsm_timer_tick', [quizId, currentQuiz.remainingTime, currentQuiz.consumedTime]);
245            $(document).trigger('qmn_timer_consumed_seconds', [quizId, qmn_quiz_data, qsm_timer_consumed_obj]);
246            $(document).trigger('load_timer_faces', [quizId, currentQuiz.remainingTime, currentQuiz.totalTime, this.secondsToTimer(currentQuiz.remainingTime)]);
247            
248            if (currentQuiz.remainingTime <= 0) {
249                this.expire(quizId);
250            }
251        },
252
253        updateDisplay: function(quizId) {
254            const currentQuiz = this.quizObjects[quizId];
255            if (!currentQuiz || currentQuiz.warnings.expired) return;
256            
257            const display = this.secondsToTimer(currentQuiz.remainingTime);
258            
259            if (currentQuiz.timer) {
260                currentQuiz.timer.text(display);
261            }
262
263            // Update browser tab title
264            if (currentQuiz.remainingTime > 0) {
265                document.title = display + ' - ' + this.originalTitle;
266            }
267        },
268
269        calculateInitialTime: function(quizId, totalTime, consumedTime) {
270            const timerStarted = localStorage.getItem('mlw_started_quiz' + quizId) || localStorage.getItem('qsm_started_quiz_' + quizId);
271            const storedConsumed = Number.parseInt(localStorage.getItem('mlw_time_consumed_quiz' + quizId), 10) || consumedTime || 1;
272            let remainingTime = totalTime;
273            
274            if (timerStarted === 'yes' && storedConsumed > 1) {
275                remainingTime = totalTime - storedConsumed + 1;
276                if (remainingTime < 0) remainingTime = 0;
277            }
278            
279            return remainingTime;
280        },
281
282        checkWarnings: function(quizId) {
283            const currentQuiz = this.quizObjects[quizId];
284            if (!currentQuiz) return;
285            
286            const percentConsumed = ((currentQuiz.totalTime - currentQuiz.remainingTime) / currentQuiz.totalTime) * 100;
287            const percentRemaining = (currentQuiz.remainingTime / currentQuiz.totalTime) * 100;
288            
289            // Check for 90% time consumed warning (matching legacy behavior)
290            const ninetyPercent = currentQuiz.totalTime - (currentQuiz.totalTime * 90 / 100);
291            if (currentQuiz.remainingTime <= ninetyPercent && !currentQuiz.warnings.ninety) {
292                currentQuiz.warnings.ninety = true;
293                this.showNinetyPercentWarning(quizId);
294            }
295            
296            // Check for 10% remaining warning
297            if (percentRemaining <= 10 && percentRemaining > 0 && !currentQuiz.warnings.ten) {
298                currentQuiz.warnings.ten = true;
299                currentQuiz.timer?.addClass('qsm-timer--warning');
300                
301                // Trigger warning event
302                $(document).trigger('qsm_timer_warning', [quizId, currentQuiz.remainingTime, percentRemaining]);
303            }
304        },
305
306        secondsToTimer: function(seconds) {
307            seconds = Number.parseInt(seconds, 10);
308            if (seconds < 0) seconds = 0;
309
310            const safeSeconds = seconds;
311            
312            const hours = Math.floor(safeSeconds / 3600);
313            const minutes = Math.floor((safeSeconds % 3600) / 60);
314            const secs = safeSeconds % 60;
315            
316            let formattedTime = '';
317            
318            // Hours
319            if (hours === 0) {
320                formattedTime = '00:';
321            } else if (hours < 10) {
322                formattedTime = '0' + hours + ':';
323            } else {
324                formattedTime = hours + ':';
325            }
326            
327            // Minutes
328            if (minutes === 0) {
329                formattedTime += '00:';
330            } else if (minutes < 10) {
331                formattedTime += '0' + minutes + ':';
332            } else {
333                formattedTime += minutes + ':';
334            }
335            
336            // Seconds
337            if (secs === 0) {
338                formattedTime += '00';
339            } else if (secs < 10) {
340                formattedTime += '0' + secs;
341            } else {
342                formattedTime += secs;
343            }
344            
345            return formattedTime;
346        },
347
348        endTimer: function(quizId) {
349            this.stop(quizId);
350            
351            // Clean up localStorage (both new and legacy keys)
352            localStorage.setItem('mlw_time_quiz' + quizId, 'completed');
353            localStorage.setItem('mlw_started_quiz' + quizId, 'no');
354            localStorage.removeItem('mlw_time_consumed_quiz' + quizId);
355            
356            // Restore original title
357            document.title = this.originalTitle;
358            
359            // Trigger events
360            $(document).trigger('qsm_end_timer', [quizId, qmn_quiz_data]);
361        },
362
363        // Public API methods for external access
364        getTimerInfo: function(quizId) {
365            const currentQuiz = this.quizObjects[quizId];
366            if (!currentQuiz || currentQuiz.warnings.expired) return null;
367            
368            return {
369                quizId: quizId,
370                totalTime: currentQuiz.totalTime,
371                remainingTime: currentQuiz.remainingTime,
372                consumedTime: currentQuiz.consumedTime,
373                isActive: currentQuiz.isActive,
374                formattedTime: this.secondsToTimer(currentQuiz.remainingTime),
375                percentRemaining: (currentQuiz.remainingTime / currentQuiz.totalTime) * 100
376            };
377        },
378
379        getAllTimers: function() {
380            const timers = {};
381            const self = this;
382            Object.keys(this.quizObjects).forEach(function(quizId) {
383                timers[quizId] = self.getTimerInfo(quizId);
384            });
385            return timers;
386        },
387        
388        // New methods for enhanced functionality
389        getQuizData: function(quizId) {
390            // Try multiple data sources for compatibility
391            let data = {};
392            
393            if (qmn_quiz_data && qmn_quiz_data[quizId]) {
394                data = qmn_quiz_data[quizId];
395            }
396            
397            return data;
398        },
399        
400        updateLegacyQuizData: function(quizId) {
401            const currentQuiz = this.quizObjects[quizId];
402            if (!currentQuiz || currentQuiz.warnings.expired) return;
403            
404            // Ensure qmn_quiz_data exists and update it
405            if (typeof qmn_quiz_data === 'undefined') {
406                qmn_quiz_data = {};
407            }
408            
409            if (!qmn_quiz_data[quizId]) {
410                qmn_quiz_data[quizId] = currentQuiz.data;
411            }
412            
413            // Update timer-specific properties
414            qmn_quiz_data[quizId].timerStatus = currentQuiz.timerStatus;
415            qmn_quiz_data[quizId].timerRemaning = currentQuiz.remainingTime;
416            qmn_quiz_data[quizId].timerConsumed = currentQuiz.consumedTime;
417            qmn_quiz_data[quizId].timerInterval = currentQuiz.timerInterval;
418        },
419        
420        shouldAutoStart: function(quizId, data) {
421            // Check if timer should auto-start based on various conditions
422            if (data.timer_auto_start) return true;
423            if (!data.first_page && !data.disable_first_page) return true;
424            if (Number(data.disable_first_page) === 1) return true;
425            
426            return false;
427        },
428        
429        checkScheduledTimeExpiration: function(quizId) {
430            const currentQuiz = this.quizObjects[quizId];
431            if (!currentQuiz || currentQuiz.warnings.expired) return false;
432            
433            const data = currentQuiz.data;
434            if (data.not_allow_after_expired_time === '1' && data.scheduled_time_end) {
435                const systemTime = Math.round(new Date().getTime() / 1000);
436                if (systemTime > data.scheduled_time_end) {
437                    MicroModal.show('modal-4');
438                    return true;
439                }
440            }
441            
442            return false;
443        },
444        
445        showNinetyPercentWarning: function(quizId) {
446            const currentQuiz = this.quizObjects[quizId];
447            if (!currentQuiz || currentQuiz.warnings.expired) return;
448            
449            const $container = currentQuiz.form.closest('.qmn_quiz_container, .qsm-quiz-container');
450            $container.find('.qsm_ninety_warning').fadeIn();
451        },
452    };
453
454    // Initialize timer on document ready
455    $(document).ready(function() {
456        QSMPagination.Timer.init();
457    });
458
459})(jQuery);

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.