-
-
Save sreichel/3d66b02c1bcdd7c400f5c04a62835ea2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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'; | |
$job = new OrphanImagesCleaner(true); | |
$job->run(); | |
/** | |
* Class OrphanImagesCleaner | |
* | |
* @author Jeroen Boersma <[email protected]> | |
* @author Adriano Cataluddi <[email protected]> | |
*/ | |
class OrphanImagesCleaner | |
{ | |
/** | |
* @var bool | |
*/ | |
protected $dryRun = true; | |
/** | |
* Class constructor | |
* @param bool $dryRun If true, just show the candidate files it would delete. | |
*/ | |
public function __construct($dryRun = true) | |
{ | |
$this->dryRun = $dryRun; | |
} | |
/** | |
* Executes the tools | |
*/ | |
public function run() | |
{ | |
$duplicateCount = 0; | |
$orphansCount = 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++; | |
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++; | |
$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', $duplicateCount)); | |
$this->wl(sprintf(' Total orphan images: %s', $orphansCount)); | |
$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__)); | |
} | |
} | |
?> | |
</pre> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment