File "img-to-picture-webp.php"

Full path: /home/kosmetik/public_html/wp-content/plugins/shortpixel-image-optimiser/class/front/img-to-picture-webp.php
File size: 20.73 B
MIME-type: text/x-php
Charset: utf-8

Download   Open   Edit   Advanced Editor   Back

<?php
namespace ShortPixel;
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;

/**
 * Class ShortPixelImgToPictureWebp - convert an <img> tag to a <picture> tag and add the webp versions of the images
 * thanks to the Responsify WP plugin for some of the code
 */
class ShortPixelImgToPictureWebp
{

    public function convert($content)
    {

        // Don't do anything with the RSS feed.
        if (is_feed() || is_admin()) {
            Log::addInfo('SPDBG convert is_feed or is_admin');
            return $content; // . (isset($_GET['SHORTPIXEL_DEBUG']) ? '<!--  -->' : '');
        }

        $new_content = $this->testPictures($content);
        if ($new_content !== false)
        {
          $content = $new_content;
        }
        else
        {
          Log::addDebug('Test Pictures returned empty.');
        }

        $content = preg_replace_callback('/<img[^>]*>/i', array($this, 'convertImage'), $content);
        //$content = preg_replace_callback('/background.*[^:](url\(.*\)[,;])/im', array('self', 'convertInlineStyle'), $content);

        // [BS] No callback because we need preg_match_all
        $content = $this->testInlineStyle($content);
      //  $content = preg_replace_callback('/background.*[^:]url\([\'|"](.*)[\'|"]\)[,;]/imU',array('self', 'convertInlineStyle'), $content);
        Log::addDebug('SPDBG WebP process done');

        return $content; // . (isset($_GET['SHORTPIXEL_DEBUG']) ? '<!-- SPDBG WebP converted -->' : '');

    }

    /** If lazy loading is happening, get source (src) from those values
    * Otherwise pass back image data in a regular way.
    */
    private function lazyGet($img, $type)
    {

      $value = false;
      $prefix = false;

       if (isset($img['data-lazy-' . $type]) && strlen($img['data-lazy-' . $type]) > 0)
       {
           $value = $img['data-lazy-' . $type];
           $prefix = 'data-lazy-';
       }
       elseif( isset($img['data-' . $type]) && strlen($img['data-' . $type]) > 0)
       {
          $value = $img['data-' . $type];
          $prefix = 'data-';
       }
       elseif(isset($img[$type]) && strlen($img[$type]) > 0)
       {
          $value = $img[$type];
          $prefix = '';
       }

      return array(
        'value' => $value,
        'prefix' => $prefix,
       );
    }

    /* Find image tags within picture definitions and make sure they are converted only by block, */
    private function testPictures($content)
    {
      // [BS] Escape when DOM Module not installed
      //if (! class_exists('DOMDocument'))
      //  return false;
    //$pattern =''
    //$pattern ='/(?<=(<picture>))(.*)(?=(<\/picture>))/mi';
    $pattern = '/<picture.*?>.*?(<img.*?>).*?<\/picture>/is';
    preg_match_all($pattern, $content, $matches);

    if ($matches === false)
      return false;

    if ( is_array($matches) && count($matches) > 0)
    {
      foreach($matches[1] as $match)
      {
           $imgtag = $match;

           if (strpos($imgtag, 'class=') !== false) // test for class, if there, insert ours in there.
           {
            $pos = strpos($imgtag, 'class=');
            $pos = $pos + 7;

            $newimg = substr($imgtag, 0, $pos) . 'sp-no-webp ' . substr($imgtag, $pos);

           }
           else {
              $pos = 4;
              $newimg = substr($imgtag, 0, $pos) . ' class="sp-no-webp" ' . substr($imgtag, $pos);
           }

           $content = str_replace($imgtag, $newimg, $content);

      }
    }

    return $content;
    }

    /* This might be a future solution for regex callbacks.
    public static function processImageNode($node, $type)
    {
      $srcsets = $node->getElementsByTagName('srcset');
      $srcs = $node->getElementsByTagName('src');
      $imgs = $node->getElementsByTagName('img');
    } */

    /** Callback function with received an <img> tag match
    * @param $match Image declaration block
    * @return String Replacement image declaration block
    */
    protected function convertImage($match)
    {
        $fs = \wpSPIO()->filesystem();

        // Do nothing with images that have the 'sp-no-webp' class.
        if (strpos($match[0], 'sp-no-webp') || strpos($match[0], 'rev-sildebg')) {
            Log::addInfo('SPDBG convertImage skipped, sp-no-webp found');
            return $match[0]; //. (isset($_GET['SHORTPIXEL_DEBUG']) ? '<!-- SPDBG convertImage sp-no-webp -->' : '');
        }

        $img = $this->get_attributes($match[0]);

        if(isset($img['style']) && strpos($img['style'], 'background') !== false) {
            //don't replace for <img>'s that have background
            return $match[0];
        }

        // [BS] Can return false in case of Module fail. Escape in that case with unmodified image
        if ($img === false)
        {
          Log::addDebug('Webp convert failed, no image found in convertImage');
          return $match[0];
        }

        // No src is not something we can handle. This can happen when creating an image in WP Gutenberg but not setting any image file on the block, but adding something like a custom class
        if (! isset($img['src']) && ! isset($img['srcset']))
        {
           return $match[0];
        }

        $srcInfo = $this->lazyGet($img, 'src');
        $srcsetInfo = $this->lazyGet($img, 'srcset');
        $sizesInfo = $this->lazyGet($img, 'sizes');

        $imageBase = apply_filters( 'shortpixel_webp_image_base', $this->getImageBase($srcInfo['value']), $srcInfo['value']);

        if($imageBase === false) {
            Log::addInfo('SPDBG baseurl doesn\'t match ' . $srcInfo['value'], array($imageBase) );
            return $match[0]; // . (isset($_GET['SHORTPIXEL_DEBUG']) ? '<!-- SPDBG baseurl doesn\'t match ' . $src . '  -->' : '');
        }
        Log::addDebug('ImageBase -'. $imageBase);

        //some attributes should not be moved from <img>
        // @todo Move these to unset on (imgpicture) and put via create_attributes back
        $altAttr = isset($img['alt'])  ? ' alt="' . $img['alt'] . '"' : '';
        $idAttr = isset($img['id']) && strlen($img['id']) ? ' id="' . $img['id'] . '"' : '';
        $heightAttr = isset($img['height']) && strlen($img['height']) ? ' height="' . $img['height'] . '"' : '';
        $widthAttr = isset($img['width']) && strlen($img['width']) ? ' width="' . $img['width'] . '"' : '';


        // We don't wanna have src-ish attributes on the <picture>
        unset($img['src']);
        unset($img['data-src']);
        unset($img['data-lazy-src']);
        unset($img['srcset']);

      //  unset($img['data-srcset']); // lazyload - don't know if this solves anything.
        unset($img['sizes']);


        //nor the ones that belong to <img>
        unset($img['alt']);
        unset($img['id']);
        unset($img['width']);
        unset($img['height']);

        $srcsetWebP = array();
        $srcsetAvif = array();

        $imagePaths = array();

        if ($srcsetInfo['value']) {
            $definitions = explode(',', $srcsetInfo['value']);
        }
        else
        {
            $definitions = array($srcInfo['value']);
        }

          //  $defs = explode(",", $srcset);
          $mime = ''; // inint

        foreach ($definitions as $item) {
                $parts = preg_split('/\s+/', trim($item));

                $fileurl = $parts[0];
                // A source that starts with data:, will not need processing.
                if (strpos($fileurl, 'data:') === 0)
                  continue;
                $condition = isset($parts[1]) ? ' ' . $parts[1] : '';

                Log::addDebug('Running item - ' . $item, $fileurl);

                $fsFile = $fs->getFile($fileurl);
                $extension = $fsFile->getExtension(); // trigger setFileinfo, which will resolve URL -> Path

                $mime = $fsFile->getMime();

                $fileWebp = $fs->getFile($imageBase . $fsFile->getFileBase() . '.webp');
                $fileWebpCompat = $fs->getFile($imageBase . $fsFile->getFileName() . '.webp');
                //$fileWebp = $fs->getFile($filepath);

                $fileurl_base = str_replace($fsFile->getFileName(), '', $fileurl);
                $files = array($fileWebp, $fileWebpCompat);

                $fileAvif = $fs->getFile($imageBase . $fsFile->getFileBase() . '.avif');

                foreach($files as $thisfile)
                {
                  $fileWebp_exists = apply_filters('shortpixel_image_exists', $thisfile->exists(), $thisfile);
                  if (! $fileWebp_exists)
                  {
                    $thisfile = $fileWebp_exists = apply_filters('shortpixel/front/webp_notfound', false, $thisfile, $fileurl, $imageBase);
                  }


                  if ($thisfile !== false)
                  {
                      // base url + found filename + optional condition ( in case of sourceset, as in 1400w or similar)
                      Log::addDebug('Adding new URL', $fileurl_base . $thisfile->getFileName() . $condition);
                       $srcsetWebP[] = $fileurl_base . $thisfile->getFileName() . $condition;
                       break;
                  }
                }

                $fileAvif_exists = apply_filters('shortpixel_image_exists', $fileAvif->exists(), $fileAvif);
                if ($fileAvif_exists !== false)
                {
                  $fileurl_base = str_replace($fsFile->getFileName(), '', $fileurl);
                  $srcsetAvif[] = $fileurl_base . $fileAvif->getFileName() . $condition;
                }




            }

        if (count($srcsetWebP) == 0 && count($srcsetAvif) == 0) {
            return $match[0]; //. (isset($_GET['SHORTPIXEL_DEBUG']) ? '<!-- SPDBG no srcsetWebP found (' . $srcsetWebP . ') -->' : '');
            Log::addInfo(' SPDBG no srcsetWebP found (' . $srcsetWebP . ')');
        }

        //add the exclude class so if this content is processed again in other filter, the img is not converted again in picture
        $img['class'] = (isset($img['class']) ? $img['class'] . " " : "") . "sp-no-webp";

        $imgpicture = $img;

        // Items that should not go on picture, but remain on img tag.
        unset($imgpicture['loading']);

        // remove certain elements for the main picture element.
        $imgpicture = $this->filterForPicture($imgpicture);

        $sizes = $sizesInfo['value'];
        $sizesPrefix = $sizesInfo['prefix'];

        $srcsetPrefix = $srcsetInfo['value'] ? $srcsetInfo['prefix'] : $srcInfo['prefix'];
        $srcset = $srcsetInfo['value'];

        $src = trim($srcInfo['value']);
        if (! $srcset)
          $srcset = $src; // if not srcset ( it's a src ), replace those.
        $srcPrefix = $srcInfo['prefix'];

        $output = '<picture ' . $this->create_attributes($imgpicture) . '>';

        if (count($srcsetAvif) > 0)
        {
            $srcsetAvif = implode(',', $srcsetAvif);
            $output .= '<source ' . $srcsetPrefix . 'srcset="' . $srcsetAvif . '"' . ($sizes ? ' ' . $sizesPrefix . 'sizes="' . $sizes . '"' : '') . ' type="image/avif">';
        }
        if (count($srcsetWebP) > 0)
        {
          $srcsetWebP = implode(',', $srcsetWebP);
          $output .= '<source ' . $srcsetPrefix . 'srcset="' . $srcsetWebP . '"' . ($sizes ? ' ' . $sizesPrefix .  'sizes="' . $sizes . '"' : '') . ' type="image/webp">';
        }
        $output .= '<source ' . $srcsetPrefix . 'srcset="' . $srcset . '"' . ($sizes ? ' ' . $sizesPrefix . 'sizes="' . $sizes . '"' : '') . ' type="' . $mime  . '">'
        .'<img ' . $srcPrefix . 'src="' . $src . '" ' . $this->create_attributes($img) . $idAttr . $altAttr . $heightAttr . $widthAttr
            . (strlen($srcset) ? ' srcset="' . $srcset . '"': '') . (strlen($sizes) ? ' sizes="' . $sizes . '"': '') . '>'
        .'</picture>';

        return $output;
    }

    /** Check and remove elements that should not be in the picture tag. Especially items within attributes. */
    private function filterForPicture($img)
    {

      if (isset($img['style']))
      {
         $bordercount = substr_count($img['style'], 'border');
         for ($i = 0; $i <= $bordercount; $i++)
         {
           $offset = strpos($img['style'], 'border');
           $end = strpos($img['style'], ';', $offset);

           $nstyle = substr($img['style'], 0, $offset);

           // if end is false, ; terminator does not exist, assume full string is border.
           if ($end !== false)
              $nstyle .= substr($img['style'], ($end+1) ); // do not include ;

              $img['style'] = $nstyle;
         }
      }

      return $img;
    }

    public function testInlineStyle($content)
    {
      //preg_match_all('/background.*[^:](url\(.*\))[;]/isU', $content, $matches);
      preg_match_all('/url\(.*\)/isU', $content, $matches);

      if (count($matches) == 0)
        return $content;

      $content = $this->convertInlineStyle($matches, $content);
      return $content;
    }

    /** Function to convert inline CSS backgrounds to webp
    * @param $match Regex match for inline style
    * @return String Replaced (or not) content for webp.
    * @author Bas Schuiling
    */
    public function convertInlineStyle($matches, $content)
    {
      // ** matches[0] = url('xx') matches[1] the img URL.
//      preg_match_all('/url\(\'(.*)\'\)/imU', $match, $matches);

  //    if (count($matches)  == 0)
  //      return $match; // something wrong, escape.

      //$content = $match;
      $allowed_exts = array('jpg', 'jpeg', 'gif', 'png');
      $converted = array();

      for($i = 0; $i < count($matches[0]); $i++)
      {
        $item = $matches[0][$i];

        preg_match('/url\(\'(.*)\'\)/imU', $item, $match);
        if (! isset($match[1]))
          continue;

        $url = $match[1];
        //$parsed_url = parse_url($url);
        $filename = basename($url);

        $fileonly = pathinfo($url, PATHINFO_FILENAME);
        $ext = pathinfo($url, PATHINFO_EXTENSION);

        if (! in_array($ext, $allowed_exts))
          continue;

        $imageBaseURL = str_replace($filename, '', $url);

        $imageBase = static::getImageBase($url);

        if (! $imageBase) // returns false if URL is external, do nothing with that.
          continue;

        $checkedFile = false;
        if (file_exists($imageBase . $fileonly . '.' . $ext . '.webp'))
        {
          $checkedFile = $imageBaseURL . $fileonly . '.' . $ext . '.webp';
        }
        elseif (file_exists($imageBase . $fileonly . '.webp'))
        {
          $checkedFile = $imageBaseURL . $fileonly . '.webp';
        }
        else
        {
          Log::addDebug('convertInlineStyle, no webp existing', $checkedFile);
        }

        if ($checkedFile)
        {
            // if webp, then add another URL() def after the targeted one.  (str_replace old full URL def, with new one on main match?
            $target_urldef = $matches[0][$i];
            if (! isset($converted[$target_urldef])) // if the same image is on multiple elements, this replace might go double. prevent.
            {
              $converted[] = $target_urldef;
              $new_urldef = "url('" . $checkedFile . "'), " . $target_urldef;
              $content = str_replace($target_urldef, $new_urldef, $content);
            }
        }

      }

      return $content;
    }

    /* ** Utility function to get ImageBase.
    **  @param String $src Image Source
    **  @returns String The Image Base
    **/
    public function getImageBase($src)
    {

      $fs = \wpSPIO()->filesystem();
      $fileObj = $fs->getFile($src);
      $fileDir = $fileObj->getFileDir();

      return $fileObj->getFileDir();  // Testing, the rest might be unneeded.
/*

        $urlParsed = parse_url($src);
        if(!isset($urlParsed['host'])) {
            if($src[0] == '/') { //absolute URL, current domain
                $src = get_site_url() . $src;
            } else {
                global $wp;
                $src = trailingslashit(home_url( $wp->request )) . $src;
            }
            $urlParsed = parse_url($src);
        }
      $updir = wp_upload_dir();


      if(substr($src, 0, 2) == '//') {
          $src = (stripos($_SERVER['SERVER_PROTOCOL'],'https') === false ? 'http:' : 'https:') . $src;
      }
      $proto = explode("://", $src);
      if (count($proto) > 1) {
          //check that baseurl uses the same http/https proto and if not, change
          $proto = $proto[0];
          if (strpos($updir['baseurl'], $proto."://") === false) {
              $base = explode("://", $updir['baseurl']);
              if (count($base) > 1) {
                  $updir['baseurl'] = $proto . "://" . $base[1];
              }
          }
      }

      $imageBase = str_replace($updir['baseurl'], SHORTPIXEL_UPLOADS_BASE, $src);

      if ($imageBase == $src) { //for themes images or other non-uploads paths
          $imageBase = str_replace(content_url(), WP_CONTENT_DIR, $src);
      }

      if ($imageBase == $src) { //maybe the site uses a CDN or a subdomain? - Or relative link
          $baseParsed = parse_url($updir['baseurl']);

          $srcHost = array_reverse(explode('.', $urlParsed['host']));
          $baseurlHost = array_reverse(explode('.', $baseParsed['host']));

          if ($srcHost[0] == $baseurlHost[0] && $srcHost[1] == $baseurlHost[1]
              && (strlen($srcHost[1]) > 3 || isset($srcHost[2]) && isset($srcHost[2]) && $srcHost[2] == $baseurlHost[2])) {
              $baseurl = str_replace($baseParsed['scheme'] . '://' . $baseParsed['host'], $urlParsed['scheme'] . '://' . $urlParsed['host'], $updir['baseurl']);
              $imageBase = str_replace($baseurl, SHORTPIXEL_UPLOADS_BASE, $src);
          }
          if ($imageBase == $src) { //looks like it's an external URL though...
              return false;
          }
      }

       Log::addDebug('Webp Image Base found: ' . $imageBase);
        $imageBase = trailingslashit(dirname($imageBase));
        return $imageBase; */
    }

    public function get_attributes($image_node)
    {
        if (function_exists("mb_convert_encoding")) {
            $image_node = mb_convert_encoding($image_node, 'HTML-ENTITIES', 'UTF-8');
        }
        // [BS] Escape when DOM Module not installed
        if (! class_exists('DOMDocument'))
        {
          Log::addWarn('Webp Active, but DomDocument class not found ( missing xmldom library )');
          return false;
        }
        $dom = new \DOMDocument();
        @$dom->loadHTML($image_node);
        $image = $dom->getElementsByTagName('img')->item(0);
        $attributes = array();

        /* This can happen with mismatches, or extremely malformed HTML.
        In customer case, a javascript that did  for (i<imgDefer) --- </script> */
        if (! is_object($image))
          return false;

        foreach ($image->attributes as $attr) {
            $attributes[$attr->nodeName] = $attr->nodeValue;
        }
        return $attributes;
    }

    /**
     * Makes a string with all attributes.
     *
     * @param $attribute_array
     * @return string
     */
    public function create_attributes($attribute_array)
    {
        $attributes = '';
        foreach ($attribute_array as $attribute => $value) {
            $attributes .= $attribute . '="' . $value . '" ';
        }

        // Removes the extra space after the last attribute
        return substr($attributes, 0, -1);
    }

    /**
     * @param $image_url
     * @return array
     */
     /* Seems not in use. @todo be removed later on.
    public function url_to_attachment_id($image_url)
    {
        // Thx to https://github.com/kylereicks/picturefill.js.wp/blob/master/inc/class-model-picturefill-wp.php
        global $wpdb;
        $original_image_url = $image_url;
        $image_url = preg_replace('/^(.+?)(-\d+x\d+)?\.(jpg|jpeg|png|gif)((?:\?|#).+)?$/i', '$1.$3', $image_url);
        $prefix = $wpdb->prefix;
        $attachment_id = $wpdb->get_col($wpdb->prepare("SELECT ID FROM " . $prefix . "posts" . " WHERE guid='%s';", $image_url));

        //try the other proto (https - http) if full urls are used
        if (empty($attachment_id) && strpos($image_url, 'http://') === 0) {
            $image_url_other_proto =  strpos($image_url, 'https') === 0 ?
                str_replace('https://', 'http://', $image_url) :
                str_replace('http://', 'https://', $image_url);
            $attachment_id = $wpdb->get_col($wpdb->prepare("SELECT ID FROM " . $prefix . "posts" . " WHERE guid='%s';", $image_url_other_proto));
        }

        //try using only path
        if (empty($attachment_id)) {
            $image_path = parse_url($image_url, PHP_URL_PATH); //some sites have different domains in posts guid (site changes, etc.)
            $attachment_id = $wpdb->get_col($wpdb->prepare("SELECT ID FROM " . $prefix . "posts" . " WHERE guid like'%%%s';", $image_path));
        }

        //try using the initial URL
        if (empty($attachment_id)) {
            $attachment_id = $wpdb->get_col($wpdb->prepare("SELECT ID FROM " . $prefix . "posts" . " WHERE guid='%s';", $original_image_url));
        }
        return !empty($attachment_id) ? $attachment_id[0] : false;
    } */
}