Kawałek Kodu

Wstęp.

Dziś odcinek w stylu Indiany Jones'a. Pobawimy się trochę linami... tzn. linkami. Szykuj się na przemierzanie gęstej dżungli węzłów!

Scena pierwsza: "Linki zewnętrzne".

//a[substring(@href,1,7)="http://" or substring(@href,1,8)="https://" or substring(@href,1,6)="ftp://"]

Ten przykład w razie potrzeby należy uzupełnić o inne protokoły.

Scena druga: "Zamiana IMG w A dla potrzeb lightbox'a".

Wykorzystamy pierwszą lepszą bibliotekę do tworzenia lightbox'a, czyli otwierania zdjęć w powiększeniu po kliknięciu na miniaturę. Jest to Lightbox by Lokesh Dhakar. Link znajdziesz na końcu wpisu.

Wymagania co do struktury HTML są następujące:

<a href="/img/duzy1.jpg" data-lightbox="gallery1"><img src="/img/maly1.jpg" alt="Obrazek 1"></a>
<a href="/img/duzy2.jpg" data-lightbox="gallery1"><img src="/img/maly2.jpg" alt="Obrazek 2"></a>

<a href="/img/duzy3.jpg" data-lightbox="gallery2"><img src="/img/maly3.jpg" alt="Obrazek 3"></a>
<a href="/img/duzy4.jpg" data-lightbox="gallery2"><img src="/img/maly4.jpg" alt="Obrazek 4"></a>
<a href="/img/duzy5.jpg" data-lightbox="gallery2"><img src="/img/maly5.jpg" alt="Obrazek 5"></a>

Każdy IMG jest owinięty w A, który posiada odnośnik do dużego zdjęcia.
Atrybut data-lightbox przypisuje obrazek do indywidualnej galerii. Mamy więc 5 obrazków i 2 odrębne galerie.

My posiadając w kodzie tylko tagi IMG zmienimy je tak, aby można było podpiąć pod wspomnianą bibliotekę.

Startujemy od:

<div>
 <img src="/img/maly1.jpg" data-big-src="/img/duzy1.jpg" data-gallery-name="gallery1" alt="Obrazek 1"/>
 <span>Tekst</span>
</div>
<img src="/img/maly2.jpg" data-big-src="/img/duzy2.jpg" data-gallery-name="gallery1" alt="Obrazek 2"/>
<img src="/img/maly3.jpg" data-big-src="/img/duzy3.jpg" data-gallery-name="gallery2" alt="Obrazek 3"/>
<img src="/img/maly4.jpg" data-big-src="/img/duzy4.jpg" data-gallery-name="gallery2" alt="Obrazek 4"/>

Dlaczego tam się znalazł DIV i SPAN wyjaśnię Ci za chwilę. Na razie wstępny kod PHP:

/* inicjalizacja DOMDocument jak zwykle w poprzedniej części wpisu */

$images = $xpath->query('//img[@data-big-src]');

foreach($images as $img){
    $parent = $img->parentNode; /* rodzic <IMG> */

    $a = $dom->createElement('a'); /* tworzymy <A> */

    $a->setAttribute('href', $img->getAttribute('data-big-src')); /* kopiujemy href */
    $a->setAttribute('data-lightbox', $img->getAttribute('data-gallery-name')); /* kopiujemy data-lightbox */

    $img->removeAttribute('data-big-src'); /* usuwamy atrybuty z obrazka */
    $img->removeAttribute('data-gallery-name');

    $a->appendChild($img); /* wstawiamy obrazek do <A> */
    $parent->appendChild($a); /* dołączamy <A> do rodzica <IMG> */
}

echo $dom->saveHTML();

Otrzymamy:

<div>
 <span>Tekst</span>
 <a href="/img/duzy1.jpg" data-lightbox="gallery1"><img src="/img/maly1.jpg" alt="Obrazek 1"></a>
</div>
<a href="/img/duzy2.jpg" data-lightbox="gallery1"><img src="/img/maly2.jpg" alt="Obrazek 2"></a>
<a href="/img/duzy3.jpg" data-lightbox="gallery2"><img src="/img/maly3.jpg" alt="Obrazek 3"></a>
<a href="/img/duzy4.jpg" data-lightbox="gallery2"><img src="/img/maly4.jpg" alt="Obrazek 4"></a>

Na pierwszy rzut oka wygląda ok, prawda? No, nie do końca, A dla pierwszego obrazka wpadł za SPAN, czyli zmienił miejsce, a być może tego nie chcemy. Odpowiedzialność za to zdarzenie ponosi metoda appendChild, która wstawiła A na koniec rodzica obrazka (czyli do DIV).

Jak tego uniknąć?

Musimy zmienić element IMG w A, w miejscu jego istnienia. Przysłuży nam się metoda replaceChild. Wywołujemy ją na rodzicu węzła, który chcemy podmienić, przekazując jako pierwszy argument nowy węzeł, a jako drugi stary węzeł. Ale w naszym przypadku nie możemy podmienić IMG na A, bo IMG jest już dzieckiem A. Musimy skopiować węzeł, wstawić kopię do A, a potem zmienić oryginalny IMG na A.

Wnętrzne pętli będzie teraz wyglądać tak:

foreach($images as $img){
    $parent = $img->parentNode;

    $imgCopy = $img->cloneNode(false); /* tworzymy kopię <IMG> */

    $a = $dom->createElement('a');
    $a->setAttribute('href', $imgCopy->getAttribute('data-big-src'));
    $a->setAttribute('data-lightbox', $imgCopy->getAttribute('data-gallery-name'));

    $imgCopy->removeAttribute('data-big-src');
    $imgCopy->removeAttribute('data-gallery-name');

    $a->appendChild($imgCopy);
    $parent->replaceChild($a, $img); /* tu już swobodnie zmieniamy <IMG> na <A> */
}

Tym razem otrzymamy:

<div>
 <a href="/img/duzy1.jpg" data-lightbox="gallery1"><img src="/img/maly1.jpg" alt="Obrazek 1"></a>
 <span>Tekst</span>
</div>
<a href="/img/duzy2.jpg" data-lightbox="gallery1"><img src="/img/maly2.jpg" alt="Obrazek 2"></a>
<a href="/img/duzy3.jpg" data-lightbox="gallery2"><img src="/img/maly3.jpg" alt="Obrazek 3"></a>
<a href="/img/duzy4.jpg" data-lightbox="gallery2"><img src="/img/maly4.jpg" alt="Obrazek 4"></a>

Scena trzecia: "Kolorowanie linków w zależności od statusu strony".

Wykorzystamy tu XPath na linki zewnętrzne (scena pierwsza). Odwiedzimy każdą stronę docelową i w zależności czy jest aktywna czy nie, dodamy odpowiednią klasę. Linki niedziałające (nieistniejące domeny, niedziałające serwery, strony nieistniejące) oznaczymy kolorem czerwonym, działające zielonym.

Nasza nieprzetworzona strona:

i jej źródło:

<!DOCTYPE html>
 <html>
  <head>
  <style>
   a.link-broken{
     color:#f00;
   }
   a.link-ok{
     color:#080;
   }   
  </style>
  </head>
  <body>
   <ul>
    <li>
     <a href="http://tastronanapewnonieistnieje.com" class="class1">http://blablablabla.com</a>
    </li>
    <li>
     <a href="http://www.google.com" class="class1">http://www.google.com</a>
    </li>
    <li>
     <a href="http://kawalekkodu.pl/fire-in-the-hole-czyli-o-dziurach-w-mysql" class="class2">http://kawalekkodu.pl/fire-in-the-hole-czyli-o-dziurach-w-mysql</a>
    </li>
    <li>
     <a href="http://kawalekkodu.pl/post/fire-in-the-hole-czyli-o-dziurach-w-mysql" class="class2">http://kawalekkodu.pl/post/fire-in-the-hole-czyli-o-dziurach-w-mysql</a>
    </li>
    <li>
     <a href="http://kawalekkodu.pl/strona-nieistniejaca" class="class2 last">http://kawalekkodu.pl/strona-nieistniejaca</a>
    </li>
  </ul>
 </body>
</html>

Kod PHP:

header('Content-Type:text/html; charset=utf-8');

/* wspieramy się własnym handlerem błędów, który zwróci wyjątek,
   jeśli użyta w kodzie funkcja zwróci Warning
*/
set_error_handler(
function ($errno, $errstr, $errfile, $errline, array $errcontext)
{
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

/* tu powinna znaleźć się inicjalizacja DOMDocument,
   DOMXPath oraz załadowanie źródła strony
*/

$anchors = $xpath->query('//a[substring(@href,1,7)="http://" or substring(@href,1,8)="https://"]');

foreach($anchors as $anchor) {
/* pobieramy atrybut href */
    $url = $anchor->getAttribute('href');
/* sprawdzamy czy to poprawny URL */
    if(filter_var($url, FILTER_VALIDATE_URL)){
        try {
            $headers = get_headers($url);
/* sprawdzamy wszystkie nagłówki, bo mogliśmy otrzymać przekierowanie 301 */
            foreach($headers as $header) {
                if (substr($header, 0, 5) === 'HTTP/') {
                    $status = explode(' ', $header)[1];
                }
            }
        }
/* dzięki własnemu handlerowi możemy obsłużyć Warning */
        catch(Exception $e) {
            $status = false;
        }
    }
    else {
        $status = false;
    }
/* jeśli status inny niż 200, to będziemy dodawać 'link-broken' */
    if ($status != '200') {
        $class = 'link-broken';
    }
    else {
        $class = 'link-ok';
    }
/* pobieramy aktualny atrybut class */
    $currentClass = $anchor->getAttribute('class');
/* dodajemy do niego ustaloną klasę */
    $anchor->setAttribute('class', $currentClass . (!empty($currentClass) ? ' ' : '') . $class);
}
/* zwracamy kod strony
   ustawiliśmy na początku odpowiednie nagłówki,
   więc dostaniemy wygenerowaną stronę,
   a nie widoczne źródło
*/
echo $dom->saveHTML();

Przy przetworzeniu przez powyższy kod, otrzymamy:

Zakończenie.

Dotarliśmy do kresu dżungli. Należy Ci się odpoczynek. Nie zapominaj jednak o kapeluszu, przyda się jeszcze w kolejnych odcinkach.

 

Przydatne linki (znów linki!):
Biblioteka Lightbox by Lokesh Dhakar
Metoda replaceChild
Metoda cloneNode
Funkcja get_headers
"The tag is out there", czyli DOMXPath S01E01
"The tag is out there", czyli DOMXPath S01E02
"The tag is out there", czyli DOMXPath S01E03