Tom McFarlin

Software Engineering in WordPress, PHP, and Backend Development

Review and Highlights of 2025

At the end of each year, I’ve usually written a “most popular articles of the year” type of article. Last year, however, I wrote a slightly longer “year in review” type of post. Given that we’re at the end of another year and I enjoyed writing about 2024, I thought it worth the time to do another one for 2025.

For the most part, I’m sticking with the same format as last year covering what I’ve read, listened to, and how I did with some of my other goals. I’m also adding a couple of sections related both to work and other projects that I completed (or at least started) this year.


Highlights of 2025

Work

I don’t talk much about my day-to-day here and haven’t so far as posted anything other than a link to a blog post on X or any other social media for at least a year.

I enjoy my work on the R&D team at Awesome Motive where the last year has been spent deep in AI, working in Laravel, Google Cloud Platform, WordPress, and other tangential tools.

Much of the work we’re doing is building out tools and infrastructure that supports security initiatives, software for internal teams, and other utilities that are best described to fall under DevOps.

Projects

This year is the first time in a few years where I not only worked on a number of different side projects, but I worked on projects that use tech stacks with which I don’t frequently work. The advent of AI has made is much easier to go from nothing to something faster all the while learning something new at a faster rate.

Here’s a short list of what I published this year in chronological order:

  • Code Standard Selector for Visual Studio Code is a Visual Studio Code extension that makes it easy to switch your PHP coding standard without having to edit any settings in your IDE. And, yes, this will also work in any of the IDEs that are a fork of VS Code.
  • Remove Empty Shortcodes is a WordPress plugin I’ve revisited that automatically removes empty or inactive shortcodes from your WordPress content while preserving your original database entries.
  • TuneLink.io For Matching Music Across Services allows you to easily find the same song across different music streaming services. Simply paste a link from Spotify or Apple Music, and TuneLink will give you the matching track on the other platform.
  • Where Can I Watch? is a web application that helps users quickly find which streaming services offer a particular show or movie.
  • Fetch Album Artwork for Apple Music Playlists is Python script to fetch album artwork for Apple Music playlists when given an artist and an album. This makes it a little bit easier to make sure album playlists have better looking artwork than what Apple Music generates on its own.
  • TM Monthly Backup is an aptly titled program I use for easily backing up photos, videos, screenshots, and even generated content from my Apple Photos library.

Out of the various projects listed above, the ones about which I’ve received the most email are my monthly backup program, Where Can I Watch?, and Code Standard Selector.

Most Popular Posts

Last year, I shared the most popular posts that I’d published in 2024. This year, I thought it interesting to look at the most popular posts in terms of visits over the year as well as the most popular posts of 2025.

Popular Posts By Traffic in 2025

Popular Posts Published in 2025

As years have passed, there’s an obvious shift in content an frequency but it’s neat to see the long tail some of these posts have (namely those that are still popular in 2025).

Books

I still try to read two books at a time – one fiction and one non-fiction – but I don’t but hard limits on how many books per month or whether or not they are the latest best sellers or most popular books. I aim to read what I want to read.

At the time of this writing, I read 22 books this year. I won’t list them all but some of the ones I enjoyed the most are:

  • Non-fiction
    • Farenheit-182 by Mark Hoppus. Since I grew up listening to blink-182, this was a fantastic [and an easy] read. It hits a lot of high notes, glosses over some of the stuff that, although would’ve been interesting, wasn’t necessarily relevant to the core band.
    • The Comfort Crisis by Michael Easter. I decided to read this after hearing Michael on a podcast. The overarching lesson of the book is still worth reading, and I won’t spoil anything here, but I really like Easter’s writing style. He intersperses his personal story with the larger points he’s making.
    • On Writing by Stephen King. I’ve been reading his books for years so reading the only memoir he’s published and gaining insight into his process, along with some other fun anecdotes, made for a good read.
    • Future Boy by Michael J. Fox. Back to the Future is one of my top five favorite movies (in fact, I think it’s one of the only good time travel movies that exist) and Michael J. Fox is an incredible person so reading this was an easy choice. If you haven’t seen the documentary Still, it plays well in conjunction with the content of this book.
  • Fiction
    • The Dark Tower VII by Stephen King. I was in the fourth book at the end of last year and wrapped the series up in February of 2025. It made for a really great adventure. Yes, it has its weak points but overall it’s hard to find a modern epic that’s as sprawling as this series.
    • Sunrise on the Reaping by Suzanne Collins. I’ve enjoyed The Hunger Games since it was published and have also liked all of the additional stories Collins has written since. Given a choice between The Ballad of Songbirds and Snakes, which I also liked, I like this one even more.

Obviously, it wasn’t a big year for fiction. If there’s an honorable mention at least for the sake of discussion it would be The Cabin at the End of the World by Paul Tremblay. It was a book I picked up on a whim unaware that the movie Knock at the Cabin was based on it. It had a terrific hook from the first page but the ending did not hold up to the quality of the rest of the story.

Fitness

In September of last year, I pinched a nerve in my lower back which brought my usual routine to a halt. I was able to get back to walking by November and was back running on a treadmill and pushing weights by January.

Since then, I’ve had to make adjustments to my usual workouts. I don’t run outside anymore, unfortunately, but I still exercise five times a week and have primarily been doing a combination of running on the treadmill, strength training, and stretching every day. Once you pinch a nerve in your back, you’ll do just about anything to prevent it from happening again.

I’m still using Gentler Streak and have all the usual stats to share but the one that’s most important to me, given where I was last year, is this: Since logging all of my exercise via my Apple Watch since 2016, this is the first year since 2018 that I’ve exercised more cumulative time in an entire year. At the time of this writing, I’ve logged 162 hours and 5 minutes and 437.69 miles in 2025.

I don’t have a goal of trying to surpass this next year but if I can maintain this (I’m not getting any younger) and if I can avoid any further issues with my back, then I’ll be happy.

Music, TV, and Podcasts

Music

The albums I enjoyed the most in 2025 include:

There’s a handful of other albums that I saved in Spotify this year (and, really, Subtitles for Feelings didn’t come out this year but I kept coming back to it). And for what it’s worth, there’s been a handful of synthwave and retrowave albums I listen to a lot when I work (most of them are by Timecop1983, FM-84, and The Midnight).

TV

As with last year, the majority of what I watch is whenever I’m on the treadmill or whenever Meghan and I are up for watching something before the day’s over. The shows I enjoyed most this year are (in no particular order):

  • Daredevil: Born Again. Though it felt a bit different from the Netflix series (reshoots and rewrites will do that), it found its footing before the end of the series and I’m glad.
  • Severance. If you’ve seen it, you get it; if you haven’t, you should.
  • Slow Horses. I don’t know what took me so long to watch this show but once I started, it’s all I watched when exercising. I covered the first four seasons during the summer and finished just in time for the fifth season to start in the fall.
  • Welcome to Derry. I liked the novel, IT, and the Muschietti’s Chapter One but I wasn’t as much a fan of Chapter Two. So I was tepid about this series. It ultimately delivered but the scope starts wide and takes it a while to find its footing and a solid pace. If you’re a fan of the book but not up for the show, the final two episodes are worth a watch.

I’d be making a mistake not to mention Stranger Things. We’ve been fans since day one and though the show isn’t wrapped at the time of this writing, we’re still here for it.

Podcasts

The podcasts I’ve listened to this year versus years past doesn’t vary wildly but a few dropped off and some new ones found their way to my rotatation:

And One More Thing

Over the last few years, my oldest daughter has been interested in playing the guitar. A little over a year ago, she fell into it hard and has been both playing and songwriting since.

She and I started an ‘album swap’ for lack of a better term (what would you call this in 2025, anyway?) where she recommends an album to me, I recommend one to her, and we listen for a couple of weeks. Then we ask each other questions and have a discussion about it.

We also record it and I archive it for future listening. It’s something I really enjoy – and is meaningful – right now. Fast forward a decade or two and I’m sure it’s going to be that much more so.

To 2026

As with anyone else, this year was also full of other milestones for the kids, our family, trips, and so on. But everything above are the personal highlights all of which are largely outside of work.

As I wrote last year:

[These] are the highlights for 2024. Like most, I have things that I’m planning to do in 2025 though I’ll wait until this time next year to share how everything went.

And I repeat that but for 2026 instead.

In retrospect, it’s been an incredibly full year in nearly every facet. Such is life the older we – and our kids – get. I’m consistently surprised how much we fit into a year and even more so in just how fast it passes.

Nonetheless, each year brings with it a combination of pursuing the same goals and interests as well as moving into new areas, as well. In that regard, there will always be something new to share.

With that, I hope your year was just as full and mostly good and the next is even better. Here’s to 2026.

Merry Christmas and Happy Holidays 2025

Though I’ve not written one of these posts every year, it started stacking up in the last few (excluding a break I took a few years ago):

Last year, around this time, I also wrote Review and Highlights of 2024 and have a similar post for 2025 scheduled to publish in a few days.

I mention all of those if for no other reason than it’s increasingly interesting (for me, at least) to look back at each year as far as my general career is concerned.

But for today, the only thing I’m really sharing is to say Merry Christmas and Happy Holidays.


Merry Christmas 2024

As I tend to say, regardless of what holiday you’re celebrating, I hope both this day and this time of year are good to you and yours.

My family celebrated Christmas with my in-laws a few days ago, are celebrating at home today, and will be with other family before the break is over. We also have an upcoming wedding in the family and some friends visiting from Maryland.

It’s very busy albeit very full time of year and I’m grateful for that.

Thanksgiving 2025

Last year, I wrote:

At this point, it’s more of a tradition to post on Thanksgiving Day than anything else. 

And I still hold that to be the case.

This year, I’d be remiss if I didn’t say something about looking forward to Stranger Things given that it’s been such a major show for the past nine(!) years and the first volume of the final began this weekend.

If you’re in the United States (or if you’re American and celebrating else where), I hope the day is great.

Next month, I look forward to doing another post on a retrospective of the year.

Easily Backup and Sort Apple Photos, Videos, Media, and AI Content

Like most, the majority of files (project files, photos, videos, etc.) are all backed up to the cloud each day because, you know, just in case. But I treat my photos and videos a bit different. Yes, I have daily backups, but I handle my monthly backups a little bit differently.

Roughly 20 years ago, I lost all digital photos and videos that I’d had taken since digital cameras and cell phones with cameras became mainstream. The short of it is that I had all of that data backed up on two external hard drives. One was always connected to my machine, the other was sync’d periodically, because redundancy is important, right?

But fires don’t care about redundancy.

Since then, it would be an understatement to say I’m extremely particular with my backup process especially as it relates to photos and videos. And even more so since we’ve had kids.

Anyway, the gist of my process for backing up photos and videos from Apple Photos roughly follows this process:

  1. Export all images and videos from the last month
  2. Convert all HEIC to lossless JPEG
  3. Separate HEIC, JPEG, video, screenshots, AI generated images, etc. in separate directories
  4. Name the files based on the date contained in the EXIF data (or the closest approximation from file creation or modification)
  5. Rsync this both with a local, external drive and a cloud backup service.

The most time-consuming part of the process are steps two through four. The rest are ideal for automation via local scripts and programs.

So to make the process a bit easier, I have a Monthly Backup utility I use help take care of the entire export. And maybe it’s something useful for others, too.


Backup and Sort Photos, Videos, Media, and AI Content

As the repository states:

A utility for easily backing up photos, videos, and screenshots from Apple Photos library.

When you export unmodified photos from your Apple Photo library, you place them in an export directory in the root of the project. Then, after setting up the Python virtual environment and installing the dependencies, run python -m src.main and you’ll have all of the files organized into subdirectories:

  • photos/ – actual photos with proper EXIF timestamps
  • videos/MOV, MP4, and other video files
  • screenshots/ – iOS and macOS screenshots automatically detected
  • generated/ – AI-generated images and heavily edited content
  • unknown/ – anything that doesn’t fit the other categories

They’ll also be renamed based on the EXIF or metadata timestamps for ease of organization. The format is simple: YYYY.MM.DD.HH.MM.SS, which makes chronological sorting trivial.

And if you’re curious to see how your content will be processed, you can run python -m src.main --dry-run to see what will be processed and how it will be organized. This way, you’ll know if this is – or isn’t – the right program for you before making any changes to your files.

Installation

Three steps: clone the repository, create the virtual environment, install the dependencies.

Or, in other words:

# Clone the repository
git clone https://github.com/tommcfarlin/tm-monthly-backup.git
cd tm-monthly-backup

# Create the virtual environment.
python3 -m venv tm-backup-env
source tm-backup-env/bin/activate

# Install the dependencies.
pip install -r requirements.txt

Then you can run the program. I typically use it once a month, as stated, after exporting that month’s photos and videos from iCloud, but you can run it as frequently or infrequently as you want.

More Technical Details

The remainder of this this article covers how the program actually works, how to use it, and how you can fork it, report bugs, or add features.

Sorting Types of Files

The program does more than just move files around. It actually looks at the content and makes intelligent decisions about how to handle it.

Images

For HEIC files, it converts them to high-quality JPEG while preserving all the EXIF data. Apple’s sidecar files (those .aae files that come along for the, ahem, ride) get cleaned up automatically since they aren’t needed when the conversion is done.

Videos

Video timestamps are extracted from the actual video metadata, not just the file creation date. This matters because if the file has been copied or moved, the filesystem date might be wrong, but the embedded metadata in the video file itself is usually accurate.

AI Content

The AI content detection is particularly useful (and something I didn’t really need until recently, for obvious reasons). Images from ChatGPT or other AI tools are automatically separated into the generated folder. It looks for C2PA metadata, UUID-style filenames, and other signals that something was generated rather than captured with a camera.

Screenshot detection works by recognizing iOS and macOS patterns. Those IMG_3XXX.PNG files or anything with “Screenshot” in the name gets sorted appropriately.

When there are duplicate timestamps, which happens especially when burst mode has been used, the tool increments the seconds to keep everything unique. And if a file is missing EXIF data entirely, it falls back to filesystem timestamps and will generate a note about this in its output.

Usage

Assuming you’ve already installed the program, here’s a few notes on how to use it.

The utility expects your exported files to be in an export/ directory and will create a backup/ directory with all your organized content. If you want to see what it’ll do before committing, that dry-run flag is your friend.

For debugging or if you’re just curious about what’s happening under the hood, there’s a verbose mode: python -m src.main --verbose. It’ll show you all the processing details, which is helpful if something isn’t working quite right or if you want to understand how a particular file got categorized.

The whole thing is built with proper CLI conventions, so python -m src.main --help will give you all the options if you need a reference.

Obligatory Contribution Note

If you’re backing up Apple Photos and related media on a regular basis and find the manual organization tedious, feel free to check it out on GitHub.

The code is MIT licensed, so you can use it, modify it, or ignore parts you don’t need. And if you find issues or have suggestions, pull requests are always welcome.

How To Use Roots Radicle with Laravel Herd

I’ve previously written about using Laravel Herd as my preferred PHP development environment. Outside of my day-to-day work, I’m also working with a couple of friends on a project that includes an iOS app that talks to a REST API via headless WordPress backend.

The web app is built using a set of tools from Roots including Radicle:

Radicle is an opinionated starting point for WordPress projects with the Roots stack.

In short, Radicle allows us to use features of Laravel within WordPress. But one of the challenges with my set up is getting Laravel Herd and Radicle to actually work.

Turns out, the solution isn’t that hard. And if you’re in a similar situation, here’s how to work with Radicle with Laravel Herd.


Roots Radicle with Laravel Herd

Project Set Up

First, I’m operating on the assumption that you have your project is already linked and secured with Herd.

In our case, we’re using a monorepo that contains both the iOS app and the web application. The web app in question is located in projects/monorepo-name/apps/wordpress. So I’ve issued the usual commands add that directly to Herd.

Secondly, I’ve got the .env file configured so that it has all of the necessary information for the database, various WP environmental variables, salts, and other information required to stand up the web app.

The Actual Problem

Third, and this was the most problematic, I had to add a custom driver that would allow Radicle to work with Laravel Herd. The path to custom drivers on your machine may vary but if you’re running the latest version of macOS and haven’t customized the Herd installation then it should look something like this:

/Users/yourname/Library/Application Support/Herd/config/valet/Drivers

Note that if the Drivers directory doesn’t exist, create it. Then touch a file named RadicleValetDriver.php in that directory and add the the following code (you shouldn’t need to change any of it):

<?php

namespace Valet\Drivers\Custom;

use Valet\Drivers\BasicValetDriver;

class RadicleValetDriver extends BasicValetDriver
{
    /**
     * Determine if the driver serves the request.
     */
    public function serves(string $sitePath, string $siteName, string $uri): bool
    {
        return file_exists($sitePath.'/public/content/mu-plugins/bedrock-autoloader.php') &&
               file_exists($sitePath.'/public/wp-config.php') &&
               file_exists($sitePath.'/bedrock/application.php');
    }

    /**
     * Determine if the incoming request is for a static file.
     *
     * @return string|false
     */
    public function isStaticFile(string $sitePath, string $siteName, string $uri)/* : string|false */
    {
        $staticFilePath = $sitePath.'/public'.$uri;
        if ($this->isActualFile($staticFilePath)) {
            return $staticFilePath;
        }

        return false;
    }

    /**
     * Get the fully resolved path to the application's front controller.
     */
    public function frontControllerPath(string $sitePath, string $siteName, string $uri): string
    {
        $_SERVER['PHP_SELF'] = $uri;
        if (strpos($uri, '/wp/') === 0) {
            return is_dir($sitePath.'/public'.$uri)
                            ? $sitePath.'/public'.$this->forceTrailingSlash($uri).'/index.php'
                            : $sitePath.'/public'.$uri;
        }

        return $sitePath.'/public/index.php';
    }

    /**
     * Redirect to uri with trailing slash.
     *
     * @return string
     */
    private function forceTrailingSlash(string $uri)
    {
        if (substr($uri, -1 * strlen('/wp/wp-admin')) == '/wp/wp-admin') {
            header('Location: '.$uri.'/');
            exit;
        }

        return $uri;
    }
}

Note this is running on PHP 8.4 so you may need to adjust your function signatures and other features if you’re running on a significantly lower version (though I didn’t really test thing on anything lower than 8).

Once done, you should be able to load your project in the web browser. If not, restarting Herd’s services should do the trick. And, on the off change you still have an error, the stack trace should be easy enough to follow to see where the problem lies.

Given a vanilla Herd set up and Radicle integration with your WordPress project, this custom driver should be all you need to get everything working with as little effort as possible.

Note: This was adapted directly from the Laravel source code which you can find on GitHub.

« Older posts

© 2025 Tom McFarlin

Theme by Anders NorenUp ↑