Anatomy of a Plugin, or How I Accidentally Became a PHP Programmer

https://xpil.eu/1IWPr

In my youth, I learned about computers using QBasic and Logo. Then came university studies with Modula-2, Pascal (both Turbo and Borland), C and C++, as well as the basics of x86 assembly. In my first job, I used VBA because it had just appeared in Office 97 (does anyone remember?), then I changed jobs an indecent number of times, learning SQL, VB.NET, basics of Bash, PowerShell, and finally Python along the way.

The above-mentioned technologies (plus a few satellite ones) I mostly like, although some have completely died, and others are a bit outdated. However, I never had the opportunity to learn PHP.

In .NET I write,
Through centuries' flight,
For who can get,
PHP's net tight?

(original: https://www.martinlechowicz.com/piosenka-o-smutnym-programiscie,859)

As recent events in the field of so-called (somewhat exaggeratedly) "artificial intelligence" show, the entry threshold into a new programming language has recently become indecently low. LLMs like ChatGPT, Claude, or Gemini are now quite proficient at writing code. Especially for small tasks or scripts, like 10s or 100s LOC. For larger projects, where juggling multiple contexts is necessary, they're not quite there yet (though that's the situation today, who knows what tomorrow will bring?).

Aware of the above, I decided to conduct a small experiment and write a plugin for WordPress.

Why?

I have short links on my blog, like "https://xpil.eu/xyzYZ", which redirect to specific articles. The idea of shortening links isn't particularly new. WordPress even offers this functionality through the overbloated JetPack plugin. Moreover, those links are on a different domain, and you know how it goes: if the domain owner changes their mind, the links could disappear. Or they might start charging for them.

There are also several URL shortening plugins in the WordPress directory, but most of them either don't work, work poorly, or in the best case, work well but apart from URL shortening, they also offer everything including the kitchen sink - I just want a straightforward URL shortener and nothing else! I want to have a short link for each post, which, when clicked, will open that post, end of story. I don’t want to: count clicks, optimize SEO, sell ego growth ointment, or fly with these links to Mars (or even the Moon).

For many years, I used one such plugin, but recently its authors decided to "modernize" and released a new version, which I completely disliked; I had to migrate data from the old plugin (which was painful), a premium version appeared, and it also has bugs that I can't get fixed because I don't pay for the premium, so I'm seen as a cost rather than a customer. As a result, some of the short links simply don't work: the target URL generated by the plugin is nonsense (it ignores the permalinks settings and randomly switches between modes). And at some point they may just turn off the tap and say either pay up or leave. They haven't done anything like it yet, but the general trend is worrisome.

So I started to ponder, heck, shortening links shouldn't be rocket science. What if I write my own plugin? True, I know about writing WP plugins about as much as an octopus knows about daisies, but I have an LLM at hand, and together as a team, we should be able to handle it.

And what?

And we did it!

I started by copying my entire blog onto my local computer, which took about three days because, firstly, I had to create a script to download everything from the internet (seems simple, but the devil is in the details), and secondly, I had to configure everything on the local WSL, where again, the devil is in the details. Things like certificates, modules, firewalls, and whatnot. But it finally worked, and the local copy of the blog looked exactly like the public one. So I got down to making the plugin.

That is, to having a dialogue with the LLM, because doing it on my own was out of the question...

It went surprisingly fast. In one afternoon, I had everything ready.

So here's the recipe for a URL-shortening plugin - if someone wants and/or needs it, they can whip it up quickly and easily.

We start by taking a backup, because as we know, Murphy's Law is always in effect. I won't provide a recipe for making a backup; everyone has to manage that on their own. Just remember: web root files are as important as the database. Backup both.

Then, we open the terminal and log in via SSH to our blog. We go to the plugins folder and create a new folder. We come up with the name ourselves - it will be the name of our plugin. In my case, it's xeu-su ("xeu" from "xpil.eu" and "su" for "short url"):

We make sure that the newly created folder is owned by Apache (so, if necessary, we do a chown www-data:www-data ./plugin-name). We enter the folder and create a PHP file with the same name as the folder:

We open the file for editing with our favorite editor (I use nano) and insert there:

<?php
/*
Plugin Name: xeu-su
Description: Simple URL shortener for my posts.
Version: 1.0
Author: xpil
*/

function generate_short_url($length = 5) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

function save_short_url($post_id) {
    global $wpdb;

    if (wp_is_post_revision($post_id)) {
        return;
    }

    $existing_url = $wpdb->get_var($wpdb->prepare("SELECT short_url FROM wp_short_urls WHERE post_id = %d", $post_id));
    if ($existing_url) {
        return $existing_url;
    }

    $short_url = generate_short_url();
    while ($wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM wp_short_urls WHERE short_url = %s", $short_url))) {
        $short_url = generate_short_url();
    }

    $wpdb->insert(
        'wp_short_urls',
        array('post_id' => $post_id, 'short_url' => $short_url),
        array('%d', '%s')
    );

    return $short_url;
}
add_action('save_post', 'save_short_url');

function append_short_url_to_content($content) {
    if (is_single()) {
        global $post, $wpdb;
        $short_url = $wpdb->get_var("SELECT short_url FROM wp_short_urls WHERE post_id = $post->ID");
        if ($short_url) {
            $full_short_url = home_url('/') . $short_url; // Constructs the full URL
            $full_short_url_html = "<p><a href='$full_short_url'>$full_short_url</a></p>";
            $content = $full_short_url_html . $content . $full_short_url_html;
        }
    }
    return $content;
}
add_filter('the_content', 'append_short_url_to_content');

function xeu_su_redirect() {
    global $wpdb;

    $requested_path = trim(parse_url(add_query_arg(array()), PHP_URL_PATH), '/');

    $post_id = $wpdb->get_var($wpdb->prepare(
        "SELECT post_id FROM wp_short_urls WHERE short_url = %s",
        $requested_path
    ));

    if ($post_id) {
        $permalink = get_permalink($post_id);
        if ($permalink) {
            wp_redirect($permalink, 301); // or 302/307 as per your requirement
            exit;
        }
    }
}
add_action('template_redirect', 'xeu_su_redirect');
?>

I don't feel like discussing the individual functions right now. Maybe some other time. We save the file and go back to the shell. Before activating the plugin, we need to create a table in the WordPress database that will store our short URLs. Theoretically, the plugin should handle this, but I was too lazy to implement it, so I created the table manually: you need to launch the MySQL client and execute the following command once:

CREATE TABLE `wp_short_urls` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `post_id` bigint(20) unsigned NOT NULL,
  `short_url` varchar(10) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `short_url` (`short_url`),
  KEY `post_id` (`post_id`),
  CONSTRAINT `wp_short_urls_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `wp_posts` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=41436 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci

After ensuring there are no error messages, we exit the MySQL client and activate the plugin in our blog's interface.

From now on, our posts will be accompanied by unique short URLs.

In my case, I also had to migrate existing short URLs (since I had sent out short links to various places on the Internet and wanted them to still work). So, a separate migrate.php file with the following code:

<?php
require_once('/var/www/xpil.eu/wp-load.php');
global $wpdb;
$shortify_links = $wpdb->get_results("SELECT * FROM wp_kc_us_links");
foreach ($shortify_links as $link) {
    $post_id = url_to_postid($link->url);
    if ($post_id != 0 && !$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM wp_short_urls WHERE post_id = %d", $post_id))) {
        $wpdb->insert(
            'wp_short_urls',
            array(
                'post_id' => $post_id,
                'short_url' => $link->slug
            ),
            array('%d', '%s')
        );
    }
}
?>

I ran it, waited a moment... and it was done!

Et voila!

https://xpil.eu/1IWPr

Leave a Comment

Komentarze mile widziane.

Jeżeli chcesz do komentarza wstawić kod, użyj składni:
[code]
tutaj wstaw swój kod
[/code]

Jeżeli zrobisz literówkę lub zmienisz zdanie, możesz edytować komentarz po jego zatwierdzeniu.