Pingbacks

Immer, wenn ich hier im Blog eine Seite verlinke, versucht WordPress einen sogenannten Pingback zu senden. Das hat den Vorteil, dass der ursprüngliche Author darüber informiert wird, dass ich seinen Beitrag verlinke, bzw. auf seiner Idee aufbaue.

Nun dachte ich mir, kann man diese Funktion nicht auch für die eigene Seite benutzen? Also nicht, um selber rumzupingen, sondern um Pingbacks zu erhalten und zu speichern. Hier meine Lösung:

Erst einmal hab ich eine Tabelle in meiner MySQL-Datenbank angelegt, um die PingBacks zu speichern:
[cc lang=“mysql“]
CREATE TABLE IF NOT EXISTS `pingback` (
`from` varchar(255) NOT NULL,
`to` varchar(255) NOT NULL,
`author` varchar(255) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`from`,`to`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
[/cc]

In meinem Root-Verzeichnis vom Stadtplan Ilmenau habe ich die Datei pingback.php mit folgenden Inhalt angelegt:

[cce lang=“php“ lines=“40″]
‚Error.‘,
PINGBACK_SOURCE_MISSING => ‚The source URI does not exist.‘,
PINGBACK_SOURCE_BROKEN => ‚The source URI does not contain a link to the target URI.‘,
PINGBACK_TARGET_MISSING => ‚The specified target URI does not exist.‘,
PINGBACK_TARGET_BROKEN => ‚The specified target URI cannot be used as a target.‘,
PINGBACK_DUPLICATE => ‚The pingback has already been registered.‘,
PINGBACK_ACCESS_DENIED => ‚Access denied.‘,
PINGBACK_UPSTREAM_ERROR => ‚Upstream error.‘,
PINGBACK_PARSE_ERROR => ‚parse error. not well formed‘,
PINGBACK_WRONG_METHOD => ’server error. requested method not found‘,
PINGBACK_WRONG_PARAMS => ’server error. invalid method parameters‘,
);
if (!$error && isset($errors[$code]))
$error = $errors[$code];
return <<





faultCode
$code


faultString
$error





XML;
}

function pingback_success($message) {
return <<
$message
XML;
}

/*
* Pingback response handler
* Wird aufgerufen, wenn ich angepingt werde
*/
class PingBackHandler {

//Variablen
public $message = NULL;

private $error = NULL, $sourceURI, $targetURI, $author = NULL, $comment = NULL;
private $html = NULL, $p;

//Konstruktor
public function __construct($data = NULL) {
if ($data == NULL)
$data = file_get_contents(‚php://input‘);

preg_match_all(‚#(.+)#‘, $data, $methods);
preg_match_all(‚#(?:)(.+?)(?:)#‘, $data, $moreparams);
$method = $methods[1][0];
$params = $moreparams[1];

if (!$params || !$method)
$this->error = PINGBACK_PARSE_ERROR;
elseif ($method != ‚pingback.ping‘)
$this->error = PINGBACK_WRONG_METHOD;
elseif (sizeof($params) < 2 || !isset($params[0]) || !isset($params[1])) $this->error = PINGBACK_WRONG_PARAMS;
else {
$this->sourceURI = $params[0];
$this->targetURI = $params[1];
if (!is_string($this->sourceURI) || !is_string($this->targetURI))
$this->error = PINGBACK_WRONG_PARAMS;
}
}

//Prüfen, ob Fehler vorliegt
public function isValid() {
//Da Fehlercode = 0 auch ein Fehler ist, muss das zu kompliziert gemacht werden
return ($this->error === NULL ? TRUE : FALSE );
}

/* Validierung */
public function validate() {
if ($this->error !== NULL)
return FALSE;
if (!isset($this->html))
$this->html = file_get_contents($this->sourceURI);
if (!$this->html)
$this->error = PINGBACK_SOURCE_MISSING;
elseif (($p = strpos($this->html, $this->targetURI)) === FALSE)
$this->error = PINGBACK_SOURCE_BROKEN;
$this->p = $p;
return $this->isValid();
}

/* Fehlereingabe! */
public function fail($code, $msg) {
$this->error = $code; $this->message = $msg;
}

public function notFound() {
$this->error = PINGBACK_TARGET_MISSING;
}

public function notValid() {
$this->error = PINGBACK_TARGET_INVALID;
}

public function notFirst() {
$this->error = PINGBACK_DUPLICATE;
}

public function notAllowed() {
$this->error = PINGBACK_ACCESS_DENIED;
}

public function __get($name) {
switch ($name) {
case ’sourceURI‘:
case ‚remoteURI‘:
return $this->sourceURI;
break;
case ‚targetURI‘:
case ‚localURI‘:
return $this->targetURI;
break;
case ‚author‘:
if (!isset($this->author))
$this->extract_author();
return $this->author;
break;
case ‚comment‘:
case ‚excerpt‘:
if (!isset($this->comment))
$this->extract_comment();
return $this->comment;
default:
throw new Exception(„Undefined property „.$name);
break;
}
}

public function respond() {
$data = $this->asXML();
header(„Content-Type: text/xml“);
header(„Content-Length: „.strlen($data));
header(„Connection: close“);
echo $data;
}

public function pong() {
return $this->respond();
}

public function asXML() {
if ($this->error === NULL) {
return pingback_success(isset($this->message) ? $this->message : ‚Pingback Accepted‘);
} else {
return pingback_failure($this->error, $this->message);
}
}

private function extract_author() {
if (isset($this->author))
return;
$this->author = “;
if ($this->html === NULL || $this->error !== NULL)
return;
if (preg_match(„#(.+)#“, $this->html, $mc))
$this->author = $mc[1];
}

private function extract_comment() {
if (isset($this->comment))
return;
$this->comment = “;
if ($this->html === NULL || $this->error !== NULL)
return;

$html = $this->html;
$p = $this->p;

$left = substr($html, 0, $p);
$right = substr($html, $p + $this->targetURI);
$gl = strrpos($left, ‚>‘, -512) + 1;
$gr = strpos($right, ‚<', 512); $nleft = substr($left, $gl); $nright = substr($right, 0, $gr); $nstr = $nleft.$nright; $nstr = strip_tags($nstr); $nstr = str_replace(array("\n","\t")," ", $nstr); $fat = strlen($nstr) - 120; if ($fat > 0) {
$lfat = $fat / 2;
$rfat = $fat – $lfat;
$nstr = substr($nstr, $lfat);
$nstr = substr($nstr, 0, -$rfat);
}

$nstr = trim($nstr);
if ($nstr)
$nstr = preg_replace(‚#^.+?(\s)|(\s)\S+?$#‘, ‚\\2[…]\\1‘, $nstr);

$this->comment = $nstr;
}
}

/* Prüfung, ob curl installiert ist */
if (function_exists(‚curl_setopt_array‘)) {
function post_remote_xml($url, $xml) {
$ch = curl_init($url);
$co = array(
CURLOPT_HEADER => 0,
CURLOPT_POST => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HTTPHEADER => array(
„Content-Type: text/xml“,
„Content-Length: „.strlen($xml) ),
CURLOPT_POSTFIELDS => $xml);
curl_setopt_array($ch, $co);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
function get_remote_head($url) {
$ch = curl_init($url);
$co = array(
CURLOPT_HEADER => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HTTPHEADER => array(„Content-Range: bytes 0-4096/*“),
);
curl_setopt_array ($ch, $co);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
} else {
function post_remote_xml($url, $xml) {
$resp = “;
$purl = parse_url($url);
if (!isset($purl[‚port‘]))
$purl[‚port‘] = 80;
$fp = fsockopen($purl[‚host‘], $purl[‚port‘], $errno, $errstr, 30);
if ($fp) {
$out = „POST „.$purl[‚path‘].“ HTTP/1.1\r\n“;
$out .= „Host: „.$purl[‚host‘].“\r\n“;
$out .= „Content-Length: „.strlen($xml).“\r\n“;
$out .= „Content-Type: text/xml\r\n“;
$out .= „Connection: Close\r\n\r\n“;
$out .= $xml;
fwrite($fp, $out);
while (!feof($fp))
$resp .= fread($fp, 1024);
fclose($fp);
}
return $resp;
}
function get_remote_head($url) {
$resp = “;
$purl = parse_url($url);
if (isset($purl[‚query‘]))
$purl[‚path‘] .= ‚?‘.$purl[‚query‘];
if (!isset($purl[‚port‘]))
$purl[‚port‘] = 80;
$fp = fsockopen($purl[‚host‘], $purl[‚port‘], $errno, $errstr, 30);
if ($fp) {
$out = „GET „.$purl[‚path‘].“ HTTP/1.1\r\n“;
$out .= „Host: „.$purl[‚host‘].“\r\n“;
$out .= „Content-Range: bytes 0-4096/*\r\n“;
$out .= „Connection: Close\r\n\r\n“;
fwrite($fp, $out);
$resp = fread($fp, 4096);
fclose($fp);
}
return $resp;
}
}

// Objekt erstellen
$ping = new PingBackHandler;

//Wenn kein Fehler vorliegt
if ($ping->validate()) {
//Datenbankverbindung aufbauen
$db = mysql_connect(‚localhost‘,’stadtplan‘,’stadtplan‘);
mysql_select_db(’stadtplan‘);

//Prüfen, ob bereits ein Pingback existiert
$result = mysql_query(„SELECT * FROM `pingback` WHERE `from` = ‚“.mysql_real_escape_string($ping->remoteURI).“‚ AND `to` = ‚“.mysql_real_escape_string($ping->localURI).“‚“);
if(mysql_num_rows($result)>0) { //Wenn ein Pingback existiert, Fehler ausgeben und Daten in Datenbank anpassen
$ping->notFirst();
mysql_query(„UPDATE `pingback` SET `author` = ‚“.mysql_real_escape_string($ping->author).“‚, `comment` = ‚“.mysql_real_escape_string($ping->comment).“‚ WHERE `from` = ‚“.mysql_real_escape_string($ping->remoteURI).“‚ AND `to` = ‚“.mysql_real_escape_string($ping->localURI).“‚“);
}
else { //Wenn kein Datensatz in der DB besteht, einen anlegen.
mysql_query(„INSERT INTO `pingback` (`author`,`comment`,`from`,`to`) VALUES (‚“.mysql_real_escape_string($ping->author).“‚,'“.mysql_real_escape_string($ping->comment).“‚,'“.mysql_real_escape_string($ping->remoteURI).“‚,'“.mysql_real_escape_string($ping->localURI).“‚)“);
}
mysql_close($db);
}
//Antworten
$ping->respond();
//muss nicht sein, ich machs aber trotzdem 😉
exit;

?>
[/cce]

Nun muss nur noch den Blogs gesagt werden, wohin sie Ihre PingBacks schicken sollen. Dafür gibt es zwei Wege. Ich hab beide angewendet, falls irgendwer irgendeinen Weg nicht versteht.

  • Header schicken [ccei lang=“php“]<?php header(„X-Pingback: http://stadtplan-ilmenau.de/pingback.php“); ?>[/ccei]
    Wichtig: [ccei lang=“php“]header();[/ccei] muss immer aufgerufen werden, bevor irgendein Inhalt gesendet wird!
  • Meta-Header im HTML-Dokument [ccei lang=“html“]<link rel=“pingback“ href=“http://stadtplan-ilmenau.de/pingback.php“ />[/ccei]

Natürlich hab ich mir all das nicht einfach so ausgedacht. Unter http://meiert.com/de/publications/translations/hixie.ch/pingback/ findet man eine deutschsprachige Version des Pingback-Standards. Der Quellcode der pingback.php kommt ursprünglich von https://github.com/driedfruit/php-pingback/blob/1e109a0c2d1ef303110bf3b9a1c282273113d49a/pingback.php. Und getestet hab ich meine jungfräuliche Schnittstellt mit redalt.com/ping.

label,

Kommentar verfassen