isset( $attributes['coursePost'] ) ? max( 0, (int) $attributes['coursePost'] ) : 0 ); } public static function render( array $attributes, string $content = '', ?\WP_Block $block = null ): string { unset( $content ); $course = Context::course_id( self::attributes( $attributes ), $block ); $user = get_current_user_id(); if ( $course <= 0 || Services::online_tests()->is_course( $course ) ) { return ''; } if ( ! empty( $attributes['invite'] ) && ( $user <= 0 || in_array( Services::reviews()->refusal( $user, $course ), array( 'login', 'not_enrolled' ), true ) ) ) { return '

' . esc_html__( 'Enrol in the course to leave a review.', 'rooster-lms' ) . '

'; } if ( $user <= 0 ) { return ''; } $inner = self::form( $course, $user, Services::reviews()->of_user( $user, $course ), true ); return '' === $inner ? '' : '
'rooster-lms-review' ) ) . '>' . $inner . '
'; } public static function form( int $course, int $user, ?array $review, bool $heading ): string { $why = Services::reviews()->refusal( $user, $course ); if ( null === $review && '' !== $why ) { $line = match ( $why ) { 'not_completed' => __( 'You can review this course once you have completed it.', 'rooster-lms' ), /* translators: %s: a percentage. */ 'not_enough_progress' => sprintf( __( 'You can review this course once you have completed %s of it.', 'rooster-lms' ), Format::percent( (int) CommunitySettings::get( 'reviews_min_percent' ) ) ), default => '', }; return '' === $line ? '' : '

' . esc_html( $line ) . '

'; } Front::enqueue( self::NAME ); $id = 'rl-review-' . $course; $rating = null === $review ? 0 : (int) $review['rating']; $config = Front::config( array( 'course' => $course, 'i18n' => self::texts(), ) ); $html = '
'; if ( $heading ) { $html .= '

' . esc_html( null === $review ? __( 'Write a review', 'rooster-lms' ) : __( 'Your review', 'rooster-lms' ) ) . '

'; } $html .= '
' . ( null === $review ? '' : self::status( $review ) ) . '
'; if ( '' !== $why ) { return $html . self::read_only( $review ) . '
'; } $html .= '
' . esc_html__( 'Your rating', 'rooster-lms' ) . ''; for ( $i = 1; $i <= 5; $i++ ) { $html .= '' /* translators: %s: number of stars (1 to 5). */ . ''; } $html .= '
'; $html .= '

' . '

'; $html .= '

' . '

'; if ( true === CommunitySettings::get( 'reviews_approval' ) ) { $html .= '

' . esc_html__( 'Reviews are published after the course staff approve them.', 'rooster-lms' ) . '

'; } $html .= '

' . Ui::button( null === $review ? __( 'Publish review', 'rooster-lms' ) : __( 'Update review', 'rooster-lms' ), 'primary', array( 'type' => 'submit' ) ) . '

' . ''; return $html; } public static function status( array $review ): string { $state = (int) $review['state']; $reply = null === ( $review['reply'] ?? null ) ? '' : trim( wp_strip_all_tags( (string) $review['reply'] ) ); if ( \RoosterLMS\Community\ReviewState::APPROVED->value === $state ) { $html = Ui::notice( __( 'Your review is published.', 'rooster-lms' ), 'success' ); if ( '' !== $reply ) { $html .= '

' . esc_html__( 'Reply from the course staff:', 'rooster-lms' ) . ' ' . esc_html( $reply ) . '

'; } return $html; } if ( \RoosterLMS\Community\ReviewState::REJECTED->value === $state ) { $html = Ui::notice( __( 'Your review was not published. You can change it and send it again.', 'rooster-lms' ), 'warning' ); if ( '' !== $reply ) { $html .= '

' . esc_html__( 'Reason:', 'rooster-lms' ) . ' ' . esc_html( $reply ) . '

'; } return $html; } return Ui::notice( __( 'Your review is waiting for approval by the course staff.', 'rooster-lms' ), 'info' ); } private static function read_only( ?array $review ): string { if ( null === $review ) { return ''; } return '

' . \RoosterLMS\Display\View::stars( (float) (int) $review['rating'] ) . '

' . ( '' !== (string) $review['title'] ? '

' . esc_html( (string) $review['title'] ) . '

' : '' ) . '
' . wp_kses( (string) $review['body'], Discussions::KSES_TAGS ) . '
'; } public static function account( $sections ) { if ( ! is_array( $sections ) ) { return $sections; } $sections[] = new Section( self::SECTION, __( 'My reviews', 'rooster-lms' ), 'progress', 'learner', 30, static fn( AccountViewer $v ): bool => $v->enrolments > 0, self::account_render( ... ), null, 'pencil' ); return $sections; } public static function account_render( AccountViewer $viewer ): string { $user = $viewer->id(); $reviews = self::reviews_of( $user ); $courses = array_values( array_unique( array_merge( array_keys( $reviews ), self::reviewable( $user ) ) ) ); if ( array() === $courses ) { return Ui::empty_state( __( 'No reviews yet', 'rooster-lms' ), __( 'When you complete a course, you can review it here.', 'rooster-lms' ) ); } $titles = Services::posts()->titles( ObjectType::COURSE, $courses ); \RoosterLMS\Community\CourseFacts::prime( $courses ); Services::enrolment_repo()->for_user( $user, $courses ); $html = '
'; foreach ( $courses as $course ) { $form = self::form( $course, $user, $reviews[ $course ] ?? null, false ); if ( '' === $form ) { continue; } Ui::card_part(); $html .= '
' . $form . '
'; } return $html . '
'; } public static function reviews_of( int $user ): array { $out = array(); foreach ( Db::rows( 'SELECT * FROM %i WHERE user_id = %d AND state <> %d ORDER BY updated_at DESC LIMIT %d', Db::table( 'course_reviews' ), $user, \RoosterLMS\Community\ReviewState::TRASHED->value, self::ACCOUNT_LIMIT ) as $row ) { $out[ (int) $row['course_id'] ] = $row; } return $out; } public static function reviewable( int $user ): array { $site = true === CommunitySettings::get( 'reviews_enabled' ); $rule = (string) CommunitySettings::get( 'reviews_eligibility' ); global $wpdb; $cond = match ( $rule ) { 'enrolled' => SqlPart::of( '1=1' ), 'progress' => SqlPart::of( '( p.status = %d OR p.percent >= %d )', ProgressStatus::COMPLETED->value, (int) CommunitySettings::get( 'reviews_min_percent' ) ), default => SqlPart::of( 'p.status = %d', ProgressStatus::COMPLETED->value ), }; $on = $site ? SqlPart::of( 'c.reviews_mode <> %d', 1 ) : SqlPart::of( 'c.reviews_mode = %d', 2 ); $rows = Db::col( SqlPart::of( 'SELECT e.course_id FROM %i e JOIN %i c ON c.id = e.course_id AND c.state = 1 LEFT JOIN %i p ON p.user_id = e.user_id AND p.course_id = e.course_id AND p.cycle = e.cycle WHERE e.user_id = %d AND e.status = %d AND e.course_id <> %d AND ', Db::table( 'enrolments' ), Db::table( 'courses' ), Db::table( 'progress' ), $user, EnrolmentStatus::ACTIVE->value, Services::online_tests()->course_id() )->add( $on )->add( ' AND ' )->add( 'enrolled' === $rule ? SqlPart::of( '1=1' ) : SqlPart::of( 'p.user_id IS NOT NULL AND ' )->add( $cond ) )->add( SqlPart::of( ' ORDER BY e.enrolled_at DESC LIMIT %d', self::ACCOUNT_LIMIT ) ) ); return array_values( array_map( 'intval', $rows ) ); } public static function texts(): array { return array( 'rating' => __( 'Choose a rating from 1 to 5 stars.', 'rooster-lms' ), 'saved' => __( 'Thank you. Your review is saved.', 'rooster-lms' ), 'pending' => __( 'Your review is waiting for approval by the course staff.', 'rooster-lms' ), 'approved' => __( 'Your review is published.', 'rooster-lms' ), 'rejected' => __( 'Your review was not published. You can change it and send it again.', 'rooster-lms' ), 'update' => __( 'Update review', 'rooster-lms' ), 'failed' => __( 'Something went wrong. Please try again.', 'rooster-lms' ), ); } }