delivery ) {
$items['delivery'] = __( 'One question at a time.', 'rooster-lms' );
$limits = self::limits( $p, $unit_id );
if ( null !== $limits ) {
$items['time'] = self::limits_text( $limits );
if ( $limits['factor'] > 100 ) {
$items['extra'] = sprintf(
/* translators: %s: the learner's extra time in percent, e.g. 50%. */
__( 'You have %s extra time on each question.', 'rooster-lms' ),
Format::percent( $limits['factor'] - 100 )
);
}
$items['timeout'] = __( 'When the time for a question runs out, the answer you have chosen is submitted; with nothing chosen the question counts as not answered. A question cannot be restarted.', 'rooster-lms' );
} elseif ( $p->timed && 0 === $p->time_factor ) {
$items['time'] = __( 'No time limit applies to you.', 'rooster-lms' );
}
$items['back'] = $p->forward_only()
? __( 'You cannot go back to an earlier question.', 'rooster-lms' )
: __( 'You can go back and change your answers until you finish.', 'rooster-lms' );
}
if ( Disclosure::AFTER_EACH_QUESTION === $p->disclosure ) {
$items['results'] = __( 'You see the correct answer after each question.', 'rooster-lms' );
} elseif ( Disclosure::AFTER_WINDOW === $p->disclosure && null !== $p->release_at ) {
$items['results'] = $p->class_release
? sprintf(
/* translators: %s: date and time the results are published. */
__( 'Results are published for your class on %s.', 'rooster-lms' ),
self::when( $p->release_at )
)
: sprintf(
/* translators: %s: date and time the learner's exam window closes. */
__( 'Results are published when your exam window closes on %s.', 'rooster-lms' ),
self::when( $p->release_at )
);
}
if ( WindowKind::NONE !== $p->window->kind && null !== $p->closes_at ) {
$items['window'] = sprintf(
/* translators: %s: date and time the exam window closes. */
__( 'The exam is open until %s.', 'rooster-lms' ),
self::when( $p->closes_at )
);
}
if ( array() === $items ) {
return '';
}
$html = '';
foreach ( $items as $key => $text ) {
$html .= '
' . esc_html( $text ) . '
';
}
return '
' . $html . '
';
}
public static function limits( EffectivePolicy $p, int $unit_id ): ?array {
$own = array();
$base = array();
$none = 0;
foreach ( Services::quiz_repo()->questions( $unit_id ) as $q ) {
$ms = $p->limit_ms( $q['time_limit'] ?? null, $q['type'] );
if ( $ms <= 0 ) {
++$none;
continue;
}
$own[] = intdiv( $ms, 1000 );
$base[] = (int) ( $q['time_limit'] ?? $p->question_time_sec ?? 0 );
}
if ( array() === $own ) {
return null;
}
return array(
'min' => min( $own ),
'max' => max( $own ),
'base_min' => min( $base ),
'base_max' => max( $base ),
'timed' => count( $own ),
'untimed' => $none,
'factor' => $p->time_factor,
);
}
private static function limits_text( array $l ): string {
$text = $l['min'] === $l['max']
? sprintf(
/* translators: %s: a duration, e.g. "30 seconds". */
__( 'Time for each question: %s.', 'rooster-lms' ),
self::duration( $l['max'] )
)
: sprintf(
/* translators: 1: shortest duration, 2: longest duration, e.g. "20 seconds", "1 minute". */
__( 'Time per question: %1$s to %2$s.', 'rooster-lms' ),
self::duration( $l['min'] ),
self::duration( $l['max'] )
);
if ( $l['untimed'] > 0 ) {
$text .= ' ' . sprintf(
/* translators: %s: number of questions without a time limit. */
_n( '%s question has no time limit.', '%s questions have no time limit.', $l['untimed'], 'rooster-lms' ),
Format::number( $l['untimed'] )
);
}
return $text;
}
public static function duration( int $seconds ): string {
$seconds = max( 0, $seconds );
$m = intdiv( $seconds, 60 );
$s = $seconds % 60;
/* translators: %s: number of seconds. */
$sec = sprintf( _n( '%s second', '%s seconds', $s, 'rooster-lms' ), Format::number( $s ) );
if ( 0 === $m ) {
return $sec;
}
/* translators: %s: number of minutes. */
$min = sprintf( _n( '%s minute', '%s minutes', $m, 'rooster-lms' ), Format::number( $m ) );
return 0 === $s ? $min : $min . ' ' . $sec;
}
public static function window_notice( string $why, EffectivePolicy $p ): string {
if ( 'window_closed' === $why ) {
$text = null === $p->closes_at
? __( 'The exam window has closed.', 'rooster-lms' )
/* translators: %s: date and time the exam window closed. */
: sprintf( __( 'The exam window closed on %s.', 'rooster-lms' ), self::when( $p->closes_at ) );
} else {
$text = null === $p->opens_at
? __( 'The exam window is not open yet. It opens once the lessons before it are available to you.', 'rooster-lms' )
/* translators: %s: date and time the exam window opens. */
: sprintf( __( 'The exam opens on %s.', 'rooster-lms' ), self::when( $p->opens_at ) );
}
return '
' . esc_html( $text ) . '
';
}
public static function withheld( Attempt $a ): string {
$view = Services::learner_view();
$class = $view->class_release( $a );
$may = $view->may_continue( $a );
$text = $class
? ReviewRenderer::withheld_text( $a )
: sprintf(
/* translators: 1: date and time the answers were submitted, 2: date and time the learner's exam window closes. */
__( 'Your answers were submitted on %1$s. Results will be published when your exam window closes on %2$s.', 'rooster-lms' ),
null === $a->submitted_at ? '' : self::when( $a->submitted_at ),
self::when( (string) $a->release_at )
);
$title = sprintf(
/* translators: %s: attempt number. */
__( 'Attempt %s', 'rooster-lms' ),
Format::number( $a->attempt_no )
);
$next = $may
? __( 'You can continue with the next lesson.', 'rooster-lms' )
: __( 'The next lesson opens when results are published.', 'rooster-lms' );
return ''
. '
'
. esc_html(
sprintf(
/* translators: %s: date and time the results were published. */
__( 'Results published on %s.', 'rooster-lms' ),
self::when( $a->released_at )
)
) . '