Skip to content

Instantly share code, notes, and snippets.

@acataluddi
Last active December 8, 2022 14:58
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save acataluddi/e5887529981f67e03eb3ffd53a577bef to your computer and use it in GitHub Desktop.
Save acataluddi/e5887529981f67e03eb3ffd53a577bef to your computer and use it in GitHub Desktop.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Remove Magento's orphan images web console</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Inconsolata:400,700&subset=latin,latin-ext' rel='stylesheet'
type='text/css'>
<style type="text/css">
body {
font-family: "Open Sans", Arial, sans-serif;
margin-left: 25px;
margin-right: 30px;
padding: 0 0 0 0;
}
pre {
font-family: Inconsolata, "Courier New", monospace;
font-size: 15px;
clear: both;
margin: 0 -0px 0 0;
background: #000;
border: 1px groove #ccc;
color: #ccc;
display: block;
width: 100%;
min-height: 600px;
padding: 5px 5px 5px 5px;
}
.logo {
float: left;
margin-right: 25px;;
}
.scriptInfo {
float: left;
}
h1 {
font-size: 16px;
margin-top: 0;
line-height: 49px;
margin-bottom: 0;
}
h1 span {
font-family: Inconsolata, "Courier New", monospace;
}
.header {
width: 100%;
height: auto;
display: table;
margin-top: 25px;
margin-bottom: 25px;;
}
</style>
</head>
<body>
<div class="header">
<div class="logo">
<img src="skin/frontend/default/modern/images/logo.gif" alt="Magento's Logo" width="173" height="49">
</div>
<div class="scriptInfo">
<h1>Delete Catalog Orphan Images <span>1.1</span></h1>
</div>
</div>
<pre>
<?php
require 'app/Mage.php';
/**
* NOTE: Set this to false if you want to actually delete files.
*/
$dryRun = true;
$job = new OrphanImagesCleaner();
$job->run($dryRun);
/**
* Class OrphanImagesCleaner
*
* @author Jeroen Boersma <jeroen@srcode.nl>
* @author Adriano Cataluddi <acataluddi@gmail.com>
*/
class OrphanImagesCleaner
{
/**
* @var bool
*/
protected $dryRun = true;
/**
* Executes the tools
* @param bool $dryRun
*/
public function run($dryRun = true)
{
$this->dryRun = $dryRun;
$duplicateCount = 0;
$duplicateTotalSize = 0;
$orphansCount = 0;
$orphansTotalSize = 0;
if (!Mage::isInstalled()) {
$this->wl('Application is not installed yet, please complete install wizard first.');
exit;
}
set_time_limit(0);
session_write_close();
umask(0);
Mage::app('admin')->setUseSessionInUrl(false);
$mediaPath = $this->getRootPath() . '/media/catalog/product';
if ($this->dryRun)
$this->wl('DRY RUN mode: the script will NOT modify any file or record.');
$this->wl();
$this->wl(' Magento release: ' . Mage::getVersion());
$this->wl(' Media path: ' . $mediaPath);
$this->wl();
$this->wl();
$this->wl('[Phase 1/2] Looking for duplicate products images...');
$this->wl(str_repeat('-', 80));
$connection = Mage::getSingleton('core/resource')
->getConnection('core_write');
$sql = 'select distinct '
. 'cp.entity_id, '
. 'cpg.value_id, '
. 'cpv.value as default_value, '
. 'cpg.value '
. 'from catalog_product_entity as cp '
. 'join catalog_product_entity_varchar as cpv on cp.entity_id = cpv.entity_id '
. 'join catalog_product_entity_media_gallery as cpg on cp.entity_id = cpg.entity_id '
. 'WHERE '
. 'cpv.attribute_id in(select attribute_id from eav_attribute where frontend_input = \'media_image\') '
. 'and '
. 'cpv.value != cpg.value;';
$results = $connection->fetchAll($sql);
$this->wl(sprintf('Found %s items to process.', sizeof($results)), true);
$lastEntityId = null;
$origSums = array();
foreach ($results as $row) {
if ($row['entity_id'] != $lastEntityId) {
$lastEntityId = $row['entity_id'];
$origSums = array();
}
$origFile = $mediaPath . $row['default_value'];
if (!file_exists($origFile)) {
continue;
}
$file = $mediaPath . $row['value'];
if (file_exists($file)) {
if (!isset($origSums[$origFile])) {
$origSums[$origFile] = md5_file($origFile);
}
$sum = md5_file($file);
if (!in_array($sum, $origSums)) {
$origSums[$file] = $sum;
}
else {
$this->wl(sprintf('Deleting image "%s" (#%s)', $file, $row['entity_id']), true);
$duplicateCount++;
$duplicateTotalSize += filesize($file);
if (!$this->dryRun) unlink($file);
}
}
if (!file_exists($file)) {
$this->wl(sprintf('Deleting record for "%s" (#%s)', $file, $row['entity_id']), true);
$deleteSql = 'delete from catalog_product_entity_media_gallery where value_id = ' . $row['value_id'] . ';';
if (!$this->dryRun) $connection->query($deleteSql);
}
}
// Find files on filesystem which aren't listed in the database
$this->wl();
$this->wl('[Phase 1/2] Finding files on filesystem which aren\'t listed in the database...');
$this->wl(str_repeat('-', 80));
$files = glob($mediaPath . '/[A-z0-9]/*/*');
foreach ($files as $file) {
$searchFile = str_replace($mediaPath, '', $file);
// Lookup
$mediaSql = "select count(*) as records from catalog_product_entity_media_gallery where value = '{$searchFile}'";
$mediaCount = $connection->fetchOne($mediaSql);
if ($mediaCount < 1) {
$orphansCount++;
$orphansTotalSize += filesize($file);
$this->wl(sprintf('Deleting image "%s"', $file), true);
if (!$this->dryRun) unlink($file);
}
}
$this->wl();
$this->wl('Done.');
$this->wl(str_repeat('-', 80));
$this->wl(sprintf(' Total duplicate images: %s (%s)', $duplicateCount, $this->formatBytes($duplicateTotalSize)));
$this->wl(sprintf(' Total orphan images: %s (%s)', $orphansCount, $this->formatBytes($orphansTotalSize)));
$this->wl(str_repeat('-', 80));
}
/**
* @param boolean $dryRun
*/
public function setDryRunEnabled($dryRun)
{
$this->dryRun = $dryRun;
}
/**
* @return boolean
*/
public function isDryRunEnabled()
{
return $this->dryRun;
}
/**
* Writes a line in console.
* @param $line
* @param bool $notifyDryRun
*/
protected function wl($line = null, $notifyDryRun = false)
{
($notifyDryRun && $this->dryRun && ($line !== null)) ?
$dryLabel = 'DRY RUN | ' :
$dryLabel = '';
print $dryLabel . $line . "\n";
}
/**
* Returns the script root path
* @return string
*/
protected function getRootPath()
{
return (dirname(__FILE__));
}
/**
* Format bytes
* @author MrCaspan (https://github.com/MrCaspan)
* @param $bytes
* @return string
*/
protected function formatBytes($bytes)
{
$i = floor(log($bytes, 1024));
return round($bytes / pow(1024, $i), [0, 0, 2, 2, 3][$i]) . ['B', 'kB', 'MB', 'GB', 'TB'][$i];
}
}
?>
</pre>
</body>
</html>
@LuizSantos22
Copy link

LuizSantos22 commented Dec 16, 2021

@kbpcit
In case you want an alternative method which works directly from admin panel, you got this one:
https://github.com/fballiano/magento1-image-cleaner

Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment