HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/epamokos.kaunokolegija.lt/wp-content/plugins/lifterlms/assets/js/llms-quiz.js
;/* global LLMS, $ */
/* jshint strict: true */

/**
 * Front End Quiz Class.
 *
 * @type {Object}
 *
 * @since 1.0.0
 * @version 7.8.0
 */( function( $ ) {

	var quiz = {

		/**
		 * Selector of all the available button elements
		 *
		 * @type {Object}
		 */
		$buttons: null,

		/**
		 * Main Question Container Element.
		 *
		 * @type {Object}
		 */
		$container: null,

		/**
		 * Main Quiz container UI element.
		 *
		 * @type {Object}
		 */
		$ui: null,

		/**
		 * Attempt key for the current quiz.
		 *
		 * @type {[type]}
		 */
		attempt_key: null,

		/**
		 * Question ID of the current question.
		 *
		 * @type {Number}
		 */
		current_question: 0,

		/**
		 * Total number of questions in the current quiz.
		 *
		 * @type {Number}
		 */
		total_questions: 0,

		/**
		 * Object of quiz question HTML.
		 *
		 * @type {Object}
		 */
		questions: {},

		/**
		 * Validator functions for question type.
		 * Third party custom question types can register validators for use when answering questions.
		 *
		 * @type {Object}
		 */
		validators: {},

		/**
		 * Records current status of a quiz session.
		 * If a user attempts to navigate away from a quiz
		 * while taking the quiz they'll be warned that their progress
		 * will not be saved if this status is not null.
		 *
		 * @type {Bool}
		 */
		status: null,

		/**
		 * Records if the quiz can be resumed.
		 */
		resumable: null,

		/**
		 * Flag if the user is exiting the quiz.
		 */
		exiting_quiz: false,

		/**
		 * Bind DOM events.
		 *
		 * @since 1.0.0
		 * @since 3.16.6 Unknown.
		 * @since 7.8.0 Add quiz resume and hide leave warning if quiz is resumable.
		 *
		 * @return {Void}
		 */
		bind: function() {

			var self = this;

			// Start quiz.
			$( '#llms_start_quiz' ).on( 'click', function( e ) {
				e.preventDefault();
				self.start_quiz();
			} );

			// Resume quiz.
			$( '#llms_resume_quiz' ).on( 'click', function( e ) {
				e.preventDefault();
				self.resume_quiz();
			} );

			// Draw quiz grade circular chart.
			$( '.llms-donut' ).each( function() {
				LLMS.Donut( $( this ) );
			} );

			// Redirect to attempt on attempt selection change.
			$( '#llms-quiz-attempt-select' ).on( 'change', function() {
				var val = $( this ).val();
				if ( val ) {
					window.location.href = val;
				}
			} );

			// Warn when quiz is running and user tries to leave the page when quiz is not resumable.
			$( window ).on( 'beforeunload', function() {
				if ( self.status && ! self.exiting_quiz ) {
					return LLMS.l10n.translate( 'Are you sure you wish to quit this quiz attempt?' );
				}

				return;
			} );

			// Complete the quiz attempt when user leaves if the quiz is running.
			$( window ).on( 'unload', function() {
				if ( self.status && ! self.resumable ) {
					self.complete_quiz();
				}
			} );

			$( document ).on( 'llms-post-append-question', self.post_append_question );

			// Register validators.
			this.register_validator( 'content', this.validate );
			this.register_validator( 'choice', this.validate_choice );
			this.register_validator( 'picture_choice', this.validate_choice );
			this.register_validator( 'true_false', this.validate_choice );

		},

		/**
		 * Add an error message to the UI
		 *
		 * @param    string   msg  error message string
		 * @return   void
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		add_error: function( msg ) {

			var self = this;

			self.$container.find( '.llms-error' ).remove();
			var $err = $( '<p class="llms-error">' + msg + '<a href="#"><i class="fa fa-times-circle" aria-hidden="true"></i></a></p>' );
			$err.on( 'click', 'a', function( e ) {
				e.preventDefault();
				$err.fadeOut( '200' );
				setTimeout( function() {
					$err.remove();
				}, 210 );
			} );
			self.$container.append( $err );

		},

		save_question: function( options ) {
			var self      = this,
				$question = this.$container.find( '.llms-question-wrapper' ),
				type      = $question.attr( 'data-type' ),
				valid;

			if ( ! this.validators[ type ] ) {
				console.log( 'No validator registered for question type ' + type );
				return;
			}

			valid = this.validators[ type ]( $question );

			var requestData = {
				action: 'quiz_answer_question',
				answer: valid.answer,
				attempt_key: self.attempt_key,
				question_id: $question.attr( 'data-id' ),
				question_type: $question.attr( 'data-type' ),
			};

			if ( options && options.exit_quiz ) {
				requestData.via_exit_quiz = true;
			}

			if ( options && options.previous_question ) {
				requestData.via_previous_question = true;
			}

			LLMS.Ajax.call( {
				data: requestData,
				success: function( r ) {
					if (options && typeof options.callback === 'function') {
						options.callback();
					}
				},
			});
		},

		/**
		 * Answer a Question
		 *
		 * @param    obj   $btn   jQuery object for the "Next Lesson" button
		 * @return   void
		 * @since    1.0.0
		 * @version  3.16.6
		 */
		answer_question: function( $btn ) {

			var self      = this,
				$question = this.$container.find( '.llms-question-wrapper' ),
				type      = $question.attr( 'data-type' ),
				valid;

			if ( ! this.validators[ type ] ) {

				console.log( 'No validator registered for question type ' + type );
				return;

			}

			valid = this.validators[ type ]( $question );
			if ( ! valid || true !== valid.valid || ! valid.answer ) {
				return self.add_error( valid.valid );
			}

			LLMS.Ajax.call( {
				data: {
					action: 'quiz_answer_question',
					answer: valid.answer,
					attempt_key: self.attempt_key,
					question_id: $question.attr( 'data-id' ),
					question_type: $question.attr( 'data-type' ),
				},
				beforeSend: function() {

					var msg = $btn.hasClass( 'llms-button-quiz-complete' ) ? LLMS.l10n.translate( 'Grading Quiz...' ) : LLMS.l10n.translate( 'Loading Question...' );
					self.toggle_loader( 'show', msg );

					self.update_progress_bar( 'increment' );

				},
				success: function( r ) {

					self.toggle_loader( 'hide' );

					if ( r.data && r.data.html ) {

						// load html from the cached questions if it exists already
						if ( r.data.question_id && self.questions[ 'q-' + r.data.question_id ] ) {

							self.load_question( self.questions[ 'q-' + r.data.question_id ] );

							// load html from server if the question's never been seen before
						} else {
							self.load_question( r.data.html );
						}

					} else if ( r.data && r.data.redirect ) {

						self.redirect( r.data.redirect );

					} else if ( r.message ) {

						self.$container.append( '<p>' + r.message + '</p>' );

					} else {

						var msg = LLMS.l10n.translate( 'An unknown error occurred. Please try again.' );
						self.$container.append( '<p>' + msg + '</p>' );

					}

				},
				error: function ( jqXHR, status, error ) {
					self.reload_question();
					self.add_error( LLMS.l10n.translate( 'An unknown error occurred. Please try again.' ) );
					console.log( error );
				}

			} );

		},

		/**
		 * Complete the quiz
		 * Called when timed quizzes reach time limit
		 * & during unload events to record the attempt as abandoned
		 *
		 * @return   void
		 * @since    1.0.0
		 * @version  3.9.0
		 */
		complete_quiz: function() {

			var self = this;

			LLMS.Ajax.call( {
				data: {
					action: 'quiz_end',
					attempt_key: self.attempt_key,
				},
				beforeSend: function() {

					self.toggle_loader( 'show', 'Grading Quiz...' );

				},
				success: function( r ) {

					self.toggle_loader( 'hide' );

					if ( r.data && r.data.redirect ) {

						self.redirect( r.data.redirect );

					} else if ( r.message ) {

						self.$container.append( '<p>' + r.message + '</p>' );

					} else {

						var msg = LLMS.l10n.translate( 'An unknown error occurred. Please try again.' );
						self.$container.append( '<p>' + msg + '</p>' );

					}

				}

			} );

		},

		/**
		 * Retrieve the index of a question by question id
		 *
		 * @param    int   qid  WP Post ID of the question
		 * @return   int
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		get_question_index: function( qid ) {

			return Object.keys( this.questions ).indexOf( 'q-' + qid );

		},

		/**
		 * Redirect on quiz completion / timeout
		 *
		 * @param    string   url  redirect url
		 * @return   void
		 * @since    3.9.0
		 * @version  3.16.0
		 */
		redirect: function( url ) {

			this.toggle_loader( 'show', 'Grading Quiz...' );
			this.status          = null;
			window.location.href = url;

		},

		reload_question: function() {
			var self = this;

			self.toggle_loader( 'show', LLMS.l10n.translate( 'Loading Question...' ) );
			self.update_progress_bar( 'reload' );

			setTimeout( function() {
				self.toggle_loader( 'hide' );
				self.load_question( self.questions[ 'q-' + self.current_question ] );
			}, 100 );

		},

		/**
		 * Return to the previous question.
		 *
		 * @since 1.0.0
		 * @since 3.16.6 Unknown.
		 * @since 7.8.0 Retrieve question HTML from the server when not cached.
		 *
		 * @return {Void}
		 */
		previous_question: function() {

			var self = this;

			this.save_question( {
				previous_question: true,
				callback: function() {

					self.toggle_loader( 'show', LLMS.l10n.translate( 'Loading Question...' ) );
					self.update_progress_bar( 'decrement' );

					var ids     = Object.keys( self.questions ),
						curr    = ids.indexOf( 'q-' + self.current_question ),
						prev_id = ids[0];

					if ( curr >= 1 ) {
						prev_id = ids[ curr - 1 ];
					}

					// Retrieve previous question HTML from the server.
					if ( ! self.questions[ prev_id ] ) {
						LLMS.Ajax.call( {
							data: {
								action     : 'quiz_get_question',
								attempt_key: self.attempt_key,
								question_id: prev_id.substring(2), // Remove 'q-'.
							},
							success: function( r ) {

								self.toggle_loader( 'hide' );
								if ( r.data && r.data.html ) {

									self.load_question( r.data.html );

								} else if ( r.data && r.data.redirect ) {

									self.redirect( r.data.redirect );

								} else if ( r.message ) {

									self.$container.append( '<p>' + r.message + '</p>' );

								} else {

									var msg = LLMS.l10n.translate( 'An unknown error occurred. Please try again.' );
									self.$container.append( '<p>' + msg + '</p>' );

								}

							}

						} );

					} else {
						setTimeout( function() {
							self.toggle_loader( 'hide' );
							self.load_question( self.questions[ prev_id ] );
						}, 100 );
					}
				}
			});
		},

		/**
		 * Register question type validator functions
		 *
		 * @param    string     type  question type id
		 * @param    function   func  callback function to validate the question with
		 * @return   void
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		register_validator: function( type, func ) {

			this.validators[ type ] = func;

		},

		/**
		 * Start a Quiz.
		 *
         * @since 1.0.0
         * @since 3.24.3 Unknown.
		 * @since 7.8.0 Abstracted the function in `init_quiz`.
		 *
		 * @return {Void}
		 */
		start_quiz: function () {

			this.init_quiz( 'quiz_start' );
		},

		/**
		 * Resume a Quiz.
		 *
		 * @since 7.8.0
		 *
		 * @return {Void}
		 */
		resume_quiz: function () {

			this.init_quiz( 'quiz_resume' );
		},

		/**
		 * Initiate 'Start' or 'Resume' action on a Quiz via AJAX call.
		 *
		 * @since 7.8.0
		 *
		 * @return {Void}
		 */
		init_quiz: function ( action ) {

			var self = this;

			if( 'quiz_resume' === action ) {
				// Disable resume button.
				$( '#llms_resume_quiz' ).attr( 'disabled', 'disabled' );
			}

			this.load_ui_elements();
			this.$ui        = $( '#llms-quiz-ui' );
			this.$buttons   = $( '#llms-quiz-nav button' );
			this.$container = $( '#llms-quiz-question-wrapper' );

			// Bind submission event for answering questions.
			$( '#llms-next-question, #llms-complete-quiz' ).on( 'click', function( e ) {
				e.preventDefault();
				self.answer_question( $( this ) );
			} );

			// Bind submission event for navigating backwards.
			$( '#llms-prev-question' ).on( 'click', function( e ) {
				e.preventDefault();
				self.previous_question();
			} );

			// Bind exit event for quiz.
			$( '#llms-quiz-nav' ).on( 'click', '#llms-exit-quiz', function( e ) {
				e.preventDefault();
				self.save_question( {
					exit_quiz: true,
					callback: function() {
						self.exiting_quiz = true;
						window.location.reload();
					}
				});
			} );

			if ( 'quiz_resume' === action ) {
				data = {
					action: 'quiz_resume',
					attempt_key: $( '#llms-attempt-key' ).val(),
				};
			} else {
				data = {
					action: 'quiz_start',
					attempt_key: $( '#llms-attempt-key' ).val(),
					lesson_id : $( '#llms-lesson-id' ).val(),
					quiz_id : $( '#llms-quiz-id' ).val(),
				};
			}

			LLMS.Ajax.call( {
				data: data,
				beforeSend: function() {

					self.status = true;
					$( '#llms-quiz-wrapper, #quiz-start-button, #quiz-resume-button' ).remove();
					$( 'html, body' ).stop().animate( {scrollTop: 0 }, 500 );
					self.toggle_loader( 'show', LLMS.l10n.translate( 'Loading Quiz...' ) );

				},
				error: function( r, s, t ) {
					console.log( r, s, t );
				},
				success: function( r ) {

					self.toggle_loader( 'hide' );

					if ( r.data && r.data.html ) {

						self.attempt_key     = r.data.attempt_key;
						self.total_questions = r.data.total;
						self.resumable       = r.data.can_be_resumed;

						if( 'quiz_resume' === action ) {
							r.data.question_ids.forEach( id => self.questions[`q-${id}`] = '' );
						} else if ( r.data.time_limit ) {
							self.start_quiz_timer( r.data.time_limit );
						}

						// Adding Exit Button in Layout if quiz is resumable.
						if ( self.resumable ) {
							$( '#llms-quiz-nav' ).append( '<button class="button llms-button-secondary" id="llms-exit-quiz" name="llms_exit_quiz">' + LLMS.l10n.translate( 'Exit Quiz' ) + '</button>' );
						}

						self.load_question( r.data.html );

						if ( 'quiz_resume' === action ) {
							self.update_progress_bar( 'reload' );
						}

					} else if ( r.message ) {

						self.$container.append( '<p>' + r.message + '</p>' );

					} else {

						var msg = LLMS.l10n.translate( 'An unknown error occurred. Please try again.' );
						self.$container.append( '<p>' + msg + '</p>' );

					}

				}

			} );

			/**
			 * Use JS mouse events instead of CSS :hover because iOS is really smart
			 *
			 * @see: https://css-tricks.com/annoying-mobile-double-tap-link-issue/
			 */
			if ( ! LLMS.is_touch_device() ) {

				this.$ui.on( 'mouseenter', 'li.llms-choice label', function() {
					$( this ).addClass( 'hovered' );
				} );
				this.$ui.on( 'mouseleave', 'li.llms-choice label', function() {
					$( this ).removeClass( 'hovered' );
				} );

			}
		},

		/**
		 * Start Quiz Timer
		 * Gets minutes from hidden field
		 * Not used as actual quiz timer. Quiz is timed on the server from the quiz class
		 * Calculates minutes to milliseconds and then converts to hours / minutes
		 * When time limit reaches 0 calls complete_quiz() to complete quiz.
		 *
		 * @return Calls get_count_down at a set interval of 1 second
		 * @since    1.0.0
		 * @version  3.16.0
		 */
		start_quiz_timer: function( total_minutes ) {

			// create and append the UI for the countdown clock
			var $el = $( '<div class="llms-quiz-timer" id="llms-quiz-timer" />' ),
				msg = LLMS.l10n.translate( 'Time Remaining' );

			$el.append( '<i class="fa fa-clock-o" aria-hidden="true"></i><span class="screen-reader-text">' + msg + '</span>' );
			$el.append( '<div id="llms-tiles" class="llms-tiles"></div>' );

			$( '#llms-quiz-header' ).append( $el );

			// start the timer
			var self        = this,
				target_date = new Date().getTime() + ( ( total_minutes * 60 ) * 1000 ), // set the countdown date
				time_limit  = ( ( total_minutes * 60 ) * 1000 ),
				countdown   = document.getElementById( 'llms-tiles' ), // get tag element
				days, hours, minutes, seconds; // variables for time units

			// set actual timer
			setTimeout( function() {
				self.complete_quiz();
			}, time_limit + 1000 );

			this.getCountdown(
				total_minutes,
				target_date,
				time_limit,
				days,
				hours,
				minutes,
				seconds,
				countdown
			);

			// call get_count_down every 1 second
			setInterval( function () {
				self.getCountdown(
					total_minutes,
					target_date,
					time_limit,
					days,
					hours,
					minutes,
					seconds,
					countdown
				);
			}, 1000 );
		},

		/**
		 * Trigger events
		 *
		 * @param    string   event  event to trigger
		 * @return   void
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		trigger: function( event ) {

			var self = this;

			// trigger question submission for the current question
			if ( 'answer_question' === event ) {

				if ( this.get_question_index( self.current_question ) === self.total_questions ) {

					$( '#llms-complete-quiz' ).trigger( 'click' );

				} else {

					$( '#llms-next-question' ).trigger( 'click' );

				}

			}

		},

		/**
		 * Load the HTML of a question into the DOM and the question cache
		 *
		 * @param    string   html  string of html
		 * @return   void
		 * @since    3.9.0
		 * @version  3.16.6
		 */
		load_question: function( html ) {

			var $html = $( html ),
				qid   = $html.attr( 'data-id' );

			// cache the question HTML for faster rewinds
			if ( ! this.questions[ 'q-' + qid ] ) {
				this.questions[ 'q-' + qid ] = $html;
			}

			this.update_progress( qid );

			this.current_question = qid;

			$( document ).trigger( 'llms-pre-append-question', $html );

			this.$container.append( $html );

			$( document ).trigger( 'llms-post-append-question', $html );

		},

		/**
		 * Constructs the quiz UI & adds the elements into the DOM
		 *
		 * @return   void
		 * @since    3.16.0
		 * @version  3.16.9
		 */
		load_ui_elements: function() {

			// Removing the quiz UI elements if they already exist.
			if ( $( '#llms-quiz-ui').length > 0 ) {
				$( '#llms-quiz-ui' ).remove();
			}

			var $html   = $( '<div class="llms-quiz-ui" id="llms-quiz-ui" />' ),
				$header = $( '<header class="llms-quiz-header" id="llms-quiz-header" />' )
				$footer = $( '<footer class="llms-quiz-nav" id="llms-quiz-nav" />' );

			$footer.append( '<button class="button large llms-button-action" id="llms-next-question" name="llms_next_question" type="submit">' + LLMS.l10n.translate( 'Next Question' ) + '</button>' );
			$footer.append( '<button class="button large llms-button-action llms-button-quiz-complete" id="llms-complete-quiz" name="llms_complete_quiz" type="submit" style="display:none;">' + LLMS.l10n.translate( 'Complete Quiz' ) + '</button>' );
			$footer.append( '<button class="button llms-button-secondary" id="llms-prev-question" name="llms_prev_question" type="submit" style="display:none;">' + LLMS.l10n.translate( 'Previous Question' ) + '</button>' );

			$header.append( '<div class="llms-progress"><div class="progress-bar-complete"></div></div>' );
			$footer.append( '<div class="llms-quiz-counter" id="llms-quiz-counter"><span class="llms-current"></span><span class="llms-sep">/</span><span class="llms-total"></span></div>' )

			$html.append( $header )
				 .append( '<div class="llms-quiz-question-wrapper" id="llms-quiz-question-wrapper" />' )
				 .append( $footer );

			$( '#llms-quiz-wrapper' ).after( $html );

		},

		/**
		 * Perform actions on question HTML after it's been appended to the DOM
		 *
		 * @param    obj      event  js event object
		 * @param    obj      html   js HTML object
		 * @return   void
		 * @since    3.16.6
		 * @version  3.16.6
		 */
		post_append_question: function( event, html ) {

			var $html = $( html );

			if ( $html.find( 'audio' ).length ) {
				wp.mediaelement.initialize();
			}

		},

		/**
		 * Show or hide the "loading" spinner with an option message
		 *
		 * @param    string   display  show|hide
		 * @param    string   msg      text to display when showing
		 * @return   void
		 * @since    3.9.0
		 * @version  3.16.6
		 */
		toggle_loader: function( display, msg ) {

			if ( 'show' === display ) {

				msg = msg || LLMS.l10n.translate( 'Loading...' );

				this.$buttons.attr( 'disabled', 'disabled' );

				this.$container.empty();
				LLMS.Spinner.start( this.$container );
				this.$container.append( '<div class="llms-quiz-loading">' + LLMS.l10n.translate( msg ) + '</div>' );

			} else {

				LLMS.Spinner.stop( this.$container );
				this.$buttons.removeAttr( 'disabled' );
				this.$container.find( '.llms-quiz-loading' ).remove();

			}

		},

		/**
		 * Update the progress bar and toggle button availability based on question the question being shown.
		 *
		 * @since 3.16.0
		 * @since 7.8.0 Show counter and set the total as when needed.
		 *
		 * @param {Int} qid Question ID.
		 * @return {Void}
		 */
		update_progress: function( qid ) {

			var index = this.get_question_index( qid );

			if ( -1 === index ) {
				return;
			}

			index++;

			$( '#llms-quiz-counter .llms-current' ).text( index );
			if ( index > 0 && ! $( '#llms-quiz-counter .llms-total' ).text() ) {
				$( '#llms-quiz-counter .llms-total' ).text( this.total_questions );
				$( '#llms-quiz-counter' ).show();
			}

			// Handle prev question.
			if ( index >= 2 ) {
				$( '#llms-prev-question' ).show();
			} else {
				$( '#llms-prev-question' ).hide();
			}

			if ( index === this.total_questions ) {
				$( '#llms-next-question' ).hide();
				$( '#llms-complete-quiz' ).show();
			} else {
				$( '#llms-next-question' ).show();
				$( '#llms-complete-quiz' ).hide();
			}

		},

		/**
		 * Increase progress bar ui element
		 *
		 * @param    string   dir  update direction [increment|decrement|reload]
		 * @return   void
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		update_progress_bar: function( dir ) {

			var index = this.get_question_index( this.current_question );
			switch ( dir ) {
				case 'increment':
					index++;
					break;
				case 'decrement':
					index--;
					break;
				case 'reload':
					break;
			}

			progress = ( index / this.total_questions ) * 100;
			this.$ui.find( '.progress-bar-complete' ).css( 'width', progress + '%' );

		},

		/**
		 * Get Count Down
		 * Called every second to update the on screen countdown timer
		 * Changes color to yellow at 1/2 of total time
		 * Changes color to red at 1/4 of total time
		 *
		 * @param  {[int]} minutes     [description]
		 * @param  {[date]} target_date [description]
		 * @param  {[int]} time_limit  [description]
		 * @param  {[int]} days        [description]
		 * @param  {[int]} hours       [description]
		 * @param  {[int]} minutes     [description]
		 * @param  {[int]} seconds     [description]
		 * @param  {[int]} countdown   [description]
		 * @return Displays updates hours, minutes on quiz timer
		 * @since    1.0.0
		 * @version  1.0.0
		 */
		getCountdown: function( total_minutes, target_date, time_limit, days, hours, minutes, seconds, countdown ){

			// find the amount of "seconds" between now and target
			var current_date = new Date().getTime(),
				seconds_left = ( target_date - current_date ) / 1000;

			if ( seconds_left >= 0 ) {

				if ( ( seconds_left * 1000 ) < ( time_limit / 2 ) ) {

					$( '#llms-quiz-timer' ).addClass( 'color-half' );

				}

				if ( ( seconds_left * 1000 ) < ( time_limit / 4 ) ) {

					$( '#llms-quiz-timer' ).removeClass( 'color-half' );
					$( '#llms-quiz-timer' ).addClass( 'color-empty' );

				}

				days         = this.pad( parseInt( seconds_left / 86400 ) );
				seconds_left = seconds_left % 86400;
				hours        = this.pad( parseInt( seconds_left / 3600 ) );
				seconds_left = seconds_left % 3600;
				minutes      = this.pad( parseInt( seconds_left / 60 ) );
				seconds      = this.pad( parseInt( seconds_left % 60 ) );

				// format countdown string + set tag value
				countdown.innerHTML = '<span class="hours">' + hours + '</span>:<span class="minutes">' + minutes + '</span>:<span class="seconds">' + seconds + '</span>';
			}
		},

		/**
		 * Pad Number
		 * pads number with 0 if single digit.
		 *
		 * @param  {[int]} n [number]
		 * @return {[string]} [padded number]
		 * @since    1.0.0
		 * @version  1.0.0
		 */
		pad: function(n) {
			return (n < 10 ? '0' : '') + n;
		},

		/**
		 * Basic validation method which performs no validation and returns a validation object
		 * in the format required by the application
		 *
		 * @param    obj   $question  jQuery selector of the question
		 * @return   obj
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		validate: function( $question ) {
			return {
				answer: [],
				valid: true,
			};
		},

		/**
		 * Validates a choice question to ensure there's at least one checked input
		 *
		 * @param    obj   $question  jQuery selector of the question
		 * @return   obj
		 * @since    3.16.0
		 * @version  3.16.0
		 */
		validate_choice: function( $question ) {

			var ret     = window.llms.quizzes.validate( $question ),
				checked = $question.find( 'input:checked' );

			if ( ! checked.length ) {
				ret.valid = LLMS.l10n.translate( 'You must select an answer to continue.' );
			} else {
				checked.each( function() {
					ret.answer.push( $( this ).val() );
				} );
			}

			return ret;

		},

	};

	quiz.bind();

	window.llms         = window.llms || {};
	window.llms.quizzes = quiz;

} )( jQuery );