forked from GNUsocial/gnu-social
		
	Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
This commit is contained in:
		@@ -1,7 +1,9 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
/**
 | 
			
		||||
 * StatusNet - the distributed open-source microblogging tool
 | 
			
		||||
 * Copyright (C) 2008, 2009, StatusNet, Inc.
 | 
			
		||||
 * Copyright (C) 2008-2010, StatusNet, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * Subscription action.
 | 
			
		||||
 *
 | 
			
		||||
 * 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
 | 
			
		||||
@@ -15,68 +17,142 @@
 | 
			
		||||
 *
 | 
			
		||||
 * 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/>.
 | 
			
		||||
 *
 | 
			
		||||
 * PHP version 5
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Action
 | 
			
		||||
 * @package   StatusNet
 | 
			
		||||
 * @author    Evan Prodromou <evan@status.net>
 | 
			
		||||
 * @copyright 2008-2010 StatusNet, Inc.
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 | 
			
		||||
 * @link      http://status.net/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 | 
			
		||||
if (!defined('STATUSNET')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Subscription action
 | 
			
		||||
 *
 | 
			
		||||
 * Subscribing to a profile. Does not work for OMB 0.1 remote subscriptions,
 | 
			
		||||
 * but may work for other remote subscription protocols, like OStatus.
 | 
			
		||||
 *
 | 
			
		||||
 * Takes parameters:
 | 
			
		||||
 *
 | 
			
		||||
 *    - subscribeto: a profile ID
 | 
			
		||||
 *    - token: session token to prevent CSRF attacks
 | 
			
		||||
 *    - ajax: boolean; whether to return Ajax or full-browser results
 | 
			
		||||
 *
 | 
			
		||||
 * Only works if the current user is logged in.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Action
 | 
			
		||||
 * @package   StatusNet
 | 
			
		||||
 * @author    Evan Prodromou <evan@status.net>
 | 
			
		||||
 * @copyright 2008-2010 StatusNet, Inc.
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 | 
			
		||||
 * @link      http://status.net/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class SubscribeAction extends Action
 | 
			
		||||
{
 | 
			
		||||
    var $user;
 | 
			
		||||
    var $other;
 | 
			
		||||
 | 
			
		||||
    function handle($args)
 | 
			
		||||
    /**
 | 
			
		||||
     * Check pre-requisites and instantiate attributes
 | 
			
		||||
     *
 | 
			
		||||
     * @param Array $args array of arguments (URL, GET, POST)
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean success flag
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function prepare($args)
 | 
			
		||||
    {
 | 
			
		||||
        parent::handle($args);
 | 
			
		||||
        parent::prepare($args);
 | 
			
		||||
 | 
			
		||||
        if (!common_logged_in()) {
 | 
			
		||||
            $this->clientError(_('Not logged in.'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user = common_current_user();
 | 
			
		||||
        // Only allow POST requests
 | 
			
		||||
 | 
			
		||||
        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
 | 
			
		||||
            common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
 | 
			
		||||
            return;
 | 
			
		||||
            $this->clientError(_('This action only accepts POST requests.'));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # CSRF protection
 | 
			
		||||
        // CSRF protection
 | 
			
		||||
 | 
			
		||||
        $token = $this->trimmed('token');
 | 
			
		||||
 | 
			
		||||
        if (!$token || $token != common_session_token()) {
 | 
			
		||||
            $this->clientError(_('There was a problem with your session token. Try again, please.'));
 | 
			
		||||
            return;
 | 
			
		||||
            $this->clientError(_('There was a problem with your session token.'.
 | 
			
		||||
                                 ' Try again, please.'));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Only for logged-in users
 | 
			
		||||
 | 
			
		||||
        $this->user = common_current_user();
 | 
			
		||||
 | 
			
		||||
        if (empty($this->user)) {
 | 
			
		||||
            $this->clientError(_('Not logged in.'));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Profile to subscribe to
 | 
			
		||||
 | 
			
		||||
        $other_id = $this->arg('subscribeto');
 | 
			
		||||
 | 
			
		||||
        $other = User::staticGet('id', $other_id);
 | 
			
		||||
        $this->other = Profile::staticGet('id', $other_id);
 | 
			
		||||
 | 
			
		||||
        if (!$other) {
 | 
			
		||||
            $this->clientError(_('Not a local user.'));
 | 
			
		||||
            return;
 | 
			
		||||
        if (empty($this->other)) {
 | 
			
		||||
            $this->clientError(_('No such profile.'));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $result = subs_subscribe_to($user, $other);
 | 
			
		||||
        // OMB 0.1 doesn't have a mechanism for local-server-
 | 
			
		||||
        // originated subscription.
 | 
			
		||||
 | 
			
		||||
        if (is_string($result)) {
 | 
			
		||||
            $this->clientError($result);
 | 
			
		||||
            return;
 | 
			
		||||
        $omb01 = Remote_profile::staticGet('id', $other_id);
 | 
			
		||||
 | 
			
		||||
        if (!empty($omb01)) {
 | 
			
		||||
            $this->clientError(_('You cannot subscribe to an OMB 0.1'.
 | 
			
		||||
                                 ' remote profile with this action.'));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle request
 | 
			
		||||
     *
 | 
			
		||||
     * Does the subscription and returns results.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Array $args unused.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function handle($args)
 | 
			
		||||
    {
 | 
			
		||||
        // Throws exception on error
 | 
			
		||||
 | 
			
		||||
        Subscription::start($this->user->getProfile(),
 | 
			
		||||
                            $this->other);
 | 
			
		||||
 | 
			
		||||
        if ($this->boolean('ajax')) {
 | 
			
		||||
            $this->startHTML('text/xml;charset=utf-8');
 | 
			
		||||
            $this->elementStart('head');
 | 
			
		||||
            $this->element('title', null, _('Subscribed'));
 | 
			
		||||
            $this->elementEnd('head');
 | 
			
		||||
            $this->elementStart('body');
 | 
			
		||||
            $unsubscribe = new UnsubscribeForm($this, $other->getProfile());
 | 
			
		||||
            $unsubscribe = new UnsubscribeForm($this, $this->other->getProfile());
 | 
			
		||||
            $unsubscribe->show();
 | 
			
		||||
            $this->elementEnd('body');
 | 
			
		||||
            $this->elementEnd('html');
 | 
			
		||||
        } else {
 | 
			
		||||
            common_redirect(common_local_url('subscriptions', array('nickname' =>
 | 
			
		||||
                                                                $user->nickname)),
 | 
			
		||||
                            303);
 | 
			
		||||
            $url = common_local_url('subscriptions',
 | 
			
		||||
                                    array('nickname' => $this->user->nickname));
 | 
			
		||||
            common_redirect($url, 303);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -501,7 +501,11 @@ class Memcached_DataObject extends Safe_DataObject
 | 
			
		||||
 | 
			
		||||
    function raiseError($message, $type = null, $behaviour = null)
 | 
			
		||||
    {
 | 
			
		||||
        throw new ServerException("DB_DataObject error [$type]: $message");
 | 
			
		||||
        $id = get_class($this);
 | 
			
		||||
        if ($this->id) {
 | 
			
		||||
            $id .= ':' . $this->id;
 | 
			
		||||
        }
 | 
			
		||||
        throw new ServerException("[$id] DB_DataObject error [$type]: $message");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function cacheGet($keyPart)
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,7 @@ class PoCo
 | 
			
		||||
    function __construct($profile)
 | 
			
		||||
    {
 | 
			
		||||
        $this->preferredUsername = $profile->nickname;
 | 
			
		||||
        $this->displayName       = $profile->getBestName();
 | 
			
		||||
 | 
			
		||||
        $this->note    = $profile->bio;
 | 
			
		||||
        $this->address = new PoCoAddress($profile->location);
 | 
			
		||||
@@ -129,6 +130,12 @@ class PoCo
 | 
			
		||||
            $this->preferredUsername
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $xs->element(
 | 
			
		||||
            'poco:displayName',
 | 
			
		||||
            null,
 | 
			
		||||
            $this->displayName
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (!empty($this->note)) {
 | 
			
		||||
            $xs->element('poco:note', null, $this->note);
 | 
			
		||||
        }
 | 
			
		||||
@@ -823,7 +830,9 @@ class Activity
 | 
			
		||||
        if ($namespace) {
 | 
			
		||||
            $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
 | 
			
		||||
                           'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
 | 
			
		||||
                           'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
 | 
			
		||||
                           'xmlns:georss' => 'http://www.georss.org/georss',
 | 
			
		||||
                           'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
 | 
			
		||||
                           'xmlns:poco' => 'http://portablecontacts.net/spec/1.0');
 | 
			
		||||
        } else {
 | 
			
		||||
            $attrs = array();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -438,14 +438,15 @@ class NoticeListItem extends Widget
 | 
			
		||||
        $this->out->text(_('at'));
 | 
			
		||||
        $this->out->text(' ');
 | 
			
		||||
        if (empty($url)) {
 | 
			
		||||
            $this->out->element('span', array('class' => 'geo',
 | 
			
		||||
            $this->out->element('abbr', array('class' => 'geo',
 | 
			
		||||
                                              'title' => $latlon),
 | 
			
		||||
                                $name);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->out->element('a', array('class' => 'geo',
 | 
			
		||||
                                           'title' => $latlon,
 | 
			
		||||
                                           'href' => $url),
 | 
			
		||||
            $this->out->elementStart('a', array('href' => $url));
 | 
			
		||||
            $this->out->element('abbr', array('class' => 'geo',
 | 
			
		||||
                                              'title' => $latlon),
 | 
			
		||||
                                $name);
 | 
			
		||||
            $this->out->elementEnd('a');
 | 
			
		||||
        }
 | 
			
		||||
        $this->out->elementEnd('span');
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -224,7 +224,7 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function onEndFindMentions($sender, $text, &$mentions)
 | 
			
		||||
    function onStartFindMentions($sender, $text, &$mentions)
 | 
			
		||||
    {
 | 
			
		||||
        preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/',
 | 
			
		||||
                       $text,
 | 
			
		||||
@@ -251,58 +251,6 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notify remote server and garbage collect unused feeds on unsubscribe.
 | 
			
		||||
     * @fixme send these operations to background queues
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Profile $other
 | 
			
		||||
     * @return hook return value
 | 
			
		||||
     */
 | 
			
		||||
    function onEndUnsubscribe($profile, $other)
 | 
			
		||||
    {
 | 
			
		||||
        $user = User::staticGet('id', $profile->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($user)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($oprofile)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Drop the PuSH subscription if there are no other subscribers.
 | 
			
		||||
 | 
			
		||||
        if ($other->subscriberCount() == 0) {
 | 
			
		||||
            common_log(LOG_INFO, "Unsubscribing from now-unused feed $oprofile->feeduri");
 | 
			
		||||
            $oprofile->unsubscribe();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $act = new Activity();
 | 
			
		||||
 | 
			
		||||
        $act->verb = ActivityVerb::UNFOLLOW;
 | 
			
		||||
 | 
			
		||||
        $act->id   = TagURI::mint('unfollow:%d:%d:%s',
 | 
			
		||||
                                  $profile->id,
 | 
			
		||||
                                  $other->id,
 | 
			
		||||
                                  common_date_iso8601(time()));
 | 
			
		||||
 | 
			
		||||
        $act->time    = time();
 | 
			
		||||
        $act->title   = _("Unfollow");
 | 
			
		||||
        $act->content = sprintf(_("%s stopped following %s."),
 | 
			
		||||
                               $profile->getBestName(),
 | 
			
		||||
                               $other->getBestName());
 | 
			
		||||
 | 
			
		||||
        $act->actor   = ActivityObject::fromProfile($profile);
 | 
			
		||||
        $act->object  = ActivityObject::fromProfile($other);
 | 
			
		||||
 | 
			
		||||
        $oprofile->notifyActivity($act);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Make sure necessary tables are filled out.
 | 
			
		||||
     */
 | 
			
		||||
@@ -312,6 +260,7 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
        $schema->ensureTable('ostatus_source', Ostatus_source::schemaDef());
 | 
			
		||||
        $schema->ensureTable('feedsub', FeedSub::schemaDef());
 | 
			
		||||
        $schema->ensureTable('hubsub', HubSub::schemaDef());
 | 
			
		||||
        $schema->ensureTable('magicsig', Magicsig::schemaDef());
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -338,13 +287,19 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
    function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
 | 
			
		||||
    {
 | 
			
		||||
        if ($notice->source == 'ostatus') {
 | 
			
		||||
            $bits = parse_url($notice->uri);
 | 
			
		||||
            $domain = $bits['host'];
 | 
			
		||||
            if ($notice->url) {
 | 
			
		||||
                $bits = parse_url($notice->url);
 | 
			
		||||
                $domain = $bits['host'];
 | 
			
		||||
                if (substr($domain, 0, 4) == 'www.') {
 | 
			
		||||
                    $name = substr($domain, 4);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $name = $domain;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            $name = $domain;
 | 
			
		||||
            $url = $notice->uri;
 | 
			
		||||
            $title = sprintf(_m("Sent from %s via OStatus"), $domain);
 | 
			
		||||
            return false;
 | 
			
		||||
                $url = $notice->url;
 | 
			
		||||
                $title = sprintf(_m("Sent from %s via OStatus"), $domain);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -359,12 +314,56 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
    {
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
 | 
			
		||||
        if ($oprofile) {
 | 
			
		||||
            $oprofile->processFeed($feed);
 | 
			
		||||
            $oprofile->processFeed($feed, 'push');
 | 
			
		||||
        } else {
 | 
			
		||||
            common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When about to subscribe to a remote user, start a server-to-server
 | 
			
		||||
     * PuSH subscription if needed. If we can't establish that, abort.
 | 
			
		||||
     *
 | 
			
		||||
     * @fixme If something else aborts later, we could end up with a stray
 | 
			
		||||
     *        PuSH subscription. This is relatively harmless, though.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Profile $subscriber
 | 
			
		||||
     * @param Profile $other
 | 
			
		||||
     *
 | 
			
		||||
     * @return hook return code
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    function onStartSubscribe($subscriber, $other)
 | 
			
		||||
    {
 | 
			
		||||
        $user = User::staticGet('id', $subscriber->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($user)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($oprofile)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$oprofile->subscribe()) {
 | 
			
		||||
            throw new Exception(_m('Could not set up remote subscription.'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Having established a remote subscription, send a notification to the
 | 
			
		||||
     * remote OStatus profile's endpoint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Profile $subscriber
 | 
			
		||||
     * @param Profile $other
 | 
			
		||||
     *
 | 
			
		||||
     * @return hook return code
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    function onEndSubscribe($subscriber, $other)
 | 
			
		||||
    {
 | 
			
		||||
        $user = User::staticGet('id', $subscriber->id);
 | 
			
		||||
@@ -402,6 +401,54 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notify remote server and garbage collect unused feeds on unsubscribe.
 | 
			
		||||
     * @fixme send these operations to background queues
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Profile $other
 | 
			
		||||
     * @return hook return value
 | 
			
		||||
     */
 | 
			
		||||
    function onEndUnsubscribe($profile, $other)
 | 
			
		||||
    {
 | 
			
		||||
        $user = User::staticGet('id', $profile->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($user)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
 | 
			
		||||
 | 
			
		||||
        if (empty($oprofile)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Drop the PuSH subscription if there are no other subscribers.
 | 
			
		||||
        $oprofile->garbageCollect();
 | 
			
		||||
 | 
			
		||||
        $act = new Activity();
 | 
			
		||||
 | 
			
		||||
        $act->verb = ActivityVerb::UNFOLLOW;
 | 
			
		||||
 | 
			
		||||
        $act->id   = TagURI::mint('unfollow:%d:%d:%s',
 | 
			
		||||
                                  $profile->id,
 | 
			
		||||
                                  $other->id,
 | 
			
		||||
                                  common_date_iso8601(time()));
 | 
			
		||||
 | 
			
		||||
        $act->time    = time();
 | 
			
		||||
        $act->title   = _("Unfollow");
 | 
			
		||||
        $act->content = sprintf(_("%s stopped following %s."),
 | 
			
		||||
                               $profile->getBestName(),
 | 
			
		||||
                               $other->getBestName());
 | 
			
		||||
 | 
			
		||||
        $act->actor   = ActivityObject::fromProfile($profile);
 | 
			
		||||
        $act->object  = ActivityObject::fromProfile($other);
 | 
			
		||||
 | 
			
		||||
        $oprofile->notifyActivity($act);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When one of our local users tries to join a remote group,
 | 
			
		||||
     * notify the remote server. If the notification is rejected,
 | 
			
		||||
@@ -417,6 +464,10 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
    {
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('group_id', $group->id);
 | 
			
		||||
        if ($oprofile) {
 | 
			
		||||
            if (!$oprofile->subscribe()) {
 | 
			
		||||
                throw new Exception(_m('Could not set up remote group membership.'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $member = Profile::staticGet($user->id);
 | 
			
		||||
 | 
			
		||||
            $act = new Activity();
 | 
			
		||||
@@ -438,7 +489,8 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
            if ($oprofile->notifyActivity($act)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new ServerException(_m("Failed joining remote group."));
 | 
			
		||||
                $oprofile->garbageCollect();
 | 
			
		||||
                throw new Exception(_m("Failed joining remote group."));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -463,12 +515,7 @@ class OStatusPlugin extends Plugin
 | 
			
		||||
        $oprofile = Ostatus_profile::staticGet('group_id', $group->id);
 | 
			
		||||
        if ($oprofile) {
 | 
			
		||||
            // Drop the PuSH subscription if there are no other subscribers.
 | 
			
		||||
    
 | 
			
		||||
            $members = $group->getMembers(0, 1);
 | 
			
		||||
            if ($members->N == 0) {
 | 
			
		||||
                common_log(LOG_INFO, "Unsubscribing from now-unused group feed $oprofile->feeduri");
 | 
			
		||||
                $oprofile->unsubscribe();
 | 
			
		||||
            }
 | 
			
		||||
            $oprofile->garbageCollect();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            $member = Profile::staticGet($user->id);
 | 
			
		||||
 
 | 
			
		||||
@@ -87,53 +87,168 @@ class OStatusSubAction extends Action
 | 
			
		||||
     */
 | 
			
		||||
    function showPreviewForm()
 | 
			
		||||
    {
 | 
			
		||||
        $this->elementStart('form', array('method' => 'post',
 | 
			
		||||
                                          'id' => 'form_ostatus_sub',
 | 
			
		||||
                                          'class' => 'form_settings',
 | 
			
		||||
                                          'action' =>
 | 
			
		||||
                                          common_local_url('ostatussub')));
 | 
			
		||||
 | 
			
		||||
        $this->hidden('token', common_session_token());
 | 
			
		||||
        $this->hidden('profile', $this->profile_uri);
 | 
			
		||||
 | 
			
		||||
        $this->elementStart('fieldset', array('id' => 'settings_feeds'));
 | 
			
		||||
 | 
			
		||||
        if ($this->oprofile->isGroup()) {
 | 
			
		||||
            $this->previewGroup();
 | 
			
		||||
            $this->submit('subscribe', _m('Join'));
 | 
			
		||||
            $ok = $this->previewGroup();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->previewUser();
 | 
			
		||||
            $this->submit('subscribe', _m('Subscribe'));
 | 
			
		||||
            $ok = $this->previewUser();
 | 
			
		||||
        }
 | 
			
		||||
        if (!$ok) {
 | 
			
		||||
            // @fixme maybe provide a cancel button or link back?
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $this->elementStart('div', 'entity_actions');
 | 
			
		||||
        $this->elementStart('ul');
 | 
			
		||||
        $this->elementStart('li', 'entity_subscribe');
 | 
			
		||||
        $this->elementStart('form', array('method' => 'post',
 | 
			
		||||
                                          'id' => 'form_ostatus_sub',
 | 
			
		||||
                                          'class' => 'form_remote_authorize',
 | 
			
		||||
                                          'action' =>
 | 
			
		||||
                                          common_local_url('ostatussub')));
 | 
			
		||||
        $this->elementStart('fieldset');
 | 
			
		||||
        $this->hidden('token', common_session_token());
 | 
			
		||||
        $this->hidden('profile', $this->profile_uri);
 | 
			
		||||
        if ($this->oprofile->isGroup()) {
 | 
			
		||||
            $this->submit('submit', _m('Join'), 'submit', null,
 | 
			
		||||
                         _m('Join this group'));
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->submit('submit', _m('Subscribe'), 'submit', null,
 | 
			
		||||
                         _m('Subscribe to this user'));
 | 
			
		||||
        }
 | 
			
		||||
        $this->elementEnd('fieldset');
 | 
			
		||||
 | 
			
		||||
        $this->elementEnd('form');
 | 
			
		||||
        $this->elementEnd('li');
 | 
			
		||||
        $this->elementEnd('ul');
 | 
			
		||||
        $this->elementEnd('div');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show a preview for a remote user's profile
 | 
			
		||||
     * @return boolean true if we're ok to try subscribing
 | 
			
		||||
     */
 | 
			
		||||
    function previewUser()
 | 
			
		||||
    {
 | 
			
		||||
        $oprofile = $this->oprofile;
 | 
			
		||||
        $profile = $oprofile->localProfile();
 | 
			
		||||
 | 
			
		||||
        $this->text(sprintf(_m("Remote user %s"), $profile->nickname));
 | 
			
		||||
        // ...
 | 
			
		||||
        $cur = common_current_user();
 | 
			
		||||
        if ($cur->isSubscribed($profile)) {
 | 
			
		||||
            $this->element('div', array('class' => 'error'),
 | 
			
		||||
                           _m("You are already subscribed to this user."));
 | 
			
		||||
            $ok = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            $ok = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
 | 
			
		||||
        $avatarUrl = $avatar ? $avatar->displayUrl() : false;
 | 
			
		||||
 | 
			
		||||
        $this->showEntity($profile,
 | 
			
		||||
                          $profile->profileurl,
 | 
			
		||||
                          $avatarUrl,
 | 
			
		||||
                          $profile->bio);
 | 
			
		||||
        return $ok;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show a preview for a remote group's profile
 | 
			
		||||
     * @return boolean true if we're ok to try joining
 | 
			
		||||
     */
 | 
			
		||||
    function previewGroup()
 | 
			
		||||
    {
 | 
			
		||||
        $oprofile = $this->oprofile;
 | 
			
		||||
        $group = $oprofile->localGroup();
 | 
			
		||||
 | 
			
		||||
        $this->text(sprintf(_m("Remote group %s"), $group->nickname));
 | 
			
		||||
        // ..
 | 
			
		||||
        $cur = common_current_user();
 | 
			
		||||
        if ($cur->isMember($group)) {
 | 
			
		||||
            $this->element('div', array('class' => 'error'),
 | 
			
		||||
                           _m("You are already a member of this group."));
 | 
			
		||||
            $ok = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            $ok = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->showEntity($group,
 | 
			
		||||
                          $group->getProfileUrl(),
 | 
			
		||||
                          $group->homepage_logo,
 | 
			
		||||
                          $group->description);
 | 
			
		||||
        return $ok;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function showEntity($entity, $profile, $avatar, $note)
 | 
			
		||||
    {
 | 
			
		||||
        $nickname = $entity->nickname;
 | 
			
		||||
        $fullname = $entity->fullname;
 | 
			
		||||
        $homepage = $entity->homepage;
 | 
			
		||||
        $location = $entity->location;
 | 
			
		||||
        
 | 
			
		||||
        if (!$avatar) {
 | 
			
		||||
            $avatar = Avatar::defaultImage(AVATAR_PROFILE_SIZE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->elementStart('div', 'entity_profile vcard');
 | 
			
		||||
        $this->elementStart('dl', 'entity_depiction');
 | 
			
		||||
        $this->element('dt', null, _('Photo'));
 | 
			
		||||
        $this->elementStart('dd');
 | 
			
		||||
        $this->element('img', array('src' => $avatar,
 | 
			
		||||
                                    'class' => 'photo avatar',
 | 
			
		||||
                                    'width' => AVATAR_PROFILE_SIZE,
 | 
			
		||||
                                    'height' => AVATAR_PROFILE_SIZE,
 | 
			
		||||
                                    'alt' => $nickname));
 | 
			
		||||
        $this->elementEnd('dd');
 | 
			
		||||
        $this->elementEnd('dl');
 | 
			
		||||
 | 
			
		||||
        $this->elementStart('dl', 'entity_nickname');
 | 
			
		||||
        $this->element('dt', null, _('Nickname'));
 | 
			
		||||
        $this->elementStart('dd');
 | 
			
		||||
        $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
 | 
			
		||||
        $this->elementStart('a', array('href' => $profile,
 | 
			
		||||
                                       'class' => 'url '.$hasFN));
 | 
			
		||||
        $this->raw($nickname);
 | 
			
		||||
        $this->elementEnd('a');
 | 
			
		||||
        $this->elementEnd('dd');
 | 
			
		||||
        $this->elementEnd('dl');
 | 
			
		||||
 | 
			
		||||
        if (!is_null($fullname)) {
 | 
			
		||||
            $this->elementStart('dl', 'entity_fn');
 | 
			
		||||
            $this->elementStart('dd');
 | 
			
		||||
            $this->elementStart('span', 'fn');
 | 
			
		||||
            $this->raw($fullname);
 | 
			
		||||
            $this->elementEnd('span');
 | 
			
		||||
            $this->elementEnd('dd');
 | 
			
		||||
            $this->elementEnd('dl');
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_null($location)) {
 | 
			
		||||
            $this->elementStart('dl', 'entity_location');
 | 
			
		||||
            $this->element('dt', null, _('Location'));
 | 
			
		||||
            $this->elementStart('dd', 'label');
 | 
			
		||||
            $this->raw($location);
 | 
			
		||||
            $this->elementEnd('dd');
 | 
			
		||||
            $this->elementEnd('dl');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!is_null($homepage)) {
 | 
			
		||||
            $this->elementStart('dl', 'entity_url');
 | 
			
		||||
            $this->element('dt', null, _('URL'));
 | 
			
		||||
            $this->elementStart('dd');
 | 
			
		||||
            $this->elementStart('a', array('href' => $homepage,
 | 
			
		||||
                                                'class' => 'url'));
 | 
			
		||||
            $this->raw($homepage);
 | 
			
		||||
            $this->elementEnd('a');
 | 
			
		||||
            $this->elementEnd('dd');
 | 
			
		||||
            $this->elementEnd('dl');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!is_null($note)) {
 | 
			
		||||
            $this->elementStart('dl', 'entity_note');
 | 
			
		||||
            $this->element('dt', null, _('Note'));
 | 
			
		||||
            $this->elementStart('dd', 'note');
 | 
			
		||||
            $this->raw($note);
 | 
			
		||||
            $this->elementEnd('dd');
 | 
			
		||||
            $this->elementEnd('dl');
 | 
			
		||||
        }
 | 
			
		||||
        $this->elementEnd('div');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -173,10 +288,15 @@ class OStatusSubAction extends Action
 | 
			
		||||
        }
 | 
			
		||||
        $this->profile_uri = $profile_uri;
 | 
			
		||||
 | 
			
		||||
        // @fixme validate, normalize bla bla
 | 
			
		||||
        try {
 | 
			
		||||
            $oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
 | 
			
		||||
            $this->oprofile = $oprofile;
 | 
			
		||||
            if (Validate::email($this->profile_uri)) {
 | 
			
		||||
                $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
 | 
			
		||||
            } else if (Validate::uri($this->profile_uri)) {
 | 
			
		||||
                $this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->error = _m("Invalid address format.");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (FeedSubBadURLException $e) {
 | 
			
		||||
            $this->error = _m('Invalid URL or could not reach server.');
 | 
			
		||||
@@ -209,11 +329,6 @@ class OStatusSubAction extends Action
 | 
			
		||||
        // And subscribe the current user to the local profile
 | 
			
		||||
        $user = common_current_user();
 | 
			
		||||
 | 
			
		||||
        if (!$this->oprofile->subscribe()) {
 | 
			
		||||
            $this->showForm(_m("Failed to set up server-to-server subscription."));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->oprofile->isGroup()) {
 | 
			
		||||
            $group = $this->oprofile->localGroup();
 | 
			
		||||
            if ($user->isMember($group)) {
 | 
			
		||||
@@ -287,7 +402,7 @@ class OStatusSubAction extends Action
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->validateFeed()) {
 | 
			
		||||
            if ($this->arg('subscribe')) {
 | 
			
		||||
            if ($this->arg('submit')) {
 | 
			
		||||
                $this->saveFeed();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -343,7 +458,7 @@ class OStatusSubAction extends Action
 | 
			
		||||
 | 
			
		||||
    function showPageNotice()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->error) {
 | 
			
		||||
        if (!empty($this->error)) {
 | 
			
		||||
            $this->element('p', 'error', $this->error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -65,12 +65,38 @@ class WebfingerAction extends Action
 | 
			
		||||
                                                               'format' => 'atom')),
 | 
			
		||||
                              'type' => 'application/atom+xml');
 | 
			
		||||
 | 
			
		||||
        // hCard
 | 
			
		||||
        $xrd->links[] = array('rel' => 'http://microformats.org/profile/hcard',
 | 
			
		||||
                              'type' => 'text/html',
 | 
			
		||||
                              'href' => common_profile_url($nick));
 | 
			
		||||
 | 
			
		||||
        // XFN
 | 
			
		||||
        $xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
 | 
			
		||||
                              'type' => 'text/html',
 | 
			
		||||
                              'href' => common_profile_url($nick));
 | 
			
		||||
        // FOAF
 | 
			
		||||
        $xrd->links[] = array('rel' => 'describedby',
 | 
			
		||||
                              'type' => 'application/rdf+xml',
 | 
			
		||||
                              'href' => common_local_url('foaf',
 | 
			
		||||
                                                         array('nickname' => $nick)));                        
 | 
			
		||||
        
 | 
			
		||||
        $salmon_url = common_local_url('salmon',
 | 
			
		||||
                                       array('id' => $this->user->id));
 | 
			
		||||
 | 
			
		||||
        $xrd->links[] = array('rel' => 'salmon',
 | 
			
		||||
                              'href' => $salmon_url);
 | 
			
		||||
 | 
			
		||||
        // Get this user's keypair
 | 
			
		||||
        $magickey = Magicsig::staticGet('user_id', $this->user->id);
 | 
			
		||||
        if (!$magickey) {
 | 
			
		||||
            // No keypair yet, let's generate one.
 | 
			
		||||
            $magickey = new Magicsig();
 | 
			
		||||
            $magickey->generate();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
 | 
			
		||||
                              'href' => 'data:application/magic-public-key;'. $magickey->keypair);
 | 
			
		||||
        
 | 
			
		||||
        // TODO - finalize where the redirect should go on the publisher
 | 
			
		||||
        $url = common_local_url('ostatussub') . '?profile={uri}';
 | 
			
		||||
        $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe',
 | 
			
		||||
 
 | 
			
		||||
@@ -29,45 +29,86 @@
 | 
			
		||||
 | 
			
		||||
require_once 'Crypt/RSA.php';
 | 
			
		||||
 | 
			
		||||
interface Magicsig
 | 
			
		||||
class Magicsig extends Memcached_DataObject
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function sign($bytes);
 | 
			
		||||
    const PUBLICKEYREL = 'magic-public-key';
 | 
			
		||||
    
 | 
			
		||||
    public function verify($signed, $signature_b64);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MagicsigRsaSha256
 | 
			
		||||
{
 | 
			
		||||
    public $__table = 'magicsig';
 | 
			
		||||
 | 
			
		||||
    public $user_id;
 | 
			
		||||
    public $keypair;
 | 
			
		||||
    public $alg;
 | 
			
		||||
    
 | 
			
		||||
    public function __construct($init = null)
 | 
			
		||||
    private $_rsa;
 | 
			
		||||
 | 
			
		||||
    public function __construct($alg = 'RSA-SHA256')
 | 
			
		||||
    {
 | 
			
		||||
        if (is_null($init)) {
 | 
			
		||||
            $this->generate();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->fromString($init);
 | 
			
		||||
        }
 | 
			
		||||
        $this->alg = $alg;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public /*static*/ function staticGet($k, $v=null)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::staticGet(__CLASS__, $k, $v);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function table()
 | 
			
		||||
    {
 | 
			
		||||
        return array(
 | 
			
		||||
            'user_id' => DB_DATAOBJECT_INT,
 | 
			
		||||
            'keypair' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
 | 
			
		||||
            'alg'     => DB_DATAOBJECT_STR
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function schemaDef()
 | 
			
		||||
    {
 | 
			
		||||
        return array(new ColumnDef('user_id', 'integer',
 | 
			
		||||
                                   null, true, 'PRI'),
 | 
			
		||||
                     new ColumnDef('keypair', 'varchar',
 | 
			
		||||
                                   255, false),
 | 
			
		||||
                     new ColumnDef('alg', 'varchar',
 | 
			
		||||
                                   64, false));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function keys()
 | 
			
		||||
    {
 | 
			
		||||
        return array_keys($this->keyTypes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function keyTypes()
 | 
			
		||||
    {
 | 
			
		||||
        return array('user_id' => 'K');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function insert()
 | 
			
		||||
    {
 | 
			
		||||
        $this->keypair = $this->toString();
 | 
			
		||||
 | 
			
		||||
        return parent::insert();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function generate($key_length = 512)
 | 
			
		||||
    {
 | 
			
		||||
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
 | 
			
		||||
 | 
			
		||||
        $keypair = new Crypt_RSA_KeyPair($key_length);
 | 
			
		||||
        $params['public_key'] = $keypair->getPublicKey();
 | 
			
		||||
        $params['private_key'] = $keypair->getPrivateKey();
 | 
			
		||||
 | 
			
		||||
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
 | 
			
		||||
        $this->keypair = new Crypt_RSA($params);
 | 
			
		||||
        $this->_rsa = new Crypt_RSA($params);
 | 
			
		||||
        PEAR::popErrorHandling();
 | 
			
		||||
 | 
			
		||||
        $this->insert();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function toString($full_pair = true)
 | 
			
		||||
    {
 | 
			
		||||
        $public_key = $this->keypair->_public_key;
 | 
			
		||||
        $private_key = $this->keypair->_private_key;
 | 
			
		||||
        $public_key = $this->_rsa->_public_key;
 | 
			
		||||
        $private_key = $this->_rsa->_private_key;
 | 
			
		||||
 | 
			
		||||
        $mod = base64_url_encode($public_key->getModulus());
 | 
			
		||||
        $exp = base64_url_encode($public_key->getExponent());
 | 
			
		||||
@@ -79,10 +120,12 @@ class MagicsigRsaSha256
 | 
			
		||||
        return 'RSA.' . $mod . '.' . $exp . $private_exp; 
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function fromString($text)
 | 
			
		||||
    public static function fromString($text)
 | 
			
		||||
    {
 | 
			
		||||
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
 | 
			
		||||
 | 
			
		||||
        $magic_sig = new Magicsig();
 | 
			
		||||
        
 | 
			
		||||
        // remove whitespace
 | 
			
		||||
        $text = preg_replace('/\s+/', '', $text);
 | 
			
		||||
 | 
			
		||||
@@ -100,33 +143,46 @@ class MagicsigRsaSha256
 | 
			
		||||
        $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');
 | 
			
		||||
        if ($params['public_key']->isError()) {
 | 
			
		||||
            $error = $params['public_key']->getLastError();
 | 
			
		||||
            print $error->getMessage();
 | 
			
		||||
            exit;
 | 
			
		||||
            common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if ($private_exp) {
 | 
			
		||||
            $params['private_key'] = new Crypt_RSA_KEY($mod, $private_exp, 'private');
 | 
			
		||||
            if ($params['private_key']->isError()) {
 | 
			
		||||
                $error = $params['private_key']->getLastError();
 | 
			
		||||
                print $error->getMessage();
 | 
			
		||||
                exit;
 | 
			
		||||
                common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->keypair = new Crypt_RSA($params);
 | 
			
		||||
        $magic_sig->_rsa = new Crypt_RSA($params);
 | 
			
		||||
        PEAR::popErrorHandling();
 | 
			
		||||
 | 
			
		||||
        return $magic_sig;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getName()
 | 
			
		||||
    {
 | 
			
		||||
        return 'RSA-SHA256';
 | 
			
		||||
        return $this->alg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getHash()
 | 
			
		||||
    {
 | 
			
		||||
        switch ($this->alg) {
 | 
			
		||||
 | 
			
		||||
        case 'RSA-SHA256':
 | 
			
		||||
            return 'sha256';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function sign($bytes)
 | 
			
		||||
    {
 | 
			
		||||
        $sig = $this->keypair->createSign($bytes, null, 'sha256');
 | 
			
		||||
        if ($this->keypair->isError()) {
 | 
			
		||||
            $error = $this->keypair->getLastError();
 | 
			
		||||
        $sig = $this->_rsa->createSign($bytes, null, 'sha256');
 | 
			
		||||
        if ($this->_rsa->isError()) {
 | 
			
		||||
            $error = $this->_rsa->getLastError();
 | 
			
		||||
            common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $sig;
 | 
			
		||||
@@ -134,11 +190,11 @@ class MagicsigRsaSha256
 | 
			
		||||
 | 
			
		||||
    public function verify($signed_bytes, $signature)
 | 
			
		||||
    {
 | 
			
		||||
        $result =  $this->keypair->validateSign($signed_bytes, $signature, null, 'sha256');
 | 
			
		||||
        if ($this->keypair->isError()) {
 | 
			
		||||
        $result =  $this->_rsa->validateSign($signed_bytes, $signature, null, 'sha256');
 | 
			
		||||
        if ($this->_rsa->isError()) {
 | 
			
		||||
            $error = $this->keypair->getLastError();
 | 
			
		||||
            //common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
 | 
			
		||||
            print $error->getMessage();
 | 
			
		||||
            common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
@@ -346,6 +346,29 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if this remote profile has any active local subscriptions, and
 | 
			
		||||
     * if not drop the PuSH subscription feed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean
 | 
			
		||||
     */
 | 
			
		||||
    public function garbageCollect()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->isGroup()) {
 | 
			
		||||
            $members = $this->localGroup()->getMembers(0, 1);
 | 
			
		||||
            $count = $members->N;
 | 
			
		||||
        } else {
 | 
			
		||||
            $count = $this->localProfile()->subscriberCount();
 | 
			
		||||
        }
 | 
			
		||||
        if ($count == 0) {
 | 
			
		||||
            common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $oprofile->feeduri");
 | 
			
		||||
            $this->unsubscribe();
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send an Activity Streams notification to the remote Salmon endpoint,
 | 
			
		||||
     * if so configured.
 | 
			
		||||
@@ -379,7 +402,8 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
                                'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
 | 
			
		||||
                                'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
 | 
			
		||||
                                'xmlns:georss' => 'http://www.georss.org/georss',
 | 
			
		||||
                                'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
 | 
			
		||||
                                'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
 | 
			
		||||
                                'xmlns:poco' => 'http://portablecontacts.net/spec/1.0');
 | 
			
		||||
 | 
			
		||||
            $entry = new XMLStringer();
 | 
			
		||||
            $entry->elementStart('entry', $attributes);
 | 
			
		||||
@@ -464,7 +488,7 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
     *
 | 
			
		||||
     * @param DOMDocument $feed
 | 
			
		||||
     */
 | 
			
		||||
    public function processFeed($feed)
 | 
			
		||||
    public function processFeed($feed, $source)
 | 
			
		||||
    {
 | 
			
		||||
        $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
 | 
			
		||||
        if ($entries->length == 0) {
 | 
			
		||||
@@ -474,7 +498,7 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
 | 
			
		||||
        for ($i = 0; $i < $entries->length; $i++) {
 | 
			
		||||
            $entry = $entries->item($i);
 | 
			
		||||
            $this->processEntry($entry, $feed);
 | 
			
		||||
            $this->processEntry($entry, $feed, $source);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -484,15 +508,12 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
     * @param DOMElement $entry
 | 
			
		||||
     * @param DOMElement $feed for context
 | 
			
		||||
     */
 | 
			
		||||
    protected function processEntry($entry, $feed)
 | 
			
		||||
    public function processEntry($entry, $feed, $source)
 | 
			
		||||
    {
 | 
			
		||||
        $activity = new Activity($entry, $feed);
 | 
			
		||||
 | 
			
		||||
        $debug = var_export($activity, true);
 | 
			
		||||
        common_log(LOG_DEBUG, $debug);
 | 
			
		||||
 | 
			
		||||
        if ($activity->verb == ActivityVerb::POST) {
 | 
			
		||||
            $this->processPost($activity);
 | 
			
		||||
            $this->processPost($activity, $source);
 | 
			
		||||
        } else {
 | 
			
		||||
            common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
 | 
			
		||||
        }
 | 
			
		||||
@@ -501,130 +522,176 @@ class Ostatus_profile extends Memcached_DataObject
 | 
			
		||||
    /**
 | 
			
		||||
     * Process an incoming post activity from this remote feed.
 | 
			
		||||
     * @param Activity $activity
 | 
			
		||||
     * @param string $method 'push' or 'salmon'
 | 
			
		||||
     * @return mixed saved Notice or false
 | 
			
		||||
     * @fixme break up this function, it's getting nasty long
 | 
			
		||||
     */
 | 
			
		||||
    protected function processPost($activity)
 | 
			
		||||
    public function processPost($activity, $method)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->isGroup()) {
 | 
			
		||||
            // A group feed will contain posts from multiple authors.
 | 
			
		||||
            // @fixme validate these profiles in some way!
 | 
			
		||||
            $oprofile = self::ensureActorProfile($activity);
 | 
			
		||||
            if ($oprofile->isGroup()) {
 | 
			
		||||
                // Groups can't post notices in StatusNet.
 | 
			
		||||
                common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Individual user feeds may contain only posts from themselves.
 | 
			
		||||
            // Authorship is validated against the profile URI on upper layers,
 | 
			
		||||
            // through PuSH setup or Salmon signature checks.
 | 
			
		||||
            $actorUri = self::getActorProfileURI($activity);
 | 
			
		||||
            if ($actorUri == $this->uri) {
 | 
			
		||||
                // @fixme check if profile info has changed and update it
 | 
			
		||||
            } else {
 | 
			
		||||
                // @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely
 | 
			
		||||
                common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri");
 | 
			
		||||
                //return;
 | 
			
		||||
                common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            $oprofile = $this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The id URI will be used as a unique identifier for for the notice,
 | 
			
		||||
        // protecting against duplicate saves. It isn't required to be a URL;
 | 
			
		||||
        // tag: URIs for instance are found in Google Buzz feeds.
 | 
			
		||||
        $sourceUri = $activity->object->id;
 | 
			
		||||
 | 
			
		||||
        $dupe = Notice::staticGet('uri', $sourceUri);
 | 
			
		||||
 | 
			
		||||
        if ($dupe) {
 | 
			
		||||
            common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
 | 
			
		||||
            return;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We'll also want to save a web link to the original notice, if provided.
 | 
			
		||||
        $sourceUrl = null;
 | 
			
		||||
 | 
			
		||||
        if ($activity->object->link) {
 | 
			
		||||
            $sourceUrl = $activity->object->link;
 | 
			
		||||
        } else if ($activity->link) {
 | 
			
		||||
            $sourceUrl = $activity->link;
 | 
			
		||||
        } else if (preg_match('!^https?://!', $activity->object->id)) {
 | 
			
		||||
            $sourceUrl = $activity->object->id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // @fixme sanitize and save HTML content if available
 | 
			
		||||
        // Get (safe!) HTML and text versions of the content
 | 
			
		||||
        $rendered = $this->purify($activity->object->content);
 | 
			
		||||
        $content = html_entity_decode(strip_tags($rendered));
 | 
			
		||||
 | 
			
		||||
        $content = $activity->object->title;
 | 
			
		||||
 | 
			
		||||
        $params = array('is_local' => Notice::REMOTE_OMB,
 | 
			
		||||
        $options = array('is_local' => Notice::REMOTE_OMB,
 | 
			
		||||
                        'url' => $sourceUrl,
 | 
			
		||||
                        'uri' => $sourceUri);
 | 
			
		||||
                        'uri' => $sourceUri,
 | 
			
		||||
                        'rendered' => $rendered,
 | 
			
		||||
                        'replies' => array(),
 | 
			
		||||
                        'groups' => array());
 | 
			
		||||
 | 
			
		||||
        $location = $activity->context->location;
 | 
			
		||||
        // Check for optional attributes...
 | 
			
		||||
 | 
			
		||||
        if ($location) {
 | 
			
		||||
            $params['lat'] = $location->lat;
 | 
			
		||||
            $params['lon'] = $location->lon;
 | 
			
		||||
            if ($location->location_id) {
 | 
			
		||||
                $params['location_ns'] = $location->location_ns;
 | 
			
		||||
                $params['location_id'] = $location->location_id;
 | 
			
		||||
            }
 | 
			
		||||
        if (!empty($activity->time)) {
 | 
			
		||||
            $options['created'] = common_sql_date($activity->time);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $profile = $oprofile->localProfile();
 | 
			
		||||
        $params['groups'] = array();
 | 
			
		||||
        $params['replies'] = array();
 | 
			
		||||
        if ($activity->context) {
 | 
			
		||||
            foreach ($activity->context->attention as $recipient) {
 | 
			
		||||
                $roprofile = Ostatus_profile::staticGet('uri', $recipient);
 | 
			
		||||
                if ($roprofile) {
 | 
			
		||||
                    if ($roprofile->isGroup()) {
 | 
			
		||||
                        // Deliver to local recipients of this remote group.
 | 
			
		||||
                        // @fixme sender verification?
 | 
			
		||||
                        $params['groups'][] = $roprofile->group_id;
 | 
			
		||||
                        continue;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Delivery to remote users is the source service's job.
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            // Any individual or group attn: targets?
 | 
			
		||||
            $replies = $activity->context->attention;
 | 
			
		||||
            $options['groups'] = $this->filterReplies($oprofile, $replies);
 | 
			
		||||
            $options['replies'] = $replies;
 | 
			
		||||
 | 
			
		||||
                $user = User::staticGet('uri', $recipient);
 | 
			
		||||
                if ($user) {
 | 
			
		||||
                    // An @-reply directed to a local user.
 | 
			
		||||
                    // @fixme sender verification, spam etc?
 | 
			
		||||
                    $params['replies'][] = $recipient;
 | 
			
		||||
                    continue;
 | 
			
		||||
            // Maintain direct reply associations
 | 
			
		||||
            // @fixme what about conversation ID?
 | 
			
		||||
            if (!empty($activity->context->replyToID)) {
 | 
			
		||||
                $orig = Notice::staticGet('uri',
 | 
			
		||||
                                          $activity->context->replyToID);
 | 
			
		||||
                if (!empty($orig)) {
 | 
			
		||||
                    $options['reply_to'] = $orig->id;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                // @fixme we need a uri on user_group
 | 
			
		||||
                // $group = User_group::staticGet('uri', $recipient);
 | 
			
		||||
                $template = common_local_url('groupbyid', array('id' => '31337'));
 | 
			
		||||
                $template = preg_quote($template, '/');
 | 
			
		||||
                $template = str_replace('31337', '(\d+)', $template);
 | 
			
		||||
                common_log(LOG_DEBUG, $template);
 | 
			
		||||
                if (preg_match("/$template/", $recipient, $matches)) {
 | 
			
		||||
                    $id = $matches[1];
 | 
			
		||||
                    $group = User_group::staticGet('id', $id);
 | 
			
		||||
                    if ($group) {
 | 
			
		||||
                        // Deliver to all members of this local group.
 | 
			
		||||
                        // @fixme sender verification?
 | 
			
		||||
                        if ($profile->isMember($group)) {
 | 
			
		||||
                            common_log(LOG_DEBUG, "delivering to group $id $group->nickname");
 | 
			
		||||
                            $params['groups'][] = $group->id;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            common_log(LOG_DEBUG, "not delivering to group $id $group->nickname because sender $profile->nickname is not a member");
 | 
			
		||||
                        }
 | 
			
		||||
                        continue;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        common_log(LOG_DEBUG, "not delivering to missing group $id");
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    common_log(LOG_DEBUG, "not delivering to groups for $recipient");
 | 
			
		||||
            $location = $activity->context->location;
 | 
			
		||||
            if ($location) {
 | 
			
		||||
                $options['lat'] = $location->lat;
 | 
			
		||||
                $options['lon'] = $location->lon;
 | 
			
		||||
                if ($location->location_id) {
 | 
			
		||||
                    $options['location_ns'] = $location->location_ns;
 | 
			
		||||
                    $options['location_id'] = $location->location_id;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $saved = Notice::saveNew($profile->id,
 | 
			
		||||
            $saved = Notice::saveNew($oprofile->profile_id,
 | 
			
		||||
                                     $content,
 | 
			
		||||
                                     'ostatus',
 | 
			
		||||
                                     $params);
 | 
			
		||||
                                     $options);
 | 
			
		||||
            if ($saved) {
 | 
			
		||||
                Ostatus_source::saveNew($saved, $this, $method);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            common_log(LOG_ERR, "Failed saving notice entry for $sourceUri: " . $e->getMessage());
 | 
			
		||||
            return;
 | 
			
		||||
            common_log(LOG_ERR, "OStatus save of remote message $sourceUri failed: " . $e->getMessage());
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        common_log(LOG_INFO, "OStatus saved remote message $sourceUri as notice id $saved->id");
 | 
			
		||||
        return $saved;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // Record which feed this came through...
 | 
			
		||||
        try {
 | 
			
		||||
            Ostatus_source::saveNew($saved, $this, 'push');
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            common_log(LOG_ERR, "Failed saving ostatus_source entry for $saved->notice_id: " . $e->getMessage());
 | 
			
		||||
    /**
 | 
			
		||||
     * Clean up HTML
 | 
			
		||||
     */
 | 
			
		||||
    protected function purify($html)
 | 
			
		||||
    {
 | 
			
		||||
        // @fixme disable caching or set a sane temp dir
 | 
			
		||||
        require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
 | 
			
		||||
        $purifier = new HTMLPurifier();
 | 
			
		||||
        return $purifier->purify($html);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filters a list of recipient ID URIs to just those for local delivery.
 | 
			
		||||
     * @param Ostatus_profile local profile of sender
 | 
			
		||||
     * @param array in/out &$attention_uris set of URIs, will be pruned on output
 | 
			
		||||
     * @return array of group IDs
 | 
			
		||||
     */
 | 
			
		||||
    protected function filterReplies($sender, &$attention_uris)
 | 
			
		||||
    {
 | 
			
		||||
        $groups = array();
 | 
			
		||||
        $replies = array();
 | 
			
		||||
        foreach ($attention_uris as $recipient) {
 | 
			
		||||
            // Is the recipient a local user?
 | 
			
		||||
            $user = User::staticGet('uri', $recipient);
 | 
			
		||||
            if ($user) {
 | 
			
		||||
                // @fixme sender verification, spam etc?
 | 
			
		||||
                $replies[] = $recipient;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Is the recipient a remote group?
 | 
			
		||||
            $oprofile = Ostatus_profile::staticGet('uri', $recipient);
 | 
			
		||||
            if ($oprofile) {
 | 
			
		||||
                if ($oprofile->isGroup()) {
 | 
			
		||||
                    // Deliver to local members of this remote group.
 | 
			
		||||
                    // @fixme sender verification?
 | 
			
		||||
                    $groups[] = $oprofile->group_id;
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Is the recipient a local group?
 | 
			
		||||
            // @fixme we need a uri on user_group
 | 
			
		||||
            // $group = User_group::staticGet('uri', $recipient);
 | 
			
		||||
            $template = common_local_url('groupbyid', array('id' => '31337'));
 | 
			
		||||
            $template = preg_quote($template, '/');
 | 
			
		||||
            $template = str_replace('31337', '(\d+)', $template);
 | 
			
		||||
            if (preg_match("/$template/", $recipient, $matches)) {
 | 
			
		||||
                $id = $matches[1];
 | 
			
		||||
                $group = User_group::staticGet('id', $id);
 | 
			
		||||
                if ($group) {
 | 
			
		||||
                    // Deliver to all members of this local group if allowed.
 | 
			
		||||
                    if ($sender->localProfile()->isMember($group)) {
 | 
			
		||||
                        $groups[] = $group->id;
 | 
			
		||||
                    }
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $attention_uris = $replies;
 | 
			
		||||
        return $groups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,6 @@
 | 
			
		||||
 * @link      http://status.net/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require_once 'magicsig.php';
 | 
			
		||||
 | 
			
		||||
class MagicEnvelope
 | 
			
		||||
{
 | 
			
		||||
    const ENCODING = 'base64url';
 | 
			
		||||
@@ -64,7 +62,7 @@ class MagicEnvelope
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $signature_alg = new MagicsigRsaSha256($this->getKeyPair($signer_uri));
 | 
			
		||||
        $signature_alg = Magicsig::fromString($this->getKeyPair($signer_uri));
 | 
			
		||||
        $armored_text = base64_encode($text);
 | 
			
		||||
 | 
			
		||||
        return array(
 | 
			
		||||
@@ -139,7 +137,7 @@ class MagicEnvelope
 | 
			
		||||
        $text = base64_decode($env['data']);
 | 
			
		||||
        $signer_uri = $this->getAuthor($text);
 | 
			
		||||
 | 
			
		||||
        $verifier = new MagicsigRsaSha256($this->getKeyPair($signer_uri));
 | 
			
		||||
        $verifier = Magicsig::fromString($this->getKeyPair($signer_uri));
 | 
			
		||||
 | 
			
		||||
        return $verifier->verify($env['data'], $env['sig']);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -185,54 +185,6 @@ class SalmonAction extends Action
 | 
			
		||||
    function saveNotice()
 | 
			
		||||
    {
 | 
			
		||||
        $oprofile = $this->ensureProfile();
 | 
			
		||||
 | 
			
		||||
        // Get (safe!) HTML and text versions of the content
 | 
			
		||||
 | 
			
		||||
        require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
 | 
			
		||||
 | 
			
		||||
        $html = $this->act->object->content;
 | 
			
		||||
 | 
			
		||||
        $purifier = new HTMLPurifier();
 | 
			
		||||
 | 
			
		||||
        $rendered = $purifier->purify($html);
 | 
			
		||||
 | 
			
		||||
        $content = html_entity_decode(strip_tags($rendered));
 | 
			
		||||
 | 
			
		||||
        $options = array('is_local' => Notice::REMOTE_OMB,
 | 
			
		||||
                         'uri' => $this->act->object->id,
 | 
			
		||||
                         'url' => $this->act->object->link,
 | 
			
		||||
                         'rendered' => $rendered,
 | 
			
		||||
                         'replies' => $this->act->context->attention);
 | 
			
		||||
 | 
			
		||||
        if (!empty($this->act->context->location)) {
 | 
			
		||||
            $options['lat'] = $location->lat;
 | 
			
		||||
            $options['lon'] = $location->lon;
 | 
			
		||||
            if ($location->location_id) {
 | 
			
		||||
                $options['location_ns'] = $location->location_ns;
 | 
			
		||||
                $options['location_id'] = $location->location_id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!empty($this->act->context->replyToID)) {
 | 
			
		||||
            $orig = Notice::staticGet('uri',
 | 
			
		||||
                                      $this->act->context->replyToID);
 | 
			
		||||
            if (!empty($orig)) {
 | 
			
		||||
                $options['reply_to'] = $orig->id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!empty($this->act->time)) {
 | 
			
		||||
            $options['created'] = common_sql_date($this->act->time);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $saved = Notice::saveNew($oprofile->profile_id,
 | 
			
		||||
                                 $content,
 | 
			
		||||
                                 'ostatus+salmon',
 | 
			
		||||
                                 $options);
 | 
			
		||||
 | 
			
		||||
        // Record that this was saved through a validated Salmon source
 | 
			
		||||
        // @fixme actually do the signature validation!
 | 
			
		||||
        Ostatus_source::saveNew($saved, $oprofile, 'salmon');
 | 
			
		||||
        return $saved;
 | 
			
		||||
        return $oprofile->processPost($this->act, 'salmon');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,10 @@ class Webfinger
 | 
			
		||||
 | 
			
		||||
        $content = $this->fetchURL($url);
 | 
			
		||||
 | 
			
		||||
        if (!$content) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return XRD::parse($content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,7 @@ button.close,
 | 
			
		||||
.form_user_unsubscribe input.submit,
 | 
			
		||||
.form_group_join input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a,
 | 
			
		||||
.entity_moderation p,
 | 
			
		||||
.entity_sandbox input.submit,
 | 
			
		||||
@@ -291,6 +292,7 @@ background-position:0 1px;
 | 
			
		||||
.form_group_leave input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_user_unsubscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a {
 | 
			
		||||
background-color:#AAAAAA;
 | 
			
		||||
color:#FFFFFF;
 | 
			
		||||
@@ -301,6 +303,7 @@ background-position:5px -1246px;
 | 
			
		||||
}
 | 
			
		||||
.form_group_join input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a {
 | 
			
		||||
background-position:5px -1181px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,7 @@ button.close,
 | 
			
		||||
.form_user_unsubscribe input.submit,
 | 
			
		||||
.form_group_join input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a,
 | 
			
		||||
.entity_moderation p,
 | 
			
		||||
.entity_sandbox input.submit,
 | 
			
		||||
@@ -290,6 +291,7 @@ background-position:0 1px;
 | 
			
		||||
.form_group_leave input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_user_unsubscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a {
 | 
			
		||||
background-color:#AAAAAA;
 | 
			
		||||
color:#FFFFFF;
 | 
			
		||||
@@ -300,6 +302,7 @@ background-position:5px -1246px;
 | 
			
		||||
}
 | 
			
		||||
.form_group_join input.submit,
 | 
			
		||||
.form_user_subscribe input.submit,
 | 
			
		||||
.form_remote_authorize input.submit,
 | 
			
		||||
.entity_subscribe a {
 | 
			
		||||
background-position:5px -1181px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user