forked from GNUsocial/gnu-social
Merge branch 'master' into 1.0.x
This commit is contained in:
@@ -30,11 +30,13 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
|
||||
class GravatarPlugin extends Plugin
|
||||
{
|
||||
function onInitializePlugin() {
|
||||
function onInitializePlugin()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function onStartAvatarFormData($action) {
|
||||
function onStartAvatarFormData($action)
|
||||
{
|
||||
$user = common_current_user();
|
||||
$hasGravatar = $this->hasGravatar($user->id);
|
||||
|
||||
@@ -43,7 +45,8 @@ class GravatarPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
function onEndAvatarFormData(&$action) {
|
||||
function onEndAvatarFormData($action)
|
||||
{
|
||||
$user = common_current_user();
|
||||
$hasGravatar = $this->hasGravatar($user->id);
|
||||
|
||||
@@ -89,7 +92,8 @@ class GravatarPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
function onStartAvatarSaveForm($action) {
|
||||
function onStartAvatarSaveForm($action)
|
||||
{
|
||||
if ($action->arg('add')) {
|
||||
$result = $this->gravatar_save();
|
||||
|
||||
@@ -178,7 +182,8 @@ class GravatarPlugin extends Plugin
|
||||
'success' => true);
|
||||
}
|
||||
|
||||
function gravatar_url($email, $size) {
|
||||
function gravatar_url($email, $size)
|
||||
{
|
||||
$url = "http://www.gravatar.com/avatar.php?gravatar_id=".
|
||||
md5(strtolower($email)).
|
||||
"&default=".urlencode(Avatar::defaultImage($size)).
|
||||
@@ -197,4 +202,4 @@ class GravatarPlugin extends Plugin
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,6 +28,15 @@ set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/ext
|
||||
|
||||
class FeedSubException extends Exception
|
||||
{
|
||||
function __construct($msg=null)
|
||||
{
|
||||
$type = get_class($this);
|
||||
if ($msg) {
|
||||
parent::__construct("$type: $msg");
|
||||
} else {
|
||||
parent::__construct($type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OStatusPlugin extends Plugin
|
||||
@@ -479,6 +488,24 @@ class OStatusPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the FeedSub infrastructure whether we have any active OStatus
|
||||
* usage for the feed; if not it'll be able to garbage-collect the
|
||||
* feed subscription.
|
||||
*
|
||||
* @param FeedSub $feedsub
|
||||
* @param integer $count in/out
|
||||
* @return mixed hook return code
|
||||
*/
|
||||
function onFeedSubSubscriberCount($feedsub, &$count)
|
||||
{
|
||||
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
|
||||
if ($oprofile) {
|
||||
$count += $oprofile->subscriberCount();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When about to subscribe to a remote user, start a server-to-server
|
||||
* PuSH subscription if needed. If we can't establish that, abort.
|
||||
|
@@ -1,18 +1,42 @@
|
||||
Plugin to support importing updates from external RSS and Atom feeds into your timeline.
|
||||
Plugin to support importing and exporting notices through Atom and RSS feeds.
|
||||
The OStatus plugin concentrates on user-to-user cases for federating StatusNet
|
||||
and similar social networking / microblogging / blogging sites, but includes
|
||||
low-level feed subscription systems which are used by some other plugins.
|
||||
|
||||
Uses PubSubHubbub for push feed updates; currently non-PuSH feeds cannot be
|
||||
subscribed unless an external PuSH hub proxy is used.
|
||||
|
||||
Uses PubSubHubbub for push feed updates; currently non-PuSH feeds cannot be subscribed.
|
||||
|
||||
Configuration options available:
|
||||
|
||||
$config['ostatus']['hub']
|
||||
(default internal hub)
|
||||
Set to URL of an external PuSH hub to use it instead of our internal hub.
|
||||
Set to URL of an external PuSH hub to use it instead of our internal hub
|
||||
for sending outgoing updates in user and group feeds.
|
||||
|
||||
$config['ostatus']['hub_retries']
|
||||
(default 0)
|
||||
Number of times to retry a PuSH send to consumers if using internal hub
|
||||
|
||||
|
||||
Settings controlling incoming feed subscription:
|
||||
|
||||
$config['feedsub']['fallback_hub']
|
||||
To subscribe to feeds that don't have a hub, an external PuSH proxy hub
|
||||
such as Superfeedr may be used. Any feed without a hub of its own will
|
||||
be subscribed through the specified hub URL instead. If the external hub
|
||||
has usage charges, be aware that there is no restriction placed to how
|
||||
many feeds may be subscribed!
|
||||
|
||||
$config['feedsub']['fallback_hub'] = 'https://superfeedr.com/hubbub';
|
||||
|
||||
$config['feedsub']['hub_user']
|
||||
$config['feedsub']['hub_password']
|
||||
If using the fallback hub mode, these settings may be used to provide
|
||||
HTTP authentication credentials for contacting the hub. Default hubs
|
||||
specified from feeds are assumed to not require
|
||||
|
||||
|
||||
For testing, shouldn't be used in production:
|
||||
|
||||
$config['ostatus']['skip_signatures']
|
||||
@@ -23,12 +47,11 @@ $config['feedsub']['nohub']
|
||||
(default require hub)
|
||||
Allow low-level feed subscription setup for feeds without hubs.
|
||||
Not actually usable at this stage, OStatus will check for hubs too
|
||||
and we have no polling backend.
|
||||
and we have no polling backend. (The fallback hub option can be used
|
||||
with a 3rd-party service to provide such polling.)
|
||||
|
||||
|
||||
Todo:
|
||||
* fully functional l10n
|
||||
* redo non-OStatus feed support
|
||||
** rssCloud support?
|
||||
** possibly a polling daemon to support non-PuSH feeds?
|
||||
* better support for feeds that aren't natively oriented at social networking
|
||||
* make use of tags/categories from feeds
|
||||
* better repeat handling
|
||||
|
@@ -61,7 +61,7 @@ class GroupsalmonAction extends SalmonAction
|
||||
function handlePost()
|
||||
{
|
||||
// @fixme process all objects?
|
||||
switch ($this->act->objects[0]->type) {
|
||||
switch ($this->activity->objects[0]->type) {
|
||||
case ActivityObject::ARTICLE:
|
||||
case ActivityObject::BLOGENTRY:
|
||||
case ActivityObject::NOTE:
|
||||
@@ -74,7 +74,7 @@ class GroupsalmonAction extends SalmonAction
|
||||
|
||||
// Notice must be to the attention of this group
|
||||
|
||||
$context = $this->act->context;
|
||||
$context = $this->activity->context;
|
||||
|
||||
if (empty($context->attention)) {
|
||||
throw new ClientException("Not to the attention of anyone.");
|
||||
|
@@ -55,10 +55,10 @@ class UsersalmonAction extends SalmonAction
|
||||
*/
|
||||
function handlePost()
|
||||
{
|
||||
common_log(LOG_INFO, "Received post of '{$this->act->objects[0]->id}' from '{$this->act->actor->id}'");
|
||||
common_log(LOG_INFO, "Received post of '{$this->activity->objects[0]->id}' from '{$this->activity->actor->id}'");
|
||||
|
||||
// @fixme: process all activity objects?
|
||||
switch ($this->act->objects[0]->type) {
|
||||
switch ($this->activity->objects[0]->type) {
|
||||
case ActivityObject::ARTICLE:
|
||||
case ActivityObject::BLOGENTRY:
|
||||
case ActivityObject::NOTE:
|
||||
@@ -72,7 +72,7 @@ class UsersalmonAction extends SalmonAction
|
||||
// Notice must either be a) in reply to a notice by this user
|
||||
// or b) to the attention of this user
|
||||
|
||||
$context = $this->act->context;
|
||||
$context = $this->activity->context;
|
||||
|
||||
if (!empty($context->replyToID)) {
|
||||
$notice = Notice::staticGet('uri', $context->replyToID);
|
||||
@@ -92,7 +92,7 @@ class UsersalmonAction extends SalmonAction
|
||||
throw new ClientException("Not to anyone in reply to anything!");
|
||||
}
|
||||
|
||||
$existing = Notice::staticGet('uri', $this->act->objects[0]->id);
|
||||
$existing = Notice::staticGet('uri', $this->activity->objects[0]->id);
|
||||
|
||||
if (!empty($existing)) {
|
||||
common_log(LOG_ERR, "Not saving notice '{$existing->uri}'; already exists.");
|
||||
@@ -143,7 +143,7 @@ class UsersalmonAction extends SalmonAction
|
||||
|
||||
function handleFavorite()
|
||||
{
|
||||
$notice = $this->getNotice($this->act->objects[0]);
|
||||
$notice = $this->getNotice($this->activity->objects[0]);
|
||||
$profile = $this->ensureProfile()->localProfile();
|
||||
|
||||
$old = Fave::pkeyGet(array('user_id' => $profile->id,
|
||||
@@ -164,7 +164,7 @@ class UsersalmonAction extends SalmonAction
|
||||
*/
|
||||
function handleUnfavorite()
|
||||
{
|
||||
$notice = $this->getNotice($this->act->objects[0]);
|
||||
$notice = $this->getNotice($this->activity->objects[0]);
|
||||
$profile = $this->ensureProfile()->localProfile();
|
||||
|
||||
$fave = Fave::pkeyGet(array('user_id' => $profile->id,
|
||||
|
@@ -207,8 +207,8 @@ class FeedSub extends Memcached_DataObject
|
||||
$discover = new FeedDiscovery();
|
||||
$discover->discoverFromFeedURL($feeduri);
|
||||
|
||||
$huburi = $discover->getAtomLink('hub');
|
||||
if (!$huburi) {
|
||||
$huburi = $discover->getHubLink();
|
||||
if (!$huburi && !common_config('feedsub', 'fallback_hub')) {
|
||||
throw new FeedSubNoHubException();
|
||||
}
|
||||
|
||||
@@ -241,8 +241,12 @@ class FeedSub extends Memcached_DataObject
|
||||
common_log(LOG_WARNING, "Attempting to (re)start PuSH subscription to $this->uri in unexpected state $this->sub_state");
|
||||
}
|
||||
if (empty($this->huburi)) {
|
||||
if (common_config('feedsub', 'nohub')) {
|
||||
if (common_config('feedsub', 'fallback_hub')) {
|
||||
// No native hub on this feed?
|
||||
// Use our fallback hub, which handles polling on our behalf.
|
||||
} else if (common_config('feedsub', 'nohub')) {
|
||||
// Fake it! We're just testing remote feeds w/o hubs.
|
||||
// We'll never actually get updates in this mode.
|
||||
return true;
|
||||
} else {
|
||||
throw new ServerException("Attempting to start PuSH subscription for feed with no hub");
|
||||
@@ -255,6 +259,9 @@ class FeedSub extends Memcached_DataObject
|
||||
/**
|
||||
* Send a PuSH unsubscription request to the hub for this feed.
|
||||
* The hub will later send us a confirmation POST to /main/push/callback.
|
||||
* Warning: this will cancel the subscription even if someone else in
|
||||
* the system is using it. Most callers will want garbageCollect() instead,
|
||||
* which confirms there's no uses left.
|
||||
*
|
||||
* @return bool true on success, false on failure
|
||||
* @throws ServerException if feed state is not valid
|
||||
@@ -264,8 +271,12 @@ class FeedSub extends Memcached_DataObject
|
||||
common_log(LOG_WARNING, "Attempting to (re)end PuSH subscription to $this->uri in unexpected state $this->sub_state");
|
||||
}
|
||||
if (empty($this->huburi)) {
|
||||
if (common_config('feedsub', 'nohub')) {
|
||||
if (common_config('feedsub', 'fallback_hub')) {
|
||||
// No native hub on this feed?
|
||||
// Use our fallback hub, which handles polling on our behalf.
|
||||
} else if (common_config('feedsub', 'nohub')) {
|
||||
// Fake it! We're just testing remote feeds w/o hubs.
|
||||
// We'll never actually get updates in this mode.
|
||||
return true;
|
||||
} else {
|
||||
throw new ServerException("Attempting to end PuSH subscription for feed with no hub");
|
||||
@@ -275,6 +286,33 @@ class FeedSub extends Memcached_DataObject
|
||||
return $this->doSubscribe('unsubscribe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any active local uses of this feed, and if not then
|
||||
* make sure it's inactive, unsubscribing if necessary.
|
||||
*
|
||||
* @return boolean true if the subscription is now inactive, false if still active.
|
||||
*/
|
||||
public function garbageCollect()
|
||||
{
|
||||
if ($this->sub_state == '' || $this->sub_state == 'inactive') {
|
||||
// No active PuSH subscription, we can just leave it be.
|
||||
return true;
|
||||
} else {
|
||||
// PuSH subscription is either active or in an indeterminate state.
|
||||
// Check if we're out of subscribers, and if so send an unsubscribe.
|
||||
$count = 0;
|
||||
Event::handle('FeedSubSubscriberCount', array($this, &$count));
|
||||
|
||||
if ($count) {
|
||||
common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->uri);
|
||||
return false;
|
||||
} else {
|
||||
common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->uri);
|
||||
return $this->unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function doSubscribe($mode)
|
||||
{
|
||||
$orig = clone($this);
|
||||
@@ -296,7 +334,21 @@ class FeedSub extends Memcached_DataObject
|
||||
'hub.secret' => $this->secret,
|
||||
'hub.topic' => $this->uri);
|
||||
$client = new HTTPClient();
|
||||
$response = $client->post($this->huburi, $headers, $post);
|
||||
if ($this->huburi) {
|
||||
$hub = $this->huburi;
|
||||
} else {
|
||||
if (common_config('feedsub', 'fallback_hub')) {
|
||||
$hub = common_config('feedsub', 'fallback_hub');
|
||||
if (common_config('feedsub', 'hub_user')) {
|
||||
$u = common_config('feedsub', 'hub_user');
|
||||
$p = common_config('feedsub', 'hub_pass');
|
||||
$client->setAuth($u, $p);
|
||||
}
|
||||
} else {
|
||||
throw new FeedSubException('WTF?');
|
||||
}
|
||||
}
|
||||
$response = $client->post($hub, $headers, $post);
|
||||
$status = $response->getStatus();
|
||||
if ($status == 202) {
|
||||
common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
|
||||
|
@@ -215,22 +215,13 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PuSH unsubscription request to the hub for this feed.
|
||||
* The hub will later send us a confirmation POST to /main/push/callback.
|
||||
* Check if this remote profile has any active local subscriptions, and
|
||||
* if not drop the PuSH subscription feed.
|
||||
*
|
||||
* @return bool true on success, false on failure
|
||||
* @throws ServerException if feed state is not valid
|
||||
*/
|
||||
public function unsubscribe() {
|
||||
$feedsub = FeedSub::staticGet('uri', $this->feeduri);
|
||||
if (!$feedsub || $feedsub->sub_state == '' || $feedsub->sub_state == 'inactive') {
|
||||
// No active PuSH subscription, we can just leave it be.
|
||||
return true;
|
||||
} else {
|
||||
// PuSH subscription is either active or in an indeterminate state.
|
||||
// Send an unsubscribe.
|
||||
return $feedsub->unsubscribe();
|
||||
}
|
||||
$this->garbageCollect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,6 +231,21 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
* @return boolean
|
||||
*/
|
||||
public function garbageCollect()
|
||||
{
|
||||
$feedsub = FeedSub::staticGet('uri', $this->feeduri);
|
||||
return $feedsub->garbageCollect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this remote profile has any active local subscriptions, so the
|
||||
* PuSH subscription layer can decide if it can drop the feed.
|
||||
*
|
||||
* This gets called via the FeedSubSubscriberCount event when running
|
||||
* FeedSub::garbageCollect().
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function subscriberCount()
|
||||
{
|
||||
if ($this->isGroup()) {
|
||||
$members = $this->localGroup()->getMembers(0, 1);
|
||||
@@ -247,13 +253,14 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
} else {
|
||||
$count = $this->localProfile()->subscriberCount();
|
||||
}
|
||||
if ($count == 0) {
|
||||
common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $this->feeduri");
|
||||
$this->unsubscribe();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
common_log(LOG_INFO, __METHOD__ . " SUB COUNT BEFORE: $count");
|
||||
|
||||
// Other plugins may be piggybacking on OStatus without having
|
||||
// an active group or user-to-user subscription we know about.
|
||||
Event::handle('Ostatus_profileSubscriberCount', array($this, &$count));
|
||||
common_log(LOG_INFO, __METHOD__ . " SUB COUNT AFTER: $count");
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,26 +445,31 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
* @param DOMElement $feed for context
|
||||
* @param string $source identifier ("push" or "salmon")
|
||||
*/
|
||||
|
||||
public function processEntry($entry, $feed, $source)
|
||||
{
|
||||
$activity = new Activity($entry, $feed);
|
||||
|
||||
// @todo process all activity objects
|
||||
switch ($activity->objects[0]->type) {
|
||||
case ActivityObject::ARTICLE:
|
||||
case ActivityObject::BLOGENTRY:
|
||||
case ActivityObject::NOTE:
|
||||
case ActivityObject::STATUS:
|
||||
case ActivityObject::COMMENT:
|
||||
break;
|
||||
default:
|
||||
throw new ClientException("Can't handle that kind of post.");
|
||||
}
|
||||
if (Event::handle('StartHandleFeedEntry', array($activity))) {
|
||||
|
||||
if ($activity->verb == ActivityVerb::POST) {
|
||||
$this->processPost($activity, $source);
|
||||
} else {
|
||||
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
|
||||
// @todo process all activity objects
|
||||
switch ($activity->objects[0]->type) {
|
||||
case ActivityObject::ARTICLE:
|
||||
case ActivityObject::BLOGENTRY:
|
||||
case ActivityObject::NOTE:
|
||||
case ActivityObject::STATUS:
|
||||
case ActivityObject::COMMENT:
|
||||
if ($activity->verb == ActivityVerb::POST) {
|
||||
$this->processPost($activity, $source);
|
||||
} else {
|
||||
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ClientException("Can't handle that kind of post.");
|
||||
}
|
||||
|
||||
Event::handle('EndHandleFeedEntry', array($activity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,8 +498,14 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
// OK here! assume the default
|
||||
} else if ($actor->id == $this->uri || $actor->link == $this->uri) {
|
||||
$this->updateFromActivityObject($actor);
|
||||
} else {
|
||||
} else if ($actor->id) {
|
||||
// We have an ActivityStreams actor with an explicit ID that doesn't match the feed owner.
|
||||
// This isn't what we expect from mainline OStatus person feeds!
|
||||
// Group feeds go down another path, with different validation.
|
||||
throw new Exception("Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}");
|
||||
} else {
|
||||
// Plain <author> without ActivityStreams actor info.
|
||||
// We'll just ignore this info for now and save the update under the feed's identity.
|
||||
}
|
||||
|
||||
$oprofile = $this;
|
||||
@@ -668,7 +686,7 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
common_log(LOG_DEBUG, "Original reply recipients: " . implode(', ', $attention_uris));
|
||||
$groups = array();
|
||||
$replies = array();
|
||||
foreach ($attention_uris as $recipient) {
|
||||
foreach (array_unique($attention_uris) as $recipient) {
|
||||
// Is the recipient a local user?
|
||||
$user = User::staticGet('uri', $recipient);
|
||||
if ($user) {
|
||||
@@ -862,12 +880,12 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
$feeduri = $discover->discoverFromFeedURL($feed_url);
|
||||
$hints['feedurl'] = $feeduri;
|
||||
|
||||
$huburi = $discover->getAtomLink('hub');
|
||||
$huburi = $discover->getHubLink();
|
||||
$hints['hub'] = $huburi;
|
||||
$salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES);
|
||||
$hints['salmon'] = $salmonuri;
|
||||
|
||||
if (!$huburi) {
|
||||
if (!$huburi && !common_config('feedsub', 'fallback_hub')) {
|
||||
// We can only deal with folks with a PuSH hub
|
||||
throw new FeedSubNoHubException();
|
||||
}
|
||||
@@ -1263,10 +1281,10 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
$discover = new FeedDiscovery();
|
||||
$discover->discoverFromFeedURL($hints['feedurl']);
|
||||
}
|
||||
$huburi = $discover->getAtomLink('hub');
|
||||
$huburi = $discover->getHubLink();
|
||||
}
|
||||
|
||||
if (!$huburi) {
|
||||
if (!$huburi && !common_config('feedsub', 'fallback_hub')) {
|
||||
// We can only deal with folks with a PuSH hub
|
||||
throw new FeedSubNoHubException();
|
||||
}
|
||||
|
@@ -87,6 +87,16 @@ class FeedDiscovery
|
||||
return ActivityUtils::getLink($this->root, $rel, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the referenced PuSH hub link from an Atom feed.
|
||||
*
|
||||
* @return mixed string or false
|
||||
*/
|
||||
public function getHubLink()
|
||||
{
|
||||
return $this->getAtomLink('hub');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param bool $htmlOk pass false here if you don't want to follow web pages.
|
||||
|
@@ -210,13 +210,13 @@ class MagicEnvelope
|
||||
}
|
||||
|
||||
$data_element = $env_element->getElementsByTagNameNS(MagicEnvelope::NS, 'data')->item(0);
|
||||
|
||||
$sig_element = $env_element->getElementsByTagNameNS(MagicEnvelope::NS, 'sig')->item(0);
|
||||
return array(
|
||||
'data' => trim($data_element->nodeValue),
|
||||
'data' => preg_replace('/\s/', '', $data_element->nodeValue),
|
||||
'data_type' => $data_element->getAttribute('type'),
|
||||
'encoding' => $env_element->getElementsByTagNameNS(MagicEnvelope::NS, 'encoding')->item(0)->nodeValue,
|
||||
'alg' => $env_element->getElementsByTagNameNS(MagicEnvelope::NS, 'alg')->item(0)->nodeValue,
|
||||
'sig' => $env_element->getElementsByTagNameNS(MagicEnvelope::NS, 'sig')->item(0)->nodeValue,
|
||||
'sig' => preg_replace('/\s/', '', $sig_element->nodeValue),
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,6 @@ class SalmonAction extends Action
|
||||
|
||||
$xml = file_get_contents('php://input');
|
||||
|
||||
|
||||
// Check the signature
|
||||
$salmon = new Salmon;
|
||||
if (!$salmon->verifyMagicEnv($xml)) {
|
||||
@@ -58,7 +57,6 @@ class SalmonAction extends Action
|
||||
$env = $magic_env->parse($xml);
|
||||
$xml = $magic_env->unfold($env);
|
||||
}
|
||||
|
||||
|
||||
$dom = DOMDocument::loadXML($xml);
|
||||
if ($dom->documentElement->namespaceURI != Activity::ATOM ||
|
||||
@@ -67,7 +65,7 @@ class SalmonAction extends Action
|
||||
$this->clientError(_m('Salmon post must be an Atom entry.'));
|
||||
}
|
||||
|
||||
$this->act = new Activity($dom->documentElement);
|
||||
$this->activity = new Activity($dom->documentElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,9 +77,9 @@ class SalmonAction extends Action
|
||||
{
|
||||
StatusNet::setApi(true); // Send smaller error pages
|
||||
|
||||
common_log(LOG_DEBUG, "Got a " . $this->act->verb);
|
||||
common_log(LOG_DEBUG, "Got a " . $this->activity->verb);
|
||||
if (Event::handle('StartHandleSalmon', array($this->activity))) {
|
||||
switch ($this->act->verb)
|
||||
switch ($this->activity->verb)
|
||||
{
|
||||
case ActivityVerb::POST:
|
||||
$this->handlePost();
|
||||
@@ -164,12 +162,12 @@ class SalmonAction extends Action
|
||||
*/
|
||||
function handleUpdateProfile()
|
||||
{
|
||||
$oprofile = Ostatus_profile::getActorProfile($this->act);
|
||||
$oprofile = Ostatus_profile::getActorProfile($this->activity);
|
||||
if ($oprofile) {
|
||||
common_log(LOG_INFO, "Got a profile-update ping from $oprofile->uri");
|
||||
$oprofile->updateFromActivityObject($this->act->actor);
|
||||
$oprofile->updateFromActivityObject($this->activity->actor);
|
||||
} else {
|
||||
common_log(LOG_INFO, "Ignoring profile-update ping from unknown " . $this->act->actor->id);
|
||||
common_log(LOG_INFO, "Ignoring profile-update ping from unknown " . $this->activity->actor->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,10 +176,10 @@ class SalmonAction extends Action
|
||||
*/
|
||||
function ensureProfile()
|
||||
{
|
||||
$actor = $this->act->actor;
|
||||
$actor = $this->activity->actor;
|
||||
if (empty($actor->id)) {
|
||||
common_log(LOG_ERR, "broken actor: " . var_export($actor, true));
|
||||
common_log(LOG_ERR, "activity with no actor: " . var_export($this->act, true));
|
||||
common_log(LOG_ERR, "activity with no actor: " . var_export($this->activity, true));
|
||||
throw new Exception("Received a salmon slap from unidentified actor.");
|
||||
}
|
||||
|
||||
@@ -191,6 +189,6 @@ class SalmonAction extends Action
|
||||
function saveNotice()
|
||||
{
|
||||
$oprofile = $this->ensureProfile();
|
||||
return $oprofile->processPost($this->act, 'salmon');
|
||||
return $oprofile->processPost($this->activity, 'salmon');
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@ print "Re-running feed discovery for profile URL $oprofile->uri\n";
|
||||
// @fixme will bork where the URI isn't the profile URL for now
|
||||
$discover = new FeedDiscovery();
|
||||
$feedurl = $discover->discoverFromURL($oprofile->uri);
|
||||
$huburi = $discover->getAtomLink('hub');
|
||||
$huburi = $discover->getHubLink();
|
||||
$salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES);
|
||||
|
||||
print " Feed URL: $feedurl\n";
|
||||
|
@@ -10,7 +10,7 @@ define('STATUSNET', true);
|
||||
define('LACONICA', true);
|
||||
|
||||
require_once INSTALLDIR . '/lib/common.php';
|
||||
require_once INSTALLDIR . '/plugins/FeedSub/feedsub.php';
|
||||
require_once INSTALLDIR . '/plugins/OStatus/lib/feeddiscovery.php';
|
||||
|
||||
class FeedDiscoveryTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@@ -53,6 +53,8 @@ class SitemapAction extends Action
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
header('Content-Type: text/xml; charset=UTF-8');
|
||||
$this->startXML();
|
||||
|
||||
@@ -67,6 +69,27 @@ class SitemapAction extends Action
|
||||
$this->endXML();
|
||||
}
|
||||
|
||||
function lastModified()
|
||||
{
|
||||
$y = $this->trimmed('year');
|
||||
|
||||
$m = $this->trimmed('month');
|
||||
$d = $this->trimmed('day');
|
||||
|
||||
$y += 0;
|
||||
$m += 0;
|
||||
$d += 0;
|
||||
|
||||
$begdate = strtotime("$y-$m-$d 00:00:00");
|
||||
$enddate = $begdate + (24 * 60 * 60);
|
||||
|
||||
if ($enddate < time()) {
|
||||
return $enddate;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function showUrl($url, $lastMod=null, $changeFreq=null, $priority=null)
|
||||
{
|
||||
$this->elementStart('url');
|
||||
|
168
plugins/SubMirror/SubMirrorPlugin.php
Normal file
168
plugins/SubMirror/SubMirrorPlugin.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2009-2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @package SubMirrorPlugin
|
||||
* @maintainer Brion Vibber <brion@status.net>
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
|
||||
|
||||
|
||||
class SubMirrorPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* Hook for RouterInitialized event.
|
||||
*
|
||||
* @param Net_URL_Mapper $m path-to-action mapper
|
||||
* @return boolean hook return
|
||||
*/
|
||||
function onRouterInitialized($m)
|
||||
{
|
||||
$m->connect('settings/mirror',
|
||||
array('action' => 'mirrorsettings'));
|
||||
$m->connect('settings/mirror/add',
|
||||
array('action' => 'addmirror'));
|
||||
$m->connect('settings/mirror/edit',
|
||||
array('action' => 'editmirror'));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically load the actions and libraries used by the plugin
|
||||
*
|
||||
* @param Class $cls the class
|
||||
*
|
||||
* @return boolean hook return
|
||||
*
|
||||
*/
|
||||
function onAutoload($cls)
|
||||
{
|
||||
$base = dirname(__FILE__);
|
||||
$lower = strtolower($cls);
|
||||
$files = array("$base/lib/$lower.php",
|
||||
"$base/classes/$cls.php");
|
||||
if (substr($lower, -6) == 'action') {
|
||||
$files[] = "$base/actions/" . substr($lower, 0, -6) . ".php";
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file)) {
|
||||
include_once $file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($notice)
|
||||
{
|
||||
// Is anybody mirroring?
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscribed = $notice->profile_id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$mirror->repeat($notice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPluginVersion(&$versions)
|
||||
{
|
||||
$versions[] = array('name' => 'SubMirror',
|
||||
'version' => STATUSNET_VERSION,
|
||||
'author' => 'Brion Vibber',
|
||||
'homepage' => 'http://status.net/wiki/Plugin:SubMirror',
|
||||
'rawdescription' =>
|
||||
_m('Pull feeds into your timeline!'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu item for settings
|
||||
*
|
||||
* @param Action &$action Action being executed
|
||||
*
|
||||
* @return boolean hook return
|
||||
*/
|
||||
|
||||
function onEndAccountSettingsNav(&$action)
|
||||
{
|
||||
$action_name = $action->trimmed('action');
|
||||
|
||||
$action->menuItem(common_local_url('mirrorsettings'),
|
||||
// TRANS: SubMirror plugin menu item on user settings page.
|
||||
_m('MENU', 'Mirroring'),
|
||||
// TRANS: SubMirror plugin tooltip for user settings menu item.
|
||||
_m('Configure mirroring of posts from other feeds'),
|
||||
$action_name === 'mirrorsettings');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onCheckSchema()
|
||||
{
|
||||
$schema = Schema::get();
|
||||
$schema->ensureTable('submirror', SubMirror::schemaDef());
|
||||
|
||||
// @hack until key definition support is merged
|
||||
SubMirror::fixIndexes($schema);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up queue handlers for outgoing hub pushes
|
||||
* @param QueueManager $qm
|
||||
* @return boolean hook return
|
||||
*/
|
||||
function onEndInitializeQueueManager(QueueManager $qm)
|
||||
{
|
||||
// After each notice save, check if there's any repeat mirrors.
|
||||
$qm->connect('mirror', 'MirrorQueueHandler');
|
||||
return true;
|
||||
}
|
||||
|
||||
function onStartEnqueueNotice($notice, &$transports)
|
||||
{
|
||||
$transports[] = 'mirror';
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the OStatus subscription garbage collection know if we're
|
||||
* making use of a remote feed, so it doesn't get dropped out
|
||||
* from under us.
|
||||
*
|
||||
* @param Ostatus_profile $oprofile
|
||||
* @param int $count in/out
|
||||
* @return mixed hook return value
|
||||
*/
|
||||
function onOstatus_profileSubscriberCount($oprofile, &$count)
|
||||
{
|
||||
if ($oprofile->profile_id) {
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscribed = $oprofile->profile_id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
77
plugins/SubMirror/actions/addmirror.php
Normal file
77
plugins/SubMirror/actions/addmirror.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes parameters:
|
||||
*
|
||||
* - feed: 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
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class AddMirrorAction extends BaseMirrorAction
|
||||
{
|
||||
var $feedurl;
|
||||
|
||||
/**
|
||||
* Check pre-requisites and instantiate attributes
|
||||
*
|
||||
* @param Array $args array of arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
$this->feedurl = $this->validateFeedUrl($this->trimmed('feedurl'));
|
||||
$this->profile = $this->profileForFeed($this->feedurl);
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveMirror()
|
||||
{
|
||||
if ($this->oprofile->subscribe()) {
|
||||
SubMirror::saveMirror($this->user, $this->profile);
|
||||
} else {
|
||||
$this->serverError(_m("Could not subscribe to feed."));
|
||||
}
|
||||
}
|
||||
}
|
169
plugins/SubMirror/actions/basemirror.php
Normal file
169
plugins/SubMirror/actions/basemirror.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes parameters:
|
||||
*
|
||||
* - feed: 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
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
abstract class BaseMirrorAction extends Action
|
||||
{
|
||||
var $user;
|
||||
var $profile;
|
||||
|
||||
/**
|
||||
* Check pre-requisites and instantiate attributes
|
||||
*
|
||||
* @param Array $args array of arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
return $this->sharedBoilerplate();
|
||||
}
|
||||
|
||||
protected function validateFeedUrl($url)
|
||||
{
|
||||
if (common_valid_http_url($url)) {
|
||||
return $url;
|
||||
} else {
|
||||
$this->clientError(_m("Invalid feed URL."));
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateProfile($id)
|
||||
{
|
||||
$id = intval($id);
|
||||
$profile = Profile::staticGet('id', $id);
|
||||
if ($profile && $profile->id != $this->user->id) {
|
||||
return $profile;
|
||||
}
|
||||
// TRANS: Error message returned to user when setting up feed mirroring, but we were unable to resolve the given URL to a working feed.
|
||||
$this->clientError(_m("Invalid profile for mirroring."));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $url
|
||||
* @return Profile
|
||||
*/
|
||||
protected function profileForFeed($url)
|
||||
{
|
||||
$oprofile = Ostatus_profile::ensureProfileURL($url);
|
||||
if ($oprofile->isGroup()) {
|
||||
$this->clientError(_m("Can't mirror a StatusNet group at this time."));
|
||||
}
|
||||
$this->oprofile = $oprofile; // @fixme ugly side effect :D
|
||||
return $oprofile->localProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @fixme none of this belongs in end classes
|
||||
* this stuff belongs in shared code!
|
||||
*/
|
||||
function sharedBoilerplate()
|
||||
{
|
||||
// Only allow POST requests
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
$this->clientError(_('This action only accepts POST requests.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// Only for logged-in users
|
||||
|
||||
$this->user = common_current_user();
|
||||
|
||||
if (empty($this->user)) {
|
||||
$this->clientError(_('Not logged in.'));
|
||||
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
|
||||
$this->saveMirror();
|
||||
|
||||
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 EditMirrorForm($this, $this->profile);
|
||||
$unsubscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
$url = common_local_url('mirrorsettings');
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
|
||||
abstract function saveMirror();
|
||||
}
|
112
plugins/SubMirror/actions/editmirror.php
Normal file
112
plugins/SubMirror/actions/editmirror.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes parameters:
|
||||
*
|
||||
* - feed: 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
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class EditMirrorAction extends BaseMirrorAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Check pre-requisites and instantiate attributes
|
||||
*
|
||||
* @param Array $args array of arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
$this->profile = $this->validateProfile($this->trimmed('profile'));
|
||||
|
||||
$this->mirror = SubMirror::pkeyGet(array('subscriber' => $this->user->id,
|
||||
'subscribed' => $this->profile->id));
|
||||
|
||||
if (!$this->mirror) {
|
||||
$this->clientError(_m("Requested invalid profile to edit."));
|
||||
}
|
||||
|
||||
$this->style = $this->validateStyle($this->trimmed('style'));
|
||||
|
||||
// DO NOT change to $this->boolean(), it will be wrong.
|
||||
// We're checking for the presence of the setting, not its value.
|
||||
$this->delete = (bool)$this->arg('delete');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validateStyle($style)
|
||||
{
|
||||
$allowed = array('repeat', 'copy');
|
||||
if (in_array($style, $allowed)) {
|
||||
return $style;
|
||||
} else {
|
||||
$this->clientError(_m("Bad form data."));
|
||||
}
|
||||
}
|
||||
|
||||
function saveMirror()
|
||||
{
|
||||
$mirror = SubMirror::getMirror($this->user, $this->profile);
|
||||
if (!$mirror) {
|
||||
$this->clientError(_m('Requested edit of missing mirror'));
|
||||
}
|
||||
|
||||
if ($this->delete) {
|
||||
$mirror->delete();
|
||||
$oprofile = Ostatus_profile::staticGet('profile_id', $this->profile->id);
|
||||
if ($oprofile) {
|
||||
$oprofile->garbageCollect();
|
||||
}
|
||||
} else if ($this->style != $mirror->style) {
|
||||
$orig = clone($mirror);
|
||||
$mirror->style = $this->style;
|
||||
$mirror->modified = common_sql_now();
|
||||
$mirror->update($orig);
|
||||
}
|
||||
}
|
||||
}
|
106
plugins/SubMirror/actions/mirrorsettings.php
Normal file
106
plugins/SubMirror/actions/mirrorsettings.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: 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/>.
|
||||
*
|
||||
* @category Plugins
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class MirrorSettingsAction extends AccountSettingsAction
|
||||
{
|
||||
/**
|
||||
* Title of the page
|
||||
*
|
||||
* @return string Page title
|
||||
*/
|
||||
|
||||
function title()
|
||||
{
|
||||
return _m('Feed mirror settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions for use
|
||||
*
|
||||
* @return string Instructions for use
|
||||
*/
|
||||
|
||||
function getInstructions()
|
||||
{
|
||||
return _m('You can mirror updates from many RSS and Atom feeds ' .
|
||||
'into your StatusNet timeline!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for OpenID management
|
||||
*
|
||||
* We have one form with a few different submit buttons to do different things.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
$this->showAddFeedForm();
|
||||
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscriber = $user->id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$this->showFeedForm($mirror);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showFeedForm($mirror)
|
||||
{
|
||||
$profile = Profile::staticGet('id', $mirror->subscribed);
|
||||
if ($profile) {
|
||||
$form = new EditMirrorForm($this, $profile);
|
||||
$form->show();
|
||||
}
|
||||
}
|
||||
|
||||
function showAddFeedForm()
|
||||
{
|
||||
$form = new AddMirrorForm($this);
|
||||
$form->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a POST request
|
||||
*
|
||||
* Muxes to different sub-functions based on which button was pushed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handlePost()
|
||||
{
|
||||
}
|
||||
}
|
229
plugins/SubMirror/classes/SubMirror.php
Normal file
229
plugins/SubMirror/classes/SubMirror.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @package SubMirrorPlugin
|
||||
* @maintainer Brion Vibber <brion@status.net>
|
||||
*/
|
||||
|
||||
class SubMirror extends Memcached_DataObject
|
||||
{
|
||||
public $__table = 'submirror';
|
||||
|
||||
public $subscriber;
|
||||
public $subscribed;
|
||||
|
||||
public $style;
|
||||
|
||||
public $created;
|
||||
public $modified;
|
||||
|
||||
public /*static*/ function staticGet($k, $v=null)
|
||||
{
|
||||
return parent::staticGet(__CLASS__, $k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* return table definition for DB_DataObject
|
||||
*
|
||||
* DB_DataObject needs to know something about the table to manipulate
|
||||
* instances. This method provides all the DB_DataObject needs to know.
|
||||
*
|
||||
* @return array array of column definitions
|
||||
*/
|
||||
|
||||
function table()
|
||||
{
|
||||
return array('subscriber' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
'subscribed' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
|
||||
'style' => DB_DATAOBJECT_STR,
|
||||
|
||||
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
|
||||
'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
|
||||
}
|
||||
|
||||
static function schemaDef()
|
||||
{
|
||||
// @fixme need a reverse key on (subscribed, subscriber) as well
|
||||
return array(new ColumnDef('subscriber', 'integer',
|
||||
null, false, 'PRI'),
|
||||
new ColumnDef('subscribed', 'integer',
|
||||
null, false, 'PRI'),
|
||||
|
||||
new ColumnDef('style', 'varchar',
|
||||
16, true),
|
||||
|
||||
new ColumnDef('created', 'datetime',
|
||||
null, false),
|
||||
new ColumnDef('modified', 'datetime',
|
||||
null, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary hack to set up the compound index, since we can't do
|
||||
* it yet through regular Schema interface. (Coming for 1.0...)
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
*/
|
||||
static function fixIndexes($schema)
|
||||
{
|
||||
try {
|
||||
$schema->createIndex('submirror', array('subscribed', 'subscriber'));
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return key definitions for DB_DataObject
|
||||
*
|
||||
* DB_DataObject needs to know about keys that the table has; this function
|
||||
* defines them.
|
||||
*
|
||||
* @return array key definitions
|
||||
*/
|
||||
|
||||
function keys()
|
||||
{
|
||||
return array_keys($this->keyTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* return key definitions for Memcached_DataObject
|
||||
*
|
||||
* Our caching system uses the same key definitions, but uses a different
|
||||
* method to get them.
|
||||
*
|
||||
* @return array key definitions
|
||||
*/
|
||||
|
||||
function keyTypes()
|
||||
{
|
||||
// @fixme keys
|
||||
// need a sane key for reverse lookup too
|
||||
return array('subscriber' => 'K', 'subscribed' => 'K');
|
||||
}
|
||||
|
||||
function sequenceKey()
|
||||
{
|
||||
return array(false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Profile $subscribed
|
||||
* @param Profile $subscribed
|
||||
* @return SubMirror
|
||||
* @throws ServerException
|
||||
*/
|
||||
public static function saveMirror($subscriber, $subscribed, $style='repeat')
|
||||
{
|
||||
// @fixme make sure they're subscribed!
|
||||
$mirror = new SubMirror();
|
||||
|
||||
$mirror->subscriber = $subscriber->id;
|
||||
$mirror->subscribed = $subscribed->id;
|
||||
$mirror->style = $style;
|
||||
|
||||
$mirror->created = common_sql_now();
|
||||
$mirror->modified = common_sql_now();
|
||||
$mirror->insert();
|
||||
|
||||
return $mirror;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Notice $notice
|
||||
* @return mixed Notice on successful mirroring, boolean if not
|
||||
*/
|
||||
public function mirrorNotice($notice)
|
||||
{
|
||||
$profile = Profile::staticGet('id', $this->subscriber);
|
||||
if (!$profile) {
|
||||
common_log(LOG_ERROR, "SubMirror plugin skipping auto-repeat of notice $notice->id for missing user $profile->id");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->style == 'copy') {
|
||||
return $this->copyNotice($profile, $notice);
|
||||
} else { // default to repeat mode
|
||||
return $this->repeatNotice($profile, $notice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror a notice using StatusNet's repeat functionality.
|
||||
* This retains attribution within the site, and other nice things,
|
||||
* but currently ends up looking like 'RT @foobar bla bla' when
|
||||
* bridged out over OStatus or TwitterBridge.
|
||||
*
|
||||
* @param Notice $notice
|
||||
* @return mixed Notice on successful repeat, true if already repeated, false on failure
|
||||
*/
|
||||
protected function repeatNotice($profile, $notice)
|
||||
{
|
||||
if($profile->hasRepeated($notice->id)) {
|
||||
common_log(LOG_INFO, "SubMirror plugin skipping auto-repeat of notice $notice->id for user $profile->id; already repeated.");
|
||||
return true;
|
||||
} else {
|
||||
common_log(LOG_INFO, "SubMirror plugin auto-repeating notice $notice->id for $profile->id");
|
||||
return $notice->repeat($profile->id, 'mirror');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror a notice by emitting a new notice with the same contents.
|
||||
* Kind of dirty, but if pulling an external data feed into an account
|
||||
* that may be what you want.
|
||||
*
|
||||
* @param Notice $notice
|
||||
* @return mixed Notice on successful repeat, true if already repeated, false on failure
|
||||
*/
|
||||
protected function copyNotice($profile, $notice)
|
||||
{
|
||||
$options = array('is_local' => Notice::LOCAL_PUBLIC,
|
||||
'url' => $notice->bestUrl(), // pass through the foreign link...
|
||||
'rendered' => $notice->rendered);
|
||||
|
||||
$saved = Notice::saveNew($profile->id,
|
||||
$notice->content,
|
||||
'feed',
|
||||
$options);
|
||||
return $saved;
|
||||
}
|
||||
|
||||
public /*static*/ function pkeyGet($v)
|
||||
{
|
||||
return parent::pkeyGet(__CLASS__, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mirroring setting for a pair of profiles, if existing.
|
||||
*
|
||||
* @param Profile $subscriber
|
||||
* @param Profile $subscribed
|
||||
* @return mixed Profile or empty
|
||||
*/
|
||||
public static function getMirror($subscriber, $subscribed)
|
||||
{
|
||||
return self::pkeyGet(array('subscriber' => $subscriber->id,
|
||||
'subscribed' => $subscribed->id));
|
||||
}
|
||||
}
|
141
plugins/SubMirror/lib/addmirrorform.php
Normal file
141
plugins/SubMirror/lib/addmirrorform.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: 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/>.
|
||||
*
|
||||
* @package StatusNet
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class AddMirrorForm extends Form
|
||||
{
|
||||
|
||||
/**
|
||||
* Name of the form
|
||||
*
|
||||
* Sub-classes should overload this with the name of their form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formLegend()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible or invisible data elements
|
||||
*
|
||||
* Display the form fields that make up the data of the form.
|
||||
* Sub-classes should overload this to show their data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->elementStart('fieldset');
|
||||
|
||||
$this->out->elementStart('ul');
|
||||
|
||||
$this->li();
|
||||
$this->doInput('addmirror-feedurl',
|
||||
'feedurl',
|
||||
_m('Web page or feed URL:'),
|
||||
$this->out->trimmed('feedurl'));
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->submit('addmirror-save', _m('Add feed'));
|
||||
$this->unli();
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('fieldset');
|
||||
}
|
||||
|
||||
private function doInput($id, $name, $label, $value=null, $instructions=null)
|
||||
{
|
||||
$this->out->element('label', array('for' => $id), $label);
|
||||
$attrs = array('name' => $name,
|
||||
'type' => 'text',
|
||||
'id' => $id,
|
||||
'style' => 'width: 80%');
|
||||
if ($value) {
|
||||
$attrs['value'] = $value;
|
||||
}
|
||||
$this->out->element('input', $attrs);
|
||||
if ($instructions) {
|
||||
$this->out->element('p', 'form_guide', $instructions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buttons for form actions
|
||||
*
|
||||
* Submit and cancel buttons (or whatever)
|
||||
* Sub-classes should overload this to show their own buttons.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* Should be unique on the page. Sub-classes should overload this
|
||||
* to show their own IDs.
|
||||
*
|
||||
* @return string ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'add-mirror-form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of the form.
|
||||
*
|
||||
* URL to post to. Should be overloaded by subclasses to give
|
||||
* somewhere to post to.
|
||||
*
|
||||
* @return string URL to post to
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('addmirror');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class of the form.
|
||||
*
|
||||
* @return string the form's class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
return 'form_settings';
|
||||
}
|
||||
|
||||
}
|
189
plugins/SubMirror/lib/editmirrorform.php
Normal file
189
plugins/SubMirror/lib/editmirrorform.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: 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/>.
|
||||
*
|
||||
* @package StatusNet
|
||||
* @copyright 2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class EditMirrorForm extends Form
|
||||
{
|
||||
function __construct($action, $profile)
|
||||
{
|
||||
parent::__construct($action);
|
||||
|
||||
$this->profile = clone($profile);
|
||||
$this->user = common_current_user();
|
||||
$this->mirror = SubMirror::pkeyGet(array('subscriber' => $this->user->id,
|
||||
'subscribed' => $this->profile->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the form
|
||||
*
|
||||
* Sub-classes should overload this with the name of their form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formLegend()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible or invisible data elements
|
||||
*
|
||||
* Display the form fields that make up the data of the form.
|
||||
* Sub-classes should overload this to show their data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->elementStart('fieldset');
|
||||
|
||||
$this->out->hidden('profile', $this->profile->id);
|
||||
|
||||
$this->out->elementStart('div', array('style' => 'float: left; width: 80px;'));
|
||||
$img = $this->getAvatar($this->profile);
|
||||
$feed = $this->getFeed($this->profile);
|
||||
$this->out->elementStart('a', array('href' => $this->profile->profileurl));
|
||||
$this->out->element('img', array('src' => $img, 'style' => 'float: left'));
|
||||
$this->out->elementEnd('a');
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
|
||||
$this->out->elementStart('div', array('style' => 'margin-left: 80px; margin-right: 20px'));
|
||||
$this->out->elementStart('p');
|
||||
$this->out->elementStart('div');
|
||||
$this->out->element('a', array('href' => $this->profile->profileurl), $this->profile->getBestName());
|
||||
$this->out->elementEnd('div');
|
||||
$this->out->elementStart('div');
|
||||
if ($feed) {
|
||||
$this->out->text(_m('LABEL', 'Remote feed:') . ' ');
|
||||
//$this->out->element('a', array('href' => $feed), $feed);
|
||||
$this->out->element('input', array('value' => $feed, 'readonly' => 'readonly', 'style' => 'width: 100%'));
|
||||
} else {
|
||||
$this->out->text(_m('LABEL', 'Local user'));
|
||||
}
|
||||
$this->out->elementEnd('div');
|
||||
$this->out->elementEnd('p');
|
||||
|
||||
$this->out->elementStart('fieldset', array('style' => 'margin-top: 20px'));
|
||||
$this->out->element('legend', false, _m("Mirroring style"));
|
||||
|
||||
$styles = array('repeat' => _m("Repeat: reference the original user's post (sometimes shows as 'RT @blah')"),
|
||||
'copy' => _m("Repost the content under my account"));
|
||||
foreach ($styles as $key => $label) {
|
||||
$this->out->elementStart('div');
|
||||
$attribs = array('type' => 'radio',
|
||||
'value' => $key,
|
||||
'name' => 'style',
|
||||
'id' => $this->id() . '-style');
|
||||
if ($key == $this->mirror->style || ($key == 'repeat' && empty($this->mirror->style))) {
|
||||
$attribs['checked'] = 'checked';
|
||||
}
|
||||
$this->out->element('input', $attribs);
|
||||
$this->out->element('span', false, $label); // @fixme should be label, but the styles muck it up for now
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
}
|
||||
$this->out->elementEnd('fieldset');
|
||||
|
||||
|
||||
$this->out->elementStart('div');
|
||||
$this->out->submit($this->id() . '-save', _m('Save'));
|
||||
$this->out->element('input', array('type' => 'submit',
|
||||
'value' => _m('Stop mirroring'),
|
||||
'name' => 'delete',
|
||||
'class' => 'submit'));
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
$this->out->elementEnd('div');
|
||||
$this->out->elementEnd('fieldset');
|
||||
}
|
||||
|
||||
private function getAvatar($profile)
|
||||
{
|
||||
$avatar = $this->profile->getAvatar(48);
|
||||
if ($avatar) {
|
||||
return $avatar->displayUrl();
|
||||
} else {
|
||||
return Avatar::defaultImage(48);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFeed($profile)
|
||||
{
|
||||
// Ok this is a bit of a hack. ;)
|
||||
if (class_exists('Ostatus_profile')) {
|
||||
$oprofile = Ostatus_profile::staticGet('profile_id', $profile->id);
|
||||
if ($oprofile) {
|
||||
return $oprofile->feeduri;
|
||||
}
|
||||
}
|
||||
var_dump('wtf');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* Should be unique on the page. Sub-classes should overload this
|
||||
* to show their own IDs.
|
||||
*
|
||||
* @return string ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'edit-mirror-form-' . $this->profile->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of the form.
|
||||
*
|
||||
* URL to post to. Should be overloaded by subclasses to give
|
||||
* somewhere to post to.
|
||||
*
|
||||
* @return string URL to post to
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('editmirror');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class of the form.
|
||||
*
|
||||
* @return string the form's class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
return 'form_settings';
|
||||
}
|
||||
|
||||
}
|
45
plugins/SubMirror/lib/mirrorqueuehandler.php
Normal file
45
plugins/SubMirror/lib/mirrorqueuehandler.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check for subscription mirroring options on each newly seen post!
|
||||
*
|
||||
* @package SubMirror
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
*/
|
||||
|
||||
class MirrorQueueHandler extends QueueHandler
|
||||
{
|
||||
function transport()
|
||||
{
|
||||
return 'mirror';
|
||||
}
|
||||
|
||||
function handle($notice)
|
||||
{
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscribed = $notice->profile_id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$mirror->mirrorNotice($notice);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user