Remove unused images from Statamic

Arlind Musliu Portrait
Arlind Musliu

July 1, 2022 · 5 min read · 298 views

Statamic Blogpost Image

Managing a content-rich website often involves many visual elements that enhance the user experience. However, over time, it's common to accumulate a surplus of unused images that can unnecessarily burden your server resources. Regularly purging these redundant files is crucial for maintaining an efficient, high-performing website. This article will explore the benefits of removing unused images and provide a step-by-step guide to streamline the process.

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

Benefits of Removing Unused Images

  1. Improved Server Performance: By eliminating unused images, you reduce the amount of disk space consumed, which can speed up backup operations and potentially lower hosting costs.

  2. Faster Page Load Times: Fewer images in your directories mean quicker response times when pages request resources, leading to an enhanced user experience.

  3. Boosted SEO Rankings: Search engines favor fast-loading websites. By optimizing your image storage, you can improve your site's SEO performance.

  4. Easier Management: Keeping your media library clean allows for easier management and reduces the risk of using outdated or incorrect images.

  5. Enhanced Security: Reducing the number of files on your server can also minimize potential attack vectors.

Step-by-Step Guide

  1. Identify Active Content: Start by gathering all active entries, such as blog posts. Extract related images and content, and save this data in a flattened array for easier searching.

  2. Catalog Public Images: Retrieve all image files from your public assets folder. Organize these files into a collection for easy manipulation, and extract the relative URLs for comparison purposes.

  3. Compare and Contrast: Cross-reference the images used in your active content with those stored in the public folder. Identify any images not being utilized in your content.

  4. Selective Deletion: Isolate the paths of unused images found in your public assets folder. Using file management tools, such as Laravel's Illuminate\Support\Facades\File, physically delete these files from your server and keep track of the deletions for reporting purposes.

Use case: Delete unused images

We explained 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!");
    }
}

Conclusion

Regularly auditing and removing unused images is a simple yet impactful practice for optimizing your website's performance. Not only does it contribute to a better user experience, but it also enhances your site's overall health and functionality. By following the outlined steps and adapting them to your specific needs, you can maintain a lean and efficient online presence.


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.

Arlind Musliu Portrait
Arlind Musliu

Cofounder and CFO of Lucky Media

Technologies:

Statamic
Heading Pattern

Related Posts

Stay up to date

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