WordPress.org

Ready to get started?Download WordPress

Forums

Change html structure of all img tags in WordPress (8 posts)

  1. kaffolder
    Member
    Posted 1 year ago #

    I have a WordPress blog and am trying to implement the foresight.js image script. In short, I need to target all post images, swap out the src, width, & height attributes with data-src, data-width, & data-height attributes. I then need to duplicate the image line and wrap it in <noscript> tags. This is the structure I'm trying to have WordPress generate/create:

    <img data-src="wordpress/image/url/pic.jpg" data-width="{get width of image with PHP & pass-in that value here} data-height="{get height of image with PHP and pass-in that value here}" class="fs-img">
    <noscript>
        <img src="wordpress/image/url/pic.jpg">
    </noscript>

    More info can be found at the foresight.js website.

    I have searched the WordPress codex and the best possible route I can find are to use filters (ie. 'get_image_tag' & 'image_tag') for modifying the html that WordPress outputs for each image. I'm thinking that one of these options should work, or that I can do some pattern matching with regex (I know, not ideal), throw in a preg_replace and then inject this back into the_content filter.

    I have tried some of these options but cannot get any to work. Found some suggestions here, here, and sort of here, but can't even get any of them to work! Could someone please offer some help?

    'get_image_tag' attempt:
    Found this particular one on the web, but it would need modified to fit my logic (see above structure)...can't make sense of what the preg_replace array is doing on my own.

    <?php function image_tag($html, $id, $alt, $title) {
        return preg_replace(array(
            '/'.str_replace('//','\/\/',get_bloginfo('url')).'/i',
            '/\s+width="\d+"/i',
            '/\s+height="\d+"/i',
            '/alt=""/i'
        ),
        array(
            '',
            '',
            '',
            alt='"' . $title . '"'
        ),
        $html);
    }
    add_filter('get_image_tag', 'image_tag', 0, 4);
    ?>

    Another 'get_image_tag' attempt:

    <?php function get_image_tag($id, $alt, $title, $align, $size='full') {
        list($width, $height, $type, $attr) = getimagesize($img_src);
        $hwstring = image_hwstring($width, $height);
    
        $class = 'align' . esc_attr($align) . ' size-' . esc_attr($size) . ' wp-image-' . $id;
        $class = apply_filters('get_image_tag_class', $class, $id, $align, $size);
    
        $html = '<img src="' . esc_attr($img_src) . '" alt="' . esc_attr($alt) . '" title="' . esc_attr($title).'" data-width="' . $width . '" data-height="' . $height . '" class="' . $class . ' fs-img" />';
        $html = apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size);
    
        return $html;
    }
    ?>

    Pattern-matching attempt:
    Tried creating my own regex on this one, but not sure if it's correct.

    <?php function restructure_imgs($content) {
        global $post;
        $pattern = "/<img(.*?)src=('|\")(.*?).(bmp|gif|jpeg|jpg|png)(|\")(.*?)>/i";
    
        list($width, $height, $type, $attr) = getimagesize($2$3.$4$5);
        $hwstring = image_hwstring($width, $height);
    
        $replacement = '<img$1data-src=$2$3.$4$5 title="'.$post->post_title.'" data-width="'.$width.'" data-height="'.$height.'" class="fs-img"$6>';
        $content = preg_replace($pattern, $replacement, $content);
        return $content;
    }
    add_filter('the_content', 'restructure_imgs');
    ?>

    Unfortunately can't get any of these examples to work. Any help or sharing your pre-written scripts/functions would be much appreciated! Thanks for helping a student learn!!

  2. linux4me2
    Member
    Posted 1 year ago #

    I think get_image_tag only fires when you insert an image, so working with "the_content" and using DOMDocument to parse the HTML would probably be the best. I haven't worked with DOMDocument much, but try a variation of this:

    <?php
    function restructure_imgs($content) {
      $doc = new DOMDocument();
      $doc->LoadHTML($content);
      $images = $doc->getElementsByTagName('img');
      $attributes = array('src'=>'data-src', 'width'=>'data-width', 'height'=>'data-height');
      foreach ($images as $image) {
        foreach ($attributes as $key=>$value) {
          // Get the value of the current attributes and set them to variables.
          $$key = $image->getAttribute($key);
          // Remove the existing attributes.
          $image->removeAttribute($key);
          // Set the new attribute.
          $image->setAttribute($value, $$key);
          // Replace existing class with the new fs-img class.
          $image->setAttribute('class', 'fs-img');
        }
        // Add the new noscript node.
        $noscript = $doc->createElement('noscript');
        $noscriptnode = $image->parentNode->insertBefore($noscript, $image);
        // Add the img node to the noscript node.
        $img = $doc->createElement('IMG');
        $newimg = $noscriptnode->appendChild($img);
        $newimg->setAttribute('src', $src);
      }
      return $doc->saveHTML();
    }
    add_filter('the_content', 'restructure_imgs');
    ?>

    This worked when I tested it on a block of text from a WP post, but I haven't tried it in WordPress itself.

    There are a couple of things about the code. It puts the "noscript" node before the new image node, which is the reverse of how you showed it above. If that matters, you could manipulate the code to arrange it with noscript following the image node. The other thing I noticed is that the code timed out, but didn't throw an error, if I used a lower-case "img" instead of the upper-case "IMG" to create the image node. I don't think that will matter for the HTML being parsed by the browser, but if it does, you could do a simple str_replace on "<IMG" to fix it before returning the modified content. (Or maybe you can figure out how to get around it using DOMDocument.)

    Let me know if it works for you. It's an interesting problem.

  3. kaffolder
    Member
    Posted 1 year ago #

    @linux4me2 - Thank you so much for your help! PHP is a newer language to me, but I had no idea that the DOMDocument class existed! I always thought that to pull off functionality like the DOMDocument does, one has to use dirty regular expressions, preg_replace and str_replace commands. I'm eager to learn more about this class and your example steered me in the right direction. Very clear...very concise.

    One more question somewhat related to this topic if you don't mind. Let's say that I want to try and pull the image's src out of the array $attributes and assign it to a variable within the foreach ($attributes as $key=>$value) loop. How might I go about doing that?

    Essentially what I'm trying to accomplish is to find the size of the image using PHP's getimagesize function. For some reason WordPress isn't passing the image's width and height properties, so I'm thinking I'm going to have to use PHP's built–in getimagesize function. Here's what I have so far:

    function restructure_imgs($content) {
         $doc = new DOMDocument();
         $doc->LoadHTML($content);
         $images = $doc->getElementsByTagName('img');
         $attributes = array('src'=>'data-src');
         foreach ($images as $image) {
              foreach ($attributes as $key=>$value) {
              // Get the value of the current attributes and set them to variables.
              $$key = $image->getAttribute($key);
              // Remove the existing attributes.
              $image->removeAttribute($key);
              // Set the new attribute.
              $image->setAttribute($value, $$key);
    
              // Somehow get image's 'src' value from $attributes array & assign its key to $image_url
              // {insert the described logic here} ?
    
              // Find size attributes
              $imagesize = getimagesize($image_url);
              // Set image size attributes
              $image->setAttribute('data-width', $imagesize[0]);
              $image->setAttribute('data-height', $imagesize[1]);
    
              // Replace existing class with the new fs-img class.
              $image->setAttribute('class', 'fs-img');
              // Add a new attribute & set value
              $image->setAttribute('data-aspect-ratio', 'auto');
         }
    
         // Add the new noscript node.
         $noscript = $doc->createElement('noscript');
         $noscriptnode = $image->parentNode->insertBefore($noscript, $image);
         // Add the img node to the noscript node.
         $img = $doc->createElement('IMG');
         $newimg = $noscriptnode->appendChild($img);
         $newimg->setAttribute('src', $src);
         }
         return $doc->saveHTML();
    }
    add_filter('the_content', 'restructure_imgs', 10);

    My guess is I somehow need to read into the array, get the specific 'src' key and then assign that to a variable? Would appreciate anyone's thoughts! Not familiar with how to do that.

  4. linux4me2
    Member
    Posted 1 year ago #

    DOMDocument has only been in PHP since PHP 5, I think, so it's fairly new. It does come in handy. It's more reliable than regex for this kind of thing because so many things can trip up a regex when parsing HTML or XML.

    I used an array for attributes so you could list anything you wanted there, e.g., "alt" to add variables you wanted to use without having to write a lot more code. I'm surprised you weren't getting the width and height of the images since they are included in the tags WordPress generates, though maybe what you're wanting is the full-sized images rather than the smaller versions that might be included in a post?

    The two "$" preceding the "key" in this line:
    $$key = $image->getAttribute($key);
    assigns the key from the attributes array as a variable, so you already have a variable for "src." That means that after the for...each loop running through the attributes array is closed, you can use any of the keys as variables. So you really need to deal with the image size outside the attributes loop like this:

    <?php
    function restructure_imgs($content) {
      $doc = new DOMDocument();
      $doc->LoadHTML($content);
      $images = $doc->getElementsByTagName('img');
      $attributes = array('src'=>'data-src');
      foreach ($images as $image) {
        foreach ($attributes as $key=>$value) {
          // Get the value of the current attributes and set them to variables.
          $$key = $image->getAttribute($key);
          // Remove the existing attributes.
          $image->removeAttribute($key);
          // Set the new attribute.
          $image->setAttribute($value, $$key);
          // Replace existing class with the new fs-img class.
          $image->setAttribute('class', 'fs-img');
        }
        // You already have the $src once the $attributes loop has run, so you can use it here.
        // Find size attributes
        $imagesize = getimagesize($src);
        // Set image size attributes
        $image->setAttribute('data-width', $imagesize[0]);
        $image->setAttribute('data-height', $imagesize[1]);
        // Add the new noscript node.
        $noscript = $doc->createElement('noscript');
        $noscriptnode = $image->parentNode->insertBefore($noscript, $image);
        // Add the img node to the noscript node.
        $img = $doc->createElement('IMG');
        $newimg = $noscriptnode->appendChild($img);
        $newimg->setAttribute('src', $src);
      }
      return $doc->saveHTML();
    }
    add_filter('the_content', 'restructure_imgs');
    ?>

    You'll be using a URL rather than a file path on the server for getimagesize(), which might not work. You'll have to try it. If it doesn't, you can use PHP to get the actual file path from the URL.

    The other problem I see with that is that the src of the image, if it's not full-sized, won't be the path to the full-sized image. It might be something like the following:

    my_image-150x150.jpg
    my_image-300x300.jpg

    instead of "my_image.jpg."

    Is that going to be a problem?

  5. kaffolder
    Member
    Posted 1 year ago #

    Solid @linux4me2! Just about have this where I want it. Hopefully last question... I'm noticing in the DOMDocument class, we have used a setAttribute function to basically set values for attributes. Is there a function/method that I can use so that instead of overriding what's currently there, I can just pass–in a value? I still need WP's default classes to output to the browser.

    By default, WordPress outputs class="alignright size-full wp-image-162". Is there a way to pass–in the class fs-image so rather than overriding WP's classes, we can just tack it on the end like class="alignright size-full wp-image-162 fs-449x675 fs-standard-resolution fs-src-default fs-image"?

    In the latest code block we have $image->setAttribute('class', 'fs-img');. I'm guessing this is where the problem is coming from. Could a fix be to change the above line to instead read $image->setAttribute('class', $class . 'fs-img');?

  6. linux4me2
    Member
    Posted 1 year ago #

    Yes, I was wondering if you'd want to keep the classes. You'll need to add "class" to the attributes array. The code you used will work with a small addition; you'll want to put a space in before "fs-img" if there are other classes, but not if there aren't. Try this:

    <?php
    function restructure_imgs($content) {
      $doc = new DOMDocument();
      $doc->LoadHTML($content);
      $images = $doc->getElementsByTagName('img');
      $attributes = array('src'=>'data-src', 'class'=>'class');
      foreach ($images as $image) {
        foreach ($attributes as $key=>$value) {
          // Get the value of the current attributes and set them to variables.
          $$key = $image->getAttribute($key);
          // Remove the existing attributes.
          $image->removeAttribute($key);
          // Set the new attribute.
          switch ($key) {
          		case 'class':
          		  if (!empty($$key)) {
    	      		  $image->setAttribute($value, $$key . ' fs-img');
    	      		} else {
    	      			$image->setAttribute($value, $$key . 'fs-img');
    	      	  }
          		break;
          		default:
    		      $image->setAttribute($value, $$key);
    		  }
        }
        // You already have the $src once the $attributes loop has run, so you can use it here.
        // Find size attributes
        $imagesize = getimagesize($image_url);
        // Set image size attributes
        $image->setAttribute('data-width', $imagesize[0]);
        $image->setAttribute('data-height', $imagesize[1]);
        // Add the new noscript node.
        $noscript = $doc->createElement('noscript');
        $noscriptnode = $image->parentNode->insertBefore($noscript, $image);
        // Add the img node to the noscript node.
        $img = $doc->createElement('IMG');
        $newimg = $noscriptnode->appendChild($img);
        $newimg->setAttribute('src', $src);
      }
      return $doc->saveHTML();
    }
    add_filter('the_content', 'restructure_imgs');
    ?>

    If you wanted to add the classes to the noscript version as well, you have the "$class" variable already set, so you could use setAttribute on "$newimg" to add it there as well.

  7. kaffolder
    Member
    Posted 1 year ago #

    Thanks @linux4me2! This solution works without a hitch! I do have one last question though concerning the use of DOMDocument. Is it less efficient to parse out the HTML like we are doing with the DOMDocument method or would it be better to hook in to some of WordPress' built-in functions and simply tweak how the image tags are written initially? I've been toying around with the below function and it mostly works, but I'm having some problems with passing in the width and height. What are your thoughts?

    add_filter('get_image_tag', 'restructure_imgs', 10, 2);
    function restructure_imgs($html, $id, $alt, $align, $size) {
        list( $img_src, $width, $height ) = image_downsize($id, $size);
        $imagesize = getimagesize($img_src);
    
        $class = 'align' . esc_attr($align) .' size-' . esc_attr($size) . ' wp-image-' . $id;
        $html = '<img data-src="' . esc_attr($img_src) . '" alt="' . esc_attr($alt) . '" data-width="' . $imagesize[0] . '" data-height="' . $imagesize[1] . '" class="' . $class . '" />';
        $before = '<div class="media">';
        $after = '<noscript><img src="' . esc_attr($img_src) . '" alt="' . esc_attr($alt) . '" class="' . $class . '" /></noscript></div>';
        $html = $before . $html . $after;
    
        return $html;
    }

    The replaced tag in my script then looks like this:

    <div class="media">
        <img data-src="url/test-photo.jpg" alt="Alt text here" data-data-class="align size- wp-image-172 fs-img" />
        <noscript><img src="url/test-photo.jpg" alt="Alt text here" class="align size- wp-image-172 fs-img" /></noscript>
    </div>

    Notice the "data-data-" error in the first img tag. The width and height seem to not be correctly passing through. Also, there is an error at "size-" in the class attribute. So not sure why that's behaving like that, but that one's not a huge deal. The big thing I'm trying to solve is passing in the width and height values into "data-width" and "data-height". Let me know if you have any thoughts, whether this is a better solution with less backend processing than DOMDocument for WP, etc. Thanks!

  8. linux4me2
    Member
    Posted 1 year ago #

    I think that using get_image_tag() would be less processor-intensive because it would only be run on inserting new images, not every time the content is displayed, and wouldn't require parsing the HTML.

    The benefit of the DOMDocument approach is that it leaves the actual HTML for a post in the database undisturbed, so that down the line when you have upteen-zillion posts and decide you no longer want this format, it's a simple matter to change over. If you go with a method that uses get_image_tag(), you're altering your data, and going back would be a lot more work. I've had too many clients change their minds about major design features of sites a few years down the line, so I try to go with things that will accomplish their goals but still be as "future-proof" as possible.

    I guess it all comes down to how sure you are that you're always going to use this method of displaying images.

Topic Closed

This topic has been closed to new replies.

About this Topic