* * 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"; } $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* \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*\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); }