*
* Automatically send Pingbacks and Trackbacks
*
* Developed and tested using PmWiki 2.2.x
*
* To use, add the following to a configuration file:
if ($action=='edit') include_once("$FarmD/cookbook/bloge-linkback-client.php");
* To limit sending linkback pings you should only include this file for such
* pages, for example by using per-group customizations.
*
* This is a part of the Bloge bundle of recipes, but may be used by itself.
* For more information, please see the online documentation at
* http://www.pmwiki.org/wiki/Cookbook/Bloge and at
* http://www.pmwiki.org/wiki/Cookbook/Bloge-Linkback#client
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
$RecipeInfo['Bloge-LinkbackClient']['Version'] = '2009-08-12';
$EditFunctions[] = 'LinkbackFindLinks';
SDV( $LinkbackClientCURLOptions, array(
CURLOPT_FOLLOWLOCATION => TRUE,
CURLOPT_MAXREDIRS => 10,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_USERAGENT => "$Version Bloge-LinkbackClient-{$RecipeInfo['Bloge-LinkbackClient']['Version']}"
));
if (!function_exists('xmlrpc_encode_request')) {
function xmlrpc_encode_request($method, $params, $output_options=NULL) {
$xml = "\n\npingback.ping\n";
foreach ( (array)$params as $p ) {
$type = gettype($p);
switch(gettype($p)) {
case 'integer':
$type = 'int';
break;
case 'array': # not implemented
case 'object': # not implemented
case 'resource':
case 'NULL':
case 'unknown type':
$type = 'string';
$p = print_r($p,TRUE);
break;
}
$xml .= "\n\t<$type>$p$type>";
}
$xml .= "\n\n";
return $xml;
}
}
function LinkbackDiscovery($url, $self, &$type) {
global $LinkbackClientCURLOptions;
if ( !function_exists('curl_init') || empty($url) ) return FALSE;
## HEAD url
$ch = curl_init($url);
curl_setopt_array($ch, $LinkbackClientCURLOptions + array(
CURLOPT_NOBODY => TRUE,
CURLOPT_HEADER => TRUE,
CURLOPT_REFERER => $self
));
$reply = curl_exec($ch);
curl_close($ch);
## Pingback header + sanity checks
if (empty($reply)) return FALSE;
else if (preg_match('/^X-Pingback: (.+)$/m', $reply, $m)) { $type = 'pingback'; return $m[1]; }
else if (!preg_match('/^Content-Type:.*(html|xml)/mi', $reply, $m)) return FALSE;
## GET url
$ch = curl_init($url);
curl_setopt_array($ch, $LinkbackClientCURLOptions + array(CURLOPT_REFERER => $self));
$reply = curl_exec($ch);
curl_close($ch);
if (preg_match('##', $reply, $m)) {
$type = 'pingback';
$target = $m[1];
} else if (preg_match('##s', $reply, $m)) {
$type = 'trackback';
$target = $m[1];
} else return FALSE;
return str_replace(
array('&', '<', '>', '"'),
array('&', '<', '>', '"'),
$target);
}
function LinkbackSendPing( $url, $ref, $post, &$reply ) {
global $Version, $RecipeInfo, $LinkbackClientCURLOptions;
if ( !function_exists('curl_init') || empty($url) || empty($post) ) return FALSE;
$ok = TRUE;
$ch = curl_init($url);
curl_setopt_array($ch, $LinkbackClientCURLOptions + array(
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $post,
CURLOPT_REFERER => $ref
));
if ($post[0]=='<') curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
$msg = curl_exec($ch);
if (empty($msg)) { $ok = FALSE; $msg = curl_error($ch) . " ($url)"; }
curl_close($ch);
if ($reply!==FALSE) $reply = $msg;
return $ok;
}
function LinkbackExternalLinks($txt) {
global $LinkPattern, $UrlExcludeChars, $IMap, $LinkbackClientFilterSites;
SDVA($LinkbackClientFilterSites, array(
'self' => '!^https?://'.preg_quote($_SERVER['SERVER_NAME'],'!').'!',
'pmwiki' => '!^http://([^/]+\.)?pmwiki\.org!',
'wikipedia' => '!^http://([^/]+\.)?wikipedia\.(com|org)!'
));
preg_match_all(
"/\\b(?>($LinkPattern))([^\\s$UrlExcludeChars]*[^\\s.,?!$UrlExcludeChars])/",
$txt, $matches, PREG_SET_ORDER );
$targets = array();
foreach( $matches as $link ) $targets[] = PUE(str_replace('$1',$link[2],$IMap[$link[1]]));
return MatchPageNames(array_unique($targets), $LinkbackClientFilterSites);
}
function LinkbackFindLinks($pagename, &$page, &$new) {
global $IsPagePosted, $Now, $MessagesFmt;
if (!$IsPagePosted || !empty($_POST['postdraft']) || !empty($_POST['postedit'])) return;
$diffkey = preg_grep("/^diff:$Now:/", array_keys($new));
if (count($diffkey)!=1) return;
preg_match_all('/^< .+$/m', $new[reset($diffkey)], $dm);
if (empty($dm[0])) return;
$targets = LinkbackExternalLinks( implode(' ',$dm[0]) );
if ($targets) register_shutdown_function('LinkbackPingLinks', $pagename, $targets, getcwd());
}
function LinkbackParseResponse($xml, $debug=FALSE) {
## pingback
if (preg_match('#\s*(.*?)\s*#s',$xml,$m_r)) {
if (preg_match('#
\s* \s* \s*
<(\w+)> \s* (.*?) \s* \1> \s*
\s* \s*
#sx', $m_r[1], $m_return)) return $debug ? "success ({$m_return[2]})" : '';
if (preg_match('#
\s* \s* \s*
(.*?) \s*
\s* \s*
#sx', $m_r[1], $m_fault)
&& ( preg_match_all('#\s*(.*?)\s*#s', $m_fault[1], $m_fm, PREG_SET_ORDER) == 2 )
) {
foreach( $m_fm as $member ) {
if (preg_match('#\s*fault(Code|String)\s*#', $member[1], $m_member_name)) {
if ( ($m_member_name[1]=='Code') && preg_match('#\s*<(?:int|i4)>\s*([\d-]*)\s*(?:int|i4)>\s*#', $member[1], $m_member_code) )
$errcode = $m_member_code[1];
else if ( ($m_member_name[1]=='String') && preg_match('#\s*\s*(.*?)\s*\s*#s', $member[1], $m_member_str) )
$errstr = $m_member_str[1];
}
}
if ( isset($errcode) && isset($errstr) ) return "error #$errcode ($errstr)";
}
}
## trackback
if (preg_match('#
\s*
\s* (\d*) \s* \s*
(?: \s* (.*?) \s* \s* )?
#six', $xml, $m_r)) {
if (empty($m_r[1])) return $debug ? 'success' : '';
else return "error ({$m_r[2]})";
}
return $debug ? $xml : 'XML parse error';
}
function LinkbackPingLinks($pagename, $targets, $dir='') {
global $CurrentTime, $LinkbackClientLogPage;
if ($dir) { flush(); chdir($dir); }
SDV($LinkbackClientLogPage, 'Site.LinkbackClientLog');
$log = "Bloge-LinkbackClient autodiscovery: $pagename ($CurrentTime)\n\n";
$self = PageVar($pagename, '$PageUrl');
foreach($targets as $tgt) {
$log .= "$tgt\n";
$pingtgt = LinkbackDiscovery($tgt, $self, $type);
if (!$pingtgt) continue;
switch($type) {
case 'pingback':
$post = xmlrpc_encode_request('pingback.ping', array($self,$tgt));
$log .= " > pingback @ $pingtgt\n";
break;
case 'trackback':
$post =
'url='.urlencode($self)
//.'&excerpt='.urlencode(PageVar($pagename, '$Description'))
//.'&blog_name='.urlencode('foo')
.'&title='.urlencode(PageVar($pagename, '$Title'));
$log .= " > trackback @ $pingtgt\n";
break;
default:
continue 2;
}
LinkbackSendPing($pingtgt, $tgt, $post, $reply);
$out = LinkbackParseResponse($reply, TRUE);
$log .= " > $out\n";
}
if (!$LinkbackClientLogPage) return;
$page = ReadPage($LinkbackClientLogPage);
$page['text'] .= "[@{$log}@]\n----\n\n";
WritePage($LinkbackClientLogPage, $page);
}