Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions includes/class-micropub.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class Micropub {
*/
const TEXT_DOMAIN = 'micropub';

/**
* Option name for storing microformats2 theme support detection.
*
* @var string
*/
const MICROFORMATS2_SUPPORT_OPTION = 'micropub_theme_supports_mf2';

/**
* Get the instance of the class.
*
Expand Down Expand Up @@ -59,6 +66,9 @@ public function init() {
\add_action( 'rest_api_init', array( $this, 'rest_init' ) );
\add_action( 'init', array( $this, 'plugin_init' ) );
\add_action( 'admin_notices', array( $this, 'ssl_notice' ) );

// Clear microformats2 support cache when theme changes.
\add_action( 'switch_theme', array( $this, 'clear_microformats2_support_cache' ) );
}

/**
Expand Down Expand Up @@ -102,4 +112,77 @@ public function ssl_notice() {
</div>
<?php
}

/**
* Clear the cached microformats2 support detection.
*/
public function clear_microformats2_support_cache() {
\delete_option( self::MICROFORMATS2_SUPPORT_OPTION );
}

/**
* Check if the current theme supports microformats2.
*
* This checks explicit theme support first, then falls back to
* cached detection results. If no cached result exists and we're
* on a singular page, it will detect and cache the result.
*
* @return bool True if theme supports microformats2.
*/
public static function theme_supports_microformats2() {
// First check explicit theme support declaration.
if ( \current_theme_supports( 'microformats2' ) ) {
return true;
}

// Check cached detection result.
$cached = \get_option( self::MICROFORMATS2_SUPPORT_OPTION );

if ( false !== $cached ) {
return 'yes' === $cached;
}

// If we're on a singular frontend page, detect and cache the result.
if ( ! \is_admin() && \is_singular() ) {
// Hook into shutdown to check the rendered output, but avoid adding multiple times.
if ( ! \has_action( 'shutdown', array( self::class, 'detect_microformats2_support' ) ) ) {
\add_action( 'shutdown', array( self::class, 'detect_microformats2_support' ), 0 );
}
}

// Return false for now, will be cached after first detection.
return false;
}

/**
* Detect microformats2 support by checking the rendered page output.
*
* This runs at shutdown to capture the full page HTML and check
* if e-content class exists in the template markup.
Comment thread
pfefferle marked this conversation as resolved.
*
* The detection result is stored in a persistent option that survives
* across requests. This cached value is reused by theme_supports_microformats2()
* on subsequent requests and is only cleared when the theme is switched.
*/
public static function detect_microformats2_support() {
$output = \ob_get_contents();

if ( empty( $output ) ) {
return;
}
Comment thread
pfefferle marked this conversation as resolved.

// Remove code and pre blocks to avoid false positives from user content.
$filtered = preg_replace( '/<(code|pre)[^>]*>.*?<\/\1>/is', '', $output );

// If the regex fails, abort detection to avoid incorrect caching.
if ( null === $filtered ) {
return;
}

// Check if e-content exists as a class attribute value.
// Matches class="...e-content..." or class='...e-content...'.
$has_support = preg_match( '/class=["\'][^"\']*\be-content\b[^"\']*["\']/', $filtered );

\update_option( self::MICROFORMATS2_SUPPORT_OPTION, $has_support ? 'yes' : 'no', false );
}
}
10 changes: 8 additions & 2 deletions includes/class-render.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,15 @@ public static function generate_post_content( $post_content, $input ) {
}

if ( ! empty( $post_content ) ) {
$lines[] = '<div class="e-content">';
// Only wrap in e-content if theme doesn't already provide microformats2 support.
$wrap_econtent = ! Micropub::theme_supports_microformats2();
if ( $wrap_econtent ) {
$lines[] = '<div class="e-content">';
}
$lines[] = $post_content;
$lines[] = '</div>';
if ( $wrap_econtent ) {
$lines[] = '</div>';
}
}

// Generate gallery markup for media fields.
Expand Down
89 changes: 89 additions & 0 deletions tests/test_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,92 @@ function test_mp_filter() {
);
}
}

class MicropubMicroformats2DetectionTest extends WP_UnitTestCase {
function tear_down() {
// Clean up after each test.
remove_theme_support( 'microformats2' );
delete_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION );
parent::tear_down();
}

function test_theme_supports_microformats2_with_explicit_support() {
add_theme_support( 'microformats2' );

$this->assertTrue( \Micropub\Micropub::theme_supports_microformats2() );
}

function test_theme_supports_microformats2_with_cached_yes() {
update_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION, 'yes' );

$this->assertTrue( \Micropub\Micropub::theme_supports_microformats2() );
}

function test_theme_supports_microformats2_with_cached_no() {
update_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION, 'no' );

$this->assertFalse( \Micropub\Micropub::theme_supports_microformats2() );
}

function test_theme_supports_microformats2_returns_false_when_not_cached() {
// No theme support and no cached value.
$this->assertFalse( \Micropub\Micropub::theme_supports_microformats2() );
}

function test_clear_microformats2_support_cache() {
update_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION, 'yes' );

$micropub = \Micropub\Micropub::get_instance();
$micropub->clear_microformats2_support_cache();

$this->assertFalse( get_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION ) );
}

function test_detect_microformats2_support_finds_econtent_in_class() {
// Start output buffering to simulate page output.
ob_start();
echo '<html><body><div class="entry-content e-content">Test</div></body></html>';

\Micropub\Micropub::detect_microformats2_support();

ob_end_clean();

$this->assertEquals( 'yes', get_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION ) );
}

function test_detect_microformats2_support_ignores_econtent_in_code_blocks() {
// Start output buffering to simulate page output.
ob_start();
echo '<html><body><code>class="e-content"</code></body></html>';

\Micropub\Micropub::detect_microformats2_support();

ob_end_clean();

$this->assertEquals( 'no', get_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION ) );
}

function test_detect_microformats2_support_ignores_econtent_in_pre_blocks() {
// Start output buffering to simulate page output.
ob_start();
echo '<html><body><pre>class="e-content"</pre></body></html>';

\Micropub\Micropub::detect_microformats2_support();

ob_end_clean();

$this->assertEquals( 'no', get_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION ) );
}

function test_detect_microformats2_support_ignores_econtent_as_plain_text() {
// Start output buffering to simulate page output.
ob_start();
echo '<html><body><p>The e-content class is used in microformats2.</p></body></html>';

\Micropub\Micropub::detect_microformats2_support();

ob_end_clean();

$this->assertEquals( 'no', get_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION ) );
}
}
57 changes: 57 additions & 0 deletions tests/test_render.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,61 @@ function test_merges_auto_generated_content() {
$post_content
);
}

function test_no_econtent_wrapper_when_theme_supports_microformats2() {
// Simulate theme support for microformats2.
add_theme_support( 'microformats2' );

$content = '<p>Test content</p>';
$input = array(
'properties' => array(
'content' => array( $content ),
),
);
$post_content = \Micropub\Render::generate_post_content( $content, $input );

// Should not have e-content wrapper.
$this->assertEquals( '<p>Test content</p>', $post_content );

// Clean up.
remove_theme_support( 'microformats2' );
}

function test_econtent_wrapper_when_theme_does_not_support_microformats2() {
// Ensure no theme support.
remove_theme_support( 'microformats2' );
// Clear any cached detection.
delete_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION );

$content = '<p>Test content</p>';
$input = array(
'properties' => array(
'content' => array( $content ),
),
);
$post_content = \Micropub\Render::generate_post_content( $content, $input );

// Should have e-content wrapper.
$this->assertEquals( "<div class=\"e-content\">\n<p>Test content</p>\n</div>", $post_content );
}

function test_no_econtent_wrapper_with_cached_mf2_support() {
// Simulate cached detection of microformats2 support.
remove_theme_support( 'microformats2' );
update_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION, 'yes' );

$content = '<p>Test content</p>';
$input = array(
'properties' => array(
'content' => array( $content ),
),
);
$post_content = \Micropub\Render::generate_post_content( $content, $input );

// Should not have e-content wrapper due to cached support.
$this->assertEquals( '<p>Test content</p>', $post_content );

// Clean up.
delete_option( \Micropub\Micropub::MICROFORMATS2_SUPPORT_OPTION );
}
}