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

Content filter: try parsing the HTML before a DB query #175

Closed
wants to merge 14 commits into from
Closed
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
35 changes: 31 additions & 4 deletions tests/test-suite.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,20 +383,31 @@ function test_tevkori_filter_content_images() {

// Function used to build HTML for the editor.
$img = get_image_tag( $id, '', '', '', 'medium' );
$img_no_size = str_replace( 'size-', '', $img );
$img_no_size_id = str_replace( 'wp-attachment-', '', $img_no_size );

$respimg = preg_replace('/src="([^"]*)/', 'src="$1" ' . $srcset . ' ' . $sizes, $img);
// Manually add srcset and sizes to the markup from get_image_tag();
$respimg = preg_replace('|<img ([^>]+) />|', '<img $1 ' . $srcset . ' ' . $sizes . ' />', $img);
$respimg_no_size = preg_replace('|<img ([^>]+) />|', '<img $1 ' . $srcset . ' ' . $sizes . ' />', $img_no_size);
$respimg_no_size_id = preg_replace('|<img ([^>]+) />|', '<img $1 ' . $srcset . ' ' . $sizes . ' />', $img_no_size_id);

$content = '<p>Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference.</p>
<p>First things first:</p>

%1$s

<ul>
<li><a href="http://wordpress.org" title="Subscribe to the WordPress mailing list for Release Notifications">Subscribe to the WordPress mailing list for release notifications</a></li>
</ul>

%1$s
%2$s

<p>As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers.<br />
When a new version is released, <a href="http://wordpress.org" title="If you are already logged in, this will take you directly to the Dashboard">log in to the Dashboard</a> and follow the instructions.<br />
Upgrading is a couple of clicks!</p>

%3$s

<p>Then you can start enjoying the WordPress experience:</p>
<ul>
<li>Edit your personal information at <a href="http://wordpress.org" title="Edit settings like your password, your display name and your contact information">Users &#8250; Your Profile</a></li>
Expand All @@ -408,9 +419,25 @@ function test_tevkori_filter_content_images() {
<li>Find answers to your questions at the <a href="http://wordpress.orgs" title="The official WordPress documentation, maintained by the WordPress community">WordPress Codex</a></li>
</ul>';

$content_unfiltered = sprintf( $content, $img );
$content_filtered = sprintf( $content, $respimg );
$content_unfiltered = sprintf( $content, $img, $img_no_size, $img_no_size_id );
$content_filtered = sprintf( $content, $respimg, $respimg_no_size, $respimg_no_size_id );

$this->assertSame( $content_filtered, tevkori_filter_content_images( $content_unfiltered ) );
}

/**
* @group 170
*/
function test_tevkori_filter_content_images_with_preexisting_srcset() {
// Make image.
$id = $this->_test_img();

// Generate HTML and add a dummy srcset attribute.
$image_html = get_image_tag( $id, '', '', '', 'medium' );
$image_html = preg_replace('|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $image_html );

// The content filter should return the image unchanged.
$this->assertSame( $image_html, tevkori_filter_content_images( $image_html ) );
}

}
152 changes: 103 additions & 49 deletions wp-tevko-responsive-images.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,75 +301,129 @@ function tevkori_get_src_sizes( $id, $size = 'thumbnail' ) {
}

/**
* Filter for the_content to add sizes and srcset attributes to images.
* Filters images in post content to add `srcset` and `sizes`.
*
* @since 3.0
*
* @param string $content The raw post content to be filtered.
* @return string Converted content with `srcset` and `sizes` added to images.
*/
function tevkori_filter_content_images( $content ) {
return preg_replace_callback(
'/<img\s([^>]+)>/i',
// Don't use an anonymous callback function because it isn't supported by PHP 5.2.
'tevkori_filter_content_images_callback',
$content
);
// Only match images in our uploads directory.
$uploads_dir = wp_upload_dir();
$path_to_upload_dir = $uploads_dir['baseurl'];

$content = preg_replace_callback( '|<img ([^>]+' . $path_to_upload_dir . '[^>]+)[\s?][\/?]>|i', '_tevkori_filter_content_images_callback', $content );

return $content;
}
add_filter( 'the_content', 'tevkori_filter_content_images', 5, 1 );

/**
* Callback function for tevkori_filter_content_images.
* Private preg_replace callback used in tevkori_filter_content_images()
*
* @since 3.0
*
* @see tevkori_filter_content_images
* @param array $matches Array containing the regular expression matches.
* @access private
* @since 3.0.0
*/
function tevkori_filter_content_images_callback( $matches ) {
$atts = $matches[1];
$sizes = $srcset = '';

// Check if srcset attribute is not already present.
if ( false !== strpos( 'srcset="', $atts ) ) {

// Get the url of the original image.
preg_match( '/src="(.+?)(\-([0-9]+)x([0-9]+))?(\.[a-zA-Z]{3,4})"/i', $atts, $url_matches );

$url = $url_matches[1] . $url_matches[5];

// Get the image ID.
$id = attachment_url_to_postid( $url );

if ( $id ) {

// Use the width and height from the image url.
if ( $url_matches[3] && $url_matches[4] ) {
$size = array(
(int) $url_matches[3],
(int) $url_matches[4]
);
} else {
$size = 'full';
}
function _tevkori_filter_content_images_callback( $image ) {
if ( empty( $image ) ) {
return false;
}

list( $image_html, $atts ) = $image;
$id = $size = false;

Copy link
Member

Choose a reason for hiding this comment

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

We should bail here if there is already a srcset attribute in the markup.

if ( false !== strpos( 'srcset="', $atts ) ) { ...}

// Bail early if a `srcset` attribute already exists.
if ( false !== strpos( $atts, 'srcset=' ) ) {
return $image_html;
}

// Grab ID and size info from core classes.
if ( preg_match( '/wp-image-([0-9]+)/i', $atts, $class_id ) ) {
(int) $id = $class_id[1];
}
Copy link
Member

Choose a reason for hiding this comment

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

add empty line

if ( preg_match( '/size-([^\s|"]+)/i', $atts, $class_size ) ) {
$size = $class_size[1];
}

// Get the srcset string.
$srcset_string = tevkori_get_srcset_string( $id, $size );
if ( $id && false === $size ) {
preg_match( '/width="([0-9]+)"/', $atts, $width );
preg_match( '/height="([0-9]+)"/', $atts, $height );

$size = array(
(int) $width[1],
(int) $height[1]
);
}

if ( $srcset_string ) {
$srcset = ' ' . $srcset_string;
/*
* If attempts to get values for `id` and `size` failed, use the
* src to query for matching values in `_wp_attachment_metadata`
*/
if ( false === $id || false === $size ) {
preg_match( '/src="([^"]+)"/', $atts, $url );

if ( ! $url[1] ) {
return $image_html;
}

// Get the sizes string.
$sizes_string = tevkori_get_sizes_string( $id, $size );
$image_filename = basename( $url[1] );

/*
* If we already have an ID, we use it to get the attachment metadata
* using `wp_get_attachment_metadata()`. Otherwise, we'll use the image
* `src` url to query the postmeta table for both the attachement ID and
* metadata, which we'll use later to get the size.
*/
if ( ! empty( $id ) ) {
$meta = wp_get_attachment_metadata( $id );
} else {
global $wpdb;
$meta_object = $wpdb->get_row( $wpdb->prepare(
"SELECT `post_id`, `meta_value` FROM $wpdb->postmeta WHERE `meta_key` = '_wp_attachment_metadata' AND `meta_value` LIKE %s",
'%' . $image_filename . '%'
) );

// If the query is successful, we can determine the ID and size.
if ( is_object( $meta_object ) ) {
$id = $meta_object->post_id;
// Unserialize the meta_value returned in our query.
$meta = maybe_unserialize( $meta_object->meta_value );
} else {
$meta = false;
}
}

if ( $sizes_string && ! preg_match( '/sizes="([^"]+)"/i', $atts ) ) {
$sizes = ' ' . $sizes_string;
/*
* Now that we have the attachment ID and metadata, we can retrieve the
* size by matching the original image's `src` filename with the sizes
* included in the attachment metadata.
*/
if ( $id && $meta ) {
/*
* First, see if the file is the full size image. If not, we loop through
* the itermediate sizes until we find a match.
*/
if ( $image_filename === basename( $meta['file'] ) ) {
$size = 'full';
} else {
foreach( $meta['sizes'] as $image_size => $image_size_data ) {
if ( $image_filename === $image_size_data['file'] ) {
$size = $image_size;
break;
}
}
}
}
}

return '<img ' . $atts . $sizes . $srcset . '>';

// If we have an ID and size, try for srcset and sizes and update the markup.
if ( $id && $size && $srcset = tevkori_get_srcset_string( $id, $size ) ) {
$sizes = tevkori_get_sizes_string( $id, $size );
$image_html = "<img " . $atts . " " . $srcset . " " . $sizes . " />";
};

return $image_html;
}

/**
Expand Down