<?php
/*
* SPEEDYCACHE
* https://speedycache.com/
* (c) SpeedyCache Team
*/
namespace SpeedyCache;
class Image{
static function init(){
global $speedycache, $speedycache_optm_method;
$speedycache->image = array();
$speedycache->image['upload_dir'] = wp_upload_dir();
$speedycache->image['id'] = false;
$speedycache->image['metadata'] = array();
$speedycache->image['name'] = '';
$speedycache->image['path'] = '';
$speedycache->image['url'] = '';
$speedycache->image['images'] = array();
$speedycache->image['images_clone'] = array();
$speedycache->image['disabled_method'] = 0;
// Default settings for img optimization
$speedycache->image['settings'] = array(
'on_upload' => false,
'automatic_optm' => false,
'url_rewrite' => false,
'compression_method' => 'gd',
'compression_quality' => '70'
);
$speedycache_optm_method = array(
'gd' => array(
'title' => 'GD',
'desc' => 'Compress using PHP\'s native Extension for compression and conversion.'
),
'imagick' => array(
'title' => 'Imagick',
'desc' => 'Imagick outputs better image quality after compression at the cost of a little bigger file size compared to GD.'
),
'cwebp' => array(
'title' => 'cWebP',
'desc' => 'cwebp is a utility which can be downloaded on your server and its fast and light on you server.'
)
);
// Binaries for cwebp
$speedycache->image['binaries'] = array(
'WINNT' => ['cwebp-122-windows-x64.exe', 'gif2webp-122-windows-x64.exe'],
'Linux' => ['cwebp-122-linux-x86-64', 'gif2webp-122-linux-x86-64']
);
if(array_key_exists(PHP_OS, $speedycache->image['binaries'])){
$speedycache->image['cwebp_binary'] = $speedycache->image['binaries'][PHP_OS][0];
$speedycache->image['cwebp_gif'] = $speedycache->image['binaries'][PHP_OS][1];
}
if($img_settings = get_option('speedycache_img')){
$speedycache->image['settings'] = array_merge($speedycache->image['settings'], $img_settings);
self::compression_method_checks();
return;
}
self::compression_method_checks();
update_option('speedycache_img', $speedycache->image['settings']);
}
static function total_reduction(){
global $wpdb;
$query = "SELECT sum(`meta_value`) as total FROM `".$wpdb->prefix."postmeta` WHERE `meta_key`= 'speedycache_optimisation_reduction'";
$result = $wpdb->get_row( $query );
if(!empty($result->total)){
$reduced = ($result->total && $result->total > 0) ? $result->total : 0;
return $reduced > 10000 ? $reduced/1000 : $reduced;
}
return 0;
}
static function optimized_file_count(){
global $wpdb;
$query = "SELECT count(`meta_value`) as optimized FROM `".$wpdb->prefix."postmeta` WHERE `meta_key`= 'speedycache_optimisation'";
$result = $wpdb->get_row($query);
if($result->optimized && $result->optimized > 0){
return $result->optimized;
}
return 0;
}
static function optm_img_count(){
return self::count_query(array(
'post_type' => 'attachment',
'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif'),
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'EXISTS'
),
array(
'key' => 'speedycache_optimisation',
'value' => base64_encode('"destination_path"'),
'compare' => 'LIKE'
)
)
));
}
static function error_count(){
return self::count_query(array(
'post_type' => 'attachment',
'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif'),
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'EXISTS'
),
array(
'key' => 'speedycache_optimisation',
'value' => base64_encode('"error_code"'),
'compare' => 'LIKE'
)
)
));
}
static function null_posts_groupby(){
return '';
}
static function count_posts_fields(){
return 'COUNT(*) as post_count_speedycache';
}
static function count_query($query_images_args){
add_filter('posts_fields', '\SpeedyCache\Image::count_posts_fields');
add_filter('posts_groupby', '\SpeedyCache\Image::null_posts_groupby');
unset($query_images_args['offset']);
unset($query_images_args['order']);
unset($query_images_args['orderby']);
$query_images_args['posts_per_page'] = -1;
$query_image = new \WP_Query( $query_images_args );
return $query_image->posts[0]->post_count_speedycache;
}
static function image_count(){
return self::count_query(array(
'post_type' => 'attachment',
'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif'),
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => '_wp_attachment_metadata',
'compare' => 'EXISTS'
)
)
));
}
static function uncompressed_count(){
return self::count_query(array(
'post_type' => 'attachment',
'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif'),
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'NOT EXISTS'
)
)
));
}
static function unoptimized(){
global $speedycache;
$tmp_image = array();
$optimization_data = get_post_meta($speedycache->image['id'], 'speedycache_optimisation', true);
$optm_json = base64_decode($optimization_data);
if(empty($optm_json)){
$percentage = 100 / count($speedycache->image['images']);
return array('meta_optimized' => array(), 'images' => $speedycache->image['images'], 'total_reduction' => 0, 'percentage' => $percentage);
}
$optm_json = json_decode($optm_json, true);
$meta_optimized = self::object_to_array($optm_json);
$percentage = count($meta_optimized) * 100/count($speedycache->image['images']);
foreach($speedycache->image['images'] as $key => $value){
$exist = false;
foreach($meta_optimized as $meta_key => $meta_value){
if($value['file'] == $meta_value['file']){
$exist = true;
//break;
}
}
if(empty($exist)){
array_push($tmp_image, $value);
}
}
//START: total reduction
$total_reduction = 0;
foreach($meta_optimized as $m_key => $m_value){
$m_value['reduction'] = isset($m_value['reduction']) ? $m_value['reduction'] : 0;
if($m_key == 0){
$reduction = $m_value['reduction'];
}
$total_reduction += $m_value['reduction'];
}
//END: total reduction
if(count($tmp_image) <= 0){
return array('meta_optimized' => array(), 'images' => array(), 'total_reduction' => 0);
}
$last = speedycache_optget('last');
if(!empty($last)){
if(preg_match('/last-(\d+)/', $last, $last_number)){
if(count($tmp_image) > 5){
$tmp_image = array_slice($tmp_image, $last_number[1]*-1, 1);
}
}
}
return array('meta_optimized' => $meta_optimized, 'images' => array_slice($tmp_image, 0, 1), 'total_reduction' => $total_reduction, 'percentage' => $percentage);
}
static function object_to_array($obj){
if(is_object($obj)){
$obj = (array) $obj;
}
if(!is_array($obj)){
$new = $obj;
return $new;
}
$new = array();
foreach($obj as $key => $val){
$new[$key] = self::object_to_array($val);
}
return $new;
}
static function reorder_by_dimensions(){
global $speedycache;
$tmp = $speedycache->image['images'];
foreach($tmp as $key => $value){
$width_list[$key] = $value['width'];
}
array_multisort($width_list, SORT_DESC, $tmp);
return $tmp;
}
static function optimize_single($id = null){
global $speedycache;
if(!empty($id)){
self::init();
}
self::set_id($id);
self::set_meta_data();
if(wp_next_scheduled('speedycache_auto_optm', array($speedycache->image['id']))){
wp_clear_scheduled_hook('speedycache_auto_optm' , array($speedycache->image['id']));
}
if(empty($speedycache->image['id'])){
return array('finish', 'success');
}
if(!isset($speedycache->image['metadata']['file']) && !empty($speedycache->image['id'])){
$meta_optimized = array();
$meta_optimized[0]['time'] = time();
$meta_optimized[0]['id'] = $speedycache->image['id'];
$meta_optimized[0]['error_code'] = 17;
update_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction', 0);
update_post_meta($speedycache->image['id'], 'speedycache_optimisation', base64_encode(json_encode($meta_optimized)));
return array('Image has been optimized', 'success', $speedycache->image['id'], 100);
}
self::set_name();
self::set_path();
self::set_url();
self::set_images();
if(!empty($speedycache->image['id']) && count($speedycache->image['images']) == 0){
$meta_optimized = array();
$meta_optimized[0]['time'] = time();
$meta_optimized[0]['id'] = $speedycache->image['id'];
$meta_optimized[0]['error_code'] = 18;
update_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction', 0);
update_post_meta($speedycache->image['id'], 'speedycache_optimisation', base64_encode(json_encode($meta_optimized)));
return array('Image has been optimized', 'success', $speedycache->image['id'], 100);
}
$error_exist = false;
$meta_optimized = array();
$total_reduction = 0;
$speedycache->image['images'] = self::unique_array($speedycache->image['images']);
$speedycache->image['images'] = self::reorder_by_dimensions();
$speedycache->image['images_clone'] = $speedycache->image['images'];
$unoptimized = self::unoptimized();
$speedycache->image['images'] = $unoptimized['images'];
$meta_optimized = $unoptimized['meta_optimized'];
$total_reduction = $unoptimized['total_reduction'];
$percentage = isset($unoptimized['percentage']) && $unoptimized['percentage'] ? $unoptimized['percentage'] : 0;
if(count($speedycache->image['images']) == 0){
return array('Image has been optimized', 'success', '', 100);
}
foreach($speedycache->image['images'] as $key => $value){
$res = self::compress($value);
if(!empty($res['success'])){
$value['destination_path'] = $res['destination_path'];
$value['reduction'] = $res['reduction'];
$total_reduction += $value['reduction'];
$value['time'] = time();
$value['id'] = $speedycache->image['id'];
array_push($meta_optimized, $value);
}else{
if(!isset($res['error_code']) && isset($res['error_message'])){
return array($res['error_message'], 'error');
break;
}
if(in_array($res['error_code'] , array(2, 6, 7, 11, 19, 20, 22, 23))){
return array($res['error_message'], 'error');
break;
}
$value['error_code'] = $res['error_code'];
$error_exist = true;
}
$value['time'] = time();
$value['id'] = $speedycache->image['id'];
if(!empty($value['error_code'])){
if($value['error_code'] != 8 || ($value['error_code'] == 8 && $key === 0)){
array_push($meta_optimized, $value);
}
}
}
$percentage = self::update_meta($total_reduction, $meta_optimized);
return array('Image has been optimized', 'success', $speedycache->image['id'], $percentage, $total_reduction);
}
static function update_meta($total_reduction, $meta_optimized){
global $speedycache;
if(isset($meta_optimized[0]) && isset($meta_optimized[0]['error_code']) && $meta_optimized[0]['error_code']){
update_post_meta($speedycache->image['id'], 'speedycache_optimisation', base64_encode(json_encode($meta_optimized)));
update_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction', 0);
return 100;
}
$percentage = 0;
$meta_temp = array();
foreach($speedycache->image['images_clone'] as $key => $value){
$backup_file = $value['file'];
$value['file'] = preg_replace('/.(jpg|jpeg|png|gif)$/', '.webp', $value['file']);
if(!file_exists($value['file']) || !file_exists($backup_file)){
continue;
}
$diff = filesize($backup_file) - filesize($value['file']);
$diff = $diff > 0 ? $diff : 0;
$value['destination_path'] = $backup_file;
$value['reduction'] = $diff;
$value['time'] = time();
$value['id'] = $speedycache->image['id'];
array_push($meta_temp, $value);
}
foreach($meta_optimized as $m_key => $m_value){
if(empty($m_value['error_code'])){
continue;
}
$exist = false;
for($i=0; $i < count($meta_temp); $i++){
if($meta_temp['file'] == $m_value['file']){
$exist = true;
}
}
if(empty($exist)){
$m_value['destination_path'] = '';
$m_value['reduction'] = 0;
$m_value['time'] = time();
$m_value['id'] = $speedycache->image['id'];
array_push($meta_temp, $m_value);
}
}
if(count($meta_temp) > 0){
$percentage = count($meta_temp)*100/count($speedycache->image['images_clone']);
}else{
$percentage = 0;
}
update_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction', $total_reduction);
update_post_meta($speedycache->image['id'], 'speedycache_optimisation', base64_encode(json_encode($meta_temp)));
return $percentage;
}
static function compress($source_image){
global $speedycache;
/*
Error Codes
2 = in backup folder parent folder not writable
3 = no need to optimize
4 = source is not writable
5 = destination is not writable
6 = ImageMagick library is not avaliable
7 = Error via api
8 = Source file does not exist
9 = Image size exceed 5mb limit while processing
11 = Empty Name
12 = Forbidden
13 = CloudFlare to restrict access
14 = No Extension
15 = Image size is 0Kb
16 = Corrupted Image
17 = Empty Metadata
18 = No Image
19 = webp is not saved
20 = file size of destination_move_source_path is zero
21 = Unacceptable file type
22 = Unable to Convert Image using cwebp.
23 = The URL of the image has webp extension.
*/
// if the url starts with /wp-content
if(preg_match('/^\/' . SPEEDYCACHE_WP_CONTENT_DIR . '/i', $source_image['url'])){
$source_image['url'] = home_url().$source_image['url'];
}
$source_path = $source_image['file'];
$res_backup = array('success' => true, 'error_message' => '');
$webp_path = preg_replace('/.(jpe?g|png|gif)$/', '.webp', $source_path);
if(strlen($speedycache->image['name']) === 0){
return array('success' => false, 'error_code' => 11);
}
if(!file_exists($source_path)){
return array('success' => false, 'error_code' => 8);
}
if(!pathinfo($source_image['url'], PATHINFO_EXTENSION)){
return array('success' => false, 'error_code' => 14);
}
// If the URL of the image is a webp
if(pathinfo($source_image['url'], PATHINFO_EXTENSION) == 'webp' && file_exists($webp_path)){
return array('success' => false, 'error_code' => 23);
}
if(@filesize($source_path) > 5000000){
return array("success" => false, 'error_code' => 9);
}
if(!self::allowed_mime($source_path)){
return array('success' => false, 'error_code' => 21);
}
if(!self::path_is_image($source_path)){
return array('success' => false, 'error_code' => 16);
}
if(filesize($source_path) == 0){
return array('success' => false, 'error_code' => 15);
}
if(@rename($source_path, $source_path.'_writabletest')){
rename($source_path.'_writabletest', $source_path);
}else{
return array('success' => false, 'error_message' => $source_path . ' is not writable', 'error_code' => 4);
}
$optm_result = self::start_optimization($source_path); // here we need to plugin compression static function
if(empty($optm_result['success'])){
//NOTE:Place some error Message here.
return $optm_result;
}
if(!file_exists($webp_path)){
return array('success' => false, 'error_code' => 19, 'error_message' => $webp_path . ' destination_path is not saved');
}
if(filesize($webp_path) <= 0){
return array('success' => false, 'error_code' => 20, 'error_message' => $webp_path . ' file size of destination_path is zero');
}
$diff = self::compare_sizes($source_path, $webp_path);
return array('success' => true, 'destination_path' => $webp_path, 'reduction' => $diff);
}
static function start_optimization($img){
global $speedycache;
switch($speedycache->image['settings']['compression_method']){
case 'gd':
return self::gd_webp($img);
case 'imagick':
return self::imagick_webp($img);
case 'cwebp':
if(defined('SPEEDYCACHE_PRO')){
return \SpeedyCache\Image::cwebp_convert($img);
}
return array('success' => false, 'error_message' => 'SpeedyCache cwebp is a Pro feature');
default:
return array('success' => false, 'error_message' => 'The provided conversion method is not valid');
}
}
static function path_is_image($source_path){
$size = getimagesize($source_path);
if(empty($size)){
return false;
}
return true;
}
static function get_quality($img){
$dimensions = $img->getImageGeometry();
if($dimensions['width'] < 200 && $dimensions['height'] < 200){
return 85;
}
return 90;
}
static function compare_sizes($source_path, $destination_path){
$diff = filesize($source_path) - filesize($destination_path);
return ($diff > 0) ? $diff : 1;
}
//TODO:: Will need it when we will add non webp compression
static function create_backup_folder($destination_path){
global $speedycache;
$destination_path = str_replace($speedycache->image['upload_dir']['basedir'], '', $destination_path);
$path_arr = explode('/', $destination_path);
$path = $speedycache->image['upload_dir']['basedir'];
for ($i=1; $i < count($path_arr) - 1; $i++){
$parent_path = $path;
$path = $path.'/'.$path_arr[$i];
if(is_dir($path)){
continue;
}
if(@mkdir($path, 0755, true)){
//
}else{
//warning
if($path_arr[$i] == 'speedycache-backup'){
//toDO: to stop cron job and warn the user
}
return array('success' => false, 'error_message' => $parent_path.' is not writable', 'error_code' => 2);
}
}
return array('success' => true, 'error_message' => '');
}
static function set_id($id = null){
global $speedycache;
$get_id = speedycache_optget('id');
if(!empty($get_id)){
$speedycache->image['id'] = intval($get_id);
}elseif(!empty($id)){
$speedycache->image['id'] = intval($id);
}else{
$speedycache->image['id'] = self::get_first_id();
}
}
static function set_images(){
global $speedycache;
if(empty($speedycache->image['metadata']['file'])){
return;
}
$arr = array(
'file' => $speedycache->image['upload_dir']['basedir'].'/'.$speedycache->image['metadata']['file'],
'url' => $speedycache->image['upload_dir']['baseurl'].'/'.$speedycache->image['metadata']['file'],
'width' => $speedycache->image['metadata']['width'],
'height' => $speedycache->image['metadata']['height'],
'mime_type' => ''
);
array_push($speedycache->image['images'], $arr);
$i = 0;
$image_error = false;
if(!is_array($speedycache->image['metadata']['sizes'])){
if(empty($image_error)){
self::not_in_metadata();
}
}
foreach((array)$speedycache->image['metadata']['sizes'] as $key => $value){
$value['url'] = $speedycache->image['url'].$value['file'];
$value['file'] = $speedycache->image['path'].$value['file'];
$value['mime_type'] = isset($value['mime-type']) ? $value['mime-type'] : '';
unset($value['mime-type']);
if($i == 0){
if(self::not_found(self::get_correct_url($speedycache->image['upload_dir']['baseurl'].'/'.$speedycache->image['metadata']['file']))){
$image_error = true;
break;
}
}
if(!self::not_found(self::get_correct_url($value['url'])) && self::allowed_mime($value['file'])){
array_push($speedycache->image['images'], $value);
}
$i++;
}
if(empty($image_error)){
self::not_in_metadata();
}
}
static function get_correct_url($path){
if(preg_match('/^\/'.SPEEDYCACHE_WP_CONTENT_DIR.'/i', $path)){
// content_url() must return HTTP but it return /wp-content so we need to check
if(content_url() == '/'.SPEEDYCACHE_WP_CONTENT_DIR && home_url() == site_url()){
$path = home_url().$path;
}
}
return $path;
}
static function not_in_metadata(){
global $speedycache;
$paths = array();
foreach($speedycache->image['images'] as $key => $value){
array_push($paths, $value['file']);
}
$files = glob($speedycache->image['path'].$speedycache->image['name'].'-'.'*');
foreach((array)$files as $file){
if(@filesize($file) > 1000000){
continue;
}
if(!preg_match('/\.(jpg|jpeg|jpe|png|gif)$/i', $file)){
continue;
}
$exp_dos = explode('/',$file);
$basename = end($exp_dos);
if(in_array($file, $paths)){
continue;
}
if(!preg_match('/'.preg_quote($speedycache->image['name'], '/').'-(\d+)x(\d+)\..+/', $basename, $dimensions)){
continue;
}
$value = array(
'url' => $speedycache->image['url'].$basename,
'file' => $file,
'width' => $dimensions[1],
'height' => $dimensions[2],
);
if(!self::not_found($value['url'])){
array_push($speedycache->image['images'], $value);
}
}
}
static function set_path(){
global $speedycache;
$speedycache->image['path'] = $speedycache->image['upload_dir']['basedir'].'/'.preg_replace('/'.preg_quote($speedycache->image['name'], '/').'.+/', '', $speedycache->image['metadata']['file']);
}
static function set_url(){
global $speedycache;
$speedycache->image['url'] = $speedycache->image['upload_dir']['baseurl'].'/'.preg_replace('/'.preg_quote($speedycache->image['name'], '/').'.+/', '', $speedycache->image['metadata']['file']);
}
static function set_name(){
global $speedycache;
if(empty($speedycache->image['metadata'])){
return;
}
if(isset($speedycache->image['metadata']['sizes']) && count($speedycache->image['metadata']['sizes']) > 0){
$array_values = array_values($speedycache->image['metadata']['sizes']);
$speedycache->image['name'] = preg_replace('/-'.$array_values[0]['width'].'x'.$array_values[0]['height'].'.+/', '', $array_values[0]['file']);
if(!$speedycache->image['name']){
$speedycache->image['name'] = substr($speedycache->image['metadata']['file'], strrpos($speedycache->image['metadata']['file'], '/') + 1);
}
return;
}
$info = pathinfo($speedycache->image['metadata']['file']);
$speedycache->image['name'] = basename($speedycache->image['metadata']['file'],'.'.$info['extension']);
//$this->name = substr($this->metadata['file'], strrpos($this->metadata['file'], '/') + 1);
}
static function set_meta_data(){
global $speedycache;
$speedycache->image['metadata'] = wp_get_attachment_metadata($speedycache->image['id']);
}
//to get last image which is not optimized
static function get_first_id(){
$query_image = new \WP_Query(array(
'order' => 'DESC',
'orderby' => 'ID',
'post_type' => 'attachment',
'post_mime_type' => 'image/jpeg, image/png, image/gif',
'post_status' => 'inherit',
'posts_per_page' => 1,
'meta_query' => array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'NOT EXISTS'
),
array(
'key' => '_wp_attachment_metadata',
'compare' => 'EXISTS'
)
)
));
return count($query_image->posts) == 1 ? $query_image->posts[0]->ID : false;
}
static function statics_data(){
$res = array(
'total_image_number' => self::image_count(),
'error' => self::error_count(),
'optimized' => self::optimized_file_count(),
'uncompressed' => self::uncompressed_count(),
'reduction' => self::total_reduction(),
'percent' => 0,
);
if($res['optimized'] > 0){
$res['percent'] = ($res['optimized'] - $res['error']) * 100/$res['optimized'];
}else{
$res['percent'] = 0;
}
$res['percent'] = number_format($res['percent'], 2);
$res['reduction'] = $res['reduction'];
return $res;
}
static function revert_all(){
global $speedycache;
if(!current_user_can('manage_options')){
wp_die('Must Be admin');
}
$images = new \WP_Query(array(
'order' => 'DESC',
'orderby' => 'ID',
'post_type' => 'attachment',
'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif'),
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'EXISTS'
)
)
));
$failed = true;
if(count($images->posts) <= 100){
if($images->posts && count($images->posts) > 0){
foreach($images->posts as $key => $post){
$speedycache->image['id'] = $post->ID;
$result = self::revert();
if($result['success'] === true){
$failed = false;
}
}
}
if(!empty($failed)){
wp_send_json(array('success' => false, 'message' => __('Files can\'t be reverted', 'speedycache')));
}
wp_send_json(array('success' => true));
}
$schedule_posts = [];
foreach($images->posts as $key => $post){
if(100 === count($schedule_posts) || !empty($erase)){
$schedule_posts = [];
}
$erase = false; // Flag to unset the $scheduled_posts
$schedule_posts[] = $post->ID;
// Check if we have reached 99 and make sure its not the last index
if(count($schedule_posts) <= 99 && $key !== (count($images->posts) - 1)){
continue;
}
// Skip if alredy on schedule list.
if(wp_next_scheduled('speedycache_img_delete', array($schedule_posts))){
$erase = true;
continue;
}
$scheduled = self::get_optimization_schedule(array('speedycache_img_delete'));
$time = time();
if(!empty($scheduled) && isset(end($scheduled)['time'])){
// getting the last index to get the last scheduled event
$time = end($scheduled)['time'];
}
$final_schd_time = $time + 10;
if(!wp_next_scheduled('speedycache_img_delete', array($schedule_posts))){
wp_schedule_single_event($final_schd_time, 'speedycache_img_delete', array($schedule_posts));
continue;
}
}
wp_send_json(array('success' => true));
}
// Schedule deletion of image to reduce load on the server at a single time.
static function scheduled_delete($img_id){
global $speedycache;
if(empty($img_id)){
return;
}
if(is_scalar($img_id)){
$speedycache->image['id'] = $img_id;
self::revert();
return;
}
foreach($img_id as $id){
$speedycache->image['id'] = $id;
self::revert();
}
}
// Reverts a single image
static function revert(){
global $speedycache;
if(empty($speedycache->image['id'])){
return array('success' => false);
}
$optimization_data = get_post_meta($speedycache->image['id'], 'speedycache_optimisation', true);
// optm = optimization
$optm_json = base64_decode($optimization_data);
$optm_json = json_decode($optm_json, true);
if(empty($optm_json)){
return array('success' => false);
}
if(!empty($optm_json) && is_array($optm_json) && count($optm_json) == 1 && !empty($optm_json[0])){
if(!empty($optm_json[0]['error_code']) && $optm_json[0]['error_code'] == 18){
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
return array('success' => true);
}
}
$result = false;
$optm_json = array_reverse($optm_json);
$error_numbers = 0;
foreach($optm_json as $key => $image){
if(@!is_writable($image['file'])){
if(isset($speedycache->image['metadata']['file']) && preg_match('/'.preg_quote($speedycache->image['metadata']['file'], '/').'/', $image['url'])){
if(file_exists($image['file'])){
$result = array('success' => true, 'message' => $image['file'].' is not writable');
break;
}
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
}
}
if(
!empty($image['url']) &&
!preg_match('/\.webp$/', $image['url']) &&
isset($image['destination_path']) &&
file_exists($image['destination_path']) &&
(pathinfo($image['destination_path'], PATHINFO_EXTENSION) == 'webp')
){
@unlink($image['file']);
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
$result = array('success' => true);
continue;
}
if(!empty($image['error_code'])){
if(isset($speedycache->image['metadata']['file']) && preg_match('/'.preg_quote($speedycache->image['metadata']['file'], '/').'/', $image['url'])){
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
$result = array('success' => true);
}else{
$error_numbers++;
if($error_numbers == count($optm_json)){
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
$result = array('success' => true);
}
}
continue;
}
if(preg_match('/'.preg_quote($speedycache->image['metadata']['file'], '/').'/', $image['url'])){
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation');
delete_post_meta($speedycache->image['id'], 'speedycache_optimisation_reduction');
$result = array('success' => true);
}
}
return $result;
}
static function revert_on_delete($id){
global $speedycache;
if(!wp_attachment_is_image($id)){
return;
}
$speedycache->image['id'] = $id;
self::revert();
}
static function get_error_text($id){
//Error Codes
$errors = array(
2 => __('In backup folder parent folder not writable', 'speedycache'),
3 => __('No need to optimize', 'speedycache'),
4 => __('Source is not writable', 'speedycache'),
5 => __('Destination is not writable', 'speedycache'),
6 => __('ImageMagick library is not avaliable', 'speedycache'),
7 => __('Error via api', 'speedycache'),
8 => __('Source file does not exist', 'speedycache'),
9 => __('Image size exceed 20mb limit while processing', 'speedycache'),
11 => __('Empty Name', 'speedycache'),
12 => __('Forbidden', 'speedycache'),
13 => __('CloudFlare to restrict access', 'speedycache'),
14 => __('No Extension', 'speedycache'),
15 => __('Image size is 0Kb', 'speedycache'),
16 => __('Corrupted Image', 'speedycache'),
17 => __('Empty metadata', 'speedycache'),
18 => __('No Image', 'speedycache'),
19 => __('destination_move_source_path is not saved', 'speedycache'),
20 => __('file size of destination_move_source_path is zero', 'speedycache'),
21 => __('Unacceptable file type', 'speedycache'),
23 => __('WEBP create by other tool', 'speedycache'),
);
return isset($errors[$id]) ? $errors[$id] : 'Unkown error code';
}
// TODO:: Will use it when we will do non-webp compression
static function backup_folder_exist(){
global $speedycache;
$backup_folder_path = $speedycache->image['upload_dir']['basedir'].'/speedycache-backup';
if(is_dir($backup_folder_path)){
return true;
}
if(@mkdir($backup_folder_path, 0755, true)){
return true;
}
return false;
}
static function push_main_image($arr){
if(!isset($arr[0]) || isset($arr[0]->error_code)){
return $arr;
}
$main = clone $arr[0];
$total_reduction = 0;
foreach($arr as $std_key => $std_value){
if(!isset($std_value->error_code)){
if(isset($std_value->reduction) && $std_value->reduction){
$total_reduction = $total_reduction + $std_value->reduction;
}
}
}
$main->reduction = $total_reduction;
array_unshift($arr, $main);
return $arr;
}
static function list_content($query_images_args = array()){
global $speedycache;
remove_filter('posts_fields', '\SpeedyCache\Image::count_posts_fields'); //was causing bug
$query_image = new \WP_Query( $query_images_args );
$return_output = '';
if(empty($query_image->posts) || count($query_image->posts) <= 0){
return self::get_empty_row();
}
$count = 0;
foreach($query_image->posts as $key => $post){
$count++;
$value_json = get_post_meta($post->ID, 'speedycache_optimisation', true);
$tmpvalue_json = base64_decode($value_json);
$std = json_decode($tmpvalue_json);
$revert = true;
$std = self::push_main_image($std);
foreach($std as $std_key => $std_value){
$content = ($std_key === 0) ? self::get_row() : self::get_child_row();
if(empty($content)){
continue;
}
$std_value->destination_path = isset($std_value->destination_path) ? $std_value->destination_path : '';
$std_value->reduction = isset($std_value->reduction) ? $std_value->reduction : 0;
if($std_key === 0 && $revert){
$revert_button = '';
}else{
$revert_button = 'display:none;';
}
if(isset($std_value->error_code) && $std_value->error_code == 8){
$revert_button = 'display:none;';
}
if(file_exists($std_value->destination_path)){
$backup_url = $std_value->url.'?v='.time();
$backup_title = 'Original Image';
$backup_error_style = '';
}else{
if(isset($std_value->error_code) && $std_value->error_code){
$backup_url = get_edit_post_link($std_value->id);
$backup_title = self::get_error_text($std_value->error_code);
$backup_error_style = 'color: #FF0000;cursor:pointer;font-weight:bold;';
}else{
$backup_url = '#';
$backup_title = '';
$backup_error_style = '';
}
}
$std_value->file = preg_replace('/.(jpg|jpeg|png|gif)$/','.webp', $std_value->file);
if(file_exists($std_value->file)){
$std_value->url = preg_replace('/.(jpg|jpeg|png|gif)$/','.webp', $std_value->url).'?v='.time();
}else{
$std_value->url = SPEEDYCACHE_PRO_URL.'/assets/images/no-image.png';
}
$short_code = array(
'{{post_id}}',
'{{attachment}}',
'{{post_title}}',
'{{url}}',
'{{width}}',
'{{height}}',
'{{reduction}}',
'{{date}}',
'{{revert_button}}',
'{{backup_url}}',
'{{backup_title}}',
'{{backup_error_style}}'
);
$datas = array(
$std_value->id,
$std_value->url,
$post->post_title,
$std_value->url,
$std_value->width,
$std_value->height,
$std_value->reduction/1000,
date('d-m-Y <br> H:i:s', $std_value->time),
$revert_button,
$backup_url,
$backup_title,
$backup_error_style
);
$return_output .= str_replace($short_code, $datas, $content);
}
}
return $return_output;
}
static function allowed_mime($filename){
global $speedycache;
$mimetype = false;
if(!file_exists($filename)){
return false;
}
if(function_exists('finfo_open')){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfo, $filename);
finfo_close($finfo);
}else if(function_exists('getimagesize')){
$img = getimagesize($filename);
$mimetype = $img['mime'];
}else{
echo 'not found mime_content_type';
exit;
}
if($speedycache->image['settings']['compression_method'] === 'cwebp' && self::gif2webp_exists()){
if(preg_match('/(jpg|jpeg|jpe|png|gif)/i', $mimetype)){
return true;
}
return false;
}
if(preg_match('/(jpg|jpeg|jpe|png)/i', $mimetype)){
return true;
}
return false;
}
// Check if the image is available, the response code should be 200
static function not_found($url){
return false;
$res = wp_remote_head($url, array('timeout' => 3 ));
if(is_wp_error($res)){
$url_header = @get_headers($url);
if(preg_match('/200\s+OK/i', $url_header[0])){
return false;
}
return true;
}
if($res['response']['code'] == 200){
return false;
}
return true;
}
//Checks if the image is Unique
static function unique_array($images){
if(count($images) <= 1){
return $images;
}
$arr = array();
$images_tmp = array();
foreach($images as $key => $value){
if(!in_array($value['file'], $arr)){
array_push($arr, $value['file']);
array_push($images_tmp, $value);
}
}
return $images_tmp;
}
//Template static functions Starts Here
//Table row for the converted Image
static function get_row(){
return '<tr class="alternate author-self status-inherit" post-id="{{post_id}}">
<td class="column-icon media-icon image-icon">
<img width="39" height="60" src="{{attachment}}" class="attachment-80x60">
</td>
<td class="title column-title">
<p style="margin-bottom: 0;"><a href="{{url}}" target="_blank">{{post_title}}</a></p>
<p style="margin-bottom: 0;">
<a style="{{backup_error_style}}" href="{{backup_url}}" target="_blank"><strong>{{backup_title}}</strong></a>
</p>
</td>
<td class="author column-author speedycache-open-image-details" style="text-align: center;cursor: pointer;">{{reduction}}KB<span class="dashicons dashicons-arrow-down-alt2"></span></td>
<td class="date column-date" style="text-align: center;">{{date}}</td>
<td class="date column-date" style="text-align: center;">
<div class="speedycache-revert" style="{{revert_button}}">
<input type="hidden" value="{{post_id}}">
</div>
</td>
</tr>';
}
//table row for different sizes of image.
static function get_child_row(){
return '<tr class="alternate author-self status-inherit" post-id="{{post_id}}" post-type="detail" style="display: none; padding-left: 20px;">
<td class="column-icon media-icon" style="font-size:3.5em; color:#ccc;"><i class="fas fa-image"></i></td>
<td class="title column-title">
<p style="margin-bottom: 0;"><a href="{{url}}" target="_blank">{{post_title}}</a></p>
<p style="margin-bottom: 0;">
<a style="{{backup_error_style}}" href="{{backup_url}}" target="_blank"><strong>{{backup_title}}</strong></a>
</p>
<p style="margin-bottom: 0;">{{width}}x{{height}}</p>
</td>
<td class="author column-author" style="text-align: center;">{{reduction}}KB</td>
<td class="date column-date" style="text-align: center;">{{date}}</td>
<td class="date column-date" style="text-align: center;">
</td>
</tr>';
}
//No Image optimized
static function get_empty_row(){
return '<tr class="author-self status-inherit">
<td colspan="4">No image has been optimized yet</td>
</tr>';
}
//Paging for the converted Images Table
static function paging(){ ?>
<div class="tablenav bottom" style="padding:5px 16px 5px 19px">
<div style="float:left;">
<span class="deleteicon">
<input type="text" style="height:28px;" placeholder="Search" id="speedycache-image-search-input" class="deletable" value="">
<span class="cleared"></span>
</span>
<input type="submit" class="button action" value="Search" id="speedycache-image-search-button" name="filter_action">
</div>
<div style="float:left;padding-left:5px;">
<select id="speedycache-image-list-filter" class="bulk-action-selector-top">
<option value="" selected="selected">All</option>
<option value='error_code'>Errors</option>
</select>
</div>
<div style="float:left;padding-left:5px;">
<select id="speedycache-image-per-page" class="bulk-action-selector-top">
<option value="5" selected="selected">5</option>
<option value="10">10</option>
<option value="25">25</option>
</select>
</div>
<div class="tablenav-pages">
<span class="pagination-links">
<a class="tablenav-pages-navspan button first-page disabled speedycache-image-list-first-page" aria-hidden="true" data-page-action="first-page">«</a>
<a class="tablenav-pages-navspan button prev-page speedycache-image-list-prev-page" aria-hidden="true" data-page-action="prev-page">‹</a>
<a class="paging-input"><label class="speedycache-current-page">1</label> / <span class="total-pages speedycache-total-pages">1</span></a>
<a class="next-page button speedycache-image-list-next-page" data-page-action="next-page">›</a>
<a class="last-page button speedycache-image-list-last-page" data-page-action="last-page">»</a>
</span>
</div>
<br class="clear">
</div>
<?php
}
//Settings for Image Optimization
static function settings(){
global $speedycache, $speedycache_optm_method;
?>
<div class="speedycache-block">
<div class="speedycache-block-title"><h2><?php _e('Settings', 'speedycache'); ?></h2></div>
<form class="speedycache-img-opt-settings">
<div class="speedycache-option-wrap">
<label for="speedycache_img_automatic_optm" class="speedycache-custom-checkbox">
<input type="checkbox" id="speedycache_img_automatic_optm" name="img_automatic_optm" <?php echo (isset($speedycache->image['settings']['automatic_optm']) && $speedycache->image['settings']['automatic_optm']) ? ' checked="true"' : '';?>/>
<div class="speedycache-input-slider"></div>
</label>
<div class="speedycache-option-info">
<span class="speedycache-option-name"><?php esc_html_e('Automatic optimization', 'speedycache'); ?></span>
<span class="speedycache-option-desc"><?php esc_html_e('Whenever user visits the website and there are images that haven\'t been converted to webp then that image(s) gets converted.', 'speedycache'); ?></span>
</div>
</div>
<div class="speedycache-option-wrap">
<label for="speedycache_img_on_upload" class="speedycache-custom-checkbox">
<input type="checkbox" id="speedycache_img_on_upload" name="img_on_upload" <?php echo (isset($speedycache->image['settings']['on_upload']) && $speedycache->image['settings']['on_upload']) ? ' checked="true"' : '';?>/>
<div class="speedycache-input-slider"></div>
</label>
<div class="speedycache-option-info">
<span class="speedycache-option-name"><?php esc_html_e('Optimize on Image Upload', 'speedycache'); ?></span>
<span class="speedycache-option-desc"><?php esc_html_e('Images will be added to a queue and will optimized after a certain interval to reduce load on the server.', 'speedycache'); ?></span>
</div>
</div>
<div class="speedycache-option-wrap">
<label for="speedycache_img_url_rewrite" class="speedycache-custom-checkbox">
<input type="checkbox" id="speedycache_img_url_rewrite" name="img_url_rewrite" <?php echo (isset($speedycache->image['settings']['url_rewrite']) && $speedycache->image['settings']['url_rewrite']) ? ' checked="true"' : '';?>/>
<div class="speedycache-input-slider"></div>
</label>
<div class="speedycache-option-info">
<span class="speedycache-option-name"><?php esc_html_e('Rewrite Image URL\'s', 'speedycache'); ?></span>
<span class="speedycache-option-desc"><?php esc_html_e('Rewrites the url of the image on the page to the webp version if webp version is avaliable.', 'speedycache'); ?></span>
</div>
</div>
<div class="speedycache-option-wrap">
<label for="speedycache_img_quality">
<input type="number" id="speedycache_img_quality" name="img_compression_quality" value="<?php echo esc_attr($speedycache->image['settings']['compression_quality']);?>" min="50" step="5" max="90"/>
</label>
<div class="speedycache-option-info">
<span class="speedycache-option-name"><?php esc_html_e('Compression Quality', 'speedycache'); ?></span>
<span class="speedycache-option-desc"><?php esc_html_e('Higher the number higher will be the converted image quality in webp', 'speedycache'); ?></span>
</div>
</div>
<div class="speedycache-block-title" style="flex-direction:column">
<h4><?php esc_html_e('Conversion Methods', 'speedycache'); ?></h4>
<p style="display:block; font-family: monospace; background-color: #f2f2f2; border-radius:6px; padding:10px;"><span style="color:var(--speedycache-red); font-weight:bold;"><i class="fas fa-exclamation"></i> Note:</span><?php esc_html_e('Only cwebp supports gif conversions to webp. But when you upload an image to WordPress whether it be a jpg, png, or gif format, WordPress creates different sizes of that image on upload so, in the process of resizing some resized version of gifs may lose their animation.', 'speedycache'); ?>
</div>
<?php foreach($speedycache_optm_method as $m_key => $method){ ?>
<div class="speedycache-option-wrap <?php echo !$method['status'] ? ' speedycache-disabled-methods' : '' ?>">
<label for="speedycache-img-<?php echo esc_attr($m_key);?>">
<input type="radio" value="<?php echo esc_attr($m_key);?>" name="img_compression_method" id="speedycache-img-<?php echo esc_attr($m_key);?>" <?php echo (isset($speedycache->image['settings']['compression_method']) && $speedycache->image['settings']['compression_method'] == $m_key ? ' checked="true"' : '');?> <?php echo !$method['status'] ? ' disabled' : '' ?>/>
</label>
<div class="speedycache-option-info">
<span class="speedycache-option-name"><?php echo esc_html($method['title']); ?></span>
<span class="speedycache-option-desc"><?php echo esc_html($method['desc']); ?></span>
</div>
<?php
// TODO:: Need to fix this _e as it is a dynamic value we will need to use sprintf
if(!$speedycache_optm_method[$m_key]['status']){ ?>
<div class="speedycache-more-info" data-info="<?php echo esc_html($speedycache_optm_method[$m_key]['message']);?>" >
<i class="fas fa-question-circle"></i>
</div>
<?php } ?>
<div class="speedycache-img-method-actions">
<?php
if($m_key == 'cwebp'){
if(defined('SPEEDYCACHE_PRO') && !file_exists(wp_upload_dir()['basedir']. '/speedycache-binary/'.$speedycache->image['cwebp_binary'])){
echo '<button style="margin-right:5px;" class="speedycache-button speedycache-btn-black speedycache-webp-download">Download Binary</button>';
}
if(defined('SPEEDYCACHE_PRO') && !self::gif2webp_exists()){
echo '<button class="speedycache-button speedycache-btn-black speedycache-webp-download" style="margin-top:2px;" data-type="gif">Download Binary for GIF</button>';
}
}
?>
</div>
</div>
<?php } ?>
</form>
</div>
<?php
}
//Image Optimization Stats
static function statics(){
$res = self::statics_data();
$ring_success = 100 - (int)$res['percent'];
$scheduled = self::get_optimization_schedule(array('speedycache_auto_optm', 'speedycache_img_delete'));
$scheduled_count = count($scheduled);
//the css below is written in one line to make regex replace easy in js update.
?>
<style>
@keyframes donut1 {
0%{stroke-dasharray: 0, 100;}
100%{stroke-dasharray: <?php echo esc_attr($res['percent'] .', ' . $ring_success);?>;}
}
</style>
<div class="speedycache-block">
<div class="speedycache-img-stats speedycache-admin-row">
<div class="speedycache-is-block speedycache-img-graph">
<div class="speedycache-card-body">
<div class="speedycache-img-start-optm">
<div class="speedycache-img-stat-info">
<div class="speedycache-img-main-stat">
<?php esc_html_e('Success', 'speedycache'); ?>
<span class="speedycache-img-success-per"><?php echo esc_html($res['percent']);?>%</span>
</div>
<div class="speedycache-img-main-stat">
<?php esc_html_e('Error', 'speedycache');?>
<span class="speedycache-img-error-count" <?php echo $res['error'] > 0 ? 'style="color:#dc3545"' : '' ?>><?php echo esc_html($res['error']);?></span>
</div>
<div class="speedycache-img-main-stat">
<?php esc_html_e('Scheduled', 'speedycache');?>
<span class="speedycache-scheduled-count <?php echo $scheduled_count > 0 ? 'speedycache-scheduled-count-indicator' : '';?>" setting-id="speedycache-img-scheduled-modal"><?php echo esc_html($scheduled_count);?></span>
</div>
</div>
<div class="speedycache-img-remain-optm">
<span class="speedycache_img_optm_status" style="background-color:<?php echo $res['uncompressed'] > 0 ? '#EED202' : '#90ee90'?>"></span>
<span>
<?php
if($res['uncompressed'] > 0){
echo esc_html($res['uncompressed']) . ' ';
esc_html_e('Files needed to be optimized', 'speedycache');
} else {
esc_html_e('All images are optimized', 'speedycache');
}
?></span>
</div>
<div class="speedycache-img-optimize-all" style="margin-left:auto;">
<button class="speedycache-button speedycache-btn-black speedycache-img-optm-btn" setting-id="speedycache-modal-optimize-all"><?php esc_html_e('Optimize All', 'speedycache'); ?></button>
<button class="speedycache-img-delete-all-conv speedycache-button speedycache-btn-transparent"><?php esc_html_e('Delete all conversions', 'speedycache'); ?></button>
</div>
</div>
</div>
</div>
<div class="speedycache-is-block speedycache-img-count">
<div>
<div class="speedycache-img-opt-stat">
<div class="speedycache-img-optm-count"><?php echo esc_html($res['optimized'] . '/' . $res['total_image_number']); ?></div>
<div class="speedycache-img-optm-count-text"><?php echo esc_html_e('Image(s) optimized', 'speedycache'); ?>
<?php echo esc_html_e('with total reduction of', 'speedycache'); ?> <span class="speedycache-img-reduced-size"><?php echo esc_html($res['reduction'] > 10000 ? round($res['reduction']/1000, 2).'MB' : $res['reduction'].'KB');?></span></div>
</div>
</div>
</div>
</div>
</div>
<div modal-id="speedycache-img-scheduled-modal" class="speedycache-modal">
<div class="speedycache-modal-wrap">
<div class="speedycache-modal-header">
<div><?php esc_html_e('Scheduled Tasks', 'speedycache'); ?></div>
<div title="Close Modal" class="speedycache-close-modal">
<span class="dashicons dashicons-no"></span>
</div>
</div>
<div class="speedycache-modal-content speedycache-info-modal">
<?php if($scheduled_count < 1){ ?>
<span><?php esc_html_e('No Image Scheduled', 'speedycache'); ?></span>
<?php } else { ?>
<!--<p style="text-align:center;"><?php //_e('Time Interval of the Schedule is 1 minute', 'speedycache');?></p>-->
<table style="margin:auto; width: 100%;">
<thead>
<tr>
<th class="speedycache-table-hitem" scope="col"><?php esc_html_e('Image', 'speedycache'); ?></th>
<th class="speedycache-table-hitem" scope="col"><?php esc_html_e('Time', 'speedycache'); ?></th>
</tr>
</thead>
<tbody>
<?php
foreach($scheduled as $key => $schedule){
$time = $schedule['time'] - time();
$time_str = 'in '.$time. ' seconds';
if($time > 59){
$time_str = 'in '. round($time/60, 1) . ' minute(s)';
} else if($time <= 0){
$time_str = 'now';
}
?>
<tr>
<td class="speedycache-table-item"><?php echo esc_html('Batch ' . ($key + 1)); ?></td>
<td class="speedycache-table-item" style="text-align:right;"><?php echo esc_html($time_str);?> <i class="fas fa-stopwatch"></i></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php } ?>
</div>
</div>
</div>
<div modal-id="speedycache-modal-all-img-revert" class="speedycache-modal">
<div class="speedycache-modal-wrap">
<div class="speedycache-modal-content">
<i class="fas fa-info-circle"></i>
<h1><?php esc_html_e('Revert All Images!', 'speedycache'); ?></h1>
<p><?php esc_html_e('Once deleted the changes won\'t be reversible.', 'speedycache'); ?></p>
<div class="speedycache-modal-db-actions">
<button class="speedycache-btn speedycache-db-confirm-yes"><?php esc_html_e('Yes', 'speedycache'); ?></button>
<button class="speedycache-btn speedycache-db-confirm-no"><?php esc_html_e('No', 'speedycache'); ?></button>
</div>
</div>
</div>
</div>
<?php
}
//Templates Ends here
//Need to create logs for Automatic Optimization
static function log(){
}
static function compression_method_checks(){
global $speedycache, $speedycache_optm_method;
$gd = self::gd_check();
$speedycache_optm_method['gd'] = array_merge($speedycache_optm_method['gd'], $gd);
if(!$speedycache_optm_method['gd']['status']){
$speedycache->image['disabled_method']++;
self::change_method_if_not_available('gd');
}
$imagick = self::imagick_check();
$speedycache_optm_method['imagick'] = array_merge($speedycache_optm_method['imagick'], $imagick);
if(!$speedycache_optm_method['imagick']['status']){
$speedycache->image['disabled_method']++;
self::change_method_if_not_available('imagick');
}
$cwebp = self::cwebp_check();
$speedycache_optm_method['cwebp'] = array_merge($speedycache_optm_method['cwebp'], $cwebp);
if(!$speedycache_optm_method['cwebp']['status']){
$speedycache->image['disabled_method']++;
self::change_method_if_not_available('cwebp');
}
}
// GD static function Starts here
static function gd_check(){
if(!extension_loaded('gd')){
return array('status' => false, 'message' => 'You dont have GD PHP extension');
}
if(isset(gd_info()['WebP Support']) && !gd_info()['WebP Support']){
return array('status' => false, 'message' => 'GD extension on your server dosen\'t support WEBP');
}
if(isset(gd_info()['WebP Support']) && gd_info()['WebP Support']){
return array('status' => true, 'message' => 'Operational');
}
}
static function gd_webp($file){
global $speedycache, $speedycache_optm_method;
if(isset($speedycache_optm_method['gd']['status']) && !$speedycache_optm_method['gd']['status']){
return array('success' => false, 'error_message' => $speedycache_optm_method['gd']['message']);
}
$image_size = getimagesize($file);
$width = $image_size[0];
$height = $image_size[1];
switch($image_size['mime']){
case 'image/jpg':
case 'image/jpeg':
$source = imagecreatefromjpeg($file);
break;
case 'image/png':
$source = imagecreatefrompng($file);
break;
case 'image/webp':
return array('success' => false, 'error_message' => 'File is alredy a WEBP file');
default:
return array('success' => false, 'error_message' => 'File has unsupported mime'.$image_size['mime']);
}
if(empty($source)){
return false;
}
$dst = imagecreatetruecolor($width, $height);
if(imagealphablending($dst, false) !== false){
//change the RGB values if you need, but leave alpha at 127
$transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
if($transparent !== false){
//simpler than flood fill
if(imagefilledrectangle($dst, 0, 0, $width, $height, $transparent) !== false){
//restore default blending
if(imagealphablending($dst, true) !== false){
if(imagecopy($dst, $source, 0, 0, 0, 0, $width, $height) !== false){
$success = true;
}
};
}
}
}
if(!$success){
return array('success' => false, 'error_message' => 'Couldn\'t convert the given image.');
}
$webp_path = preg_replace('/.(jpe?g|png)$/', '.webp', $file);
imagedestroy($source);
imagewebp($dst, $webp_path, $speedycache->image['settings']['compression_quality']);
imagedestroy($dst);
if(!file_exists($webp_path)){
return array('url' => $webp_path, 'success' => true, 'error_message' => 19);
}
return array(
'url' => $webp_path,
'success' => true,
'error_message' => ''
);
}
//GD Ends Here
//Imagick static functions
static function imagick_check(){
if(!extension_loaded('imagick')){
return array('status' => false, 'message' => 'Imagick extension isn\'t installed');
}
$image = new \Imagick();
if(!in_array('WEBP', $image->queryFormats('WEBP'))){
$image->clear();
$image->destroy();
return array('status' => false, 'message' => 'Imagick installed on your system is without WEBP support');
}
return array('status' => true, 'message' => 'Operational');
}
static function change_method_if_not_available($m_name){
global $speedycache, $speedycache_optm_method;
if($speedycache->image['settings']['compression_method'] != $m_name){
return;
}
$valid_method = '';
foreach($speedycache_optm_method as $key => $method){
if($key == $m_name){
continue;
}
if(!empty($method['status'])){
$speedycache->image['settings']['compression_method'] = $key;
return;
}
}
$speedycache->image['settings']['compression_method'] = $valid_method;
}
//Conversion of image to webp
static function imagick_webp($file){
global $speedycache, $speedycache_optm_method;
if(isset($speedycache_optm_method['imagick']['status']) && !$speedycache_optm_method['imagick']['status']){
return array('success' => false, 'error_message' => $speedycache_optm_method['imagick']['message']);
}
$dest = preg_replace('/.(png|jpe?g)$/', '.webp', $file);
$image = new \Imagick();
$image->readImage($file);
$image->setImageCompressionQuality($speedycache->image['settings']['compression_quality']);
$image->setImageFormat('WEBP');
if($image->writeImage($dest)){
$image->clear();
$image->destroy();
return array('success' => true, 'error_message' => '');
}
$image->clear();
$image->destroy();
return array('success' => false, 'error_message' => 'File didn\'t got saved');
}
//cwebp static functions
static function cwebp_check(){
global $speedycache;
if(!defined('SPEEDYCACHE_PRO')){
return array('status' => false, 'message' => 'cwebp is a SPEEDYCACHE PRO feature.');
}
if(!function_exists('exec')){
return array('status' => false, 'message' => 'You dont have access to exec static function hence you cant use cWebP for Image optimizations');
}
if(isset($speedycache->image['cwebp_binary'])){
if(!file_exists(wp_upload_dir()['basedir'].'/speedycache-binary/'.$speedycache->image['cwebp_binary'])){
return array('status' => false, 'message' => 'You don\'t have the cwebp Binary on your server.', 'downloaded' => false);
}
}
return array('status' => true, 'message' => 'Operational');
}
static function rewrite_url_to_webp($content){
if(strpos(speedycache_optserver('HTTP_ACCEPT'), 'image/webp') < 0){
return $content;
}
if( ! preg_match_all( '/<img\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ){
return $content;
}
foreach($matches as $match){
///(?i)(https?:\/\/|www.|\w+\.(png|jpe?g)$)[^\s]+/
preg_match_all('/https?:\/(\/[^\/]+)+\.(?:jpe?g|png|gif)/', $match[0], $urls, PREG_SET_ORDER);
foreach($urls as $url){
$file_url = preg_replace('/.(jpe?g|png|gif)$/', '.webp', $url[0]);
$file_path = explode(SPEEDYCACHE_WP_CONTENT_DIR.'/uploads', $file_url);
if(!isset($file_path[1]) || !file_exists(wp_upload_dir()['basedir'].$file_path[1])){
continue;
}
$content = str_replace($url[0], $file_url, $content);
}
}
return $content;
}
//Adding wp event for automatic optm on image upload
static function convert_on_upload($id){
global $speedycache;
if(!$speedycache->image['settings']['on_upload']){
return;
}
if(!wp_attachment_is_image($id)){
return;
}
$scheduled = self::get_optimization_schedule(array('speedycache_auto_optm'));
$time = time();
if(!empty($scheduled) && isset(end($scheduled)['time'])){
$time = end($scheduled)['time'];
}
// Convert after 5 minutes of upload
if(!wp_next_scheduled('speedycache_auto_optm', array($id))){
wp_schedule_single_event($time + 300, 'speedycache_auto_optm', array($id));
return;
}
}
//Optimizing image on Upload
static function auto_optimize($img_id){
self::optimize_single($img_id);
}
//Whenever user visits a page and there are images that dont have a webp versions then those images are set to be converted.
static function optimize_on_fly($content){
global $wpdb, $speedycache;
//Sitepad dosen't have this static function.
if(function_exists('wp_filter_content_tags')){
$content = wp_filter_content_tags($content);
}
if(!preg_match_all('/<img\s[^>]+>/', $content, $matches, PREG_SET_ORDER)){
return $content;
}
foreach($matches as $m_key => $match){
if(!preg_match('/wp-image-([\d]+)/i', $match[0], $post_id)){
//This one will work on the new versions of pagelayer.
if(!preg_match('/pl-image-([\d]+)/i', $match[0], $post_id)){
/*
* This is for compatibility with other editors.
* what we do is find the url from 'src' attribute and then get the file name by
* exploding using "/" so the last index of array will be the file name
* then we will look if that file name is present in any meta_value to get the Image id.
*/
//<img(?:[^>]|[^\/>]).*src[\s*]?=[\s*]?"(.+?)" just in case the one used causes bugs
if(!preg_match('/<img.*?src="(.*?)"/', $match[0], $url)){
continue;
}
$file = explode('/', esc_url($url[1]));
$file = end($file);
$query = "SELECT `post_id` FROM `".$wpdb->prefix."postmeta` WHERE `meta_value` LIKE '%$file%'";
$result = $wpdb->get_row($query);
if(empty($result) || !$result->post_id){
continue;
}
$post_id = [$file, $result->post_id];
}
}
if(empty($post_id)){
continue;
}
$attachment_id = (int)$post_id[1];
if(!$attachment_id){
continue;
}
//If the image is already scheduled for optimization then just skip it.
if(wp_next_scheduled('speedycache_auto_optm', array($attachment_id))){
continue;
}
if(!get_post($attachment_id)){
continue;
}
$query = "SELECT count(`meta_key`) as optimized FROM `".$wpdb->prefix."postmeta` WHERE `meta_key`= 'speedycache_optimisation' AND `post_id`=$attachment_id";
$result = $wpdb->get_row($query);
if($result->optimized){
continue;
}
/*
* https?:\/(\/[^\/]+)+\.(?:jpe?g|png|gif) this regex dosent matches if it finds "//" anywhere after
* the slashes of http://. Which was causing a issue in sitepad as after site-data/uploads// 2 slashes were added.
* so the regex being used here dosent checks for "//" double slashes.
*/
// if(!preg_match('/https?:\/\/.*\.(?:jpe?g|png|gif)$/', $match[0], $url)){
// continue;
// }
// $path = explode(SPEEDYCACHE_WP_CONTENT_DIR.'/uploads', $url[0]);
// $path = wp_upload_dir()['basedir'] . $path[1];
// if(!file_exists($path)){
// continue;
// }
$scheduled = self::get_optimization_schedule(array('speedycache_auto_optm'));
$time = time();
if(!empty($scheduled) && isset(end($scheduled)['time'])){
// getting the last index to get the last scheduled event
$time = end($scheduled)['time'];
}
$final_schd_time = $time + 60;
if(!wp_next_scheduled('speedycache_auto_optm', array($attachment_id))){
wp_schedule_single_event($final_schd_time, 'speedycache_auto_optm', array($attachment_id));
continue;
}
}
return $content;
}
// Returns an array of cron event "speedycache_auto_optm"
static function get_optimization_schedule($event_name){
$cron = _get_cron_array();
if(empty($cron)){
return false;
}
$scheduled = array();
foreach($cron as $key => $crn){
foreach($crn as $e_key => $event){
if(!in_array($e_key, $event_name)){
continue;
}
foreach($event as $evt){
$args = $evt['args'][0];
}
array_push($scheduled, array('name' => get_the_title($args), 'time' => $key));
}
}
return $scheduled;
}
static function gif2webp_exists(){
global $speedycache;
if(file_exists(wp_upload_dir()['basedir']. '/speedycache-binary/'.$speedycache->image['cwebp_gif'])){
return true;
}
return false;
}
static function download_cwebp(){
global $speedycache;
check_ajax_referer('speedycache_ajax_nonce', 'security');
$binary_name = $speedycache->image['cwebp_binary'];
$type = speedycache_optget('type');
switch($type){
case 'gif':
$binary_name = $speedycache->image['cwebp_gif'];
break;
case 'cwebp':
$binary_name = $speedycache->image['cwebp_binary'];
break;
default:
return;
}
$binary_path = wp_upload_dir()['basedir'].'/speedycache-binary';
if(!file_exists($binary_path)){
@mkdir($binary_path);
}
//If The binary alredy exists on the server
if(file_exists($binary_path.'/'.$binary_name)){
wp_send_json(array('success' => false, 'error_message' => 'Binary Exists already.'));
}
$resp = wp_remote_get(SPEEDYCACHE_API.'/files/cwebp/'.$binary_name);
$json = wp_remote_get(SPEEDYCACHE_API.'/files/hash.json');
$hash = json_decode($json['body'], true);
$file = $resp['body'];
if(!$hash){
wp_send_json(array('success' => false, 'error_message' => 'Unable to verify the downloaded binary.'));
}
if(!$file){
wp_send_json(array('success' => false, 'error_message' => 'Could not download the file please try again later.'));
}
$binary_file = $binary_path.'/'.$binary_name;
file_put_contents($binary_file, $file);
if(!file_exists($binary_file)){
wp_send_json(array('success' => false, 'error_message' => 'There was issue downloading the binary.'));
}
if(hash_file('md5', $binary_file) != $hash[$binary_name]){
@unlink($binary_path.'/'.$binary_name);
wp_send_json(array('success' => false, 'error_message' => 'Hash of the file downloaded didn\'t match'));
}
@chmod($binary_file, 0755);
$output = null;
$res = null;
if(!function_exists('exec')){
wp_send_json(array('success' => false, 'error_message' => 'Your server dosen\'t supports exec'));
}
$exec_name = $type === 'gif' ? 'gf2webp' : 'cwebp';
/*
* To check if the binary is supported by the server.
*/
exec('"'.$binary_file.'" '.$exec_name.' -version 2>&1', $output, $res);
if($res != 0){
wp_send_json(array('success' => false, 'error_messsage' => 'The binary for cwebp isn\'t supported by your server'));
}
if($type = 'gif'){
$speedycache->image['cwebp_gif'] = $binary_name;
} else {
$speedycache->image['cwebp_binary'] = $binary_name;
}
wp_send_json(array('success' => true));
}
static function cwebp_convert($file){
global $speedycache, $speedycache_optm_method;
if(isset($speedycache_optm_method['cwebp']['status']) && !$speedycache_optm_method['cwebp']['status']){
return array('success' => false, 'error_message' => $speedycache_optm_method['cwebp']['message']);
}
$mime = mime_content_type($file);
if($mime == 'image/gif'){
$binary_path = wp_upload_dir()['basedir']. '/speedycache-binary/'.$speedycache->image['cwebp_gif'];
$binary_name = 'gif2webp';
} else {
$binary_path = wp_upload_dir()['basedir']. '/speedycache-binary/'.$speedycache->image['cwebp_binary'];
$binary_name = 'cwebp';
}
$output_file = preg_replace('/.(jpe?g|png|gif)$/', '.webp', $file);
$res = null;
$output = null;
if(!file_exists($binary_path)){
return array('success' => false, 'error_message' => 'The binary to use cwebp not found');
}
/*
* -- Use "" around the paths in exec, as without "" can cause a bug on some servers installs.
* -- The Structure of the command below is :-
* "path_of_binary" cwebp -q [quality_in_int] "original_file_path" -o "output_file.webp"
* -- 2>&1 is used to redirect stderr to same place as stdout
* https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
*/
exec('"'.$binary_path.'" '.$binary_name.' -q '.$speedycache->image['settings']['compression_quality'].' "'.$file. '" -o "'. $output_file. '" 2>&1', $output, $res);
if($res != 0){
return array('success' => false, 'error_message' => 'Couldn\'t convert the given image using cwebp.', 'error_code' => 22);
}
return array('success' => true, 'error_message' => '', 'dest' => $output_file);
}
static function list_image_html(){
?>
<div id="speedycache-image-list">
<?php \SpeedyCache\Image::paging(); ?>
<div style="width:100%; overflow-x:auto;">
<table class="wp-list-table widefat fixed media" style="width: 95%; margin-left: 20px;">
<thead>
<tr style="height: 35px;">
<th scope="col" id="icon" class="manage-column column-icon" style=""></th>
<th scope="col" id="title" class="manage-column column-title sortable desc" style="width: 323px;">
<span style="padding-left: 8px;"><?php esc_html_e('File Name', 'speedycache'); ?></span>
</th>
<th scope="col" id="author" class="manage-column column-author sortable desc" style="width: 120px;text-align: center;">
<span><?php esc_html_e('Reduction', 'speedycache'); ?></span>
</th>
<th scope="col" id="date" class="manage-column column-date sortable asc" style="width: 91px;text-align: center;">
<span><?php esc_html_e('Date', 'speedycache'); ?></span>
</th>
<th scope="col" id="date" class="manage-column column-date sortable asc" style="width: 60px;text-align: center;">
<span><?php esc_html_e('Revert', 'speedycache'); ?></span>
</th>
</tr>
</thead>
<tbody id="the-list">
<?php
$query_images_args = array();
$query_images_args['order'] = 'DESC';
$query_images_args['orderby'] = 'ID';
$query_images_args['post_type'] = 'attachment';
$query_images_args['post_mime_type'] = array('image/jpeg', 'image/png', 'image/gif');
$query_images_args['post_status'] = 'inherit';
$query_images_args['posts_per_page'] = 5;
$query_images_args['meta_query'] = array(
array(
'key' => 'speedycache_optimisation',
'compare' => 'EXISTS'
)
);
echo \SpeedyCache\Image::list_content($query_images_args);
?>
</tbody>
</table>
</div>
</div>
<?php
}
}