1301877dfe
* Subscription::start was sometimes passing users instead of profiles to hooks, which broke OStatus subscription notifications; now normalizing to profiles for processing. * H-card parsing would trigger a lot of PHP warnings and notices in hKit. Now suppressing warnings and notices for the duration of the call to keep them out of output when display_errors is on. * H-card parsing would trigger a PHP fatal error if the source page was not well-formed XML and Tidy was not present on the system. Switched normalization to use the PHP DOM module which is always present, as we have no need for Tidy's extra features here. * Trying to fetch avatars from Google profiles failed and triggered a PHP warning due to the relative URL not being resolved during h-card parsing. Now passing profile page URL into hKit by sneaking a <base> tag in while we normalize the HTML source. * Profile pages without a "Link" header could trigger PHP notices due to a bad NULL -> array(NULL) conversion in LinkHeader::getLink(). Now checking that there was a return value before converting single return value into array.
210 lines
6.7 KiB
PHP
210 lines
6.7 KiB
PHP
<?php
|
|
/*
|
|
* StatusNet - the distributed open-source microblogging tool
|
|
* Copyright (C) 2010, StatusNet, Inc.
|
|
*
|
|
* Some utilities for generating hint data
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
class DiscoveryHints {
|
|
|
|
static function fromXRD($xrd)
|
|
{
|
|
$hints = array();
|
|
|
|
foreach ($xrd->links as $link) {
|
|
switch ($link['rel']) {
|
|
case Discovery::PROFILEPAGE:
|
|
$hints['profileurl'] = $link['href'];
|
|
break;
|
|
case Salmon::NS_REPLIES:
|
|
$hints['salmon'] = $link['href'];
|
|
break;
|
|
case Discovery::UPDATESFROM:
|
|
$hints['feedurl'] = $link['href'];
|
|
break;
|
|
case Discovery::HCARD:
|
|
$hints['hcardurl'] = $link['href'];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $hints;
|
|
}
|
|
|
|
static function fromHcardUrl($url)
|
|
{
|
|
$client = new HTTPClient();
|
|
$client->setHeader('Accept', 'text/html,application/xhtml+xml');
|
|
$response = $client->get($url);
|
|
|
|
if (!$response->isOk()) {
|
|
return null;
|
|
}
|
|
|
|
return self::hcardHints($response->getBody(),
|
|
$response->getUrl());
|
|
}
|
|
|
|
static function hcardHints($body, $url)
|
|
{
|
|
common_debug("starting tidy");
|
|
|
|
$body = self::_tidy($body, $url);
|
|
|
|
common_debug("done with tidy");
|
|
|
|
set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/');
|
|
require_once('hkit.class.php');
|
|
|
|
// hKit code is not clean for notices and warnings
|
|
$old = error_reporting();
|
|
error_reporting($old & ~E_NOTICE & ~E_WARNING);
|
|
|
|
$h = new hKit;
|
|
$hcards = $h->getByString('hcard', $body);
|
|
|
|
error_reporting($old);
|
|
|
|
if (empty($hcards)) {
|
|
return array();
|
|
}
|
|
|
|
if (count($hcards) == 1) {
|
|
$hcard = $hcards[0];
|
|
} else {
|
|
foreach ($hcards as $try) {
|
|
if (array_key_exists('url', $try)) {
|
|
if (is_string($try['url']) && $try['url'] == $url) {
|
|
$hcard = $try;
|
|
break;
|
|
} else if (is_array($try['url'])) {
|
|
foreach ($try['url'] as $tryurl) {
|
|
if ($tryurl == $url) {
|
|
$hcard = $try;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// last chance; grab the first one
|
|
if (empty($hcard)) {
|
|
$hcard = $hcards[0];
|
|
}
|
|
}
|
|
|
|
$hints = array();
|
|
|
|
if (array_key_exists('nickname', $hcard)) {
|
|
$hints['nickname'] = $hcard['nickname'];
|
|
}
|
|
|
|
if (array_key_exists('fn', $hcard)) {
|
|
$hints['fullname'] = $hcard['fn'];
|
|
} else if (array_key_exists('n', $hcard)) {
|
|
$hints['fullname'] = implode(' ', $hcard['n']);
|
|
}
|
|
|
|
if (array_key_exists('photo', $hcard)) {
|
|
$hints['avatar'] = $hcard['photo'];
|
|
}
|
|
|
|
if (array_key_exists('note', $hcard)) {
|
|
$hints['bio'] = $hcard['note'];
|
|
}
|
|
|
|
if (array_key_exists('adr', $hcard)) {
|
|
if (is_string($hcard['adr'])) {
|
|
$hints['location'] = $hcard['adr'];
|
|
} else if (is_array($hcard['adr'])) {
|
|
$hints['location'] = implode(' ', $hcard['adr']);
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('url', $hcard)) {
|
|
if (is_string($hcard['url'])) {
|
|
$hints['homepage'] = $hcard['url'];
|
|
} else if (is_array($hcard['url'])) {
|
|
// HACK get the last one; that's how our hcards look
|
|
$hints['homepage'] = $hcard['url'][count($hcard['url'])-1];
|
|
}
|
|
}
|
|
|
|
return $hints;
|
|
}
|
|
|
|
/**
|
|
* hKit needs well-formed XML for its parsing.
|
|
* We'll take the HTML body here and normalize it to XML.
|
|
*
|
|
* @param string $body HTML document source, possibly not-well-formed
|
|
* @param string $url source URL
|
|
* @return string well-formed XML document source
|
|
* @throws Exception if HTML parsing failed.
|
|
*/
|
|
private static function _tidy($body, $url)
|
|
{
|
|
if (empty($body)) {
|
|
throw new Exception("Empty HTML could not be parsed.");
|
|
}
|
|
$dom = new DOMDocument();
|
|
|
|
// Some HTML errors will trigger warnings, but still work.
|
|
$old = error_reporting();
|
|
error_reporting($old & ~E_WARNING);
|
|
|
|
$ok = $dom->loadHTML($body);
|
|
|
|
error_reporting($old);
|
|
|
|
if ($ok) {
|
|
// hKit doesn't give us a chance to pass the source URL for
|
|
// resolving relative links, such as the avatar photo on a
|
|
// Google profile. We'll slip it into a <base> tag if there's
|
|
// not already one present.
|
|
$bases = $dom->getElementsByTagName('base');
|
|
if ($bases && $bases->length >= 1) {
|
|
$base = $bases->item(0);
|
|
if ($base->hasAttribute('href')) {
|
|
$base->setAttribute('href', $url);
|
|
}
|
|
} else {
|
|
$base = $dom->createElement('base');
|
|
$base->setAttribute('href', $url);
|
|
$heads = $dom->getElementsByTagName('head');
|
|
if ($heads || $heads->length) {
|
|
$head = $heads->item(0);
|
|
} else {
|
|
$head = $dom->createElement('head');
|
|
$root = $dom->documentRoot;
|
|
if ($root->firstChild) {
|
|
$root->insertBefore($head, $root->firstChild);
|
|
} else {
|
|
$root->appendChild($head);
|
|
}
|
|
}
|
|
$head->appendChild($base);
|
|
}
|
|
return $dom->saveXML();
|
|
} else {
|
|
throw new Exception("Invalid HTML could not be parsed.");
|
|
}
|
|
}
|
|
}
|