Twitter Cards mit Textpattern

Permalink

Die letzte Folge der Technikwürze behandelte Twitter Cards. Dabei handelt es sich um ein Metadatenformat für HTML-Seiten. Twitter als Herausgeber der Spezifikation erhofft sich damit die Darstellung von getwitterten Links attraktiver zu machen.

Spezifikation

Twitter versucht mit ihren Cards zusätzliche Informationen in Webseiten unterzubringen um diese in ihre eigenen Plattform nutzen zu können. Dazu haben sie drei Arten von Inhalten identifiziert die durch HTML-Seiten transportiert werden können. Ganz allgemein gehalten sind die sogenannten Summary Card. Hierunter versammelt sich alles was sich mit einem Titel und einer kurzen Beschreibung darstellen lässt – also ziemlich alles. Etwas spezieller sind die Photo Cards. Hiermit werden einzelne Bilder in den Mittelpunkt gerückt. Die dritte Art von Inhalten sind solche die sich abspielen lassen. Die Player Cards ermöglichen es Video- und Audiodateien direkt auf Twitter abspielen zu lassen.

Um die nötigen Informationen wie Titel, Beschreibung und URL des Bildes oder des Players für Twitter zugänglich zu machen definiert die Spezifikation feste Namen. Diese werden zusammen mit den Werten in Form von Meta-Elementen in beliebige HTML-Seiten eingebunden.

<html>
  <head>
    ...
    <meta name="twitter:card" content="summary">
    <meta name="twitter:url" content="http://human-injection.de/articles/twittercards">
    <meta name="twitter:title" content="Twitter Cards mit Textpattern">
    <meta name="twitter:description" content="Twitter Cards als Plugin für Textpattern">
  </head>
  <body>
    ...
  </body>
</html>

Das ganze erinnert an das Open Graph protocol das von Facebook zu ähnlichem Zweck entwickelt wurde. Twitter geht sogar soweit, die Informationen aus dem Open Graph als Fallback heranzuziehen wenn eine Twitter Card nicht vorliegt oder nicht vollständig ist.

Ein alleiniges Merkmal der Twitter Cards ist es das man zwei Twitter-Accounts mit seinen Webseiten verknüpfen kann. Man gibt mit site den Herausgeber und mit creator den Urheber eines Textes, Bildes oder Videos an:

<meta name="twitter:site" content="@HumanInjection">
<meta name="twitter:creator" content="@SebastiansIT">

Wenn nun der Link zu einer derart präparierten HTML-Seite getwittert wird holt sich Twitter mit seinem Twitterbot die Informationen und stellt sie in ihren Clients da, auch in den Mobilen. Damit das aber auch wirklich geschieht muss man sich zur Zeit noch für die Teilname an diesem Prozess bewerben. Twitter will sich hier wohl noch nicht zu weit vorwagen und jedermann Tür und Tor öffnen.

An dieser Stelle möchte ich empfehlen kurz zur Spezifikation der Cards zu wechseln und sich dort einen besseren Überblick über das Format und seine Möglichkeiten zu verschaffen.

Implementierung

Da ich einfach nicht widerstehen kann wenn es um zusätzliche Meta-Elemente in meinen Webseiten geht hatte ich endlich einen Vorwand um ein Plugin für Textpattern (das hier eingesetzte CMS) zu schreiben.

Wer mag kann das Ergebnis shs_twittercards herunterladen und installieren. Den Quellcode gibt es auch. Das Plugin steht unter einer GNU General Public License. Wer nun wissen möchte wie ich das gemacht habe kann jetzt weiterlesen.

Für den Einstieg in Textpattern und dessen Plugins kann ich das Buch Textpattern Solutions empfehlen. Hier wird alles beschrieben was man für den Einstieg braucht.

Grundfunktionalität

Mein Plugin ist eigentlich ganz einfach. Zu erst wir ein TXP-Tag shs_twittercards durch eine gleichnamige Funktion definiert.

function shs_twittercards($atts) {
  ...
}

Diesem Tag muss nun in die Lage versetzt werden die Informationen für die Cards aufzunehmen. Dies erfolgt über Attribute – eines für jeden spezifizierten Wert. Alles was nicht angegeben wird, wird durch das Plugin mit Standartwerte initialisiert.

<txp:shs_twittercards site="@humaninjection" creator="@SebastiansIT" />

Der folgende Quellcode erzeugt aus den Attributen Variablen und belegt diese wenn nötig mit den Standartwerten

function shs_twittercards($atts) {
  ...
  extract(lAtts(array(
    'onlytwitteragent' => '0',
    'cardtype' => 'summary',
    'site' => '',
    'siteid' => '',
    'creator' => '',
    'creatorid' => '',
    'url' => processTags('permlink', ''),
    'description' => $GLOBALS['thisarticle']['excerpt'],
    'title' => $GLOBALS['thisarticle']['title'],
    'image' => '',
    'ignorearticleimage' => '0',
    'imagedefault' => '',
    'imagewidth' => '',
    'imageheight' => '',
    'player' => '',
    'playerwidth' => '',
    'playerheight' => '',
    'playerstream' => '',
    'playerstreamcontenttype' => ''
  ), $atts));
  ...
}

Wie man sehen kann sind einige dieser Standartwerte mit passenden Feldern eines Artikels belegt. Zum Beispiel wird der Titel des Artikels gleichzeitig als Titel der Card genutzt. Der Zugriff auf diese Felder erfolgt über globale Variablen die Textpattern beim aufrufen eines Artikels anlegt.

...
'title' => $GLOBALS['thisarticle']['title'],
...

Andere Standartwerte ergeben sich aus der Ausgabe von anderen TXP-Tags. Diese kann man in einem Plugin aufrufen indem man die Funktion processTags(string, string) mit dem Namen des Tags und ihren Attributen aufruft. Das folgende Beispiel erzeugt die URL eines Artikels indem es das Tag <txp:permlink /> aufruft.

processTags('permlink', '')

Einzig die Angabe zum Bild der Card muss noch speziell aufbereitet werden. Im ersten Schritt wird dazu die Quelle bestimmt. Priorität hat die Angabe aus dem Image-Attribut. Wenn diese nicht vorliegt wird das Artikelbild herangezogen. Fehlt auch dies wird der Wert zum Standardbild verwendet.

// Check the article_image for
//  a) a picture is set
 if ($image != '') {
  $image_url = $image;
}
 //  b) a article image exists
else if ($GLOBALS['thisarticle']['article_image'] != '') {
  $image_url = $GLOBALS['thisarticle']['article_image'];
}
//  c) a default image is set
else if ($imagedefault != '') {
  $image_url = $imagedefault;
}
else {
  $image_url = '';
}

Danach muss die vorliegende Art der Referenzierung auf das Bild (ID-Nummer des Bildes, relative und absolute URL) in eine absolute URL umgewandelt werden. Dies geschieht im Fall einer ID-Nummer durch anwenden des TXP-Tags <txp:image_url link="0" />. Relative URL’s werden hingegen mit UrlToAbsolute, einer PHP Bibliothek unter BSD-Lizenz, umgewandelt.

//Convert the actual image_url value to an absolute url
if ($image_url != '') {
  // a) a picture ID
  if (is_numeric($image_url)) {
    $image_url = processTags('image_url', 'id="'.$image_url.'" link="0"');
  }
  // b) a relative URL (absolute URL's starts width 'http://' or 'https://')
  else if (!strpos($image_url, 'http://') 
            && !strpos($image_url, 'https://')) {
    $baseUrl = $_SERVER['HTTP_HOST'] . $GLOBALS['pretext']['request_uri'];
    if (array_key_exists('HTTPS', $_SERVER)) {
      $baseUrl = 'https://' . $baseUrl;
    }
    else {
      $baseUrl = 'http://' . $baseUrl;
    }
    $image_url = url_to_absolute($baseUrl, $image_url);
  }
  // c) a absolute URL
  else {
    //do nothing
    //$image_url = $image_url
  }
}

Nachdem nun alle Variablen vorbereitet sind werden, falls ein Wert vorhanden ist, die entsprechenden Meta-Element erzeugt.

function shs_twittercards($atts) {
  ...
  $returnvalue = '<!-- Twitter Card --> ';
  $returnvalue = $returnvalue.'<meta name="twitter:card" content="'.$cardtype.'"> ';
  if ($site != '' && $siteid == '') 
    $returnvalue = $returnvalue.'<meta name="twitter:site" content="'.$site.'"> ';
  if ($siteid != '') 
    $returnvalue = $returnvalue.'<meta name="twitter:site:id" content="'.$siteid.'"> ';
  if ($creator != '' && $creatorid == '')  
    $returnvalue = $returnvalue.'<meta name="twitter:creator" content="'.$creator.'"> ';
  if ($creatorid != '') 
    $returnvalue = $returnvalue.'<meta name="twitter:creator:id" content="'.$creatorid.'"> ';
  if ($url != '') 
    $returnvalue = $returnvalue.'<meta name="twitter:url" content="'.$url.'"> ';
  if ($description != '') 
    $returnvalue = $returnvalue.'<meta name="twitter:description" content="'.trim(strip_tags($description)).'"> ';
  if ($title != '') 
    $returnvalue = $returnvalue.'<meta name="twitter:title" content="'.$title.'"> ';		
  if ($image_url != '') {
    $returnvalue = $returnvalue.'<meta name="twitter:image" content="'.$image_url.'" /> ';
    if ($imagewidth != '') {
      $returnvalue = $returnvalue.'<meta name="twitter:image:width" content="'.$imagewidth.'" /> ';
    }
    if ($imageheight != '') {
      $returnvalue = $returnvalue.'<meta name="twitter:image:height" content="'.$imageheight.'" /> ';
    }
  }
  if ($player != '') {
    $returnvalue = $returnvalue.'<meta name="twitter:player:height" content="'.$player.'"> ';
    $returnvalue = $returnvalue.'<meta name="twitter:player:width" content="'.$playerwidth.'"> ';
    $returnvalue = $returnvalue.'<meta name="twitter:player:height" content="'.$playerheight.'"> ';
  }
  if ($playerstream != '') {
    $returnvalue = $returnvalue.'<meta name="twitter:player:stream" content="'.$playerstream.'"> ';
    $returnvalue = $returnvalue.'<meta name="twitter:player:stream:content_type" content="'.$playerstreamcontenttype.'"> ';
  }
  ...
}

Zum Schluss werden die gesammelten Elemente aus der Funktion zurückgegeben und damit auf der Webseite ausgegeben.

function shs_twittercards($atts) {
  ...
  return $returnvalue;
}

Erweiterungen

Twitterbod

Um das unnötige “Zumüllen” des HTML-Codes mit Metainformationen zu vermeiden habe ich eine kleine Besonderheit eingebaut die die Twitter Card nur dann erzeugt wenn der HTTP-Request vom Twitterbot ausgeht. Normale Browser bekommen so von der Card nichts zu sehen und sparen sich also die paar KB am Downloadvolumen.

 function shs_twittercards($atts) {
  ...
  if ( isTwitterAgent() || $onlytwitteragent == '0') {
    //Create the Twitter Card Meta Elements
    ...
  }
  return $returnvalue;
}

Dieses Verhalten lässt sich abstellen (z.B. fürs Debuggen) indem man das Attribute onlytwitteragent auf den Wert 0 setzt.

<txp:shs_twittercards onlytwitteragent="0" />

Herauszufinden ob der Twitterbod den Request ausgelöst hat ist Aufgabe der Funktion isTwitterAgent(). Sie prüft den User Agent um den Crawler von Twitter zu identifizieren.

function isTwitterAgent() {
  if ( stristr($_SERVER["HTTP_USER_AGENT"],"Twitterbot") ) {
    return true;
  }
  else {
    return false;
  }
}