diff --git a/changelog/fix-wpml-save-course-structure b/changelog/fix-wpml-save-course-structure new file mode 100644 index 0000000000..e459b8cc36 --- /dev/null +++ b/changelog/fix-wpml-save-course-structure @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +WPML compatibility fix: Add translations for lessons and quizzes that were created while saving the course structure. diff --git a/includes/class-sensei-course-structure.php b/includes/class-sensei-course-structure.php index 734f639c1b..6206c2712b 100644 --- a/includes/class-sensei-course-structure.php +++ b/includes/class-sensei-course-structure.php @@ -519,14 +519,26 @@ private function create_lesson( array $item ) { ], ]; - $post_id = wp_insert_post( $post_args ); - if ( ! $post_id ) { + $lesson_id = wp_insert_post( $post_args ); + if ( ! $lesson_id ) { return false; } - $this->create_quiz( $post_id ); + /** + * Fires after a lesson is created while saving the course structure. + * + * @since $$next-version$$ + * + * @hook sensei_course_structure_lesson_created + * + * @param {int} $lesson_id Lesson post ID. + * @param {int} $course_id Course post ID. + */ + do_action( 'sensei_course_structure_lesson_created', $lesson_id, $this->course_id ); - return $post_id; + $this->create_quiz( $lesson_id ); + + return $lesson_id; } /** @@ -549,8 +561,23 @@ private function create_quiz( int $lesson_id ) { ]; $quiz_id = wp_insert_post( $post_args ); + if ( ! $quiz_id ) { + return; + } + update_post_meta( $lesson_id, '_lesson_quiz', $quiz_id ); + /** + * Fires after a quiz is created while saving the course structure. + * + * @since $$next-version$$ + * + * @hook sensei_course_structure_quiz_created + * + * @param {int} $quiz_id Quiz post ID. + * @param {int} $lesson_id Course post ID. + */ + do_action( 'sensei_course_structure_quiz_created', $quiz_id, $lesson_id ); } /** diff --git a/includes/wpml/class-sensei-wpml.php b/includes/wpml/class-sensei-wpml.php index c68838dc15..fbb8c31293 100644 --- a/includes/wpml/class-sensei-wpml.php +++ b/includes/wpml/class-sensei-wpml.php @@ -1,10 +1,30 @@ $course_id, + 'element_type' => 'course', + ) + ); + if ( ! $language_code ) { + // Use current language if course language is not set. + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + $language_code = apply_filters( 'wpml_current_language', null ); + } + + $args = array( + 'element_id' => $lesson_id, + 'element_type' => 'post_lesson', + 'trid' => false, + 'language_code' => $language_code, + ); + + // Set language details for the lesson. + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + do_action( 'wpml_set_element_language_details', $args ); + } + + /** + * Set language details for the quiz when it is created. + * + * @since $$next-version$$ + * + * @internal + * + * @param int $quiz_id Quiz ID. + * @param int $lesson_id Lesson ID. + */ + public function set_language_details_when_quiz_created( $quiz_id, $lesson_id ) { + + // Get lesson language_code. + $language_code = apply_filters( + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + 'wpml_element_language_code', + null, + array( + 'element_id' => $lesson_id, + 'element_type' => 'lesson', + ) + ); + if ( ! $language_code ) { + // Use current language if lesson language is not set. + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + $language_code = apply_filters( 'wpml_current_language', null ); + } + + $args = array( + 'element_id' => $quiz_id, + 'element_type' => 'post_quiz', + 'trid' => false, + 'language_code' => $language_code, + ); + + // Set language details for the lesson. + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + do_action( 'wpml_set_element_language_details', $args ); } } diff --git a/tests/unit-tests/test-class-sensei-course-structure.php b/tests/unit-tests/test-class-sensei-course-structure.php index 74408a92e5..912936f988 100644 --- a/tests/unit-tests/test-class-sensei-course-structure.php +++ b/tests/unit-tests/test-class-sensei-course-structure.php @@ -25,7 +25,7 @@ public function setUp(): void { /** * Test getting course structure when just lessons when one lesson is unpublished in view context. */ - public function testGetJustLessonsView() { + public function testGet_WithViewContextWhenStructureSavedWithLessonsOnly_ReturnSameStructureWithIdsAndTypes() { $course_id = $this->factory->course->create(); $course_lesson_args = [ 'meta_input' => [ @@ -66,7 +66,7 @@ public function testGetJustLessonsView() { /** * Test getting course structure when just lessons when one lesson is unpublished in edit context. */ - public function testGetJustLessonsEdit() { + public function testGet_WithEditContextWhenStructureSavedWithLessonsOnly_ReturnsSameStuctureWithIdsAndTypes() { $course_id = $this->factory->course->create(); $course_lesson_args = [ 'meta_input' => [ @@ -107,7 +107,7 @@ public function testGetJustLessonsEdit() { /** * Test getting course structure when just modules on the first level. */ - public function testGetJustModules() { + public function testGet_WithViewContextWhenStructureSavedWithModulesOnly_ReturnsSameStructure() { $course_id = $this->factory->course->create(); $lessons = $this->factory->lesson->create_many( 4 ); @@ -156,7 +156,7 @@ public function testGetJustModules() { /** * Test getting course structure when just modules with no lessons and one rogue lesson while in edit context. */ - public function testGetModulesWithEmptyLessonsEdit() { + public function testGet_WithEditContextWhenStructureSavedWithModulesWithEmptyLessons_ReturnsSameStructure() { $this->login_as_admin(); $course_id = $this->factory->course->create(); @@ -1654,7 +1654,7 @@ private function saveStructure( int $course_id, array $structure, $module_parent } } - public function testGetForEditMode_WhenCalled_ReturnsTeacherNameWithModulesProperly() { + public function testGet_CalledAsAdminWithEditContextAndTeacherAssignedToModule_ReturnsTeacherNameWithModulesProperly() { /* Arrange */ global $current_screen; $initial_current_screen = $current_screen; @@ -1715,4 +1715,60 @@ public function testGetForEditMode_WhenCalled_ReturnsTeacherNameWithModulesPrope // @see https://core.trac.wordpress.org/ticket/53431 $current_screen = $initial_current_screen; } + + public function testSave_WithNewLesson_FiresLessonCreatedAction() { + /* Arrange. */ + $this->login_as_teacher(); + + $course_id = $this->factory->course->create(); + + $new_structure = array( + array( + 'type' => 'lesson', + 'title' => 'New lesson', + ), + ); + + $course_structure = Sensei_Course_Structure::instance( $course_id ); + + $lesson_created_action_fired = false; + $action = function( $lesson_id, $course_id ) use ( &$lesson_created_action_fired ) { + $lesson_created_action_fired = true; + }; + add_action( 'sensei_course_structure_lesson_created', $action, 10, 2 ); + + /* Act. */ + $course_structure->save( $new_structure ); + + /* Assert. */ + $this->assertTrue( $lesson_created_action_fired ); + } + + public function testSave_WithNewLesson_FiresQuizCreatedAction() { + /* Arrange. */ + $this->login_as_teacher(); + + $course_id = $this->factory->course->create(); + + $new_structure = array( + array( + 'type' => 'lesson', + 'title' => 'New lesson', + ), + ); + + $course_structure = Sensei_Course_Structure::instance( $course_id ); + + $quiz_created_action_fired = false; + $action = function( $quiz, $lesson ) use ( &$quiz_created_action_fired ) { + $quiz_created_action_fired = true; + }; + add_action( 'sensei_course_structure_quiz_created', $action, 10, 2 ); + + /* Act. */ + $course_structure->save( $new_structure ); + + /* Assert. */ + $this->assertTrue( $quiz_created_action_fired ); + } } diff --git a/tests/unit-tests/wpml/test-class-sensei-wpml.php b/tests/unit-tests/wpml/test-class-sensei-wpml.php new file mode 100644 index 0000000000..fb0b2c30d3 --- /dev/null +++ b/tests/unit-tests/wpml/test-class-sensei-wpml.php @@ -0,0 +1,147 @@ +set_language_details_when_lesson_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_element_language_code', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } + + public function testSetLanguageDetailsWhenLessonCreated_WhenCalled_AppliesWpmlCurrentLangugeFilter() { + /* Arrange. */ + $wpml = new Sensei_WPML(); + + $filter_language_code_function = function( $language_code, $element_data ) use ( &$filter_applied ) { + return null; + }; + add_filter( 'wpml_element_language_code', $filter_language_code_function, 10, 2 ); + + $filter_applied = false; + $filter_function = function( $language_code ) use ( &$filter_applied ) { + $filter_applied = true; + return $language_code; + }; + add_filter( 'wpml_current_language', $filter_function, 10, 1 ); + + /* Act. */ + $wpml->set_language_details_when_lesson_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_element_language_code', $filter_language_code_function, 10 ); + remove_filter( 'wpml_current_language', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } + + public function testSetLanguageDetailsWhenLessonCreated_WhenCalled_AppliesWpmlSetElementLanguageDetails() { + /* Arrange. */ + $wpml = new Sensei_WPML(); + + $filter_applied = false; + $filter_function = function( $data ) use ( &$filter_applied ) { + $filter_applied = true; + return $data; + }; + + add_filter( 'wpml_set_element_language_details', $filter_function, 10, 1 ); + + /* Act. */ + $wpml->set_language_details_when_lesson_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_set_element_language_details', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } + + public function testSetLanguageDetailsWhenQuizCreated_WhenCalled_AppliesWpmlElementLanguageCodeFilter() { + /* Arrange. */ + $wpml = new Sensei_WPML(); + + $filter_applied = false; + $filter_function = function( $language_code, $element_data ) use ( &$filter_applied ) { + $filter_applied = true; + return $language_code; + }; + + add_filter( 'wpml_element_language_code', $filter_function, 10, 2 ); + + /* Act. */ + $wpml->set_language_details_when_quiz_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_element_language_code', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } + + public function testSetLanguageDetailsWhenQuizCreated_WhenCalled_AppliesWpmlCurrentLangugeFilter() { + /* Arrange. */ + $wpml = new Sensei_WPML(); + + $filter_language_code_function = function( $language_code, $element_data ) use ( &$filter_applied ) { + return null; + }; + add_filter( 'wpml_element_language_code', $filter_language_code_function, 10, 2 ); + + $filter_applied = false; + $filter_function = function( $language_code ) use ( &$filter_applied ) { + $filter_applied = true; + return $language_code; + }; + add_filter( 'wpml_current_language', $filter_function, 10, 1 ); + + /* Act. */ + $wpml->set_language_details_when_quiz_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_element_language_code', $filter_language_code_function, 10 ); + remove_filter( 'wpml_current_language', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } + public function testSetLanguageDetailsWhenQuizCreated_WhenCalled_AppliesWpmlSetElementLanguageDetails() { + /* Arrange. */ + $wpml = new Sensei_WPML(); + + $filter_applied = false; + $filter_function = function( $data ) use ( &$filter_applied ) { + $filter_applied = true; + return $data; + }; + + add_filter( 'wpml_set_element_language_details', $filter_function, 10, 1 ); + + /* Act. */ + $wpml->set_language_details_when_quiz_created( 1, 2 ); + + /* Clean up & Assert. */ + remove_filter( 'wpml_set_element_language_details', $filter_function, 10 ); + + $this->assertTrue( $filter_applied ); + } +}