Remove unused images from Statamic

Statamic Blogpost Image

Published:July 1, 2022

Updated: November 15, 2023

Views: 214

Statamic
Laravel

Removing unused images

When dealing with websites that have lots of content, it's not unusual to find yourself with hundreds or even thousands of unused images that give an extra burden to the server. That's why it's important to remove those additional unused images and free up some space.

(Edit: There is a new addon on the marketplace that works similarly, namely Clear Assets).

Use case: Delete unused images

We explain in a previous post how we migrated a WordPress website to Statamic v3. Another issue was deleting old images that were not used anymore. That's why we created a command that would simplify the process of removing those images. In our use case, these images only belonged to posts.

Get all active entries

Let's start by retrieving all posts and extracting the image and content of those posts. We flatten the return and save it as an array. We need to flatten because it's easier to search through a single level array in comparison to a multidimensional array.

<?php

//get all entries, transform them and flatten array
$allEntries = Entry::query()->where('collection', 'posts')
    ->get()
    ->transform(function ($entry) {
        return [
            'image' => $entry->image,
            'content' => $entry->content
        ];
    })
    ->flatten()
    ->toArray();

Get all images from the public folder

Next thing to do is retrieve all files from the public assets folder. We save the results as a collection so we can use the transform function for modifying the output of the data. In our case, we replace the full path and only retrieve the relative URL after the assets/. We save this as the name. We also get the full path because we need it for the end when we delete unused images.

<?php

// get all file names from folder and save the name (for comparing) and path (for deletion)
$files = collect(File::allFiles(public_path('assets/wp/uploads')));

$files->transform(function ($file) {
    return [
        'name' => Str::replace('\\', '/', Str::replace('C:\laragon\www\f2n2\public\assets/', '', $file->getPathName())),
        'path' => Str::replace('\\', '/', $file->getPathName()),
    ];
});

Compare images from entries and from the folder

Now that we have both data, we can compare to see which of the retrieved images used in our posts are in our folder as well. As for the other images that are not matched, we get their paths because they are not used anywhere in the posts.

Please note: This will not exclude images that you use on other collections (ex. pages, authors). In our use case we didn't need to check for those collections, but your case may be different.
<?php

// check if the name of the image (from the folders) can be found within the array of data (from the entries)

$notUsed = [];

foreach ($files as $image) {
  
    if (in_array($image['name'], $allEntries)) {
        continue;
    }
    if (in_array('assets/'.$image['name'], $allEntries)) {
        continue;
    }
    
    $notUsed[] = $image['path'];

    $this->line(sprintf('Unused image: %s, found at: %s', $image['name'], $image['path']));
}

Delete unused images

We extracted the paths of the images that are in our public assets folder but not used in any of our posts. We can now physically delete all images that exist on our server. We do this with the help of Laravel's use Illuminate\Support\Facades\File;. We also count all the images that we have deleted so that we can provide some statistics.

<?php

// delete all images that are not used
File::delete($notUsed);

$removed = count($notUsed);

return $this->info("Successfully removed {$removed} unused images!");

Final Solution

That solved our issue. Maybe in your case, you need to check for images on other collections as well, such as pages, authors, products, etc. That's why make sure that you modify the code for your needs. If we have a different use case in the future, we will be happy to include that as well.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Statamic\Facades\Entry;


class RemoveUnusedImages extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'remove:images';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Removes unused post images by searching the md files';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     */
    public function handle()
    {
        //get all entries, transform them and flatten array
        $allEntries = Entry::query()->where('collection', 'posts')
            ->get()
            ->transform(function ($entry) {
                return [
                    'image' => $entry->image,
                    'content' => $entry->content
                ];
            })
            ->flatten()
            ->toArray();

        // get all file names from folder and transform them to save the name (for comparing) and the path (for deletion)
        $files = collect(File::allFiles(public_path('assets/wp/uploads')));

        $files->transform(function ($file) {
            return [
                'name' => Str::replace('\\', '/', Str::replace('C:\laragon\www\f2n2\public\assets/', '', $file->getPathName())),
                'path' => Str::replace('\\', '/', $file->getPathName()),
            ];
        });

        // start the progress bar
        $bar = $this->output->createProgressBar($files->count());
        $bar->start();

        // check if the name of the image (from the folders) can be found within the array of data (from the entries)
        $notUsed = [];
        foreach ($files as $image) {
            $bar->advance();

            if (in_array($image['name'], $allEntries)) {
                continue;
            }
            if (in_array('assets/' . $image['name'], $allEntries)) {
                continue;
            }
            $notUsed[] = $image['path'];

            $this->line(PHP_EOL); // new line
            $this->line(sprintf('Unused image: %s, found at: %s', $image['name'], $image['path']));
        }

        // delete all images that are not used
        File::delete($notUsed);

        $bar->finish();
        $this->line(PHP_EOL); // new line
        
        $removed = count($notUsed);

        return $this->info("Successfully removed {$removed} unused images!");
    }
}

This post is a follow-up to our recent explanation of a WordPress to Statamic migration.


Bring Your Ideas to Life πŸš€

Kickstart Your Statamic Project with the #1 Statamic Agency

Are you planning a new Statamic project or thinking about migrating your WordPress site to Statamic? Learn more about our expertise as a renowned Statamic development agency.

Lucky Media is proud to be recognized as the #1 best Statamic Agency by Clutch, a leading B2B ratings and reviews platform.

Our Services and Specializations

At Lucky Media, we offer a range of services including website development, web application development, and mobile apps development. We specialize in Statamic, React Native, Next.js, AI and ML solutions. We also provide staff augmentation and TALL stack development services.

Case Studies: Our Work in Action

For more insights into our work, check out our case studies on revolutionising lead generation with AI, customized coaching site, healthcare digitization, next-level performance, lead generation and patient journey, WordPress to Statamic migration, and improving user experience. These case studies provide a glimpse into how we tailor our technology choices to meet specific client needs and deliver exceptional results.

Heading Pattern

Related Posts

Stay up to date

Be updated with all news, products and tips we share!