Anatomia wtyczki, czyli jak niechcący zostałem programistą PHP

https://xpil.eu/6UjYm

Za młodu uczyłem się komputerów na QBasic-u i Logo. Potem przyszły studia i Modula-2, Pascal (obydwa: Turbo i Borland), C i C++, a także podstawy asemblera x86. W pierwszej pracy używałem VBA, bo się akurat był pojawił w Office 97 (pamięta ktoś?), potem zmieniałem pracę nieprzyzwoicie dużo razy, ucząc się po drodze SQL, VB.NET, podstaw Bash-a, PowerShell-a, wreszcie Python.

Wyżej wymienione technologie (plus parę innych, satelitarnych) w większości lubię, choć niektóre całkiem już umarły, inne trącą myszką. Jednak jakoś nigdy nie miałem okazji nauczyć się PHP.

piszę w dotnecie
już trzecie stulecie
bo kto się w pehapie
połapie?

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

Jak pokazują niedawne wydarzenia na niwie tak zwanej (na wyrost) "sztucznej inteligencji", próg wejścia w nowy język programowania stał się ostatnio nieprzyzwoicie niski. LLM typu ChatGPT, Claude czy Gemini potrafią dziś pisać kod całkiem sprawnie. Zwłaszcza pchełki, czyli klamoty na kilkadziesiąt, kilkaset linii. Na większych projektach, gdzie trzeba żonglować wieloma kontekstami jeszcze sobie tak do końca nie radzą (acz to jest dzisiaj, kto wie co będzie jutro?).

Mając świadomość powyższego, postanowiłem przeprowadzić mały eksperyment i napisać wtyczkę do WordPress.

Dlaczego?

Mam na blogu krótkie linki, postaci "https://xpil.eu/xyzYZ", które przekierowują na konkretne artykuły. Pomysł ze skracaniem linków nie jest specjalnie nowy. WordPress oferuje nawet tę funkcję w ramach wtyczki JetPack, której jednak unikam bo jest przeładowana zbędnymi zbędnościami, poza tym tamte linki są w innej domenie, a wiadomo, jak się właściciel domeny rozmyśli to linki poznikają. Albo każą za nie płacić.

Jest też w katalogu WordPressa kilka wtyczek do skracania URL-i, ale większość z nich albo nie działa, albo działa koślawo, albo w najlepszym przypadku działa dobrze, ale oprócz skracania URL-i oferuje jeszcze golenie, pierdolenie, krawatów wiązanie i ciąż usuwanie, a ja chcę po prostu skracacz URL-i - i nic poza tym! Chcę mieć dla każdego wpisu krótkiego linka, którego kliknięcie otworzy ten wpis. Nie chcę: zliczać kliknięć, optymalizować SEO, sprzedawać maści na porost ego, lecieć tymi linkami na Marsa (ani nawet na Księżyc).

Od wielu lat używałem jednej z takich wtyczek, ale ostatnimi czasy jej autorzy postanowili się "unowocześnić" i wydali nową wersję, która mi się kompletnie nie podoba; trzeba było migrować (z bólami), pojawiła się wersja premium, a w dodatku ma błędy, o których naprawienie nie mogę się doprosić, bo nie płacę premium, więc jestem kosztem a nie klientem. W efekcie część krótkich linków po prostu nie działa. No i nie wiadomo czy w pewnym momencie nie zakręcą kurka i nie powiedzą, że albo płać, albo spadówa. Na razie póki co nie powiedzieli, ale gradient jest niepokojący.

Myślę sobie, kurdę, przecież skracanie linków to nie powinna być jakaś kosmicznie trudna sprawa. A co gdyby napisać sobie własną wtyczkę? Co prawda na pisaniu wtyczek do WP znam się jak ośmiornica na stokrotkach, ale mam pod ręką LLM, wespół - zespół jakoś to ogarniemy.

I co?

I ogarnęliśmy!

Zacząłem od tego, że skopiowałem sobie całego bloga na lokalny komputer, co zajęło mi jakieś trzy dni, bo raz, musiałem sobie zrobić skrypt ściągający wszystko z netu (niby nic, ale diabeł tkwi w szczegółach), a dwa, trzeba było skonfigurować wszystko na lokalnym WSL, a tu znowu diabeł tkwi w szczegółach. Jakieś certyfikaty, moduły, fajerłole i inne, curwa, kyrki. No ale w końcu zabanglało i lokalna kopia blogu zaczęła wyglądać toczka w toczkę jak ta publiczna. Zabrałem się więc za robienie wtyczki.

To znaczy, za dialog z LLM, bo sam to se mogę...

Poszło nad wyraz szybko. W jedno popołudnie miałem wszystko gotowe.

Oto podaję więc przepis na wtyczkę do skracania URL-i - jak ktoś chce i/lub potrzebuje, może sobie machnąć rachu-ciachu i jest.

Zaczynamy od wykonania kopii zapasowej, bo wiadomo, Murphy nie śpi. Przepisu na kopię zapasową nie podam, każdy już sobie musi we własnym zakresie.

Potem otwieramy terminal i logujemy się po ssh do naszego blogu. Wchodzimy do folderu z wtyczkami i tworzymy nowy folder. Nazwę wymyślamy sami - będzie to nazwa naszej wtyczki. W moim przypadku jest to xeu-su ("xeu" od "xpil.eu" i "su" od "short url"):

Upewniamy się, że nowo utworzony folder jest własnością Apacza (a więc w razie potrzeby robimy chown www-data:www-data ./nazwa-wtyczki). Wchodzimy do środka i tworzymy tam plik php o nazwie takiej samej jak nazwa folderu:

Otwieramy plik do edycji naszym ulubionym edytorem (ja używam nano) i wstawiamy tam:

<?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');
?>

Nie chce mi się teraz omawiać poszczególnych funkcji. Może, kiedyś. Zapisujemy plik i wracamy do powłoki. Zanim aktywujemy wtyczkę, trzeba jeszcze stworzyć tabelę w bazie WordPress, która będzie przechowywać nasze krótkie URL-e. Teoretycznie powinna to załatwić sama wtyczka, ale ja już byłem zbyt leniwy, żeby to zrobić, więc utworzyłem tę tabelę ręcznie: trzeba uruchomić klienta mysql i wykonać jednorazowo:

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

Upewniwszy się, że nie ma komunikatów błędu, wychodzimy z klienta mysql i aktywujemy wtyczkę w interfejsie naszego blogu.

Od tej pory nasze wpisy będą opatrzone unikalnymi krótkimi URL-ami.

W moim przypadku musiałem jeszcze przemigrować istniejące krótkie URL-e (bo przecież powysyłałem kiedyś tam w różne miejsca Internetu krótkie linki i chciałbym, żeby one nadal działały). A więc osobny plik migrate.php z następującym kodem:

<?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')
        );
    }
}
?>

Uruchomiłem, odczekałem chwilę... i gotowe!

Et voila!

https://xpil.eu/6UjYm

13 komentarzy

    1. Nie jest to głupie podejście … ale @xpil zrobił sobie furtkę, gdyby kiedyś chciał pokazac srodkowy palec WP i przejsc na własny CMS :… to ma juz gotową bazę 😉

      1. Ale w tym aspekcie, czy w ogóle funkcja skracania linków jest potrzebna w CMSie (na potrzeby posiadania krótkich linków)?? Czy w tym przypadku już nie lepiej mieć “system” do skracania nie tylko linków z CMS ale, też tych z internetu np. https://go.xpil.eu/6UjYm

        A tylko dorobić sobie funkcję, by “CMS” przy okazji publikacji wpisów automatycznie dodawał sobie rekord do tego drugiego rozwiązania?

        1. Skracanie linków ogólnie (tzn. do całego netu, nie tylko do wpisów lokalnych) – niby fajna sprawa, ale do tego już są pierdyliony usług typu bitly czy tiny, a więc szkoda wysiłku / czasu na implementowanie tego samego u siebie. Poza tym dochodzi też kłopot z weryfikacją linków (czy dalej żyją itd) – niby są do tego osobne wtyczki, ale to już się zaczyna robić strzelanie z armaty do much.

      2. > gdyby kiedyś chciał pokazac srodkowy palec WP i przejsc na własny CMS :… to ma juz gotową bazę

        Prawda. W sumie to nawet o tym nie pomyślałem, ale zgadza się. Co prawda z linkami typu “?p=xyz” też bym jakoś ogarnął, ale byłoby nieco trudniej.

    2. Wersja z ID posta nie działa równocześnie z wersją z ładnym, skorelowanym z tytułem URLem, prawda? Tyle, że z kolei ja wolę używać wszędzie tej ładnej, długiej wersji. Ale to też ma wady, pisałem ostatnio o porządkach na blogu, gdzie jednak musiałem poprawiać linki, bo niektóre rozjechały się przy migracji między systemami blogowymi.

      1. Właśnie działa, wewnętrzny redireck przenosi posta z ?p=ID, na posta z pełnym adresem URL. Tak więc powinno wystarczyć, pobawić się regułami w .htaccess by ruch z /ID/ kierował na ?p=ID i powinno działać.

        Jedyny problem jaki to konfiguracja wordpress z url jak tutaj – bez żadnych dodatkowych pól pośrednich jak rok posta itp.

        1. A faktycznie. Zmyliło mnie to, że pamiętam o ustawieniu w WP jak ma wyglądać URL, plus ID są mniej po kolei, niż się spodziewałem.

          W czym przeszkadza brak pól pośrednich? I czemu /ID/ w URL, a nie ?p=ID, po prostu? W razie czego to też powinno się dać ogarnąć prostym rewrite.

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.