-avatar/*
-background/*
-files/*
-file/*
-local/*
-_darcs/*
-logs/*
-log/*
-run/*
-config.php
-.htaccess
-httpd.conf
-*.tmproj
-dataobject.ini
-*~
-*.bak
-*.orig
-*.rej
-.#*
-*.swp
-.buildpath
-.project
-.settings
-TODO.rym
-config-*.php
-good-config.php
-lac08.log
-php.log
-.DS_Store
-nbproject
-*.mo
-*log*
-htaccess-sample
-installer.txt
-extlib/DB.php
-.gitmodules
+/nbproject/private/
+/nbproject/*~
+/manifest.mf
+/build/
+/dist/
+/data/*
+/*.properties
+/*-ejb/nbproject/private/
+/*-ejb/nbproject/*~
+/*-ejb/build/
+/*-ejb/dist/
The upgrade script will likely take a long time because it will
upgrade the tables to another character encoding and make other
automated upgrades. Make sure it ends without errors. If you get
- errors, create a new task on https://bugz.foocorp.net/
+ errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
4. Start your queue daemons again (you can run this command even if you
do not use the queue daemons):
$ bash scripts/startdaemons.sh
-5. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
+5. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
If you are using ssh keys to log in to your server, you can make this
procedure pretty painless (assuming you have automated backups already).
2. Unpack your GNU social code to a fresh directory. You can do this
by cloning our git repository:
- $ git clone https://gitorious.org/social/mainline.git gnusocial
+ $ git clone https://git.gnu.io/gnu/gnu-social.git gnusocial
3. Synchronize your local files to the GNU social directory. These
will be the local files such as avatars, config and files:
The upgrade script will likely take a long time because it will
upgrade the tables to another character encoding and make other
automated upgrades. Make sure it ends without errors. If you get
- errors, create a new task on https://bugz.foocorp.net/
+ errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
6. Start your queue daemons: 'bash scripts/startdaemons.sh'
-7. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
+7. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Documentation class.
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
-class DocAction extends Action
+class DocAction extends ManagedAction
{
var $output = null;
var $filename = null;
var $title = null;
- function prepare(array $args=array())
+ protected function doPreparation()
{
- parent::prepare($args);
-
$this->title = $this->trimmed('title');
if (!preg_match('/^[a-zA-Z0-9_-]*$/', $this->title)) {
$this->title = 'help';
$this->output = null;
$this->loadDoc();
- return true;
- }
-
- /**
- * Handle a request
- *
- * @param array $args array of arguments
- *
- * @return nothing
- */
- function handle(array $args=array())
- {
- parent::handle($args);
- $this->showPage();
- }
-
- /**
- * Page title
- *
- * Gives the page title of the document. Override default for hAtom entry.
- *
- * @return void
- */
- function showPageTitle()
- {
- $this->element('h1', array('class' => 'entry-title'), $this->title());
}
- /**
- * Block for content.
- *
- * Overrides default from Action to wrap everything in an hAtom entry.
- *
- * @return void.
- */
- function showContentBlock()
+ public function title()
{
- $this->elementStart('div', array('id' => 'content', 'class' => 'h-entry'));
- $this->showPageTitle();
- $this->showPageNoticeBlock();
- $this->elementStart('div', array('id' => 'content_inner',
- 'class' => 'e-content'));
- // show the actual content (forms, lists, whatever)
- $this->showContent();
- $this->elementEnd('div');
- $this->elementEnd('div');
+ return ucfirst($this->title);
}
/**
$this->raw($this->output);
}
- /**
- * Page title.
- *
- * Uses the title of the document.
- *
- * @return page title
- */
- function title()
+ function showNoticeForm()
{
- return ucfirst($this->title);
+ // no notice form
}
/**
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
- * Table Definition for notice
+ * Table Definition for deleted_notice
*/
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Deleted_notice extends Managed_DataObject
{
- ###START_AUTOCODE
- /* the code below is auto generated do not remove the above tag */
-
- public $__table = 'deleted_notice'; // table name
+ public $__table = 'deleted_notice'; // table name
public $id; // int(4) primary_key not_null
public $profile_id; // int(4) not_null
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $created; // datetime() not_null
public $deleted; // datetime() not_null
- /* the code above is auto generated do not remove the tag below */
- ###END_AUTOCODE
-
public static function schemaDef()
{
return array(
$this->_profile[$this->profile_id] = $profile;
}
+ public function deleteAs(Profile $actor)
+ {
+ if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) {
+ return $this->delete();
+ }
+ throw new AuthorizationException('You are not allowed to delete other user\'s notices');
+ }
+
function delete($useWhere=false)
{
// For auditing purposes, save a record that the notice
public function hasPassword()
{
try {
- return !empty($this->getUser()->hasPassword());
+ return $this->getUser()->hasPassword();
} catch (NoSuchUserException $e) {
return false;
}
if (!empty($targetEl)) {
$this->target = new ActivityObject($targetEl);
- } elseif (ActivityUtils::compareTypes($this->verb, array(ActivityVerb::FAVORITE))) {
+ } elseif (ActivityUtils::compareVerbs($this->verb, array(ActivityVerb::FAVORITE))) {
// StatusNet didn't send a 'target' for their Favorite atom entries
$this->target = clone($this->objects[0]);
}
function isMyVerb($verb) {
$verb = $verb ?: ActivityVerb::POST; // post is the default verb
- return ActivityUtils::compareTypes($verb, $this->verbs());
+ return ActivityUtils::compareVerbs($verb, $this->verbs());
}
function isMyType($type) {
} elseif ($target instanceof Profile && $target->isLocal()) {
$original = null;
// FIXME: Shouldn't favorites show up with a 'target' activityobject?
- if (!ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) {
+ if (!ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) {
// If this is not a post, it's a verb targeted at something (such as a Favorite attached to a note)
if (!empty($activity->objects[0]->id)) {
$activity->context->replyToID = $activity->objects[0]->id;
$actor = $oactor->localProfile();
// FIXME: will this work in all cases? I made it work for Favorite...
- if (ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST))) {
+ if (ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST))) {
$object = $activity->objects[0];
} else {
$object = $activity;
if (!empty($guidEl)) {
$this->id = $guidEl->textContent;
- if ($guidEl->hasAttribute('isPermaLink')) {
+ if ($guidEl->hasAttribute('isPermaLink') && $guidEl->getAttribute('isPermaLink') != 'false') {
// overwrites <link>
$this->link = $this->id;
}
}
}
-
-/**
- * A class for representing MediaLinks in JSON Activities
- *
- * @category Feed
- * @package StatusNet
- * @author Zach Copley <zach@status.net>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-class ActivityStreamsMediaLink extends ActivityStreamsLink
-{
- private $linkDict;
-
- function __construct(
- $url = null,
- $width = null,
- $height = null,
- $mediaType = null, // extension
- $rel = null, // extension
- $duration = null
- )
- {
- parent::__construct($url, $rel, $mediaType);
- $this->linkDict = array(
- 'width' => intval($width),
- 'height' => intval($height),
- 'duration' => intval($duration)
- );
- }
-
- function asArray()
- {
- return array_merge(
- parent::asArray(),
- array_filter($this->linkDict)
- );
- }
-}
-
-/*
- * Collection primarily as the root of an Activity Streams doc but can be used as the value
- * of extension properties in a variety of situations.
- *
- * A valid Collection object serialization MUST contain at least the url or items properties.
- */
-class JSONActivityCollection {
-
- /* Non-negative integer specifying the total number of activities within the stream */
- protected $totalItems;
-
- /* An array containing a listing of Objects of any object type */
- protected $items;
-
- /* IRI referencing a JSON document containing the full listing of objects in the collection */
- protected $url;
-
- /**
- * Constructor
- *
- * @param array $items array of activity items
- * @param string $url url of a doc list all the objs in the collection
- * @param int $totalItems total number of items in the collection
- */
- function __construct($items = null, $url = null)
- {
- $this->items = empty($items) ? array() : $items;
- $this->totalItems = count($items);
- $this->url = $url;
- }
-
- /**
- * Get the total number of items in the collection
- *
- * @return int total the total
- */
- public function getTotalItems()
- {
- $this->totalItems = count($items);
- return $this->totalItems;
- }
-}
-
-/**
- * A class for representing links in JSON Activities
- *
- * @category Feed
- * @package StatusNet
- * @author Zach Copley <zach@status.net>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-class ActivityStreamsLink
-{
- private $linkDict;
-
- function __construct($url = null, $rel = null, $mediaType = null)
- {
- // links MUST have a URL
- if (empty($url)) {
- throw new Exception('Links must have a URL.');
- }
-
- $this->linkDict = array(
- 'url' => $url,
- 'rel' => $rel, // extension
- 'type' => $mediaType // extension
- );
- }
-
- function asArray()
- {
- return array_filter($this->linkDict);
- }
-}
-
--- /dev/null
+<?php
+
+/**
+ * A class for representing links in JSON Activities
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class ActivityStreamsLink
+{
+ private $linkDict;
+
+ function __construct($url = null, $rel = null, $mediaType = null)
+ {
+ // links MUST have a URL
+ if (empty($url)) {
+ throw new Exception('Links must have a URL.');
+ }
+
+ $this->linkDict = array(
+ 'url' => $url,
+ 'rel' => $rel, // extension
+ 'type' => $mediaType // extension
+ );
+ }
+
+ function asArray()
+ {
+ return array_filter($this->linkDict);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * A class for representing MediaLinks in JSON Activities
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class ActivityStreamsMediaLink extends ActivityStreamsLink
+{
+ private $linkDict;
+
+ function __construct(
+ $url = null,
+ $width = null,
+ $height = null,
+ $mediaType = null, // extension
+ $rel = null, // extension
+ $duration = null
+ )
+ {
+ parent::__construct($url, $rel, $mediaType);
+ $this->linkDict = array(
+ 'width' => intval($width),
+ 'height' => intval($height),
+ 'duration' => intval($duration)
+ );
+ }
+
+ function asArray()
+ {
+ return array_merge(
+ parent::asArray(),
+ array_filter($this->linkDict)
+ );
+ }
+}
return null;
}
- static function compareTypes($type, array $objects) // this does verbs too!
+ static function compareTypes($type, array $objects)
{
$type = self::resolveUri($type);
foreach ($objects as $object) {
return false;
}
+ static function compareVerbs($type, $objects)
+ {
+ return self::compareTypes($type, $objects);
+ }
+
static function resolveUri($uri, $make_relative=false)
{
if (empty($uri)) {
const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend';
const JOIN = 'http://activitystrea.ms/schema/1.0/join';
const TAG = 'http://activitystrea.ms/schema/1.0/tag';
+ const DELETE = 'delete'; // the url part is not used anymore, and this feature is new enough to avoid problems with legacy nodes if used without http://...
// Custom OStatus verbs for the flipside until they're standardized
- const DELETE = 'http://ostatus.org/schema/1.0/unfollow';
const UNFAVORITE = 'http://activitystrea.ms/schema/1.0/unfavorite';
const UNLIKE = 'http://activitystrea.ms/schema/1.0/unlike'; // This is a synonym of unfavorite
const UNFOLLOW = 'http://ostatus.org/schema/1.0/unfollow';
'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
if (is_array($notice)) {
- $notice = new ArrayWrapper($notice);
+ //FIXME: make everything calling showJsonTimeline use only Notice objects
+ $ids = array();
+ foreach ($notice as $n) {
+ $ids[] = $n->getID();
+ }
+ $notice = Notice::multiGet('id', $ids);
}
while ($notice->fetch()) {
$this->element('ttl', null, '40');
if (is_array($notice)) {
- $notice = new ArrayWrapper($notice);
+ //FIXME: make everything calling showJsonTimeline use only Notice objects
+ $ids = array();
+ foreach ($notice as $n) {
+ $ids[] = $n->getID();
+ }
+ $notice = Notice::multiGet('id', $ids);
}
while ($notice->fetch()) {
$this->element('subtitle', null, $subtitle);
if (is_array($notice)) {
- $notice = new ArrayWrapper($notice);
+ //FIXME: make everything calling showJsonTimeline use only Notice objects
+ $ids = array();
+ foreach ($notice as $n) {
+ $ids[] = $n->getID();
+ }
+ $notice = Notice::multiGet('id', $ids);
}
while ($notice->fetch()) {
if (is_array($notice)) {
//FIXME: make everything calling showJsonTimeline use only Notice objects
- common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!');
$ids = array();
foreach ($notice as $n) {
$ids[] = $n->getID();
--- /dev/null
+<?php
+
+/*
+ * Collection primarily as the root of an Activity Streams doc but can be used as the value
+ * of extension properties in a variety of situations.
+ *
+ * A valid Collection object serialization MUST contain at least the url or items properties.
+ */
+class JSONActivityCollection {
+
+ /* Non-negative integer specifying the total number of activities within the stream */
+ protected $totalItems;
+
+ /* An array containing a listing of Objects of any object type */
+ protected $items;
+
+ /* IRI referencing a JSON document containing the full listing of objects in the collection */
+ protected $url;
+
+ /**
+ * Constructor
+ *
+ * @param array $items array of activity items
+ * @param string $url url of a doc list all the objs in the collection
+ * @param int $totalItems total number of items in the collection
+ */
+ function __construct($items = null, $url = null)
+ {
+ $this->items = empty($items) ? array() : $items;
+ $this->totalItems = count($items);
+ $this->url = $url;
+ }
+
+ /**
+ * Get the total number of items in the collection
+ *
+ * @return int total the total
+ */
+ public function getTotalItems()
+ {
+ $this->totalItems = count($items);
+ return $this->totalItems;
+ }
+}
protected $options = true;
protected $maxchars = 0; // if <= 0 it means use full posts
protected $item_tag = 'li';
+ protected $pa = null;
/**
* constructor
$this->elementStart('section', array('class'=>'notice-headers'));
$this->showNoticeTitle();
$this->showAuthor();
- if ($this->addressees) { $this->showAddressees(); }
+
+ if (!empty($this->notice->reply_to) || count($this->getProfileAddressees()) > 0) {
+ $this->elementStart('div', array('class' => 'parents'));
+ if (!empty($this->notice->reply_to)) { $this->showParent(); }
+ if ($this->addressees) { $this->showAddressees(); }
+ $this->elementEnd('div');
+ }
$this->elementEnd('section');
}
function showAuthor()
{
$attrs = array('href' => $this->profile->profileurl,
- 'class' => 'h-card p-author',
+ 'class' => 'h-card',
'title' => $this->profile->getNickname());
+ if(empty($this->repeat)) { $attrs['class'] .= ' p-author'; }
if (Event::handle('StartShowNoticeItemAuthor', array($this->profile, $this->out, &$attrs))) {
$this->out->elementStart('a', $attrs);
}
}
+ function showParent()
+ {
+ $this->out->element(
+ 'a',
+ array(
+ 'href' => $this->notice->getParent()->getUrl(),
+ 'class' => 'u-in-reply-to',
+ 'rel' => 'in-reply-to'
+ ),
+ 'in reply to'
+ );
+ }
+
function showAddressees()
{
$pa = $this->getProfileAddressees();
function getProfileAddressees()
{
- $pa = array();
+ if($this->pa) { return $this->pa; }
+ $this->pa = array();
$attentions = $this->getReplyProfiles();
foreach ($attentions as $attn) {
$class = $attn->isGroup() ? 'group' : 'account';
- $pa[] = array('href' => $attn->profileurl,
- 'title' => $attn->getNickname(),
- 'class' => "addressee {$class}",
- 'text' => $attn->getStreamName());
+ $this->pa[] = array('href' => $attn->profileurl,
+ 'title' => $attn->getNickname(),
+ 'class' => "addressee {$class}",
+ 'text' => $attn->getStreamName());
}
- return $pa;
+ return $this->pa;
}
function getReplyProfiles()
$actobj = $act->objects[0];
$object = Fave::saveActivityObject($actobj, $stored);
- $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
+ $stored->object_type = $object->getObjectType();
return $object;
}
$expected_verb = $exists ? ActivityVerb::UNFAVORITE : ActivityVerb::FAVORITE;
switch (true) {
- case $exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
- case !$exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+ case $exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+ case !$exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
common_redirect(common_local_url('activityverb',
array('id' => $target->getID(),
'verb' => ActivityUtils::resolveUri($expected_verb, true))));
protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
{
switch (true) {
- case ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+ case ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
Fave::addNew($scoped, $target);
break;
- case ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+ case ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
Fave::removeEntry($scoped, $target);
break;
default:
$target = self::getTargetFromStored($stored);
// The following logic was copied from StatusNet's Activity plugin
- if (ActivityUtils::compareTypes($target->verb, array(ActivityVerb::POST))) {
+ if (ActivityUtils::compareVerbs($target->verb, array(ActivityVerb::POST))) {
// "I like the thing you posted"
$act->objects = $target->asActivity()->objects;
} else {
// notice content
$c = $notice->content;
$this->notice = $notice;
- // Ignoring results
- common_replace_urls_callback($c,
- array($this, 'linkbackUrl'));
+
+ if(!$notice->getProfile()->
+ getPref("linkbackplugin", "disable_linkbacks")
+ ) {
+ // Ignoring results
+ common_replace_urls_callback($c,
+ array($this, 'linkbackUrl'));
+ }
+
+ if($notice->isRepeat()) {
+ $repeat = Notice::getByID($notice->repeat_of);
+ $this->linkbackUrl($repeat->getUrl());
+ } else if(!empty($notice->reply_to)) {
+ $parent = $notice->getParent();
+ $this->linkbackUrl($parent->getUrl());
+ }
+
+ $replyProfiles = Profile::multiGet('id', $notice->getReplies());
+ foreach($replyProfiles->fetchAll('profileurl') as $profileurl) {
+ $this->linkbackUrl($profileurl);
+ }
}
return true;
}
return $orig;
}
- $pb = null;
- $tb = null;
+ // XXX: Should handle relative-URI resolution in these detections
- if (array_key_exists('X-Pingback', $result->headers)) {
- $pb = $result->headers['X-Pingback'];
- } else if (preg_match('/<link rel="pingback" href="([^"]+)" ?\/?>/',
- $result->body,
- $match)) {
- $pb = $match[1];
- }
-
- if (!empty($pb)) {
- $this->pingback($result->final_url, $pb);
+ $wm = $this->getWebmention($result);
+ if(!empty($wm)) {
+ // It is the webmention receiver's job to resolve source
+ // Ref: https://github.com/converspace/webmention/issues/43
+ $this->webmention($url, $wm);
} else {
- $tb = $this->getTrackback($result->body, $result->final_url);
- if (!empty($tb)) {
- $this->trackback($result->final_url, $tb);
+ $pb = $this->getPingback($result);
+ if (!empty($pb)) {
+ // Pingback still looks for exact URL in our source, so we
+ // must send what we have
+ $this->pingback($url, $pb);
+ } else {
+ $tb = $this->getTrackback($result);
+ if (!empty($tb)) {
+ $this->trackback($result->final_url, $tb);
+ }
}
}
return $orig;
}
+ // Based on https://github.com/indieweb/mention-client-php
+ // which is licensed Apache 2.0
+ function getWebmention($result) {
+ // XXX: the fetcher only gives back one of each header, so this may fail on multiple Link headers
+ if(preg_match('~<((?:https?://)?[^>]+)>; rel="webmention"~', $result->headers['Link'], $match)) {
+ return $match[1];
+ } elseif(preg_match('~<((?:https?://)?[^>]+)>; rel="http://webmention.org/?"~', $result->headers['Link'], $match)) {
+ return $match[1];
+ }
+
+ if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $result->body, $match)
+ || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+ return $match[1];
+ } elseif(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="http:\/\/webmention\.org\/?"[ ]*\/?>/i', $result->body, $match)
+ || preg_match('/<(?:link|a)[ ]+rel="http:\/\/webmention\.org\/?"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+ return $match[1];
+ }
+ }
+
+ function webmention($url, $endpoint) {
+ $source = $this->notice->getUrl();
+
+ $payload = array(
+ 'source' => $source,
+ 'target' => $url
+ );
+
+ $request = HTTPClient::start();
+ try {
+ $response = $request->post($endpoint,
+ array(
+ 'Content-type: application/x-www-form-urlencoded',
+ 'Accept: application/json'
+ ),
+ $payload
+ );
+
+ if(!in_array($response->getStatus(), array(200,202))) {
+ common_log(LOG_WARNING,
+ "Webmention request failed for '$url' ($endpoint)");
+ }
+ } catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_WARNING,
+ "Webmention request failed for '$url' ($endpoint)");
+ }
+ }
+
+ function getPingback($result) {
+ if (array_key_exists('X-Pingback', $result->headers)) {
+ return $result->headers['X-Pingback'];
+ } else if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]*\/?>/i', $result->body, $match)
+ || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+ return $match[1];
+ }
+ }
+
function pingback($url, $endpoint)
{
- $args = array($this->notice->uri, $url);
+ $args = array($this->notice->getUrl(), $url);
if (!extension_loaded('xmlrpc')) {
if (!dl('xmlrpc.so')) {
$request = HTTPClient::start();
try {
+ $request->setBody(xmlrpc_encode_request('pingback.ping', $args));
$response = $request->post($endpoint,
array('Content-Type: text/xml'),
- xmlrpc_encode_request('pingback.ping', $args));
+ false);
$response = xmlrpc_decode($response->getBody());
if (xmlrpc_is_fault($response)) {
common_log(LOG_WARNING,
// Largely cadged from trackback_cls.php by
// Ran Aroussi <ran@blogish.org>, GPL2 or any later version
// http://phptrackback.sourceforge.net/
- function getTrackback($text, $url)
+ function getTrackback($result)
{
+ $text = $result->body;
+ $url = $result->final_url;
+
if (preg_match_all('/(<rdf:RDF.*?<\/rdf:RDF>)/sm', $text, $match, PREG_SET_ORDER)) {
for ($i = 0; $i < count($match); $i++) {
if (preg_match('|dc:identifier="' . preg_quote($url) . '"|ms', $match[$i][1])) {
'or <a href="http://www.movabletype.org/docs/mttrackback.html">Trackback</a> protocols.'));
return true;
}
+
+ public function onStartInitializeRouter(URLMapper $m)
+ {
+ $m->connect('settings/linkback', array('action' => 'linkbacksettings'));
+ return true;
+ }
+
+ function onEndAccountSettingsNav($action)
+ {
+ $action_name = $action->trimmed('action');
+
+ $action->menuItem(common_local_url('linkbacksettings'),
+ // TRANS: OpenID plugin menu item on user settings page.
+ _m('MENU', 'Send Linkbacks'),
+ // TRANS: OpenID plugin tooltip for user settings menu item.
+ _m('Opt-out of sending linkbacks.'),
+ $action_name === 'linkbacksettings');
+ return true;
+ }
}
--- /dev/null
+<?php
+/**
+ * Settings for Linkback
+ *
+ * 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 Settings
+ * @package StatusNet
+ * @author Stephen Paul Weber <singpolyma@singpolyma.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Settings for Linkback
+ *
+ * Lets users opt out of sending linkbacks
+ *
+ * @category Settings
+ * @author Stephen Paul Weber <singpolyma@singpolyma.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ */
+class LinkbacksettingsAction extends SettingsAction
+{
+ /**
+ * Title of the page
+ *
+ * @return string Page title
+ */
+ function title()
+ {
+ // TRANS: Title of Linkback settings page for a user.
+ return _m('TITLE','Linkback settings');
+ }
+
+ /**
+ * Instructions for use
+ *
+ * @return string Instructions for use
+ */
+ function getInstructions()
+ {
+ // TRANS: Form instructions for Linkback settings.
+ return _m('Linkbacks inform post authors when you link to them. ' .
+ 'You can disable this feature here.');
+ }
+
+ function showContent()
+ {
+ $this->elementStart('form', array('method' => 'post',
+ 'class' => 'form_settings',
+ 'action' =>
+ common_local_url('linkbacksettings')));
+ $this->hidden('token', common_session_token());
+
+ $this->elementStart('fieldset');
+ $this->element('legend', null, _m('LEGEND','Preferences'));
+ $this->checkbox('disable_linkbacks', "Opt out of sending linkbacks for URLs you post", $this->scoped->getPref("linkbackplugin", "disable_linkbacks"));
+ // TRANS: Button text to save OpenID prefs
+ $this->submit('settings_linkback_prefs_save', _m('BUTTON','Save'), 'submit', 'save_prefs');
+ $this->elementEnd('fieldset');
+
+ $this->elementEnd('form');
+ }
+
+ /**
+ * Handle a POST request
+ *
+ * @return void
+ */
+ protected function doPost()
+ {
+ $x = $this->scoped->setPref("linkbackplugin", "disable_linkbacks", $this->boolean('disable_linkbacks'));
+
+ return _m('Linkback preferences saved.');
+ }
+}
// Incoming from a foreign PuSH hub
$qm->connect('pushin', 'PushInQueueHandler');
+
+ // Re-subscribe feeds that need renewal
+ $qm->connect('pushrenew', 'PushRenewQueueHandler');
return true;
}
}
return true;
}
+
+ public function onCronDaily()
+ {
+ try {
+ $sub = FeedSub::renewalCheck();
+ } catch (NoResultException $e) {
+ common_log(LOG_INFO, "There were no expiring feeds.");
+ return;
+ }
+
+ $qm = QueueManager::get();
+ while ($sub->fetch()) {
+ $item = array('feedsub_id' => $sub->id);
+ $qm->enqueue($item, 'pushrenew');
+ }
+ }
}
{
$fs = new FeedSub();
// the "" empty string check is because we historically haven't saved unsubscribed feeds as NULL
- $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() - INTERVAL 1 day');
+ $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() + INTERVAL 1 day');
if (!$fs->find()) { // find can be both false and 0, depending on why nothing was found
throw new NoResultException($fs);
}
$response = $client->post($hub, $headers, $post);
$status = $response->getStatus();
// PuSH specificed response status code
- if ($status == 202) {
+ if ($status == 202 || $status == 204) {
common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
return;
} else if ($status >= 200 && $status < 300) {
}
}
+ $obj = ActivityUtils::getFeedAuthor($feedEl);
+
// @todo FIXME: We should check whether this feed has elements
// with different <author> or <dc:creator> elements, and... I dunno.
// Do something about that.
- $obj = ActivityObject::fromRssChannel($feedEl);
+ if(empty($obj)) { $obj = ActivityObject::fromRssChannel($feedEl); }
return self::ensureActivityObjectProfile($obj, $hints);
}
--- /dev/null
+<?php
+/*
+ * 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/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Renew an expiring feedsub
+ * @package FeedSub
+ * @author Stephen Paul Weber <singpolyma@singpolyma.net>
+ */
+class PushRenewQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'pushrenew';
+ }
+
+ function handle($data)
+ {
+ $feedsub_id = $data['feedsub_id'];
+ $feedsub = FeedSub::getKV('id', $feedsub_id);
+ if ($feedsub instanceof FeedSub) {
+ try {
+ common_log(LOG_INFO, "Renewing feed subscription\n\tExp.: {$feedsub->sub_end}\n\tFeed: {$feedsub->uri}\n\tHub: {$feedsub->huburi}");
+ $feedsub->renew();
+ } catch(Exception $e) {
+ common_log(LOG_ERR, "Exception during PuSH renew processing for $feedsub->uri: " . $e->getMessage());
+ }
+ } else {
+ common_log(LOG_ERR, "Discarding renew for unknown feed subscription id $feedsub_id");
+ }
+ return true;
+ }
+}
'class' => 'h-card p-author',
'title' => $repeater->getFancyName());
- $nli->out->elementStart('span', 'repeat h-entry');
+ $nli->out->elementStart('span', 'repeat');
// TRANS: Addition in notice list item if notice was repeated. Followed by a span with a nickname.
$nli->out->raw(_('Repeated by').' ');
$url = common_local_url('webfinger') . '?resource='.$acct;
foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
- header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"');
+ header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"', false);
}
}
}
width:100%;
}
-.notice .p-author {
- margin-right: 8px;
+.notice .parents {
+ display: inline;
}
-.notice .addressees::before {
- content: '\25B8';
+.notice .parents::before {
+ content: '\25B8 ';
}
.notice .addressees, .notice .addressees li {
display: inline;