Skip to content

Instantly share code, notes, and snippets.

@migliori
Last active February 13, 2024 06:30
Show Gist options
  • Save migliori/69e86e5ec6488c73c50d7f87fdd207db to your computer and use it in GitHub Desktop.
Save migliori/69e86e5ec6488c73c50d7f87fdd207db to your computer and use it in GitHub Desktop.
Bulk RECURSIVE Image Optimizer - PHP Ajax - from https://github.com/spatie/image-optimizer #optimization

Bulk Image Optimizer

Recursive image optimizer - optimize all the images in a parent folder and all its subdirectories without deph limitation.

Instructions

  1. Install spatie image optimizer with Composer: composer require spatie/image-optimizer

  2. Install all the optimizers on Linux server (SSH):

     sudo apt-get install optipng
     sudo apt-get install pngquant
     sudo npm install -g svgo
     sudo apt-get install gifsicle
    
  3. Add the html & js code to the main page (image-optimizer.php)

    The #img-optimizer-btn will trigger Ajax and launch the image optimization on click.

  4. Create the Ajax php file which will do the job (ajax-image-optimizer.php).

<?php
use Spatie\ImageOptimizer\OptimizerChainFactory;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// sanitize root directory separator
$root = rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR);
$root = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $root) . DIRECTORY_SEPARATOR;
define('ROOT', $root);
define('COMPOSER_AUTOLOAD', ROOT . 'vendor/autoload.php');
function listFolders($dir)
{
$output_directories = array();
$ffs = scandir($dir);
unset($ffs[array_search('.', $ffs, true)]);
unset($ffs[array_search('..', $ffs, true)]);
// prevent empty ordered elements
if (count($ffs) < 1) {
return;
}
foreach ($ffs as $ff) {
if (is_dir($dir.'/'.$ff)) {
$output_directories[] = $dir.'/'.$ff;
$sub = listFolders($dir.'/'.$ff);
if (!empty($sub)) {
$output_directories = array_merge($output_directories, $sub);
}
}
}
return $output_directories;
}
function listFiles($dir)
{
$output_files = array();
$ffs = scandir($dir . '/');
unset($ffs[array_search('.', $ffs, true)]);
unset($ffs[array_search('..', $ffs, true)]);
// prevent empty ordered elements
if (count($ffs) < 1) {
return;
}
foreach ($ffs as $ff) {
if (!is_dir($dir.'/'.$ff)) {
$output_files[] = $dir.'/'.$ff;
}
}
return $output_files;
}
function optimizeImages($file)
{
$output = array(
'html' => '',
'saved_kb' => 0
);
$allowedTypes = array(IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF);
$detectedType = exif_imagetype($file);
if (in_array($detectedType, $allowedTypes)) {
$origin_filesize = filesize($file)/1000;
$output['html'] = '<small class="d-inline-block w-75 pr-2">' . str_replace(ROOT, '', $file) . ': ' . $origin_filesize . 'kb => ';
include_once COMPOSER_AUTOLOAD;
$optimizerChain = OptimizerChainFactory::create();
// $logger = new Logger('my_logger');
// $logger->pushHandler(new StreamHandler(ROOT . 'log/image-optimizer.log', Logger::DEBUG));
$optimizerChain
// ->useLogger($logger)
->optimize($file);
$dest_filesize = filesize($file)/1000;
$saved_kb = number_format($origin_filesize - $dest_filesize, 2);
$output['html'] .= $dest_filesize . 'kb</small>';
if ($saved_kb > 0) {
$output['html'] .= '<small class="d-inline-block w-25 text-success">- ' . $saved_kb . 'kb</small>';
$output['saved_kb'] = $saved_kb;
} else {
$output['html'] .= '<small class="d-inline-block w-25 text-muted">[already optimized]</small>';
}
$output['html'] .= '<br>';
echo json_encode($output);
}
}
if (isset($_POST['main_dir'])) {
/*=================================================
= 1st STEP: GET DIRECTORIES =
=================================================*/
$dir = $_POST['main_dir'];
$output = listFolders($dir);
echo json_encode($output);
} elseif (isset($_POST['img_dir'])) {
/*===========================================
= 2nd STEP: GET FILES =
===========================================*/
$dir = $_POST['img_dir'];
$output = listFiles($dir);
echo json_encode($output);
} elseif (isset($_POST['file'])) {
/*===========================================
= 3rd STEP: OPTIMIZE IMAGES =
===========================================*/
$file = $_POST['file'];
$output = optimizeImages($file);
echo $output;
}
<?php
// sanitize root directory separator
$root = rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR);
$root = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $root) . DIRECTORY_SEPARATOR;
define('ROOT', $root);
define('IMAGES_DIR', str_replace('\\', '/', ROOT) . 'assets/images'); // Replace with your custom path
define('AJAX_OPTIMIZER_URL', 'inc/ajax-image-optimizer.php'); // Replace with your custom path
?>
<button type="button" class="btn btn-outline-primary btn-lg mb-2" id="img-optimizer-btn">Optimiser les images<sup>*</sup><i class="{{ constant('ICON_CHECKMARK') }} ml-2"></i></button>
<p class="text-muted"><sup>*</sup> Réduit la taille des images (articles, catégories, marques, ...) et améliore les temps de chargement</p>
<div id="img-optimizer-content" class="alert alert-light border" style="visibility: hidden; overflow-y: auto">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<div id="img-optimizer-output" class="pt-3 mb-5" style="max-height:400px;overflow-y: auto;clear:right;"></div>
<div id="img-optimizer-saved-kb" class="text-center text-success"></div>
</div>
<script>
var target = $('#img-optimizer-output'),
target2 = $('#img-optimizer-saved-kb'),
directories = [],
directoriesCount = 0,
files = [],
filesCount = 0,
currentDirectory = '',
currentFile = '',
currentDirIndex = 0,
currentFileIndex = 0,
savedKb = 0,
mainDir = '<?php echo IMAGES_DIR; ?>',
dfd;
var getDirectories = function() {
$.ajax({
url: '<?php echo AJAX_OPTIMIZER_URL; ?>',
type: 'POST',
data: {
'main_dir': mainDir
}
}).done(function(data) {
dfd.resolve(data);
}).fail(function(data, statut, error) {
console.log(error);
});
}
var getFiles = function() {
$.ajax({
url: '<?php echo AJAX_OPTIMIZER_URL; ?>',
type: 'POST',
data: {
'img_dir': currentDirectory
}
}).done(function(data) {
files = $.merge(files, JSON.parse(data));
if (currentDirIndex + 1 < directoriesCount) {
currentDirIndex ++;
currentDirectory = directories[currentDirIndex];
getFiles();
} else {
// console.log(files);
filesCount = files.length;
currentFile = files[currentFileIndex];
optimizeImages();
}
}).fail(function(data, statut, error) {
console.log(error);
});
}
var optimizeImages = function() {
$.ajax({
url: '<?php echo AJAX_OPTIMIZER_URL; ?>',
type: 'POST',
data: {
'file': currentFile
}
}).done(function(data) {
if(data.length > 0) {
data = JSON.parse(data);
target.append(data['html']);
target.scrollTop(target[0].scrollHeight);
savedKb += parseFloat(data['saved_kb']);
target2.html('TOTAL SAVED: ' + savedKb.toFixed(2) + 'Kb');
}
if (currentFileIndex + 1 < filesCount) {
currentFileIndex ++;
currentFile = files[currentFileIndex];
optimizeImages();
} else {
target2.append('<br>--- DONE ---');
target2.append('<br>--- ' + filesCount + ' OPTIMIZED IMAGES ---');
}
}).fail(function(data, statut, error) {
console.log(error);
});
}
if ($('#img-optimizer-btn')[0]) {
$('#img-optimizer-btn').on('click', function() {
$('#img-optimizer-content').css('visibility', 'visible');
dfd = $.Deferred();
dfd.done(function(data) {
directories = JSON.parse(data);
directoriesCount = directories.length;
currentDirectory = directories[currentDirIndex];
getFiles();
});
// get all subdirectories
getDirectories();
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment