Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WPML: Shared student progress and quiz submission #7492

Merged
merged 51 commits into from
Apr 9, 2024
Merged

Conversation

merkushin
Copy link
Member

@merkushin merkushin commented Feb 22, 2024

Resolves #2788

Unfortunately, questions are not translated automatically. (You can see these strings in the WPML translation editor, but they don't appear in the translated lesson.)
For now, the user has to translate them manually in lessons: open the translated lesson in the WordPress editor and change texts for questions.

Proposed Changes

  • Share course/lesson/quiz progress across different translations of the same course/lesson/quiz.
  • Share quiz submission.

Testing Instructions

  1. Install and activate WPML, add the second language.
  2. Create three courses with lessons and quizzes.
  3. Create translations for each of them.
  4. Go to the first translated course on the frontend, complete it. There should be no issues.
  5. Go to the backend, check in Students and Grading there is the student's progress attached to the original course/lesson/quiz.
  6. Go to Sensei > Settings > Experimental Features and enable HPPS. Enable sync, use "classic" storage.
  7. Go to the second translated course on the frontend, complete it. No issues expected.
  8. Go to the backend, check in Students and Grading there is the student's progress attached to the original course/lesson/quiz.
  9. Go to Sensei > Settings > Experimental Features and choose custom tables as a storage for progress.
  10. Go to the third translated course on the frontend, complete it. No issues expected.
  11. Go to the backend, check in Students and Grading there is the student's progress attached to the original course/lesson/quiz.

New/Updated Hooks

  • sensei_block_take_course_course_id - Filters the course ID for the take course block.
  • sensei_course_is_user_enrolled_course_id - Filters the course ID for the Sensei_Course::is_user_enrolled method.
  • sensei_course_start_course_id - Filter the course ID for the course being started.
  • sensei_utils_check_for_activity_before_get_comments - This action runs before getting the comments for the given request.
  • sensei_utils_check_for_activity_after_get_comments - This action runs after getting the comments for the given request.
  • sensei_utils_user_completed_lesson_lesson_id - Filter lesson ID for Sensei_Utils::user_completed_lesson method.
  • sensei_course_manual_enrolment_enroll_learner_course_id - Filter the course ID for manual enrolment when the student is being added to the course.
  • sensei_course_manual_enrolment_withdraw_learner_course_id - Filter the course ID for manual enrolment when the student is being removed from the course.
  • sensei_quiz_answer_create_submission_id - Filters the submission ID when quiz answer is created.
  • sensei_quiz_answer_create_question_id - Filters the question ID when quiz answer is created.
  • sensei_quiz_answer_get_all_submission_id - Filters the submission ID when getting all quiz answers.
  • sensei_quiz_answer_delete_all_submission_id - Filters the submission ID when deleting all quiz answers.
  • sensei_quiz_grade_create_submission_id - Filters the submission ID when quiz grade is created.
  • sensei_quiz_grade_create_question_id - Filters the question ID when quiz grade is created.
  • sensei_quiz_grade_get_all_submission_id - Filter the submission ID when getting all quiz grades.
  • sensei_quiz_grade_save_many_submission_id - Filters the submission ID when saving many quiz grades.
  • sensei_quiz_grade_save_many_question_id - Filters the question ID when saving many quiz grades.
  • sensei_quiz_grade_delete_all_submission_id - Filters the submission ID when deleting all quiz grades.
  • sensei_quiz_submission_create_quiz_id - Filters the quiz ID when quiz submission is created.
  • sensei_quiz_submission_get_or_create_quiz_id - Filters the quiz ID when quiz submission is created.
  • sensei_quiz_submission_get_quiz_id - Filters the quiz ID when quiz submission is retrieved.
  • sensei_quiz_submission_get_question_ids_submission_id - Filters the quiz submission ID when getting the question IDs.
  • sensei_course_progress_create_course_id - Filter the course ID for a created course progress.
  • sensei_course_progress_get_course_id - Filter the course ID for a course progress we want to get.
  • sensei_course_progress_has_course_id - Filter the course ID for a course progress we want to check.
  • sensei_course_progress_delete_for_course_course_id - Filter the course ID for a course progress we want to delete.
  • sensei_course_progress_find_course_id - Filter the course ID for a course progress we want to find.
  • sensei_lesson_progress_create_lesson_id - Filter lesson id for lesson progress creation.
  • sensei_lesson_progress_get_lesson_id - Filter lesson id for lesson progress creation.
  • sensei_lesson_progress_has_lesson_id - Filter lesson id for lesson progress check.
  • sensei_lesson_progress_delete_for_lesson_lesson_id - Filter lesson id for lesson progress deletion.
  • sensei_lesson_progress_count_course_id - Filter course id for lesson progress counting.
  • sensei_lesson_progress_find_lesson_id - Filter lesson id when finding lesson progress.
  • sensei_quiz_progress_create_quiz_id - Filter quiz id for quiz progress creation.
  • sensei_quiz_progress_get_quiz_id - Filter quiz id for quiz progress retrieval.
  • sensei_quiz_progress_has_quiz_id - Filter quiz id for quiz progress existence check.
  • sensei_quiz_progress_delete_for_quiz_quiz_id - Filter quiz id for quiz progress deletion.
  • sensei_quiz_progress_find_quiz_id - Filter quiz id for quiz progress retrieval.

Pre-Merge Checklist

  • PR title and description contain sufficient detail and accurately describe the changes
  • Acceptance criteria is met
  • Decisions are publicly documented
  • Adheres to coding standards (PHP, JavaScript, CSS, HTML)
  • All strings are translatable (without concatenation, handles plurals)
  • Follows our naming conventions (P6rkRX-4oA-p2)
  • Hooks (p6rkRX-1uS-p2) and functions are documented
  • New UIs are responsive and use a mobile-first approach
  • New UIs match the designs
  • Different user privileges (admin, teacher, subscriber) are tested as appropriate
  • Legacy courses (course without blocks) are tested
  • Code is tested on the minimum supported PHP and WordPress versions
  • User interface changes have been tested on the latest versions of Chrome, Firefox and Safari
  • "Needs Documentation" label is added if this change requires updates to documentation
  • Known issues are created as new GitHub issues

@merkushin merkushin added Hooks This change adds or modifies one or more hooks. WPML Compatibility issues with WPML labels Feb 22, 2024
@merkushin merkushin added this to the 4.21.0 milestone Feb 22, 2024
@donnapep
Copy link
Collaborator

donnapep commented Mar 27, 2024

Here's some further testing feedback, some of which may not necessarily be related to this PR. Feel free to open separate issues as needed.

  • I took a quiz in the translated course and chose an answer. When I clicked the Save Progress button, I got a fatal error:
PHP Fatal error:  Uncaught RuntimeException: Missing lesson status. in /Users/donnapep/Local Sites/wpml/app/public/wp-content/plugins/sensei/includes/internal/quiz-submission/submission/repositories/class-comments-based-submission-repository.php:58

This was my quiz:

Screenshot 2024-03-27 at 10 57 36 AM

  • For the same quiz, when I selected an answer and clicked Complete Quiz, nothing happened. The quiz reverted to its initial state with no answer selected.
  • When I clicked the Complete Lesson button, the lesson status changed to Completed on the Students page, but there was nothing in the Date Completed column. This seemed to be a sporadic issue. Some lessons had a completed date but one didn't. I'm honestly not sure I could reproduce it again, but still thought it worth documenting.

I then reset the student's progress, and re-enrolled them to test again. This time, when I took the quiz I didn't see anything at all on the frontend:

Screenshot 2024-03-27 at 11 40 20 AM

Although it's there in the editor:

Screenshot 2024-03-27 at 11 40 52 AM

At this point I decided to stop testing quizzes and focused on course and lesson progress instead. 😅 So I removed all quizzes from the original lessons and translated lessons for a different course. Although everything looked good in the editor, I noticed that questions still retained their reference to the lesson:

Screenshot 2024-03-27 at 11 46 43 AM

Screenshot 2024-03-27 at 11 47 24 AM

I then created a brand new course without quizzes from the very start and didn't encounter any issues with course or lesson progress when taking either the original or the translated course.

I think the main concern from my testing is the unpredictable behavior when quizzes are involved. Do any of the issues I documented here seem to be related to the known issues we currently have?

@merkushin
Copy link
Member Author

@donnapep Thanks for testing it!

I'll check it thoroughly a bit later.

But I can say right now that previous experiments and tests might affect the result. The process is brittle 😢 I hope we'll be able to achieve a more stable state having more testing and feedback.

@donnapep
Copy link
Collaborator

donnapep commented Apr 1, 2024

I continued testing (this time using WPML to translate) and have found an issue that has blocked me from further testing - I'm not seeing the Take Quiz button on a translated lesson, only the Complete Lesson button. The lesson has a quiz, and I've translated the strings, but there is no button to take the quiz and no Quiz link in the sidebar. Only the original language shows the button and links:

Frontend

Screenshot 2024-04-01 at 9 53 38 AM

Editor

Screenshot 2024-04-01 at 9 54 48 AM

@donnapep
Copy link
Collaborator

donnapep commented Apr 2, 2024

I followed the instructions documented here and things went much more smoothly. 🎉

There are a couple of small things I noticed that I'm not sure are related to this PR, so I'd like to dig a bit deeper, but they shouldn't be considered blockers for this PR. I will open separate issues if they end up being legitimate bugs. I did also notice what you were saying about incorrect feedback being displayed even though I answered the question correctly. Just checking that we have an open issue for that one?

I haven't done a code review, but I think as soon as we have that, we can approve this one and get it merged.

@merkushin
Copy link
Member Author

@donnapep Yay! Yes, both issues with grading are logged here: https://github.com/orgs/Automattic/projects/404/views/51 (ToDo column).

Copy link
Contributor

@Imran92 Imran92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added some initial comments, including some queries

* @param {WP_Post} $post Post object for course.
* @return {string|bool} Filtered URL to redirect students to after starting course. Return `false` to prevent redirect.
*/
$redirect_url = apply_filters( 'sensei_start_course_redirect_url', get_permalink( $post->ID ), $post );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, we are using the filtered $course_id instead of $post->ID directly for all the operations above (I'm referring to the line $course_id = (int) apply_filters( 'sensei_course_start_course_id', $post->ID ?? 0 );), is there a reason are using just $post->ID here? Not an issue, but was just wondering. (Maybe because WPML auto redirects the request?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we need to have the original post ID to redirect the user to a proper location in the same course.
Filtered course ID might be different (in case of WPML for a translated course it is definitely so).

@@ -47,12 +59,12 @@ public function create( int $course_id, int $user_id ): Course_Progress_Interfac
throw new \RuntimeException( "Can't create a course progress" );
}

$progress = $this->get( $course_id, $user_id );
if ( ! $progress ) {
$comment = get_comment( $comment_id );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, any particular reason we removed the usage of the 'get' function and fetched the comment directly here now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, it helped me to partially solve an issue with a case when it can't find a comment right after creating it.
Later, I found it didn't solve it entirely and found another WPML-focused solution, but decided to leave this change as it should be a more optimized version of fetching a comment (it uses the primary key here).

includes/wpml/class-lesson-progress.php Outdated Show resolved Hide resolved
Comment on lines 28 to 33
add_filter( 'sensei_utils_user_completed_lesson_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
add_filter( 'sensei_lesson_progress_create_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
add_filter( 'sensei_lesson_progress_get_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
add_filter( 'sensei_lesson_progress_has_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
add_filter( 'sensei_lesson_progress_delete_for_lesson_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
add_filter( 'sensei_lesson_progress_find_lesson_id', array( $this, 'translate_lesson_id' ), 10, 1 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can remove all the 10, 1 values as they are default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here: 96814c9

* @return int
*/
public function translate_course_id( $course_id ): int {
$course_id = (int) $course_id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not doing this explicit casting operation for lessons or quiz progress, is there any reason behind doing it for Course and Quiz?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think I encountered an error with course id at some point. Or, which is more likely Psalm argued about it.
Added explicit casting in all these cases for consistency: b9c61df
However, the reversal approach (not cast at all) is fine here as well.

Comment on lines 46 to 61
public function translate_lesson_id( $lesson_id ) {
$details = (array) apply_filters(
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
'wpml_element_language_details',
null,
array(
'element_id' => $lesson_id,
'element_type' => 'lesson',
)
);

$original_language_code = $details['source_language_code'] ?? $details['language_code'] ?? null;

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
return (int) apply_filters( 'wpml_object_id', $lesson_id, 'lesson', true, $original_language_code );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, but all these translate functions look very similar except for a couple of different values in the apply_filter line, should we move this to a common place instead? Maybe all these entity specific translation classes can have a parent class that contains this function or an util class or something? WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a separate PR where we extract all WPML filters and actions in a form of methods into a trait: #7566

Without WPML details these methods look much shorter and clearer, I don't think it makes sense to create additional abstraction here:

public function translate_lesson_id( $lesson_id ) {
$details = $this->get_element_language_details( $lesson_id, 'lesson' );
$original_language_code = $details['source_language_code'] ?? $details['language_code'] ?? null;
return $this->get_object_id( $lesson_id, 'lesson', true, $original_language_code );
}

merkushin and others added 2 commits April 3, 2024 10:44
Co-authored-by: Imran Hossain <imranh920@gmail.com>
Copy link

github-actions bot commented Apr 3, 2024

Test the previous changes of this PR with WordPress Playground.

Copy link

github-actions bot commented Apr 3, 2024

Test the previous changes of this PR with WordPress Playground.

Copy link

github-actions bot commented Apr 3, 2024

Test the previous changes of this PR with WordPress Playground.

@Imran92
Copy link
Contributor

Imran92 commented Apr 4, 2024

I'm trying to test the quiz part using the branch fix/wpml-translate-slugs as you've suggested. For some reason, I could translate Course and Lesson, but getting 404 only for quizzes. I'm attaching videos in case it helps. I've also tried checking the "Don’t translate Sensei slugs" checkbox in Sensei LMS -> Settings -> WPML. But didn't help

Course and Lessons in backend:

Screen.Recording.2024-04-04.at.6.17.55.AM.mov

Quiz in Frontend:

Screen.Recording.2024-04-04.at.6.23.27.AM.mov

@Imran92
Copy link
Contributor

Imran92 commented Apr 4, 2024

Ah okay, I added a Quiz for the second lesson and this time the URL is working. So maybe I made a mistake following the step. But don't know why the answers are appearing empty in the translated lesson -

Screen.Recording.2024-04-04.at.7.06.22.AM.mov

Just wanted to document what I am seeing. I'll keep testing by putting values manually in those empty answer fields and see how it works going forward

Copy link
Contributor

@Imran92 Imran92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the main target was to provide the users with one flow to translate, I think we can merge this PR, I was able to test Course and Lesson translation, and direct translation of Quizzes (without WPML editor) as per the instructions 👍 🚢 :shipit:

I think we should create an issue for #7492 (comment) in case we don't have it already and if it's reproducible.

Copy link
Contributor

@Imran92 Imran92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a separate note, I am a bit curious about the linked issue and this PR. Because in the linked issue, it seems it is talking about the student profile page (Check in Steps to Reproduce and the attached images on the issue) -

Create a translated version of the My Courses page that contains the [sensei_user_courses] shortcode.
As a user, enroll the course in one language and do some progress
On the user profile page check the progress in a secondary language

But in this PR's testing instruction, there isn't any step for checking the profile page. So tried to do it, and it seems like it's still not working.

I'm attaching a video here. Even though I made this progress in Spanish (a secondary language), they don't appear when I select Spanish.

Screen.Recording.2024-04-04.at.9.28.25.AM.mov

Maybe I'm missing something, but in case this issue actually persists, should we remove the link to the issue from this PR and handle it separately as we're actually not fixing the issue here.

@Imran92
Copy link
Contributor

Imran92 commented Apr 4, 2024

I was just testing for paid courses, the purchase button disappears when I switch language -

Screen.Recording.2024-04-04.at.9.54.46.AM.mov

Copy link
Contributor

@Imran92 Imran92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've found a progress related bug here. When we remove student enrolments and/or progress, do we also remove the progress added for translated versions of a lesson quiz?

I saw a behavior, and it made me doubt if we are doing that. For test purpose, I removed the enrollment and progress of a user. Then again, I tried to access the Course's lesson. Problem is, when I switched to Spanish, I saw a message on top saying 1/1 question completed. I think this implies that my progress made under that language didn't get cleared. Can you kindly check if that's the case? If it is, then I think we should clear those as well.

I'm attaching a video-

Screen.Recording.2024-04-04.at.9.58.04.AM.mov

@renatho renatho modified the milestones: 4.23.0, 4.23.1 Apr 4, 2024
Copy link

github-actions bot commented Apr 4, 2024

Test the previous changes of this PR with WordPress Playground.

Copy link

github-actions bot commented Apr 8, 2024

Test the previous changes of this PR with WordPress Playground.

@merkushin merkushin merged commit 7bba262 into trunk Apr 9, 2024
23 checks passed
@merkushin merkushin deleted the fix/wpml-progress branch April 9, 2024 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Hooks This change adds or modifies one or more hooks. WPML Compatibility issues with WPML
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sync course progress with WPML translations
4 participants