# How OpenMage Image Cleaner Detects Orphan Category Images vs. Product Images

> Discover how OpenMage Image Cleaner distinguishes orphan category images from product images by analyzing database entries versus media files efficiently.

- Repository: [Fabrizio Balliano/openmage-image-cleaner](https://github.com/fballiano/openmage-image-cleaner)
- Tags: how-to-guide
- Published: 2026-03-01

---

**OpenMage Image Cleaner identifies orphaned images by comparing physical files in the media directory against database references, using distinct EAV queries for categories (checking only varchar attributes) versus products (checking varchar attributes, media gallery tables, and configurable placeholder images).**

OpenMage Image Cleaner is a disk-space recovery tool for Magento 1 and OpenMage LTS that finds image files present on the filesystem but no longer linked to any database records. The extension implements two separate detection algorithms in [`app/code/community/Fballiano/ImageCleaner/controllers/Adminhtml/FbimagecleanerController.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/app/code/community/Fballiano/ImageCleaner/controllers/Adminhtml/FbimagecleanerController.php) to handle the architectural differences between how Magento stores category and product image references.

## How Category Orphan Detection Works

The category detection process focuses solely on the EAV varchar table for catalog categories, as category images are stored as simple string attributes without gallery support.

### Querying the Category EAV Structure

The process begins by identifying the correct entity type ID for categories using `Mage::getModel('catalog/category')->getResource()->getTypeId()` (lines 28–31). The code then queries the `eav_attribute` table to find all attributes where `frontend_input='image'` and the `entity_type_id` matches the category type (lines 41–43).

These attribute IDs filter a `SELECT value FROM catalog_category_entity_varchar` query that retrieves every non-empty image path currently assigned to categories (line 44). This yields the complete list of filenames the database considers active.

### Scanning the Filesystem and Computing Orphans

The scanner recursively walks `Mage::getBaseDir('media') . '/catalog/category'` using the helper method `Mage::helper('fballiano_imagecleaner')->scandirRecursive()` (lines 45–46), stripping the base path to produce relative filenames. A simple `array_diff($fs_images, $db_images)` (line 48) isolates files existing on disk but absent from the varchar table.

Each detected orphan is inserted into the custom table `fb_imagecleaner_image` (lines 50–60) with the category entity-type ID, making it available for review and deletion in the admin grid.

## How Product Orphan Detection Works

Product detection is more complex because Magento stores product images across multiple tables and supports configurable placeholders.

### Collecting Product Image Attributes

Like the category process, this starts by fetching the product entity type ID via `Mage::getModel('catalog/product')->getResource()->getTypeId()` (lines 66–70). However, it queries `eav_attribute` for `frontend_input='media_image'` (lines 79–81)—the input type used for `image`, `small_image`, and `thumbnail` attributes.

The code reads `value` from `catalog_product_entity_varchar` for these attributes, strips leading slashes, and explicitly excludes the Magento placeholder value `'no_selection'` (lines 81–84).

### Including Gallery and Placeholder References

Unlike categories, products store additional images in the `catalog_product_entity_media_gallery` table. The scanner pulls all `value` entries from this table (lines 91–93) to capture gallery images that might not be assigned to the main product attributes.

Additionally, the detection algorithm prevents false positives by treating placeholder images as "used." It queries `core_config_data` for paths like `catalog/placeholder/small_image_placeholder`, prepends `placeholder/` to those filenames, and includes them in the valid reference set (lines 85–89).

### The Set Difference Operation

The final orphan list is computed using `array_diff($fs_images, $db_images, $media_gallery)` (line 97). Only files not referenced in the main product attributes, the media gallery, **or** the placeholder list are considered orphaned. Results are stored in `fb_imagecleaner_image` with the product entity-type ID (lines 99–109).

## Shared Filesystem Infrastructure

Both detection methods rely on [`app/code/community/Fballiano/ImageCleaner/Helper/Data.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/app/code/community/Fballiano/ImageCleaner/Helper/Data.php) for directory traversal. The `scandirRecursive()` method recursively scans directories while respecting a blacklist that excludes `cache`, `watermark`, and `.thumbs` directories from the orphan calculation. This ensures temporary or system-generated files do not clutter the results.

## Practical Usage Examples

Administrators can trigger detection via the admin panel (System → Tools → Image Cleaner) or programmatically:

```php
// Detect orphan category images
Mage::app()->getRequest()
    ->setControllerName('fbimagecleaner')
    ->setActionName('synccategory')
    ->setRouteName('adminhtml')
    ->dispatch();

// Detect orphan product images  
Mage::app()->getRequest()
    ->setControllerName('fbimagecleaner')
    ->setActionName('syncproduct')
    ->setRouteName('adminhtml')
    ->dispatch();

```

Direct URL access (replace `<admin>` with your admin front-name):

- `https://example.com/<admin>/fbimagecleaner/synccategory`
- `https://example.com/<admin>/fbimagecleaner/syncproduct`

## Summary

- **Category detection** queries only `catalog_category_entity_varchar` for attributes with `frontend_input='image'` and compares against files in `media/catalog/category`.
- **Product detection** aggregates references from `catalog_product_entity_varchar` (media_image attributes), `catalog_product_entity_media_gallery`, and placeholder configurations from `core_config_data`.
- Both processes use `array_diff()` to isolate files present on disk but missing from the aggregated database references.
- The shared `scandirRecursive()` helper in [`Helper/Data.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/Helper/Data.php) handles directory walking with blacklist support for system directories.
- Detected orphans are persisted to the `fb_imagecleaner_image` table for administrative review and bulk deletion.

## Frequently Asked Questions

### What database tables does OpenMage Image Cleaner query for orphan detection?

For categories, the extension queries `eav_attribute` and `catalog_category_entity_varchar`. For products, it queries `eav_attribute`, `catalog_product_entity_varchar`, `catalog_product_entity_media_gallery`, and `core_config_data` (for placeholder paths). These queries run in [`FbimagecleanerController.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/FbimagecleanerController.php) during the `synccategoryAction` and `syncproductAction` methods.

### Why does product orphan detection require checking placeholder images?

Placeholder images are stored in `media/catalog/product/placeholder/` and configured via `core_config_data`, not referenced in product EAV tables. Without checking these config values, the tool would flag placeholder images as orphans even though they are actively used for products missing images. The code explicitly prepends `placeholder/` to these filenames and excludes them from the orphan list (lines 85–89).

### How does the extension handle the media directory blacklist?

The `scandirRecursive()` method in [`Helper/Data.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/Helper/Data.php) accepts a blacklist array containing `cache`, `watermark`, and `.thumbs`. It skips these directories during the recursive scan to prevent system-generated files from appearing in the orphan detection results.

### Can I run the orphan detection programmatically without the admin UI?

Yes. You can dispatch the controller actions programmatically using `Mage::app()->getRequest()` with `setControllerName('fbimagecleaner')` and `setActionName('synccategory')` or `syncproduct`, as shown in the usage examples above. This allows integration with cron jobs or automated maintenance scripts.