Skip to content

Instantly share code, notes, and snippets.

@indikatordesign
Last active September 26, 2023 06:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indikatordesign/71a82ce84717dd021474e5ef2f03ffa3 to your computer and use it in GitHub Desktop.
Save indikatordesign/71a82ce84717dd021474e5ef2f03ffa3 to your computer and use it in GitHub Desktop.
Bring your WordPress images into the modern Webp format
<?php
/*
Plugin Name: Rewrite JPG, JPEG, PNG, GIF to WEBP
Plugin URI: https://indikator-design.com/get-divi-theme-to-100-in-your-pagespeed-score/
Description: Rewrite image extensions from images in mediathek and linked in posts.
Version: 1.0
Author: Bruno Bouyajdad | Indikator Design
Author URI: https://indikator-design.com
Author Email: contact@indikator-design.com
*/
/**
* Do not allow direct access
*
* @since 1.0
*/
if ( ! defined( 'ABSPATH' ) ) die( 'Nothing to find Ma\'am..' );
/**
* Add the Menu Page
*
* @since 1.0
*/
if ( ! class_exists( 'rwwebpControllerMenu' ) )
{
final class rwwebpControllerMenu
{
/**
* Constructor
*
* @since 1.0
*/
public function __construct()
{
add_action( 'admin_menu', [ $this, 'addMenu' ] );
} // end constructor
/**
* Add menu in admin area
*
* @since 1.0
*/
public function addMenu()
{
$hook = add_submenu_page
(
'tools.php',
'Rewrite WEBP',
'Rewrite WEBP Execution',
'manage_options',
'rewrite-webp',
[ $this, 'menuCallback' ],
10
);
} // end implement
/**
* Menu Callback
*
* @since 1.0
*/
public function menuCallback()
{
( new rwwebpViewExecution )->render();
} // end menuCallback
} // end rwwebpControllerMenu
} // end if
new rwwebpControllerMenu;
/**
* Execute the Ajax Request
*
* @since 1.0
*/
if ( ! class_exists( 'rwwebpControllerAjax' ) )
{
final class rwwebpControllerAjax
{
/**
* Define properties
*
* @since 1.0
*/
private $transient;
const ACTION = 'rwwebp_execute_rewrite';
/**
* Constructor
*
* @since 1.0
*/
public function __construct()
{
$this->transient = 'rwwebp_list_blog_ids';
add_action( 'wp_ajax_' . self::ACTION, [ $this, 'ajax' ] );
} // end constructor
/**
* Execute the ajax call
*
* @since 1.0
*/
public function ajax()
{
$p = $_POST;
if ( empty( $p['nonce'] ) || empty( $p['blog'] ) || empty( $p['blogs'] ) )
$this->result( 'error' );
if ( wp_verify_nonce( esc_attr( $p['nonce'] ), self::ACTION . '_nonce' ) ) :
if ( ! is_multisite() ) :
$blogToExecute = esc_attr( $p['blog'] );
( new rwwebpControllerDB( new rwwebpModelDatabase( $blogToExecute ) ) )->initialize();
else :
if ( ! ( $transientBlogs = get_site_transient( $this->transient ) ) ) :
foreach ( $p['blogs'] as $oneBlog )
$transientBlogs[ (int) esc_attr( $oneBlog ) ] = false;
endif;
$blogToExecute = (int) esc_attr( $p['blog'] );
if ( ! $transientBlogs[$blogToExecute] ) :
$currentBlog = (int) get_current_blog_id();
$blogChange = ( $blogToExecute != $currentBlog );
if ( $blogChange )
switch_to_blog( $blogToExecute );
( new rwwebpControllerDB( new rwwebpModelDatabase( $blogToExecute ) ) )->initialize();
if ( $blogChange )
switch_to_blog( $currentBlog );
$transientBlogs[$blogToExecute] = true;
endif;
foreach ( $transientBlogs as $blog => $bool ) :
if ( ! $bool ) :
$notDelete = true;
break;
endif;
endforeach;
// delete as update doesn't work well
delete_site_transient( $this->transient );
if ( isset( $notDelete ) ) // just if still needed
set_site_transient( $this->transient, $transientBlogs, DAY_IN_SECONDS * 7 );
endif;
$this->result( 'success' );
endif;
$this->result( 'error' );
} // end ajax
/**
* Ajax error handler
*
* @since 1.0
*/
private function result( $case )
{
$obj[$case] = true;
echo json_encode( (object) $obj );
wp_die();
} // end error
} // end rwwebpControllerAjax
} // end if
new rwwebpControllerAjax;
/**
* Database Controller to execute the changes
*
* @since 1.0
*/
if ( ! class_exists( 'rwwebpControllerDB' ) )
{
final class rwwebpControllerDB
{
/**
* Define properties
*
* @since 1.0
*/
private $posts;
private $modelDB;
/**
* Constructor
*
* @since 1.0
*/
public function __construct( rwwebpModelDatabase $modelDB )
{
/**
* Set properties
*
* @since 1.0
*/
$this->modelDB = $modelDB;
$this->images = $this->modelDB->getIds( '' );
$this->posts = $this->modelDB->getIds( 'post' );
} // end constructor
/**
* Initialize the class
*
* @since 1.0
*/
public function initialize()
{
$this->posts();
$this->attachments();
} // end initialize
/**
* Execute the posts
*
* @since 1.0
*/
public function posts()
{
foreach ( $this->posts as $post ) :
$content = $this->postLinks( $this->modelDB->getPostContent( $post->ID )[0]->post_content );
$this->modelDB->setPostContent( $post->ID, $content );
endforeach;
} // end posts
/**
* Execute the attachments
*
* @since 1.0
*/
public function attachments()
{
foreach ( $this->images as $image ) :
$image = $this->modelDB->getPost( $image->ID );
$update['ID'] = $image->ID;
$update['post_mime_type'] = $this->replaceMeta( $image->post_mime_type );
// The guid value is deliberately ignored:
// https://wordpress.org/support/article/changing-the-site-url/#important-guid-note
$this->modelDB->setPost( $update );
$fileKey = '_wp_attached_file';
$metaKey = '_wp_attachment_metadata';
$imageFile = $this->modelDB->getMeta( $image->ID, $fileKey );
$this->modelDB->setMeta( $image->ID, $fileKey, $this->replaceMeta( $imageFile ) );
$metaData = $this->modelDB->getMeta( $image->ID, $metaKey );
$this->modelDB->setMeta( $image->ID, $metaKey, $this->adjustMetaArray( $metaData ) );
endforeach;
} // end attachments
/**
* Adjust the meta data array
*
* @since 1.0
*/
function adjustMetaArray( $arr )
{
$arr['file'] = $this->replaceMeta( $arr['file'] );
foreach ( $arr['sizes'] as $key => $val ) :
$arr['sizes'][$key]['file'] = $this->replaceMeta( $val['file'] );
$arr['sizes'][$key]['mime-type'] = $this->replaceMeta( $val['mime-type'] );
endforeach;
return $arr;
} // end adjustMetaArray
/**
* Replace the extension from post content
*
* @since 1.0
*/
function postLinks( $content )
{
$regex = '#(?<=wp-content\/uploads\/)([^\s\"]+.{1})(jpg|jpeg|png|gif)#i';
return preg_replace( $regex, '$1webp', $content );
} // end postLinks
/**
* Replace the extension from meta
*
* @since 1.0
*/
function replaceMeta( $source )
{
return preg_replace( '#(?<=\.|\/)(jpg|jpeg|png|gif)$#i', 'webp', $source );
} // end replaceMeta
} // end rwwebpControllerDB
} // end if
// /**
// * Database Model to get and update images and posts
// *
// * @since 1.0
// */
if ( ! class_exists( 'rwwebpModelDatabase' ) )
{
final class rwwebpModelDatabase
{
/**
* Define properties
*
* @since 1.0
*/
private $blog;
private $wpdb;
private $post;
/**
* Constructor
*
* @since 1.0
*/
public function __construct( $blogToExecute )
{
global $wpdb;
/**
* Set properties
*
* @since 1.0
*/
$this->wpdb = $wpdb;
$this->blog = $blogToExecute;
$this->post = $this->getPostsTable();
} // end constructor
/**
* Get the attachment ids
*
* @since 1.0
*/
public function getIds( $type )
{
$post = 'post' == $type ? '!=' : '=';
$query = "SELECT ID FROM {$this->post} WHERE post_type {$post} 'attachment'";
return $this->wpdb->get_results( $query );
} // end getMediaIds
/**
* Get the post content
*
* @since 1.0
*/
public function getPostContent( $id )
{
$query = "SELECT post_content FROM {$this->post} WHERE ID = %d";
return $this->wpdb->get_results( $this->wpdb->prepare( $query, $id ) );
} // end getPostContent
/**
* Update the post content
*
* @since 1.0
*/
public function setPostContent( $id, $content )
{
return $this->wpdb->update( $this->post, [ 'post_content' => $content ], [ 'ID' => $id ] );
} // end setPostContent
/**
* Get the posts table
*
* @since 1.0
*/
public function getPostsTable()
{
$blog = ( 'single' == $this->blog ) ? '' : $this->blog . '_';
return $this->wpdb->base_prefix . $blog . 'posts';
} // end getPostsTable
/**
* Get a post by id
*
* @since 1.0
*/
public function getPost( $id )
{
return get_post( $id );
} // end getPost
/**
* Update a post by id
*
* @since 1.1
*/
public function setPost( $data )
{
wp_update_post( $data, true );
} // end setPost
/**
* Get the image meta data
*
* @since 1.0
*/
public function getMeta( $id, $key, $single = true )
{
return get_post_meta( $id, $key, $single );
} // end getMeta
/**
* Update the image meta
*
* @since 1.0
*/
public function setMeta( $id, $key, $value )
{
update_post_meta( $id, $key, $value );
} // end setMeta
} // end rwwebpModelDatabase
} // end if
/**
* Rewrite View Controller
*
* @since 1.0
*/
if ( ! class_exists( 'rwwebpControllerView' ) )
{
final class rwwebpControllerView
{
/**
* Define properties
*
* @since 1.0
*/
private $action;
/**
* Constructor
*
* @since 1.0
*/
public function __construct()
{
/**
* Set properties
*
* @since 1.0
*/
$this->action = rwwebpControllerAjax::ACTION;
} // end constructor
/**
* Get Site ID's
*
* @since 1.0
*/
private function getSiteIds()
{
if ( ! is_multisite() )
return [ [ 'single' ], [ $this->hostOrigin() ] ];
foreach ( get_sites() as $index => $site ) :
$blogIds[] = $site->blog_id;
$domains[] = $site->domain;
endforeach;
return [ $blogIds, $domains ];
} // end getSiteIds
/**
* Get the values for JS
*
* @since 1.0
*/
public function getProps()
{
$info = $this->getSiteIds();
return
[
'props' =>
[
'blogs' => $info[0],
'domains' => $info[1],
'action' => $this->action,
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( $this->action . '_nonce' ),
],
];
} // end getProps
/**
* Get the host origin
*
* @since 1.0
*/
private function hostOrigin()
{
$s = $_SERVER;
return isset( $s[ 'HTTP_HOST' ] )
? esc_attr( $s[ 'HTTP_HOST' ] )
: ( function() use ( $s )
{
if ( isset( $s['HTTP_ORIGIN'] ) ) :
$parse = parse_url( $s['HTTP_ORIGIN'] );
if ( isset( $parse['host'] ) )
return $parse['host'];
else
return false;
endif;
return false;
})();
} // end hostOrigin
} // end rwwebpControllerView
} // end if
/**
* Rewrite WEBP View
*
* @since 1.0
*/
if ( ! class_exists( 'rwwebpViewExecution' ) )
{
final class rwwebpViewExecution
{
/**
* Render the view
*
* @since 1.0
*/
public function render()
{ ?>
<!-- View Style -->
<style>
h1 {
padding-bottom: 0px;
}
h1, p {
color: #656565;
font-weight: 200;
font-family: Open Sans,Arial,sans-serif;
}
p {
font-size: 16px;
}
#wpwrap {
background-color: white!important;
}
.outer-container {
width: 80%;
margin: 60px 10% 10%;
border: 1px solid #ccc;
}
.head {
padding: 20px;
border-bottom: 1px solid #ccc;
}
.start {
margin-bottom: 20px;
}
.main {
padding: 20px;
min-height: 100px;
}
button.start {
font-weight: 200;
background: white;
color: #656565!important;
font-size: 16px!important;
border-radius: 0px!important;
padding: 5px 100px!important;
border: 1px solid #656565!important;
}
.progress {
width: 100%;
height: auto;
display: none;
}
.outer {
width: 100%;
height: 30px;
display: flex;
border: 1px solid #ccc;
}
.inner {
width: 0;
height: 30px;
margin-right: auto;
background-color: #a5d3e5;
}
#confetti-canvas {
top: 0;
position: fixed;
z-index: 1!important;
}
</style>
<!-- View -->
<div class="outer-container">
<div class="info-container">
<div class="head">
<h1>Rewrite Image Paths To Webp</h1>
</div>
<div class="main">
<p class="start">Press start to execute the script:</p>
<button id="start-process" class="start">Start</button>
<div id="progress" class="progress">
<div id="outer" class="outer">
<div id="inner" class="inner"></div>
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript Rewrite Image Extensions Handler -->
<script>
(function($,w){$(function(){
class rewriteWebp
{
setProps()
{
this.props;
this.blogs;
this.multi;
} // end setProps
constructor()
{
this.setProps();
this.getPhpProps();
this.bindEvents();
} // end constructor
ajax( self = this )
{
const data =
{
nonce : self.props.nonce,
blogs : self.props.blogs,
action : self.props.action,
blog : self.blogs.shift(),
};
$( '#blog' ).html( 'single' == data.blog ? 1 : data.blog );
$( '#domain' ).html( self.props.domains.shift() );
$.ajax(
{
data : data,
type : 'post',
dataType : 'json',
url : self.props.ajax_url,
success: function( data )
{
if ( data.hasOwnProperty( 'success' ) )
{
if ( 0 == self.blogs.length )
{
if ( ! self.multi )
$( '#inner' ).css( 'width', '100%' )
$( 'p.start' ).html( self.finished() );
$( '#inner' ).css( 'backgroundColor', '#a7e5a5' );
return $.getScript( self.confetti(), function()
{
startConfetti();
setTimeout( () => { stopConfetti(); }, 5000 );
});
}
$( '#inner' ).css( 'width', self.progressWidth() + '%' );
self.ajax();
}
if ( data.hasOwnProperty( 'error' ) )
self.error();
},
error: function( data )
{
self.error();
}
});
} // end ajax
error()
{
$( 'p.start' ).html( 'Something did not work. Check your "debug.log" for errors and correct them. Then restart the script.' );
$( '#inner' ).css( { 'width': '100%', 'backgroundColor': '#b86969' } );
} // end error
getPhpProps()
{
this.php = <?php echo json_encode( ( new rwwebpControllerView )->getProps() ); ?>;
this.props = this.php.props;
} // end getPhpProps
bindEvents( self = this )
{
$( '#start-process' ).on( 'click', function( e )
{
e.preventDefault();
if ( confirm( self.confirm() ) )
{
$( this ).fadeOut( '10', function()
{
$( '#progress' ).fadeIn( '300', function()
{
self.blogs = self.props.blogs.slice();
self.multi = self.blogs.length > 1 ? true : false;
$( '#inner' ).css( 'width', ( self.multi ? self.progressWidth() : '33' ) + '%' );
$( 'p.start' ).html( self.start() );
return self.ajax();
});
});
}
});
} // end bindEvents
progressWidth()
{
let all = this.props.blogs.length;
return ( 100 / all ) * ( all - this.blogs.length + 1 );
} // end progressWidth
start()
{
return 'Process is being executed.. At the moment on Blog: <span id="blog"></span> - Domain: <span id="domain" />';
} // end start
confirm()
{
return 'If the process is started, it cannot be undone without an existing backup of the database. Are you sure you want to start now?';
} // end confirm
finished()
{
return 'Finished.. The paths in your blogs are all adjusted. Don\'t forget to remove the script from the "mu-plugins" folder. A total of ' + this.props.blogs.length + ' blogs were edited.<br><br>If you feel like donating me a coffee or something, feel free to do so here: <a href="https://www.paypal.com/donate/?hosted_button_id=YBQSPNKV7UYXA" target="_blank">donation</a>. Much appreciated 🙏 Or check my <a href="https://www.elegantthemes.com/marketplace/author/indikator-design/" target="_blank">plugins</a>.';
} // end finished
confetti()
{
// Just to celebrate. Check the code here: https://cdn.indikator-design.com/media/blogs/divi-to-100/confetti/confetti.js
return 'https://cdn.indikator-design.com/media/blogs/divi-to-100/confetti/confetti.min.js';
} // end confetti
} // end class rewriteWebp
new rewriteWebp;
});}(jQuery,window));
</script>
<?php } // end render
} // end rwwebpViewExecution
} // end if
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment