3

I'm searching for a option to remove all unused images every minute.

The images I use are for Craft Commerce.

Alex Roper
  • 2,632
  • 1
  • 11
  • 23

2 Answers2

3

Best way to handle this is probably with a console command provided by a module.

The console command would need to fetch all asset IDs that don’t have any corresponding rows in the relations database table.

use Craft;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Asset;
use yii\console\ExitCode;
use craft\helpers\Console;

public function actionPurgeUnusedAssets(): int
{
    // Find any asset IDs that aren't related to anything
    $assetIds = (new Query())
        ->select(['a.id'])
        ->from(['a' => Table::ASSETS])
        ->leftJoin(Table::RELATIONS . ' r', '[[r.targetId]] = [[a.id]]')
        ->where(['r.id' => null])
        ->column();

    if (empty($assetIds)) {
        $this->stdout('No unrelated assets to delete.' . PHP_EOL);
        return ExitCode::OK;
    }

    // Now fetch and delete them
    $assets = Asset::find()
        ->id($assetIds)
        ->all();

    $this->stdout('Found ' . count($assets) . ' unrelated assets.' . PHP_EOL);

    foreach ($assets as $asset) {
        $this->stdout(" - Deleting asset {$asset->filename} ... ");
        Craft::$app->elements->deleteElement($asset);
        $this->stdout('done' . PHP_EOL, Console::FG_GREEN);
    }

    $this->stdout('Finished deleting unrelated assets.' . PHP_EOL);
    return ExitCode::OK;
}

Then, you can set up a cron job on your server that hits this command every 24 hours.

0 0 * * * /path/to/project/craft your-module/purge-unused-assets
Brandon Kelly
  • 34,307
  • 2
  • 71
  • 137
1

I once wrote a custom service doing this (not for commerce, though). I'll post the code, and maybe it can help you along the way. You can set it up in a module, make a console controller, and have a cronjob hit it every minute (although, are you sure you need to do it that often?).

The DeleteAssets is a very simple extension of BaseJob, basically looping through the assets, and deleting them one by one, just so it's handled as queue job. You could also loop through and delete each with Craft::$app->getElements()->deleteElementById(), but doing it as a queue job is probably better.

    public function deleteUnusedAssets() {

        //First fetch all ids from elements table
        $allIds = Entry::find()
                 ->limit(null)
                 ->ids();

        // Query for all related Assets (assets that are used somewhere)
        $usedAssets = Asset::find()
                ->relatedTo($allIds)
                ->limit(null)
                ->ids();

        // Query for all assets NOT in the list of the used ones
        $excludeString = 'and, not ' . implode(', not ', $usedAssets);
        $unUsedAssets = Asset::find()
                ->id($excludeString)
                ->limit(null)
                ->ids();

        $queue = Craft::$app->getQueue();
        $jobID = $queue->push(new DeleteAssets([
            'description' => 'Deleting ' . count($unUsedAssets) . ' assets'),
            'elements' => $unUsedAssets,
        ]));
        $queue->run();

    }
KSP
  • 849
  • 6
  • 16
  • Unable to locate message source for category 'cinema-program'. – Bram Beekman Feb 06 '20 at 14:25
  • Ah, that is the handle for my plugin. Just use your own, or replace description with something that doesn't use the translate function. I'll update the answer. – KSP Feb 09 '20 at 19:18