Skip to content

Commit a689a23

Browse files
authored
Merge pull request #3040 from gocodebox/dev
Release 9.1.0
2 parents 1499ce3 + 1aafd91 commit a689a23

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1248
-257
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,48 @@
11
LifterLMS Changelog
22
===================
33

4+
v9.1.0 - 2025-11-03
5+
-------------------
6+
7+
##### New Features
8+
9+
+ New tabs to view students who have not yet attempted a quiz, and listing of all quiz attempts for a student.
10+
+ Option to allow unlimited time for a time-limited quiz to certain users.
11+
12+
##### Updates and Enhancements
13+
14+
+ Apply filters to save any additional fields added to LifterLMS metaboxes.
15+
+ Adjusting syllabus styling for clarity on what can be clicked to navigate to a lesson. [#3041](https://github.com/gocodebox/lifterlms/issues/3041)
16+
+ Template changes for improved accessibility when taking a quiz when using a keyboard or screen reader.
17+
+ Re-label "Exit Quiz" button for clarity on resumable quizzes. [#3025](https://github.com/gocodebox/lifterlms/issues/3025)
18+
+ Show order summary for free enrolments.
19+
+ Removing "Estimated Completion Time" option from the Course Information block. [#3016](https://github.com/gocodebox/lifterlms/issues/3016)
20+
+ Show warning and avoid generating invalid checkout URLs if no Checkout Page is configured. [#2984](https://github.com/gocodebox/lifterlms/issues/2984)
21+
+ Making default password strength "weak" and changing strength requirements to minimize friction during checkout. [#2848](https://github.com/gocodebox/lifterlms/issues/2848)
22+
23+
##### Bug Fixes
24+
25+
+ Fix wording for adding a featured video on memberships. [#3034](https://github.com/gocodebox/lifterlms/issues/3034)
26+
+ Fixing "user email required" warning when editing a LifterLMS form and changing a pattern. [#2644](https://github.com/gocodebox/lifterlms/issues/2644)
27+
28+
##### Developer Notes
29+
30+
+ Additional filter to change available merge codes for certificates.
31+
+ Adds llms_embed_shortcode_output filter.
32+
+ Filter to control whether a question choice is marked as correct.
33+
34+
##### Updated Templates
35+
36+
+ [templates/admin/reporting/tabs/quizzes/non-attempts.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/admin/reporting/tabs/quizzes/non-attempts.php)
37+
+ [templates/admin/reporting/tabs/students/quiz_attempts.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/admin/reporting/tabs/students/quiz_attempts.php)
38+
+ [templates/admin/reporting/tabs/students/student.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/admin/reporting/tabs/students/student.php)
39+
+ [templates/checkout/form-checkout.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/checkout/form-checkout.php)
40+
+ [templates/quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/quiz/meta-information.php)
41+
+ [templates/quiz/questions/content-choice.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/quiz/questions/content-choice.php)
42+
+ [templates/quiz/questions/content-picture_choice.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/quiz/questions/content-picture_choice.php)
43+
+ [templates/quiz/results.php](https://github.com/gocodebox/lifterlms/blob/9.1.0/templates/quiz/results.php)
44+
45+
446
v9.0.7 - 2025-09-16
547
-------------------
648

assets/js/app/llms-password-strength.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ $.extend( LLMS.PasswordStrength, {
203203
val = -1;
204204
} else {
205205
val = wp.passwordStrength.meter( pass, this.get_blocklist(), conf );
206-
// 0 & 1 are both very-weak
206+
// 0 is very weak (ie. using something on the block list), 1 is considered weak.
207+
if ( 1 === val ) {
208+
val = 2;
209+
}
207210
if ( 0 === val ) {
208211
val = 1;
209212
}
@@ -241,7 +244,7 @@ $.extend( LLMS.PasswordStrength, {
241244
* @return {string}
242245
*/
243246
get_minimum_strength: function() {
244-
return this.get_setting( 'min_strength', 'strong' );
247+
return this.get_setting( 'min_strength', 'weak' );
245248
},
246249

247250
/**

assets/js/llms-metabox-product.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@
463463
var self = this;
464464
self.$plans.find( '.llms-access-plan' ).each(
465465
function() {
466-
$(this).toggleClass( 'llms-needs-attention', $(this).find('p.notice').length > 0 );
466+
$(this).toggleClass( 'llms-needs-attention', $(this).find('.notice').not('.llms-payment-gateway-warning').length > 0 );
467467
}
468468
);
469469
}

assets/js/llms-quiz.js

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@
165165

166166
var self = this;
167167

168-
self.$container.find( '.llms-error' ).remove();
168+
self.$container.find( '#llms-quiz-error-container' ).remove();
169+
self.$container.append( '<div id="llms-quiz-error-container" role="alert" aria-atomic="true"></div>' );
170+
const $error_container = self.$container.find( '#llms-quiz-error-container' );
171+
169172
var $err = $( '<p class="llms-error">' + msg + '<a href="#"><i class="fa fa-times-circle" aria-hidden="true"></i></a></p>' );
170173
$err.on( 'click', 'a', function( e ) {
171174
e.preventDefault();
@@ -174,7 +177,7 @@
174177
$err.remove();
175178
}, 210 );
176179
} );
177-
self.$container.append( $err );
180+
$error_container.append( $err );
178181

179182
},
180183

@@ -596,7 +599,7 @@
596599

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

602605
self.load_question( r.data.html );
@@ -655,15 +658,18 @@
655658
msg = LLMS.l10n.translate( 'Time Remaining' );
656659

657660
$el.append( '<i class="fa fa-clock-o" aria-hidden="true"></i><span class="screen-reader-text">' + msg + '</span>' );
658-
$el.append( '<div id="llms-tiles" class="llms-tiles"></div>' );
661+
const quiz_header = $( '#llms-quiz-header' );
662+
$el.append( '<time aria-describedby="llms-timer-hint" id="llms-quiz-time-remaining" class="llms-tiles" datetime=""></time>' );
659663

660-
$( '#llms-quiz-header' ).append( $el );
664+
quiz_header.append( $el );
665+
quiz_header.append( '<div id="llms-timer-live" class="sr-only" aria-live="polite" aria-atomic="true"></div>' );
666+
quiz_header.append( '<p id="llms-timer-hint" class="sr-only">Announcements every minute; every 10 seconds in the last minute.</p>' );
661667

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

669675
// set actual timer
@@ -930,7 +936,9 @@
930936

931937
// find the amount of "seconds" between now and target
932938
var current_date = new Date().getTime(),
933-
seconds_left = ( target_date - current_date ) / 1000;
939+
seconds_left = parseInt( ( target_date - current_date ) / 1000 );
940+
941+
const live = document.getElementById( 'llms-timer-live' );
934942

935943
if ( seconds_left >= 0 ) {
936944

@@ -947,15 +955,54 @@
947955

948956
}
949957

950-
days = this.pad( parseInt( seconds_left / 86400 ) );
951-
seconds_left = seconds_left % 86400;
952-
hours = this.pad( parseInt( seconds_left / 3600 ) );
953-
seconds_left = seconds_left % 3600;
954-
minutes = this.pad( parseInt( seconds_left / 60 ) );
955-
seconds = this.pad( parseInt( seconds_left % 60 ) );
958+
const shouldAnnounce = (s) => {
959+
if ( s === parseInt( time_limit / 1000 ) ) return true; // first render
960+
if ( s <= 10 ) return true; // last 10 seconds: every second
961+
if ( s <= 60 ) return s % 10 === 0; // last minute: every 10s
962+
return s % 60 === 0; // otherwise: each minute mark
963+
};
956964

957-
// format countdown string + set tag value
958-
countdown.innerHTML = '<span class="hours">' + hours + '</span>:<span class="minutes">' + minutes + '</span>:<span class="seconds">' + seconds + '</span>';
965+
const speak = ( hours, mins, secs ) => {
966+
if ( hours > 0 ) {
967+
// Translators: %1$s hours, %2$s minutes, and %3$s seconds remaining.
968+
live.textContent = hours > 1 ? LLMS.l10n.replace( '%1$s hours, %2$s minutes remaining', {
969+
'%1$s': hours,
970+
'%2$s': mins,
971+
} ) :
972+
// Translators: 1 hour, %2$s minutes.
973+
LLMS.l10n.replace( '1 hour, %2$s minutes remaining', {
974+
'%2$s': mins,
975+
} );
976+
} else if ( mins > 0 && secs === 0 ) {
977+
// Translators: %1$s minutes remaining.
978+
live.textContent = mins > 1 ? LLMS.l10n.replace( '%1$s minutes remaining', {
979+
'%1$s': mins,
980+
} ) :
981+
// Translators: 1 minute remaining.
982+
LLMS.l10n.replace( '%1$s minute remaining', {
983+
minutes: mins,
984+
} );
985+
} else if ( mins === 0 ) {
986+
live.textContent = LLMS.l10n.replace( '%1$s seconds remaining', {
987+
'%1$s': secs,
988+
} );
989+
}
990+
};
991+
992+
days = parseInt( seconds_left / 86400 );
993+
let remainder = seconds_left % 86400;
994+
hours = parseInt( remainder / 3600 );
995+
remainder = seconds_left % 3600;
996+
minutes = parseInt( remainder / 60 );
997+
seconds = parseInt( remainder % 60 );
998+
999+
countdown.innerHTML = this.pad( hours ) + ':' + this.pad( minutes ) + ':' + this.pad( seconds );
1000+
1001+
$( countdown ).attr( 'datetime', 'PT' + ( hours > 0 ? hours + 'H' : '' ) + ( minutes > 0 ? minutes + 'M' : '' ) + ( seconds > 0 ? seconds + 'S' : '' ) );
1002+
1003+
if ( shouldAnnounce( seconds_left ) ) {
1004+
speak( hours, minutes, seconds );
1005+
}
9591006
}
9601007
},
9611008

assets/scss/frontend/_llms-quizzes.scss

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,20 @@
175175
margin-bottom: 15px;
176176
}
177177

178-
ol.llms-question-choices {
178+
.llms-question-choices {
179+
border: none;
180+
181+
&.type--picture {
182+
display: grid;
183+
grid-template-columns: auto auto auto;
184+
gap: 20px;
185+
}
186+
179187
list-style-type: none;
180188
margin: 0;
181189
padding: 0;
182190

183-
li.llms-choice {
191+
.llms-choice {
184192
border-bottom: 1px solid #e8e8e8;
185193
margin: 0;
186194
padding: 0;
@@ -190,18 +198,94 @@
190198
border-bottom: none;
191199
}
192200

201+
label {
202+
display: block;
203+
margin: 0;
204+
padding: 10px 20px;
205+
position: relative;
206+
}
207+
208+
input {
209+
opacity: 0;
210+
211+
+ label {
212+
display: inline-block;
213+
cursor: pointer;
214+
width: calc( 100% - 90px );
215+
216+
.llms-choice-text {
217+
line-height: 1.6;
218+
vertical-align: middle;
219+
font-size: 18px;
220+
font-weight: 400;
221+
margin: 5px 0 0 0;
222+
display: inline-block;
223+
}
224+
225+
&:before {
226+
content: attr(data-marker);
227+
position: absolute;
228+
background: $color-white;
229+
color: $color-black;
230+
display: flex;
231+
left: -32px;
232+
width: 40px;
233+
font-size: 20px;
234+
font-weight: inherit;
235+
text-align: center;
236+
height: 40px;
237+
align-items: center;
238+
justify-content: center;
239+
transition: all 0.2s ease;
240+
}
241+
}
242+
243+
&[type="checkbox"] {
244+
+ label:before {
245+
border-radius: 4px;
246+
}
247+
}
248+
249+
&[type="radio"] {
250+
+ label:before {
251+
border-radius: 50%;
252+
}
253+
}
254+
255+
&:checked {
256+
+ label:before {
257+
background: $color-brand-pink;
258+
color: #fff;
259+
}
260+
}
261+
262+
&:focus {
263+
+ label:before {
264+
box-shadow: 0 0px 8px $color-brand-pink;
265+
}
266+
}
267+
268+
&:not(:checked) {
269+
+ label.hovered:before {
270+
box-shadow: 0 0px 8px $color-brand-pink;
271+
}
272+
}
273+
}
274+
193275
&.type--picture {
194276
border-bottom: none;
195277
label {
196278
display: inline-block;
197279
padding: 0;
280+
height: auto;
281+
282+
&:before {
283+
left: auto;
284+
right: 10px;
285+
bottom: 10px;
286+
}
198287
}
199-
.llms-marker {
200-
bottom: 10px;
201-
margin: 0;
202-
position: absolute;
203-
right: 10px;
204-
}
288+
205289
.llms-choice-image {
206290
margin: 2px;
207291
padding: 20px;
@@ -212,34 +296,9 @@
212296
width: 100%;
213297
}
214298
}
215-
input:checked ~ .llms-choice-image {
216-
background: #efefef
217-
}
218-
}
219-
220-
input {
221-
display: none;
222-
left: 0;
223-
pointer-events: none;
224-
position: absolute;
225-
top: 0;
226-
visibility: hidden;
227-
}
228-
229-
label {
230-
display: block;
231-
margin: 0;
232-
padding: 10px 20px;
233-
position: relative;
234-
// &:hover {
235-
&.hovered {
236-
.llms-marker:not(.type--lister) {
237-
.iterator {
238-
display: none;
239-
}
240-
.fa {
241-
display: inline;
242-
}
299+
input:checked {
300+
+ label .llms-choice-image {
301+
background: #efefef
243302
}
244303
}
245304
}
@@ -268,27 +327,8 @@
268327

269328
}
270329

271-
input:checked + .llms-marker {
272-
background: $color-brand-pink;
273-
color: #fff;
274-
.iterator {
275-
display: none;
276-
}
277-
.fa {
278-
display: inline;
279-
}
280-
}
281330

282-
.llms-choice-text {
283-
display: inline-block;
284-
font-size: 18px;
285-
font-weight: 400;
286-
line-height: 1.6;
287-
margin: 0;
288-
padding: 0;
289-
vertical-align: middle;
290-
width: calc( 100% - 60px );
291-
}
331+
292332

293333
}
294334
}

0 commit comments

Comments
 (0)