# How to Extend OpenMage Image Cleaner to Detect Orphans in Custom Module Database Tables

> Extend OpenMage Image Cleaner to detect orphaned images in custom module database tables. Add a sync action to query custom tables and compare image paths against the filesystem.

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

---

**Extend OpenMage Image Cleaner by adding a new sync action in [`FbimagecleanerController.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/FbimagecleanerController.php) that queries your custom table for image paths, compares them against the filesystem using the helper's `scandirRecursive()` method, and inserts orphaned files into the `fb_imagecleaner_image` table for admin review.**

The OpenMage Image Cleaner extension by Fabrizio Balliano provides a robust framework for identifying unused images in the `media/` directory by cross-referencing core Magento tables. While it ships with built-in support for products, categories, and WYSIWYG content, the architecture is intentionally extensible to accommodate custom modules. You can integrate orphan detection for any custom database table by following the established four-step synchronization pattern used by the core actions.

## Understanding the Extension Architecture

The cleanup workflow relies on a clear separation between database references and filesystem scans. At its core, the extension compares what is stored in the database against what exists on disk, then persists the difference as orphan records.

Key components in `fballiano/openmage-image-cleaner` include:

- **`Fballiano_ImageCleaner_Adminhtml_FbimagecleanerController`** located at [`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) — Contains the sync actions (`syncproductAction`, `synccategoryAction`, etc.) that implement the comparison logic.
- **`Fballiano_ImageCleaner_Helper_Data`** at [`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) — Provides `scandirRecursive()` for filesystem traversal and `getMediaDirByEntityTypeId()` for path resolution.
- **`Fballiano_ImageCleaner_Model_Image`** and its resource model — Represents rows in the `fb_imagecleaner_image` table, which powers the admin grid UI.

Each sync action follows an identical pattern: determine an **entity type ID**, fetch image paths from relevant database tables, scan the corresponding media subdirectory, compute the difference, and write orphan entries to the cleaner table.

## Step-by-Step Implementation Guide

### Choose a Unique Entity Type ID

Select a positive integer that does not conflict with existing core IDs (category = 3, product = 4). For a custom module, you can hard-code this value or make it configurable via [`system.xml`](https://github.com/fballiano/openmage-image-cleaner/blob/main/system.xml).

```xml
<!-- app/code/local/YourNamespace/YourModule/etc/config.xml -->
<default>
    <admin>
        <custom_image_cleaner>
            <entity_type_id>5</entity_type_id>
        </custom_image_cleaner>
    </admin>
</default>

```

Retrieve it in your controller using:

```php
$entity_type_id = (int)Mage::getStoreConfig('admin/custom_image_cleaner/entity_type_id');

```

### Create the Custom Sync Action

Add a new method to [`FbimagecleanerController.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/FbimagecleanerController.php) that mirrors the logic of `syncproductAction`. This method queries your custom table, normalizes the paths, and populates the orphan table.

```php
public function syncCustomAction()
{
    $entity_type_id = (int)Mage::getStoreConfig('admin/custom_image_cleaner/entity_type_id');
    $media_dir      = Mage::getBaseDir('media') . '/custom';
    $resource       = Mage::getSingleton('core/resource');
    $db             = $resource->getConnection('core_read');
    $helper         = Mage::helper('fballiano_imagecleaner');

    if (!is_dir($media_dir)) {
        Mage::getSingleton('adminhtml/session')
            ->addError($this->__('"media/custom" folder does not exist.'));
        $this->_redirect('*/*');
        return;
    }

    // 1️⃣ Fetch image references from custom DB table
    $db_images = $db->fetchCol(
        "SELECT file_path FROM {$resource->getTableName('my_module_image')} WHERE file_path IS NOT NULL"
    );
    $db_images = array_map('ltrim', $db_images, ['/']);

    // 2️⃣ Scan the filesystem recursively
    $fs_images = $helper->scandirRecursive($media_dir);
    $fs_images = str_replace("$media_dir/", '', $fs_images);

    // 3️⃣ Compute orphans (files on disk not in DB)
    $unused_images = array_diff($fs_images, $db_images);

    // 4️⃣ Insert new orphans into fb_imagecleaner_image
    if ($unused_images) {
        $cleaner_table = $resource->getTableName('fb_imagecleaner_image');
        $already_seen  = $db->fetchCol(
            "SELECT path FROM {$cleaner_table} WHERE entity_type_id = {$entity_type_id}"
        );
        $unused_images = array_diff($unused_images, $already_seen);

        foreach ($unused_images as $path) {
            $db->insertIgnore($cleaner_table, [
                'entity_type_id' => $entity_type_id,
                'path'           => $path
            ]);
        }
    }

    $this->_redirect('*/*');
}

```

Replace `my_module_image` and `file_path` with your actual table and column names. The `insertIgnore` method prevents duplicate entries if the same orphan is detected across multiple sync operations.

### Register the Admin Action (Optional)

To expose a button in the admin UI, extend [`system.xml`](https://github.com/fballiano/openmage-image-cleaner/blob/main/system.xml) to add a new configuration field or button that routes to `*/*/syncCustom`. Alternatively, you can trigger the action manually via URL:

```

https://yourstore.com/index.php/admin/fbimagecleaner/syncCustom

```

### Verify Orphan Detection Results

After running the sync action, navigate to **System → Tools → Image Cleaner** in the admin panel. Orphaned images from your custom module will appear in the grid with the corresponding entity type ID, allowing you to review and delete them individually or in bulk using the existing interface.

## Summary

- **Reuse the existing controller pattern** in [`FbimagecleanerController.php`](https://github.com/fballiano/openmage-image-cleaner/blob/main/FbimagecleanerController.php) to maintain consistency with core sync actions.
- **Assign a unique entity type ID** to differentiate custom module images from core entities in the `fb_imagecleaner_image` table.
- **Leverage the helper's `scandirRecursive()` method** to reliably scan media subdirectories without writing custom filesystem logic.
- **Normalize database paths** by stripping leading slashes to ensure accurate comparison with filesystem results.
- **No UI modifications required** — the admin grid automatically displays any rows inserted into the cleaner table.

## Frequently Asked Questions

### What database table stores the detected orphan images?

The extension uses the `fb_imagecleaner_image` table, mapped by `Fballiano_ImageCleaner_Model_Resource_Image`. When you insert records with a specific `entity_type_id` and `path`, they immediately appear in the Image Cleaner admin grid without requiring additional code changes.

### Can I use the same entity type ID for multiple custom modules?

No, you should assign a unique entity type ID to each distinct image source. The ID acts as a filter in the admin grid and determines which media subdirectory the scanner targets. Reusing IDs would cause collisions in the orphan list and incorrect association of images.

### Does the extension automatically delete files from the custom module's folder?

No, the sync action only **detects** and **records** orphaned files. The actual deletion occurs through the admin UI when an administrator selects records and chooses the delete action. This two-step process prevents accidental removal of potentially critical files.

### How do I handle multiple image columns in a single custom table?

Extend the database query in your sync action to fetch all relevant columns, then merge the results into a single flat array before comparing against the filesystem. For example, if your table has `thumbnail` and `main_image` columns, perform two `fetchCol` calls and use `array_merge()` to create a comprehensive list of referenced paths.