Jak wyciągnąć pierwszy link z treści wpisu (PHP snippet)

Jak wyciągnąć pierwszy link z treści wpisu (PHP snippet)

Ostatnio zweryfikowano: 14 października 2026
17 min czytania
Poradnik
Full-stack developer

Czasami budujemy motyw typu agregator newsów, gdzie wpis nie ma własnej długiej treści, a jedynie linkuje do zewnętrznego artykułu. Albo chcemy, żeby pierwszy obrazek w treści automatycznie stawał się miniaturą, jeśli redaktor zapomni ustawić featured image.

W obu przypadkach trzeba przeskanować treść posta (the_content) i wyłowić z niej pierwszy tag <a> albo <img>. Ten wpis pokazuje stabilne podejścia: WP_HTML_Tag_Processor z rdzenia WordPressa (od 6.2), wbudowaną funkcję get_url_in_content(), WP_HTML_Processor do zapytań z kontekstem, klasyczny DOMDocument oraz nowy Dom\HTMLDocument z PHP 8.4. Do tego typowe pułapki UTF-8 i to, jak wpiąć wynik w pętlę WordPress bez XSS.

#Do czego służy wyciąganie pierwszego linku z wpisu

Scenariusze, które wracają w projektach agencyjnych:

  • Linkout posts: tytuł i zajawka lokalnie, „czytaj dalej” prowadzi na medium zewnętrzne.
  • Import z RSS/Atom, gdzie treść przychodzi z linkami i obrazkami, a featured image jest puste.
  • Karty na listingu, które mają pokazać „pierwszy zewnętrzny URL” jako CTA.
  • Migracje ze starych motywów, gdzie redakcja wkładała obrazek tylko w treść, nigdy w meta box.

W jednym magazynie branżowym po migracji z klasycznego edytora do bloku około jednej trzeciej archiwalnych wpisów nie miała _thumbnail_id. Skrypt nocny brał pierwszy <img> z treści, pobierał plik na media library i ustawiał miniaturę. Regex padał na obrazkach z srcset i na figurach z nested markupiem; DOM przechodził.

#Dlaczego nie regex na HTML

Wielu programistów sięga po wyrażenia regularne, bo „to tylko jeden tag”. Parsowanie HTML regexem to zła praktyka z uzasadnionych powodów: atrybuty w dowolnej kolejności, cudzysłowy mieszane, HTML5 void tags, komentarze, shortcode’y WordPressa w środku. Typowy wzorzec z forów wygląda tak:

// PODEJŚCIE PROBLEMATYCZNE: naiwny regex
preg_match( '/<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>/siU', $content, $matches );
$first_url = $matches[2] ?? null;

Przechodzi test na jednym zdaniu i po cichu zawodzi na prawdziwych wpisach:

  1. Komentarze bloków i szkice. Gutenberg otacza każdy blok komentarzami HTML w rodzaju <!-- wp:paragraph -->. Jeśli redaktor zakomentował stary link, wzorzec złapie go przed czymkolwiek, co widzi czytelnik.
  2. Zawartość <script> i <style>. Blok „Własny HTML” z JavaScriptem, który składa string "<a href=...>", zawiera dla regexa całkiem poprawny tag kotwicy. Parser HTML wie, że wnętrze <script> to tekst, a nie markup.
  3. Łamanie linii i kolejność atrybutów. Atrybuty rozbite na kilka linii, apostrofy zamiast cudzysłowów i data-* przed href psują naiwne wzorce.
  4. Limit backtrackingu. Silnik PCRE w PHP poddaje się, gdy wzorzec przekroczy pcre.backtrack_limit, domyślnie 1000000 według dokumentacji PHP. Wtedy preg_match() zwraca false zamiast 0 albo 1, a o PREG_BACKTRACK_LIMIT_ERROR mówi tylko preg_last_error(). Na długich wpisach z zachłannym (.*) jak powyżej link po prostu znika, bez śladu w logu.

Regex może przejść testy na pięciu fixture’ach z edytora i wybuchnąć na pierwszym wpisie wklejonym z Google Docs. DOMDocument też nie jest idealny (libxml jest konserwatywny wobec HTML5), ale daje drzewo węzłów zamiast zgadywania pozycji nawiasów. Sam rdzeń WordPressa przeszedł tę drogę: get_url_in_content() przez ponad dekadę działała na regexie, zanim przeszła na HTML API, o czym niżej.

Od WordPressa 6.2 masz w rdzeniu WP_HTML_Tag_Processor, lżejszy skaner niż pełny DOM. Dokumentacja klasy opisuje go wprost: nie parsuje HTML w całości i nie schodzi rekurencyjnie w strukturę, tylko liniowo przechodzi przez dokument i parsuje same tagi otwierające. Do „znajdź pierwszy <a href>” to wystarczy i nie ciągnie libxml. Nadal: nie buduj na nim walidatora całego dokumentu; to narzędzie do celowanych odczytów atrybutów.

Sprawdziliśmy zachowanie na kodzie WordPressa 7.1.3: next_tag( 'a' ) pomija komentarze HTML i zawartość <script>, a get_attribute( 'href' ) zwraca wartość już zdekodowaną, więc &amp; w query stringu dociera do kodu jako &. Brak atrybutu daje null, atrybut logiczny bez wartości daje true, dlatego sprawdzenie is_string() nie jest opcjonalne.

<?php
declare(strict_types=1);

/**
 * Zwraca URL pierwszego linku http(s) we fragmencie HTML.
 */
function wppoland_pierwszy_link_tag_processor( string $content ): ?string {
    if ( '' === trim( $content ) ) {
        return null;
    }

    $processor = new WP_HTML_Tag_Processor( $content );

    // Przechodzi po tagach <a> w kolejności dokumentu
    while ( $processor->next_tag( 'a' ) ) {
        $href = $processor->get_attribute( 'href' );

        if ( ! is_string( $href ) || '' === trim( $href ) ) {
            continue;
        }

        $href     = trim( $href );
        $schemat  = strtolower( (string) wp_parse_url( $href, PHP_URL_SCHEME ) );

        // Pomija mailto:, tel:, kotwice i ścieżki względne
        if ( in_array( $schemat, array( 'http', 'https' ), true ) ) {
            return esc_url_raw( $href );
        }
    }

    return null;
}

Zalety w skrócie: brak drzewa w pamięci, odporność na komentarze, <script>, <style> i atrybuty bez cudzysłowów (procesor trzyma się reguł tokenizacji HTML, a nie dopasowuje tekstu), zero dodatkowych rozszerzeń poza samym WordPressem. DOMDocument zostaje czytelnym wyborem tam, gdzie kod działa poza WordPressem albo zespół zna go lepiej niż HTML API rdzenia.

#Jak użyć get_url_in_content w WordPressie

Zanim napiszesz własną funkcję, sprawdź tę z rdzenia: get_url_in_content( $content ), opisaną w oficjalnej dokumentacji jako funkcja, która „wyciąga i zwraca pierwszy URL z przekazanej treści” (tłumaczenie własne). Jest w WordPressie od wersji 3.6.0 i służy formatowi wpisu „link”: motyw Twenty Thirteen wywołuje ją w twentythirteen_get_link_url(), a gdy nic nie znajdzie, wraca do permalinku wpisu.

Implementacja zmieniła się w istotny sposób. Porównaliśmy kod gałęzi 6.8 i 6.9 w oficjalnym repozytorium:

  • Do WordPressa 6.8 funkcja używała preg_match( '/<a\s[^>]*?href=([\'"])(.+?)\1/is', ... ). Na tym samym testowym HTML co w całym artykule wzorzec zwrócił link z wnętrza komentarza HTML, czyli taki, którego żaden czytelnik nie widział.
  • Od WordPressa 6.9 funkcja tworzy WP_HTML_Tag_Processor, przechodzi po tagach A i zwraca pierwszy niepusty href przepuszczony przez sanitize_url(). Zakomentowany link przestaje być znajdowany.
$url = get_url_in_content( get_the_content() );

if ( $url ) {
    printf(
        '<a href="%s" rel="noopener">Czytaj u źródła</a>',
        esc_url( $url )
    );
}

Gdy nic nie znajdzie, zwraca false. Dwa ograniczenia: akceptuje każdy schemat, więc mailto: albo #komentarze na początku tekstu wygra z linkiem redakcyjnym, a na stronie z WordPressem 6.8 lub starszym dostajesz zachowanie regexa. Na 6.9 i nowszych, gdy wystarcza pierwszy href, bierz funkcję z rdzenia; gdy trzeba filtrować schematy albo wspierać starsze wersje, lepsza jest funkcja z poprzedniej sekcji.

Tag Processor nie wie, gdzie jest. Link owijający obrazek w bloku core/image i link w środku akapitu to dla niego po prostu dwa tagi a. Gdy reguła brzmi „pierwszy link wpisany w tekst, nie link obrazka”, potrzebny jest WP_HTML_Processor, dostępny od WordPressa 6.4. Śledzi stos otwartych elementów i udostępnia go przez get_breadcrumbs().

<?php
function wppoland_pierwszy_link_w_akapicie( string $content ): ?string {
    $processor = WP_HTML_Processor::create_fragment( $content );

    if ( null === $processor ) {
        return null;
    }

    while ( $processor->next_tag( 'A' ) ) {
        $sciezka = $processor->get_breadcrumbs();

        // Tylko linki wewnątrz <p> i poza <figure>
        if ( ! in_array( 'P', $sciezka, true ) || in_array( 'FIGURE', $sciezka, true ) ) {
            continue;
        }

        $href = $processor->get_attribute( 'href' );
        if ( is_string( $href ) && '' !== trim( $href ) ) {
            return esc_url_raw( trim( $href ) );
        }
    }

    return null;
}

Ścieżkę sprawdzamy sami zamiast zapytania next_tag( array( 'breadcrumbs' => array( 'P', 'A' ) ) ). To zapytanie wymaga, żeby A był bezpośrednim dzieckiem P; w naszym teście link w <p><strong><a> został przez nie pominięty, a sprawdzenie przez in_array() go znalazło.

Dokumentacja klasy jasno opisuje jedną zasadę: gdy procesor trafi na markup, którego jeszcze nie obsługuje, przerywa zamiast zgadywać. Wtedy next_tag() zwraca false, a get_last_error() przestaje być null. Jeśli dla wpisu z widocznymi linkami dostajesz null, sprawdź get_last_error() i wróć do Tag Processora.

Oto funkcja, którą możesz trzymać w małej wtyczce narzędziowej albo w inc/ motywu dziecka:

function get_first_link_url( $content ) {
    if ( empty( $content ) ) {
        return false;
    }

    $doc = new DOMDocument();
    $poprzednie = libxml_use_internal_errors( true );

    // Nagłówek XML wymusza UTF-8 w parserze libxml2
    $doc->loadHTML( '<?xml encoding="utf-8" ?>' . $content );

    libxml_clear_errors();
    libxml_use_internal_errors( $poprzednie );

    $links = $doc->getElementsByTagName( 'a' );

    if ( $links->length > 0 ) {
        $href = $links->item( 0 )->getAttribute( 'href' );
        return $href ? $href : false;
    }

    return false;
}

libxml_use_internal_errors(true) wycisza ostrzeżenia przy <section>, <figure> i dziurawym markupie z pasty. libxml_clear_errors() sprząta kolejkę, żeby ostrzeżenia nie rosły w długich cronach.

Starsza wersja tego wpisu używała mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' ). PHP 8.2 oznaczył kodowanie HTML-ENTITIES jako przestarzałe we wszystkich funkcjach mbstring (lista zmian PHP 8.2), a PHP 8.4 wypisuje przy tym wywołaniu komunikat Deprecated. Nagłówek <?xml encoding="utf-8" ?> albo mb_encode_numericentity() robią to samo bez ostrzeżeń. Ważne, żeby polskie znaki w anchor text nie psuły drzewa - to częstszy bug niż „brak linku”.

#Jak użyć pierwszego linku w pętli WordPress

$raw     = get_the_content();
$content = apply_filters( 'the_content', $raw );
$link    = get_first_link_url( $content );

if ( $link ) {
    printf(
        '<a href="%s" class="read-more-external" rel="noopener noreferrer">Czytaj oryginał</a>',
        esc_url( $link )
    );
}

Dwa detale bezpieczeństwa:

  1. Zawsze esc_url() na wyjściu. Treść bywa z importu; javascript: w href to realny wektor.
  2. Świadomie zdecyduj, czy parsujesz surowy get_the_content(), czy po filtrach. Shortcode’y i bloki czasem wstawiają linki dopiero po the_content.

Jeśli linkujesz rel=“nofollow” do partnerów, dopisz atrybut w printf, zamiast polegać na tym, że redaktor go wklei.

#Jak użyć Dom\HTMLDocument w PHP 8.4

PHP 8.4 dodał nowe API DOM w przestrzeni nazw Dom, ze „zgodną ze standardami obsługą parsowania dokumentów HTML5”, jak piszą informacje o wydaniu (tłumaczenie własne). Dom\HTMLDocument parsuje HTML zgodnie z living standard, więc znikają ostrzeżenia o tagach HTML5 i sztuczka z nagłówkiem XML. DOMDocument zostaje dla zgodności wstecznej.

<?php
function wppoland_pierwszy_link_dom84( string $html ): ?string {
    if ( '' === trim( $html ) ) {
        return null;
    }

    $doc = Dom\HTMLDocument::createFromString( $html, LIBXML_NOERROR, 'UTF-8' );

    foreach ( $doc->querySelectorAll( 'a[href]' ) as $link ) {
        $href    = trim( $link->getAttribute( 'href' ) );
        $schemat = strtolower( (string) parse_url( $href, PHP_URL_SCHEME ) );

        if ( in_array( $schemat, array( 'http', 'https' ), true ) ) {
            return $href;
        }
    }

    return null;
}

Trzy szczegóły ze strony podręcznika createFromString(): drugi argument przyjmuje stałe LIBXML_* łączone przez OR; trzeci, overrideEncoding, ustala kodowanie zamiast zostawiać zgadywanie PHP; nieznane kodowanie rzuca ValueError. Na PHP 8.4.24 funkcja zwróciła https://exemplo.pt/ação?x=1&y=2 bez zmian, z &amp; zdekodowanym. Selektory CSS pozwalają też na zapytania typu p > a[href^="https://"], które w DOMDocument wymagają XPath.

#Którą metodę wybrać do pierwszego linku

Nie publikujemy czasów wykonania: zależą od serwera, wersji PHP i długości wpisu, a sens mają tylko pomiary z własnego projektu. Różnice konstrukcyjne są za to stałe:

KryteriumRegex (preg_match)DOMDocument (libxml2)Dom\HTMLDocumentWP_HTML_Tag_Processor
Model parsowaniaWzorzec tekstowyDrzewo DOM, reguły sprzed HTML5Drzewo DOM, HTML5Liniowy odczyt tagów otwierających
PamięćNiskaCałe drzewoCałe drzewoBez drzewa
Komentarze i <script>ZawodnePoprawniePoprawniePoprawnie
Kontekst (rodzic linku)NieTakTak, selektory CSSNie (użyj WP_HTML_Processor)
Typowa awariafalse po limicie backtrackinguOstrzeżenia i UTF-8ValueError przy złym kodowaniuNie zna kontekstu linku
Minimalne wymaganieDowolne PHPRozszerzenie DOMPHP 8.4WordPress 6.2+

Na aktualnym WordPressie Tag Processor jest najprostszym pewnym wyborem. Dom\HTMLDocument wygrywa, gdy kod działa poza WordPressem albo potrzebuje selektorów.

#Jak ustawić pierwszy obrazek jako miniaturę

Analogiczna funkcja na <img>:

function get_first_image_src( $content ) {
    if ( empty( $content ) ) {
        return false;
    }

    $doc = new DOMDocument();
    libxml_use_internal_errors( true );
    $doc->loadHTML( '<?xml encoding="utf-8" ?>' . $content );
    libxml_clear_errors();

    $images = $doc->getElementsByTagName( 'img' );

    if ( $images->length > 0 ) {
        $src = $images->item( 0 )->getAttribute( 'src' );
        return $src ? $src : false;
    }

    return false;
}

Żeby ustawić featured image, potrzebujesz jeszcze sideload do media library (media_sideload_image albo download_url + media_handle_sideload) i set_post_thumbnail. Rób to w WP-CLI / cronie, nie synchronicznie przy każdym page view - inaczej pierwszy visit po imporcie zamieni się w timeout.

Pomijaj trackery i 1x1 pixele: filtruj po rozszerzeniu, minimalnych wymiarach z atrybutów width/height albo po domenie (odrzucaj znane hosty analytics).

#Najczęstsze błędy przy parsowaniu pierwszego linku w WordPress

Linki wewnętrzne kotwic. Pierwszy <a href="#section"> to nie „linkout”. Jeśli budujesz agregator, filtruj href zaczynające się od # oraz puste href. To samo dotyczy mailto: i tel:; funkcje na Tag Processorze i Dom\HTMLDocument z tego wpisu przepuszczają tylko http i https.

Linki relative. href="/oferta/" na obcym originie po imporcie może wskazywać złą domenę. Jeśli wiesz, że treść jest lokalna, zamień ścieżkę na adres absolutny przez WP_Http::make_absolute_url( $href, home_url( '/' ) ) (w rdzeniu od WordPressa 3.4.0). Nie myl tego z wp_make_link_relative(), która robi odwrotnie: obcina domenę.

Blok Classic vs Gutenberg. W bloku treść w bazie to komentarze <!-- wp:... -->. get_the_content() zwraca markup z komentarzami; po the_content dostajesz HTML. Parsuj świadomie ten etap, którego potrzebujesz.

Wydajność. DOMDocument na liście 50 wpisów w archiwum jest kosztowny. Cache’uj wynik w post meta (_first_external_url) przy save_post, a w frontcie czytaj meta. Przeliczaj ponownie tylko gdy treść się zmienia.

Multisite i escaping. Na sieci z UGC upewnij się, że nie renderujesz surowego HTML z pierwszego noda - bierzesz atrybut, nie nodeValue z zagnieżdżonym markupem.

#Jakie URL-e odrzucają wp_http_validate_url i FILTER_VALIDATE_URL

Obie nazwy sugerują „czy to poprawny URL”. Żadna nie odpowiada na to pytanie dla linków redakcyjnych.

wp_http_validate_url() chroni zapytania HTTP wysyłane przez sam serwer. Z dokumentacji funkcji i jej kodu wynika, że:

  • akceptuje tylko http i https i odrzuca adresy z loginem i hasłem;
  • odrzuca adresy względne, bo wymaga hosta;
  • dla każdego hosta innego niż własna strona wywołuje gethostbyname(), czyli robi zapytanie DNS, a nazwa, która w tej chwili się nie rozwiązuje, zostaje odrzucona;
  • odrzuca adresy z sieci prywatnych, chyba że pozwoli na nie filtr http_request_host_is_external;
  • domyślnie dopuszcza tylko porty 80, 443 i 8080 (filtr http_allowed_safe_ports).

W hooku save_post oznacza to jedno zapytanie DNS na każdy zapis i ciche gubienie poprawnych linków do domen chwilowo niedostępnych albo do serwerów demo na niestandardowym porcie. Używaj jej tylko wtedy, gdy kod faktycznie ma pobrać ten URL.

Filtr FILTER_VALIDATE_URL w PHP ma inny problem, opisany w podręczniku: filtr „działa tylko z adresami ASCII”, więc domeny z polskimi znakami są zawsze odrzucane (tłumaczenie własne). W naszym teście na PHP 8.4 adres https://exemplo.pt/ação został odrzucony, a FILTER_SANITIZE_URL zamienił go na https://exemplo.pt/ao, po cichu kasując litery z akcentami. Dla adresów w rodzaju /oferta-dla-szkół/ to realny błąd. Schemat sprawdzaj przez wp_parse_url(), wartość czyść esc_url_raw() przed zapisem i esc_url() przed wypisaniem.

#Jakie testy warto mieć dla parsera pierwszego linku

Zanim wkleisz snippet na produkcję, trzymaj fixture’y:

  • treść z jednym linkiem na początku,
  • treść z obrazkiem przed linkiem (gdy chcesz link, nie img),
  • polskie znaki w anchor text,
  • href z query string i &amp;,
  • pusty content i content bez <a>,
  • link javascript:alert(1) (musi zostać złapany przez esc_url jako pusty/odrzut),
  • zakomentowany link <!-- <a href="..."> --> i blok <script> ze stringiem <a href, oba do zignorowania.

To pięć minut pracy, które oszczędzają godzinę po imporcie 2000 wpisów.

#Jak cache’ować pierwszy link w post meta

Parsowanie DOM przy każdym renderze archiwum to zbędny koszt. Przy save_post wylicz pierwszy zewnętrzny URL i zapisz go pod kluczem w stylu _wppoland_first_external_url. Na froncie czytaj meta. Jeśli meta pusta, możesz policzyć raz i uzupełnić (lazy backfill), ale nie rób tego synchronicznie dla całej strony głównej naraz.

add_action( 'save_post', function ( int $post_id, WP_Post $post ): void {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( wp_is_post_revision( $post_id ) || 'post' !== $post->post_type ) {
        return;
    }

    $url = wppoland_pierwszy_link_tag_processor( $post->post_content );

    if ( $url ) {
        update_post_meta( $post_id, '_wppoland_first_external_url', $url );
    } else {
        delete_post_meta( $post_id, '_wppoland_first_external_url' );
    }
}, 10, 2 );

WP_Query domyślnie ładuje meta wpisów z pętli do cache obiektów, więc odczyt w archiwum nie dokłada zapytania na każdą kartę. Jeśli dopiszesz do hooka current_user_can(), pamiętaj, że zapisy bez zalogowanego użytkownika, jak wp post update w WP-CLI bez --user albo import z crona, przestaną aktualizować meta.

Invalidacja: czyść meta, gdy zmienia się treść, gdy import nadpisuje post, albo gdy redaktor ręcznie ustawia featured link w osobnym polu. Prefiks podkreślnika chowa klucz przed UI custom fields - świadomy wybór, jeśli nie chcesz, żeby ktoś edytował go ręcznie bez walidacji.

#Jak uzupełnić post meta starych wpisów przez WP-CLI

Hook save_post obejmuje tylko wpisy zapisane po wdrożeniu kodu. Dla archiwum wystarczą dwie udokumentowane komendy WP-CLI. wp post list --field=ID wypisuje jeden ID na linię i według dokumentacji komendy domyślnie zwraca wszystkie pasujące wpisy (--posts_per_page ma wartość -1). wp eval-file uruchamia plik PHP z załadowanym WordPressem i wkłada argumenty pozycyjne do zmiennej $args (dokumentacja).

<?php
// uzupelnij-pierwszy-link.php
foreach ( $args as $post_id ) {
    $post = get_post( (int) $post_id );
    if ( ! $post ) {
        continue;
    }

    $url = wppoland_pierwszy_link_tag_processor( $post->post_content );

    if ( $url ) {
        update_post_meta( $post->ID, '_wppoland_first_external_url', $url );
    } else {
        delete_post_meta( $post->ID, '_wppoland_first_external_url' );
        WP_CLI::log( "Brak linku: {$post->ID}" );
    }
}
wp post list --post_type=post --post_status=publish --field=ID \
  | xargs -n 100 wp eval-file uzupelnij-pierwszy-link.php

xargs -n 100 przekazuje ID paczkami po 100, a każda paczka startuje nowy proces PHP, więc pamięć nie rośnie razem z archiwum. Puść to najpierw na stagingu. Przy WP-CLI migracjach batchem loguj posty, dla których parser nic nie znalazł (robi to WP_CLI::log() powyżej). To zwykle drafty, cytaty bez linków albo treści zbudowane wyłącznie z bloków bez kotwic HTML.

#Dlaczego linki w blokach Gutenberg umykają parserowi

Gutenberg potrafi trzymać URL w atrybutach bloku (JSON w komentarzu), a nie w klasycznym <a href>. Tak działa na przykład blok dynamiczny core/navigation-link: jego url siedzi w komentarzu bloku, a HTML powstaje na serwerze. Jeśli po get_the_content() bez filtrów „nie ma linku”, a w edytorze go widać, prawdopodobnie patrzysz na surowy markup bloku. Odpal the_content albo renderuj blok przez do_blocks() przed parsowaniem.

Shortcode’y galerii i embedów YouTube też dokładają kotwice dopiero po rozwinięciu. Decyzja produktowa: czy „pierwszy link” ma być tym, co redaktor wpisał w tekście, czy tym, co ostatecznie trafia do HTML na froncie. Zapisz tę decyzję w komentarzu funkcji - za rok ktoś podziękuje. Pamiętaj, że pełny filtr the_content uruchamia też to, co dokładają wtyczki (przyciski udostępniania, powiązane wpisy), więc może wstawić link przed Twoim.

Blok przycisku (core/button) jest statyczny: według jego block.json atrybut url jest czytany z href tagu a w zapisanym HTML (źródło attribute, selektor a), więc parser znajdzie go już w surowym post_content. Atrybuty każdego bloku sprawdzisz w dokumentacji bloków rdzenia. Gorzej z blokami wtyczek trzecich, które trzymają link tylko w atrybutach i renderują go JS-em na froncie - wtedy parser PHP nic nie znajdzie, bo w HTML odpowiedzi nie ma jeszcze <a>.

Jeśli budujesz feed „linkout” dla newslettera, rozważ osobne pole meta na URL docelowy zamiast zgadywania z treści. Parser zostaje jako fallback dla archiwum, a nowe wpisy mają jawne źródło prawdy.

#Podsumowanie

Wyciąganie pierwszego URL z treści to mały snippet, który zostaje w projekcie na lata. Lepiej oprzeć go na Tag Processorze (albo get_url_in_content() od WordPressa 6.9, DOMDocument czy Dom\HTMLDocument poza WordPressem) niż na regexie, schemat sprawdzać przez wp_parse_url(), cache’ować wynik w meta i zawsze escapować wyjście. Trzymaj fixture’y w repozytorium obok funkcji - migracje treści i importy RSS najszybciej pokazują, gdzie parser się myli.

Parsowanie treści wpisu to typowy przykład kodu, który nikt nie pamięta po pół roku. Gdy takich fragmentów zbiera się więcej, porządkujemy je przy przeglądzie motywu. Warto wtedy spisać, które meta klucze są źródłem prawdy, a które tylko cache’em wyliczonym z HTML. Przy code review pytaj wprost: czy wynik jest cache’owany przy zapisie, czy parser odpala się na każdym page view archiwum. Ta jedna decyzja zwykle różni motyw „działa na stagingu” od motywu, który dusi CPU na produkcji przy paginacji. Cache meta przy save_post jest tu domyślnym wyborem produkcyjnym.

Potrzebujesz pomocy przy imporcie archiwum albo automatycznym ustawianiu miniatur? Napisz przez formularz kontaktowy.

Następny krok

Przekuj artykuł w realne wdrożenie

Pod tym wpisem dokładam linki, które domykają intencję użytkownika i prowadzą dalej w strukturze serwisu.

Chcesz wdrożyć ten temat na swojej stronie?

Jeśli chcesz przełożyć wiedzę z artykułu na działającą stronę, sklep albo przebudowę serwisu, przygotuję konkretny zakres prac.

Powiązany klaster

Sprawdź inne usługi WordPress i bazę wiedzy

Wzmocnij swój biznes dzięki profesjonalnemu wsparciu technicznemu w kluczowych obszarach ekosystemu WordPress.

FAQ do artykułu

Często zadawane pytania

Najważniejsze odpowiedzi, które pomagają wdrożyć temat w praktyce.

SEO-readyGEO-readyAEO-ready5 Q&A
Czy mogę użyć regex zamiast DOMDocument?#
Możesz, ale HTML z atrybutami w różnej kolejności, shortcode'ami i uszkodzonym markupem szybko psuje wzorzec. DOMDocument buduje drzewo węzłów, więc jest wyraźnie mniej kruchy.
Jak wyciągnąć pierwszy obrazek zamiast linku?#
Zamień getElementsByTagName('a') na getElementsByTagName('img') i czytaj atrybut src (albo srcset, jeśli budujesz fallback miniatury).
Czy get_the_content() odpala filtry the_content?#
Samo get_the_content() nie aplikuje filtrów. Jeśli potrzebujesz HTML po shortcode'ach i embedach, użyj apply_filters( 'the_content', ... ) albo osobno do_shortcode.
Czy get_url_in_content() zwraca linki mailto albo kotwice?#
Zwraca pierwszy niepusty href, niezależnie od schematu. Link mailto: albo kotwica #komentarze na początku tekstu wygrywa z linkiem redakcyjnym, więc jeśli potrzebujesz tylko http i https, napisz własną funkcję.
Czy do Dom\HTMLDocument potrzebuję WordPressa?#
Nie. Dom\HTMLDocument należy do rozszerzenia DOM w PHP 8.4 i działa w każdym skrypcie PHP, na przykład w importerze uruchamianym poza WordPressem.

Potrzebujesz FAQ dopasowanego do branży i rynku? Przygotujemy wersję pod Twoje cele biznesowe.

Porozmawiajmy

Polecane artykuły