From 3d4a3de3d71c701d063b2471aa3a49c6e5dbfd64 Mon Sep 17 00:00:00 2001 From: Thomas Patrick Levy Date: Fri, 19 Feb 2021 17:08:11 -0700 Subject: [PATCH 01/12] content filtering conditions WIP --- includes/functions/llms-functions-content.php | 41 ++++++++- .../class-llms-test-functions-content.php | 85 ++++++++++++++++++- 2 files changed, 119 insertions(+), 7 deletions(-) diff --git a/includes/functions/llms-functions-content.php b/includes/functions/llms-functions-content.php index bc2993c0d6..cc09cac2b9 100644 --- a/includes/functions/llms-functions-content.php +++ b/includes/functions/llms-functions-content.php @@ -63,7 +63,6 @@ function llms_get_post_content( $content ) { $template_after = llms_get_template_part_contents( 'content', 'single-lesson-after' ); } } elseif ( 'llms_quiz' === $post->post_type ) { - $template_before = llms_get_template_part_contents( 'content', 'single-quiz-before' ); $template_after = llms_get_template_part_contents( 'content', 'single-quiz-after' ); @@ -84,7 +83,7 @@ function llms_get_post_content( $content ) { /** * Filter the post_content of a LifterLMS post type. * - * @since Unknown. + * @since [version] * * @param string $content Post content. * @param WP_Post $post Post object. @@ -94,4 +93,40 @@ function llms_get_post_content( $content ) { } } -add_filter( 'the_content', 'llms_get_post_content' ); + +/** + * Initialize LifterLMS post type content filters + * + * This method is used to determine whether or `llms_get_post_content()` should automatically + * be added as a filter callback for the WP core `the_content` filter. + * + * When working with posts on the admin panel (during course building, importing) we don't want + * other plugins that may desire running `apply_filters( 'the_content', $content )` to apply our + * plugin's filters. + * + * Additionally, we don't want to return template information when processing REST requests. + * + * @since [version] + * + * @return boolean Returns `true` if content filters are added and `false` if not. + */ +function llms_post_content_init() { + + // Don't filter any requests on the admin panel or when processing REST requests. + $should_filter = ( ! is_admin() && ! llms_is_rest() ); + + /** + * Filters whether or not LifterLMS content filters should be applied. + * + * @since [version] + * + * @param boolean $should_filter Whether or not to filter the content. + */ + if ( apply_filters( 'llms_should_filter_post_content', $should_filter ) ) { + return add_filter( 'the_content', 'llms_get_post_content' ); + } + + return false; + +} +add_action( 'init', 'llms_post_content_init' ); diff --git a/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php b/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php index 9306baac51..333fa7df3f 100644 --- a/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php +++ b/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php @@ -1,10 +1,13 @@ Lorem ipsum dolor sit amet.

'; $post_types = array( 'llms_membership', 'course', 'lesson', 'post', 'page' ); foreach ( $post_types as $post_type ) { @@ -34,4 +39,76 @@ public function test_llms_get_post_content() { } + /** + * Test that llms_get_post_content() will return early if the `$post` global is not set. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_no_global() { + + llms_post_content_init(); + + $input = 'whatever'; + $this->assertEquals( $input, llms_get_post_content( $input ) ); + + } + + /** + * Test llms_post_content_init() when filters should be applied + * + * @since [version] + * + * @return void + */ + public function test_llms_post_content_init() { + + remove_filter( 'the_content', 'llms_get_post_content' ); + + $this->assertTrue( llms_post_content_init() ); + $this->assertEquals( 10, has_filter( 'the_content', 'llms_get_post_content' ) ); + + } + + /** + * Test llms_post_content_init() when on the admin panel + * + * @since [version] + * + * @return void + */ + public function test_llms_post_content_init_is_admin() { + + remove_filter( 'the_content', 'llms_get_post_content' ); + + set_current_screen( 'admin.php' ); + + $this->assertFalse( llms_post_content_init() ); + $this->assertFalse( has_filter( 'the_content', 'llms_get_post_content' ) ); + + set_current_screen( 'front' ); // Reset. + + } + + /** + * Test llms_post_content_init() during REST requests + * + * @since [version] + * + * @runInSeparateProcess + * @preserveGlobalState disabled + * + * @return void + */ + public function test_llms_post_content_init_is_rest() { + + remove_filter( 'the_content', 'llms_get_post_content' ); + + define( 'REST_REQUEST', true ); + $this->assertFalse( llms_post_content_init() ); + $this->assertFalse( has_filter( 'the_content', 'llms_get_post_content' ) ); + + } + } From 9408aed448172984ac9c6c342751cfc969105bcd Mon Sep 17 00:00:00 2001 From: Thomas Patrick Levy Date: Fri, 19 Feb 2021 20:54:11 -0700 Subject: [PATCH 02/12] Add 'llms-sales-page' as a post type feature --- includes/class.llms.post-types.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/includes/class.llms.post-types.php b/includes/class.llms.post-types.php index c13d635c71..905e618413 100644 --- a/includes/class.llms.post-types.php +++ b/includes/class.llms.post-types.php @@ -5,7 +5,7 @@ * @package LifterLMS\Classes * * @since 1.0.0 - * @version 4.5.1 + * @version [version] */ defined( 'ABSPATH' ) || exit; @@ -344,6 +344,7 @@ public static function register_post_type( $name, $data ) { * @since 3.33.0 `llms_question` post type is not publicly queryable anymore. * @since 3.37.12 Added 'revisions' support to course, lesson, and llms_mebership post types. * @since 4.5.1 Removed "excerpt" support for the course post type. + * @since [version] Add "llms-sales-page" feature to course and membership post types. * * @return void */ @@ -385,7 +386,7 @@ public static function register_post_types() { 'feeds' => true, ), 'query_var' => true, - 'supports' => array( 'title', 'author', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions', 'llms-clone-post', 'llms-export-post' ), + 'supports' => array( 'title', 'author', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions', 'llms-clone-post', 'llms-export-post', 'llms-sales-page' ), 'has_archive' => ( $catalog_id && get_page( $catalog_id ) ) ? get_page_uri( $catalog_id ) : _x( 'courses', 'course archive url slug', 'lifterlms' ), 'show_in_nav_menus' => true, 'menu_position' => 52, @@ -580,7 +581,7 @@ public static function register_post_types() { 'feeds' => true, ), 'query_var' => true, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions' ), + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions', 'llms-sales-page' ), 'has_archive' => ( $membership_page_id && get_page( $membership_page_id ) ) ? get_page_uri( $membership_page_id ) : _x( 'memberships', 'membership archive url slug', 'lifterlms' ), 'show_in_nav_menus' => true, 'menu_position' => 52, From 5e57fe45faf1cd3853ae8fff62d49f9ae983e47a Mon Sep 17 00:00:00 2001 From: Thomas Patrick Levy Date: Fri, 19 Feb 2021 20:54:29 -0700 Subject: [PATCH 03/12] Refactor --- includes/functions/llms-functions-content.php | 113 ++++++++++-------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/includes/functions/llms-functions-content.php b/includes/functions/llms-functions-content.php index cc09cac2b9..32a6c8eff3 100644 --- a/includes/functions/llms-functions-content.php +++ b/includes/functions/llms-functions-content.php @@ -5,7 +5,7 @@ * @package LifterLMS/Functions * * @since 3.25.1 - * @version 3.25.2 + * @version [version] */ defined( 'ABSPATH' ) || exit; @@ -15,10 +15,11 @@ /** * Post Template Include * - * Appends LLMS content above and below post content. + * Adds LifterLMS template content before and after the post's default content. * * @since 1.0.0 * @since 3.25.2 Unknown. + * @since [version] TODO. * * @param string $content WP_Post post_content. * @return string @@ -26,74 +27,84 @@ function llms_get_post_content( $content ) { global $post; - if ( ! $post instanceof WP_Post ) { + if ( ! $post instanceof WP_Post || ! in_array( $post->post_type, array( 'course', 'llms_membership', 'lesson', 'llms_quiz' ), true ) ) { return $content; } - $page_restricted = llms_page_restricted( $post->ID ); - $before = ''; - $template_before = ''; - $after = ''; - $template_after = ''; + $restrictions = llms_page_restricted( $post->ID ); + $post_type = str_replace( 'llms_', '', $post->post_type ); + $template_before = 'single-' . $post_type . '-before'; + $template_after = 'single-' . $post_type . '-after'; - if ( 'course' === $post->post_type || 'llms_membership' === $post->post_type ) { - - $sales_page = get_post_meta( $post->ID, '_llms_sales_page_content_type', true ); - - if ( $page_restricted['is_restricted'] && ( '' === $sales_page || 'content' === $sales_page ) ) { - - add_filter( 'the_excerpt', array( $GLOBALS['wp_embed'], 'autoembed' ), 9 ); - if ( $post->post_excerpt ) { - $content = llms_get_excerpt( $post->ID ); - } - } - - $template_name = str_replace( 'llms_', '', $post->post_type ); - $template_before = llms_get_template_part_contents( 'content', 'single-' . $template_name . '-before' ); - $template_after = llms_get_template_part_contents( 'content', 'single-' . $template_name . '-after' ); - - } elseif ( 'lesson' === $post->post_type ) { - - if ( $page_restricted['is_restricted'] ) { + if ( $restrictions['is_restricted'] ) { + $content = llms_get_post_sales_page_content( $post, $content ); + if ( in_array( $post->post_type, array( 'lesson', 'llms_quiz' ), true ) ) { $content = ''; - $template_before = llms_get_template_part_contents( 'content', 'no-access-before' ); - $template_after = llms_get_template_part_contents( 'content', 'no-access-after' ); - } else { - $template_before = llms_get_template_part_contents( 'content', 'single-lesson-before' ); - $template_after = llms_get_template_part_contents( 'content', 'single-lesson-after' ); + $template_before = 'no-access-before'; + $template_after = 'no-access-after'; } - } elseif ( 'llms_quiz' === $post->post_type ) { - $template_before = llms_get_template_part_contents( 'content', 'single-quiz-before' ); - $template_after = llms_get_template_part_contents( 'content', 'single-quiz-after' ); - } - if ( $template_before ) { - ob_start(); - load_template( $template_before, false ); - $before = ob_get_clean(); - } + ob_start(); + load_template( llms_get_template_part_contents( 'content', $template_before ), false ); + $before = ob_get_clean(); - if ( $template_after ) { - ob_start(); - load_template( $template_after, false ); - $after = ob_get_clean(); - } + ob_start(); + load_template( llms_get_template_part_contents( 'content', $template_after ), false ); + $after = ob_get_clean(); /** * Filter the post_content of a LifterLMS post type. * - * @since [version] + * @since Unknown * - * @param string $content Post content. - * @param WP_Post $post Post object. - * @param array $page_restricted Result from `llms_page_restricted()` for the current post. + * @param string $content Post content. + * @param WP_Post $post Post object. + * @param array $restrictions Result from `llms_page_restricted()` for the current post. */ - return apply_filters( 'llms_get_post_content', do_shortcode( $before . $content . $after ), $post, $page_restricted ); + return apply_filters( 'llms_get_post_content', do_shortcode( $before . $content . $after ), $post, $restrictions ); } } +/** + * Retrieve the sales page content for a course or membership + * + * By default only courses and memberships support sales pages, the meta property + * must be set to `content` or an empty string, and the post must have a `post_excerpt` + * property value. + * + * @since [version] + * + * @param WP_Post $post The post object. + * @param string $default Optional. Default content to use when no override content can be found. + * @return string + */ +function llms_get_post_sales_page_content( $post, $default = '' ) { + + $content = $default; + + if ( post_type_supports( $post->post_type, 'llms-sales-page' ) ) { + $sales_page = get_post_meta( $post->ID, '_llms_sales_page_content_type', true ); + if ( $post->post_excerpt && ( '' === $sales_page || 'content' === $sales_page ) ) { + add_filter( 'the_excerpt', array( $GLOBALS['wp_embed'], 'autoembed' ), 9 ); + $content = llms_get_excerpt( $post->ID ); + } + } + + /** + * Filters the HTML content of a LifterLMS post type's sales page content + * + * @since [version] + * + * @param string $content HTML content of the sales page. + * @param WP_Post $content Post object. + * @param string $default Default content used when no override content can be found. + */ + return apply_filters( 'llms_post_sales_page_content', $content, $post, $default ); + +} + /** * Initialize LifterLMS post type content filters * From 978f06a6f0a7acbad4d59fc1ea563af30dc4de11 Mon Sep 17 00:00:00 2001 From: Thomas Patrick Levy Date: Fri, 19 Feb 2021 20:57:30 -0700 Subject: [PATCH 04/12] Add better test coverage for all possible conditions --- .../class-llms-test-functions-content.php | 340 +++++++++++++++++- 1 file changed, 337 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php b/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php index 333fa7df3f..10c7e84248 100644 --- a/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php +++ b/tests/phpunit/unit-tests/functions/class-llms-test-functions-content.php @@ -11,21 +11,73 @@ */ class LLMS_Test_Functions_Content extends LLMS_UnitTestCase { - public function get_post_content( $post ) { + /** + * Helper to retrieve filtered post content for a given post + * + * @since [version] + * + * @param WP_Post $post Post object + * @return string + */ + private function get_post_content( $post ) { return trim( apply_filters( 'the_content', $post->post_content ) ); } + /** + * Retrieve a mock post of a give type with expected content and excerpts. + * + * @since [version] + * + * @param WP_Post $post Post object + * @return WP_Post + */ + private function get_mock_post( $post_type ) { + + global $post; + $post = $this->factory->post->create_and_get( array( + 'post_type' => $post_type, + 'post_content' => '

Post Content

', + 'post_excerpt' => '

Post Excerpt

', + ) ); + + return $post; + + } + + /** + * Callback for `llms_page_restricted` filter to force a page to look restricted + * + * @since [version] + * + * @param array $restrictions Restriction data array from llms_page_restricted(). + * @return array + */ + public function make_restricted( $restrictions ) { + $restrictions['is_restricted'] = true; + return $restrictions; + } + + /** + * Test llms_get_post_content() for various post types + * + * This test was never a very good one but it's retained as it does ensure WP core post types + * are not affected by our functions. + * + * @since [version] + * + * @return void + */ public function test_llms_get_post_content() { llms_post_content_init(); $content = '

Lorem ipsum dolor sit amet.

'; - $post_types = array( 'llms_membership', 'course', 'lesson', 'post', 'page' ); + $post_types = array( 'llms_membership', 'course', 'lesson', 'llms_quiz', 'post', 'page' ); foreach ( $post_types as $post_type ) { global $post; $post = $this->factory->post->create_and_get( array( - 'post_type' => $post_type, + 'post_type' => $post_type, 'post_content' => $content, ) ); @@ -39,6 +91,268 @@ public function test_llms_get_post_content() { } + /** + * Test llms_get_post_content() for the course post type. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_course_restricted_no_sales_page() { + + $before = did_action( 'lifterlms_single_course_before_summary' ); + $after = did_action( 'lifterlms_single_course_after_summary' ); + + llms_post_content_init(); + $post = $this->get_mock_post( 'course' ); + + $res = $this->get_post_content( $post ); + + // Starts with the default post content. + $this->assertSame( 0, strpos( $res, '

Post Content

' ) ); + + // Additions added to the end. + $additions = array( + '
', + '
', + '
', + ); + foreach ( $additions as $add ) { + $this->assertStringContains( $add, $res ); + } + + $this->assertEquals( ++$before, did_action( 'lifterlms_single_course_before_summary' ) ); + $this->assertEquals( ++$after, did_action( 'lifterlms_single_course_after_summary' ) ); + + } + + /** + * Test llms_get_post_content() for the course post type with restrictions and a salse page. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_course_restricted_with_sales_page() { + + $before = did_action( 'lifterlms_single_course_before_summary' ); + $after = did_action( 'lifterlms_single_course_after_summary' ); + + add_filter( 'llms_page_restricted', array( $this, 'make_restricted' ) ); + + llms_post_content_init(); + $post = $this->get_mock_post( 'course' ); + + update_post_meta( $post->ID, '_llms_sales_page_content_type', 'content' ); + + $res = $this->get_post_content( $post ); + + // Starts with the post's excerpt post content. + $this->assertSame( 0, strpos( $res, '

Post Excerpt

' ) ); + + // Post's content should not be found. + $this->assertSame( false, strpos( $res, '

Post Content

' ) ); + + // Additions added to the end. + $additions = array( + '
', + '
', + '
', + ); + foreach ( $additions as $add ) { + $this->assertStringContains( $add, $res ); + } + + $this->assertEquals( ++$before, did_action( 'lifterlms_single_course_before_summary' ) ); + $this->assertEquals( ++$after, did_action( 'lifterlms_single_course_after_summary' ) ); + + remove_filter( 'llms_page_restricted', array( $this, 'make_restricted' ) ); + + } + + /** + * Test llms_get_post_content() for the membership post type. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_membership_restricted_no_sales_page() { + + $before = did_action( 'lifterlms_single_membership_before_summary' ); + $after = did_action( 'lifterlms_single_membership_after_summary' ); + + llms_post_content_init(); + $post = $this->get_mock_post( 'llms_membership' ); + + $res = $this->get_post_content( $post ); + + // No additions to the post content. + $this->assertEquals( '

Post Content

', $res ); + + $this->assertEquals( ++$before, did_action( 'lifterlms_single_membership_before_summary' ) ); + $this->assertEquals( ++$after, did_action( 'lifterlms_single_membership_after_summary' ) ); + + } + + /** + * Test llms_get_post_content() for the membership post type with restrictions and a salse page. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_membership_restricted_with_sales_page() { + + $before = did_action( 'lifterlms_single_membership_before_summary' ); + $after = did_action( 'lifterlms_single_membership_after_summary' ); + + $handler = function( $restrictions ) { + $restrictions['is_restricted'] = true; + return $restrictions; + }; + add_filter( 'llms_page_restricted', $handler ); + + llms_post_content_init(); + $post = $this->get_mock_post( 'llms_membership' ); + + update_post_meta( $post->ID, '_llms_sales_page_content_type', 'content' ); + + $res = $this->get_post_content( $post ); + + // Just the excerpt. + $this->assertEquals( '

Post Excerpt

', $res ); + + $this->assertEquals( ++$before, did_action( 'lifterlms_single_membership_before_summary' ) ); + $this->assertEquals( ++$after, did_action( 'lifterlms_single_membership_after_summary' ) ); + + remove_filter( 'llms_page_restricted', $handler ); + + } + + /** + * Test llms_get_post_content() for the lesson post type. + * + * @since [version] + * + * @return void + */ + public function test_llms_get_post_content_lesson() { + + $before = did_action( 'lifterlms_single_lesson_before_summary' ); + $after = did_action( 'lifterlms_single_lesson_after_summary' ); + + llms_post_content_init(); + $post = $this->get_mock_post( 'lesson' ); + + $res = $this->get_post_content( $post ); + + // Starts with the back to course link. + $this->assertSame( 0, strpos( $res, '

Post Content

', // Default content. + '