c .
* @param string $target URL
* @return mixed setting $this->c('pingbackServer', $target);
*/
public function discoverPingbackEndpoint($target)
{
if($this->c('supportsPingback', $target) === null) {
$this->c('supportsPingback', $target, false);
// First try a HEAD request and look for X-Pingback header
if(!$this->c('headers', $target)) {
$head = static::_head($target);
$target = $head['url'];
$this->c('headers', $target, $head['headers']);
}
$headers = $this->c('headers', $target);
if(array_key_exists('X-Pingback', $headers)) {
self::_debug("discoverPingbackEndpoint: Found pingback server in header");
$this->c('pingbackServer', $target, $headers['X-Pingback']);
$this->c('supportsPingback', $target, true);
} else {
self::_debug("discoverPingbackEndpoint: No pingback server found in header, looking in the body now");
if(!$this->c('body', $target)) {
$body = static::_get($target);
$target = $body['url'];
$this->c('body', $target, $body['body']);
$this->_parseBody($target, $body['body']);
}
if($rels=$this->c('rels', $target)) {
// If the mf2 parser is present, then rels will have been set, and use that instead
if(count($rels)) {
if(array_key_exists('pingback', $rels)) {
$this->c('pingbackServer', $target, $rels['pingback'][0]);
$this->c('supportsPingback', $target, true);
}
}
} else {
$body = $this->c('body', $target);
if(preg_match("//i", $body, $match)) {
$this->c('pingbackServer', $target, $match[1]);
$this->c('supportsPingback', $target, true);
}
}
}
self::_debug("discoverPingbackEndpoint: pingback server: " . $this->c('pingbackServer', $target));
}
return $this->c('pingbackServer', $target);
}
/**
* Sends pingback to endpoints
* @param $endpoint string URL for pingback listener
* @param $source string originating post URL
* @param $target string URL like permalink of target post
* @return bool Successful response MUST contain a single string
*/
public static function sendPingbackToEndpoint($endpoint, $source, $target)
{
self::_debug("sendPingbackToEndpoint: Sending pingback now!");
$payload = static::xmlrpc_encode_request('pingback.ping', array($source, $target));
$response = static::_post($endpoint, $payload, array(
'Content-type: application/xml'
));
if($response['code'] != 200 || empty($response['body']))
return false;
// collapse whitespace just to be safe
$body = strtolower(preg_replace('/\s+/', '', $response['body']));
// successful response MUST contain a single string
return $body && strpos($body, '') === false && strpos($body, '') !== false;
}
/**
* Public function to send pingbacks to $targetURL
* @param $sourceURL string URL for source of pingback
* @param $targetURL string URL for destination of pingback
* @return bool runs sendPingbackToEndpoint().
* @see MentionClient::sendPingbackToEndpoint()
*/
public function sendPingback($sourceURL, $targetURL)
{
// If we haven't discovered the pingback endpoint yet, do it now
if($this->c('supportsPingback', $targetURL) === null) {
$this->discoverPingbackEndpoint($targetURL);
}
$pingbackServer = $this->c('pingbackServer', $targetURL);
if($pingbackServer) {
self::_debug("sendPingback: Sending to pingback server: " . $pingbackServer);
return self::sendPingbackToEndpoint($pingbackServer, $sourceURL, $targetURL);
} else {
return false;
}
}
/**
* Parses body of html. Protected method.
* @param $target string the URL of the target page
* @param $html string the HTML of page
*/
protected function _parseBody($target, $html)
{
if(class_exists('\Mf2\Parser') && $this->usemf2) {
$parser = new \Mf2\Parser($html, $target);
list($rels, $alternates) = $parser->parseRelsAndAlternates();
$this->c('rels', $target, $rels);
}
}
/**
* finds webmention endpoints in the body. protected function
* @param $body
* @param string $targetURL
* @return bool
*/
protected function _findWebmentionEndpointInHTML($body, $targetURL = false)
{
$endpoint = false;
$body = preg_replace('//Us', '', $body);
if(preg_match('/<(?:link|a)[ ]+href="([^"]*)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $body, $match)
|| preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]*)"[ ]*\/?>/i', $body, $match)) {
$endpoint = $match[1];
}
if($endpoint !== false && $targetURL && function_exists('\Mf2\resolveUrl')) {
// Resolve the URL if it's relative
$endpoint = \Mf2\resolveUrl($targetURL, $endpoint);
}
return $endpoint;
}
/**
* @param $link_header
* @param string $targetURL
* @return bool
*/
protected function _findWebmentionEndpointInHeader($link_header, $targetURL = false)
{
$endpoint = false;
if(preg_match('~<((?:https?://)?[^>]+)>; rel="?(?:https?://webmention.org/?|webmention)"?~', $link_header, $match)) {
$endpoint = $match[1];
}
if($endpoint && $targetURL && function_exists('\Mf2\resolveUrl')) {
// Resolve the URL if it's relative
$endpoint = \Mf2\resolveUrl($targetURL, $endpoint);
}
return $endpoint;
}
/**
* Finds webmention endpoints at URL. Examines header request.
* Also modifies $this->c to indicate if $target accepts webmention
* @param $target string the URL to examine for endpoints.
* @return mixed
*/
public function discoverWebmentionEndpoint($target)
{
if($this->c('supportsWebmention', $target) === null) {
$this->c('supportsWebmention', $target, false);
// First try a HEAD request and look for Link header
if(!$this->c('headers', $target)) {
$head = static::_head($target);
$target = $head['url'];
$this->c('headers', $target, $head['headers']);
}
$headers = $this->c('headers', $target);
$link_header = false;
if(array_key_exists('Link', $headers)) {
if(is_array($headers['Link'])) {
$link_header = implode(", ", $headers['Link']);
} else {
$link_header = $headers['Link'];
}
}
if($link_header && ($endpoint=$this->_findWebmentionEndpointInHeader($link_header, $target))) {
self::_debug("discoverWebmentionEndpoint: Found webmention server in header");
$this->c('webmentionServer', $target, $endpoint);
$this->c('supportsWebmention', $target, true);
} else {
self::_debug("discoverWebmentionEndpoint: No webmention server found in header, looking in body now");
if(!$this->c('body', $target)) {
$body = static::_get($target);
$target = $body['url'];
$this->c('body', $target, $body['body']);
$this->_parseBody($target, $body['body']);
}
if($rels=$this->c('rels', $target)) {
// If the mf2 parser is present, then rels will have been set, so use that instead
if(count($rels)) {
if(array_key_exists('webmention', $rels)) {
$endpoint = $rels['webmention'][0];
$this->c('webmentionServer', $target, $endpoint);
$this->c('supportsWebmention', $target, true);
} elseif(array_key_exists('http://webmention.org/', $rels) || array_key_exists('http://webmention.org', $rels)) {
$endpoint = $rels[array_key_exists('http://webmention.org/', $rels) ? 'http://webmention.org/' : 'http://webmention.org'][0];
$this->c('webmentionServer', $target, $endpoint);
$this->c('supportsWebmention', $target, true);
}
}
} else {
if($endpoint=$this->_findWebmentionEndpointInHTML($this->c('body', $target), $target)) {
$this->c('webmentionServer', $target, $endpoint);
$this->c('supportsWebmention', $target, true);
}
}
}
self::_debug("discoverWebmentionEndpoint: webmention server: " . $this->c('webmentionServer', $target));
}
return $this->c('webmentionServer', $target);
}
/**
* Static function can send a webmention to an endpoint via static::_post
* @param $endpoint string URL of endpoint detected
* @param $source string URL of originating post (other server will check probably)
* @param $target string URL of target post
* @param array $additional extra optional stuff that will be included in payload.
* @return array
*/
public static function sendWebmentionToEndpoint($endpoint, $source, $target, $additional = [])
{
self::_debug("sendWebmentionToEndpoint: Sending webmention now!");
$payload = http_build_query(array_merge(array(
'source' => $source,
'target' => $target
), $additional));
return static::_post($endpoint, $payload, array(
'Content-type: application/x-www-form-urlencoded',
'Accept: application/json, */*;q=0.8'
));
}
/**
* Sends webmention to a target url. may use
* @param $sourceURL
* @param $targetURL
* @param array $additional
* @return array|bool
* @see MentionClient::sendWebmentionToEndpoint()
*/
public function sendWebmention($sourceURL, $targetURL, $additional = [])
{
// If we haven't discovered the webmention endpoint yet, do it now
if($this->c('supportsWebmention', $targetURL) === null) {
$this->discoverWebmentionEndpoint($targetURL);
}
$webmentionServer = $this->c('webmentionServer', $targetURL);
if($webmentionServer) {
self::_debug("sendWebmention: Sending to webmention server: " . $webmentionServer);
return self::sendWebmentionToEndpoint($webmentionServer, $sourceURL, $targetURL, $additional);
} else {
return false;
}
}
/**
* Scans outgoing links in block of text $input.
* @param $input string html block.
* @return array array of unique links or empty.
*/
public static function findOutgoingLinks($input)
{
// Find all outgoing links in the source
if(is_string($input)) {
preg_match_all("/]+href=.(https?:\/\/[^'\"]+)/i", $input, $matches);
return array_unique($matches[1]);
} elseif (is_array($input) && array_key_exists('items', $input) && array_key_exists(0, $input['items'])) {
$links = array();
// Find links in the content HTML
$item = $input['items'][0];
if (array_key_exists('content', $item['properties'])) {
if (is_array($item['properties']['content'][0])) {
$html = $item['properties']['content'][0]['html'];
$links = array_merge($links, self::findOutgoingLinks($html));
} else {
$text = $item['properties']['content'][0];
$links = array_merge($links, self::findLinksInText($text));
}
}
// Look at all properties of the item and collect all the ones that look like URLs
$links = array_merge($links, self::findLinksInJSON($item));
return array_unique($links);
} else {
return array();
}
}
/**
* find all links in text.
* @param $input string text block
* @return mixed array of links in text block.
*/
public static function findLinksInText($input)
{
preg_match_all('/https?:\/\/[^ ]+/', $input, $matches);
return array_unique($matches[0]);
}
/**
* find links in JSON input string.
* @param $input string JSON object.
* @return array of links in JSON object.
*/
public static function findLinksInJSON($input)
{
$links = array();
// This recursively iterates over the whole input array and searches for
// everything that looks like a URL regardless of its depth or property name
foreach(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($input)) as $key => $value) {
if(substr($value, 0, 7) == 'http://' || substr($value, 0, 8) == 'https://')
$links[] = $value;
}
return $links;
}
/**
* Tries to send webmention and pingbacks to each link on $sourceURL. Depends on Microformats2
* @param $sourceURL string URL to examine to send mentions to
* @param bool $sourceBody if true will search for outgoing links with this (string).
* @return int
* @see \Mf2\parse
*/
public function sendMentions($sourceURL, $sourceBody = false)
{
if($sourceBody) {
$this->_sourceBody = $sourceBody;
$this->_links = self::findOutgoingLinks($sourceBody);
} else {
$body = static::_get($sourceURL);
$this->_sourceBody = $body['body'];
$parsed = \Mf2\parse($this->_sourceBody, $sourceURL);
$this->_links = self::findOutgoingLinks($parsed);
}
$totalAccepted = 0;
foreach($this->_links as $target) {
self::_debug("sendMentions: Checking $target for webmention and pingback endpoints");
if($this->sendFirstSupportedMention($sourceURL, $target)) {
$totalAccepted++;
}
}
return $totalAccepted;
}
/**
* @param $source
* @param $target
* @return bool|string
*/
public function sendFirstSupportedMention($source, $target)
{
$accepted = false;
// Look for a webmention endpoint first
if($this->discoverWebmentionEndpoint($target)) {
$result = $this->sendWebmention($source, $target);
if($result &&
($result['code'] == 200
|| $result['code'] == 201
|| $result['code'] == 202)) {
$accepted = 'webmention';
}
// Only look for a pingback server if we didn't find a webmention server
} else if($this->discoverPingbackEndpoint($target)) {
$result = $this->sendPingback($source, $target);
if($result) {
$accepted = 'pingback';
}
}
return $accepted;
}
/**
* Enables debug messages to appear during activity. Not recommended for production use.
* @codeCoverageIgnore
*/
public static function enableDebug()
{
self::$_debugEnabled = true;
}
/**
* @codeCoverageIgnore
*/
private static function _debug($msg)
{
if(self::$_debugEnabled)
echo "\t" . $msg . "\n";
}
/**
* @param $url
* @return array
* @codeCoverageIgnore
*/
protected static function _head($url, $headers = [])
{
if(self::$_userAgent)
$headers[] = 'User-Agent: '.self::$_userAgent;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy);
$response = curl_exec($ch);
return array(
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'headers' => self::_parse_headers(trim($response)),
'url' => curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
);
}
/**
* Protected static function
* @param $url string URL to grab through curl.
* @return array with keys 'code' 'headers' and 'body'
* @codeCoverageIgnore
*/
protected static function _get($url, $headers = [])
{
if(self::$_userAgent)
$headers[] = 'User-Agent: '.self::$_userAgent;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy);
$response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
return array(
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))),
'body' => substr($response, $header_size),
'url' => curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
);
}
/**
* @param $url
* @param $body
* @param array $headers
* @return array
* @codeCoverageIgnore
*/
protected static function _post($url, $body, $headers = [])
{
if(self::$_userAgent)
$headers[] = 'User-Agent: '.self::$_userAgent;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_HEADER, true);
if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy);
$response = curl_exec($ch);
self::_debug($response);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
return array(
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))),
'body' => substr($response, $header_size)
);
}
/**
* Protected static function to parse headers.
* @param $headers
* @return array
*/
protected static function _parse_headers($headers)
{
$retVal = array();
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $headers));
foreach($fields as $field) {
if (preg_match('/([^:]+): (.+)/m', $field, $match)) {
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) {
return strtoupper($m[0]);
}, strtolower(trim($match[1])));
// If there's already a value set for the header name being returned, turn it into an array and add the new value
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) {
return strtoupper($m[0]);
}, strtolower(trim($match[1])));
if (isset($retVal[$match[1]])) {
if (!is_array($retVal[$match[1]]))
$retVal[$match[1]] = array($retVal[$match[1]]);
$retVal[$match[1]][] = $match[2];
} else {
$retVal[$match[1]] = trim($match[2]);
}
}
}
return $retVal;
}
/**
* Static function for XML-RPC encoding request.
* @param $method string goes into MethodName XML tag
* @param $params array set of strings that go into param/value XML tags.
* @return string
*/
public static function xmlrpc_encode_request($method, $params)
{
$xml = '';
$xml .= '';
$xml .= ''.htmlspecialchars($method).'';
$xml .= '';
foreach ($params as $param) {
$xml .= ''.htmlspecialchars($param).'';
}
$xml .= '';
return $xml;
}
/**
* Caching key/value system for MentionClient
* @param $type
* @param $url
* @param mixed $val If not null, is set to default value
* @return mixed
*/
public function c($type, $url, $val = null)
{
// Create the empty record if it doesn't yet exist
$key = '_'.$type;
if(!array_key_exists($url, $this->{$key})) {
$this->{$key}[$url] = null;
}
if($val !== null) {
$this->{$key}[$url] = $val;
}
return $this->{$key}[$url];
}
}__halt_compiler();----SIGNATURE:----anhdke7uxJVjr8u7bJ5hTxxPFKfuqtYgXLdDAgUWLFKmRR0JS+6mTe5ZyUM8e4kZNfzRr0jJQ+0M7VEwG+A058te6CQQa4iaDpp4NXbKwUth/SUieBzYXG1Ksgg4ZLDo8YTq/U8Hj3+yMnHjZryN8NS8k/jV1tz8s8bX3CrE9N5Q8/7RCqF7vul+u6nuZVt5YrOdjnRmKToEIYOh3fKtwZRNEz1mCxZZjvdpGX1rQRh7gI4oyjxVuZFZEJE9pHofHpme0BS6+8Jm7n1u5n+xTJEvQ2eOGzebsvx+ARNOcprlrn4jIVjsaLF4bRGYIwajJBaMIvDN32KvN+klI4AqwSEYq4uqY+0sF/6ysV2WiDtAFU2MpaUwo2CxXnelnWOhOyDfjzI5rg/+/dfKa0N9/ZYEzYsmG0z51eldGzUQWZaxhljvFHX3neXzvWovkWhcnPBL4kQLZtoubeCUjDWvWSJCtQd80EQJf9htXaYGNP6TPIuJcQ2hkl4ND3RfYfBJV4NV4O+FSXXT0m4Twldiivu6w0zfzmGZ0ob8iBHTSScy9qPMfWdh/8tURt7XmBR+n7neyk0CKFyeXwhQBdvpccqWtgLAy0a/Yex/xdKe00VXyNoXZFlS9MjMV7i8V//WTQQuMkXJQ+lQjzDMal7PROlMjAOjaDfyCGXwnQjp0oE=----ATTACHMENT:----ODg0NDE3NjYwOTM4NDgyNyA2MDMxNzM4OTcwODY0MTQwIDk4MzMzNTUxNzA5NTI3NDc=