summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po10
-rw-r--r--plugins/CacheLog/locale/nb/LC_MESSAGES/CacheLog.po26
-rw-r--r--plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po10
-rw-r--r--plugins/GroupFavorited/groupfavoritedaction.php7
-rw-r--r--plugins/Mapstraction/allmap.php7
-rw-r--r--plugins/Mapstraction/usermap.php7
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php113
-rw-r--r--plugins/OStatus/locale/OStatus.pot64
-rw-r--r--plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po70
-rw-r--r--plugins/OStatus/locale/ia/LC_MESSAGES/OStatus.po70
-rw-r--r--plugins/OStatus/locale/mk/LC_MESSAGES/OStatus.po70
-rw-r--r--plugins/OStatus/locale/nl/LC_MESSAGES/OStatus.po70
-rw-r--r--plugins/OStatus/locale/uk/LC_MESSAGES/OStatus.po70
-rw-r--r--plugins/RSSCloud/RSSCloudPlugin.php23
-rw-r--r--plugins/Realtime/README4
-rw-r--r--plugins/Realtime/RealtimePlugin.php27
-rw-r--r--plugins/Realtime/locale/Realtime.pot53
-rw-r--r--plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po58
-rw-r--r--plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po58
-rw-r--r--plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po58
-rw-r--r--plugins/Realtime/realtimeupdate.css2
-rw-r--r--plugins/Realtime/realtimeupdate.js23
-rw-r--r--plugins/Sample/SamplePlugin.php17
-rw-r--r--plugins/TwitterBridge/TwitterBridgePlugin.php11
-rw-r--r--plugins/TwitterBridge/daemons/twitterdaemon.php314
-rwxr-xr-xplugins/TwitterBridge/daemons/twitterstatusfetcher.php591
-rw-r--r--plugins/TwitterBridge/jsonstreamreader.php265
-rw-r--r--plugins/TwitterBridge/scripts/fakestream.php147
-rw-r--r--plugins/TwitterBridge/scripts/streamtest.php244
-rw-r--r--plugins/TwitterBridge/tweetctlqueuehandler.php59
-rw-r--r--plugins/TwitterBridge/tweetinqueuehandler.php63
-rw-r--r--plugins/TwitterBridge/twitterimport.php651
-rw-r--r--plugins/TwitterBridge/twittersettings.php14
-rw-r--r--plugins/TwitterBridge/twitterstreamreader.php285
-rw-r--r--plugins/UserFlag/UserFlagPlugin.php60
-rw-r--r--plugins/UserFlag/User_flag_profile.php23
-rw-r--r--plugins/UserFlag/flagprofile.php15
-rw-r--r--plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po26
-rw-r--r--plugins/XCache/locale/fi/LC_MESSAGES/XCache.po30
39 files changed, 2747 insertions, 968 deletions
diff --git a/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po
index cbe5e0554..66ffa6b7a 100644
--- a/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po
@@ -10,14 +10,14 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Adsense\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:09+0000\n"
+"POT-Creation-Date: 2010-10-30 23:18+0000\n"
+"PO-Revision-Date: 2010-10-30 23:20:58+0000\n"
"Language-Team: Brazilian Portuguese <http://translatewiki.net/wiki/Portal:pt-"
"br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:11:50+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75708); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: pt-br\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -80,7 +80,7 @@ msgstr "Código colocado dentro de um retângulo."
#: adsenseadminpanel.php:188
msgid "Leaderboard"
-msgstr ""
+msgstr "Classificação"
#: adsenseadminpanel.php:189
msgid "Leaderboard slot code"
diff --git a/plugins/CacheLog/locale/nb/LC_MESSAGES/CacheLog.po b/plugins/CacheLog/locale/nb/LC_MESSAGES/CacheLog.po
deleted file mode 100644
index e9622df88..000000000
--- a/plugins/CacheLog/locale/nb/LC_MESSAGES/CacheLog.po
+++ /dev/null
@@ -1,26 +0,0 @@
-# Translation of StatusNet - CacheLog to Norwegian (bokmål)‬ (‪Norsk (bokmål)‬)
-# Expored from translatewiki.net
-#
-# Author: Nghtwlkr
-# --
-# This file is distributed under the same license as the StatusNet package.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: StatusNet - CacheLog\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:21+0000\n"
-"Language-Team: Norwegian (bokmål)‬ <http://translatewiki.net/wiki/Portal:no>\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 18:57:00+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
-"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
-"X-Language-Code: no\n"
-"X-Message-Group: #out-statusnet-plugin-cachelog\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: CacheLogPlugin.php:106
-msgid "Log reads and writes to the cache."
-msgstr "Logg leser og skriver til hurtiglageret."
diff --git a/plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po b/plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po
index 5b60c244d..0680fec71 100644
--- a/plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po
+++ b/plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Facebook\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:35+0000\n"
+"POT-Creation-Date: 2010-10-30 23:18+0000\n"
+"PO-Revision-Date: 2010-10-30 23:21:30+0000\n"
"Language-Team: Dutch <http://translatewiki.net/wiki/Portal:nl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 18:57:01+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:45+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75708); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-facebook\n"
@@ -37,7 +37,7 @@ msgid ""
msgstr ""
"Hallo %1$s.\n"
"\n"
-"Het spijt ons je te moeten meedelen dat het niet mogelijk is uw "
+"Het spijt ons u te moeten meedelen dat het niet mogelijk is uw "
"Facebookstatus bij te werken vanuit %2$s. De Facebookapplicatie is "
"uitgeschakeld voor uw gebruiker. Dit kan komen doordat u de toegangsrechten "
"voor de Facebookapplicatie hebt ingetrokken of omdat u uw Facebookgebruiker "
diff --git a/plugins/GroupFavorited/groupfavoritedaction.php b/plugins/GroupFavorited/groupfavoritedaction.php
index dbd37abbc..dcbf7d0bc 100644
--- a/plugins/GroupFavorited/groupfavoritedaction.php
+++ b/plugins/GroupFavorited/groupfavoritedaction.php
@@ -41,12 +41,7 @@ class GroupFavoritedAction extends ShowgroupAction
*/
function title()
{
- if (!empty($this->group->fullname)) {
- // @todo Create a core method to create this properly. i18n issue.
- $base = $this->group->fullname . ' (' . $this->group->nickname . ')';
- } else {
- $base = $this->group->nickname;
- }
+ $base = $this->group->getFancyName();
if ($this->page == 1) {
// TRANS: %s is a group name.
diff --git a/plugins/Mapstraction/allmap.php b/plugins/Mapstraction/allmap.php
index 6e2e1d122..62d8d0445 100644
--- a/plugins/Mapstraction/allmap.php
+++ b/plugins/Mapstraction/allmap.php
@@ -61,12 +61,7 @@ class AllmapAction extends MapAction
function title()
{
- if (!empty($this->profile->fullname)) {
- // @todo FIXME: Bad i18n. Should be "%1$s (%2$s)".
- $base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
- } else {
- $base = $this->user->nickname;
- }
+ $base = $this->profile->getFancyName();
if ($this->page == 1) {
// TRANS: Page title.
diff --git a/plugins/Mapstraction/usermap.php b/plugins/Mapstraction/usermap.php
index 0ee956159..54412146e 100644
--- a/plugins/Mapstraction/usermap.php
+++ b/plugins/Mapstraction/usermap.php
@@ -58,12 +58,7 @@ class UsermapAction extends MapAction
function title()
{
- if (!empty($this->profile->fullname)) {
- // @todo FIXME: Bad i18n. Should be '%1$s (%2$s)'
- $base = $this->profile->fullname . ' (' . $this->user->nickname . ')';
- } else {
- $base = $this->user->nickname;
- }
+ $base = $this->profile->getFancyName();
if ($this->page == 1) {
// @todo CHECKME: inconsisten with paged variant below. " map" missing.
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 03fcb71df..3dd00de29 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -25,7 +25,8 @@ if (!defined('STATUSNET')) {
* @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
-class Ostatus_profile extends Memcached_DataObject
+
+class Ostatus_profile extends Managed_DataObject
{
public $__table = 'ostatus_profile';
@@ -47,74 +48,35 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * 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 table definition for Schema setup and DB_DataObject usage.
*
* @return array array of column definitions
*/
- function table()
- {
- return array('uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
- 'profile_id' => DB_DATAOBJECT_INT,
- 'group_id' => DB_DATAOBJECT_INT,
- 'feeduri' => DB_DATAOBJECT_STR,
- 'salmonuri' => DB_DATAOBJECT_STR,
- 'avatar' => 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()
{
- return array(new ColumnDef('uri', 'varchar',
- 255, false, 'PRI'),
- new ColumnDef('profile_id', 'integer',
- null, true, 'UNI'),
- new ColumnDef('group_id', 'integer',
- null, true, 'UNI'),
- new ColumnDef('feeduri', 'varchar',
- 255, true, 'UNI'),
- new ColumnDef('salmonuri', 'text',
- null, true),
- new ColumnDef('avatar', 'text',
- null, true),
- new ColumnDef('created', 'datetime',
- null, false),
- new ColumnDef('modified', 'datetime',
- null, false));
- }
-
- /**
- * 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()
- {
- return array('uri' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U');
- }
-
- function sequenceKey()
- {
- return array(false, false, false);
+ return array(
+ 'fields' => array(
+ 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true),
+ 'profile_id' => array('type' => 'integer'),
+ 'group_id' => array('type' => 'integer'),
+ 'feeduri' => array('type' => 'varchar', 'length' => 255),
+ 'salmonuri' => array('type' => 'varchar', 'length' => 255),
+ 'avatar' => array('type' => 'text'),
+ 'created' => array('type' => 'datetime', 'not null' => true),
+ 'modified' => array('type' => 'datetime', 'not null' => true),
+ ),
+ 'primary key' => array('uri'),
+ 'unique keys' => array(
+ 'ostatus_profile_profile_id_idx' => array('profile_id'),
+ 'ostatus_profile_group_id_idx' => array('group_id'),
+ 'ostatus_profile_feeduri_idx' => array('feeduri'),
+ ),
+ 'foreign keys' => array(
+ 'ostatus_profile_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
+ 'ostatus_profile_group_id_fkey' => array('user_group', array('group_id' => 'id')),
+ ),
+ );
}
/**
@@ -188,10 +150,10 @@ class Ostatus_profile extends Memcached_DataObject
} else if ($this->group_id && !$this->profile_id) {
return true;
} else if ($this->group_id && $this->profile_id) {
- // TRANS: Server exception.
+ // TRANS: Server exception. %s is a URI.
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs set for %s.'),$this->uri));
} else {
- // TRANS: Server exception.
+ // TRANS: Server exception. %s is a URI.
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs empty for %s.'),$this->uri));
}
}
@@ -405,6 +367,7 @@ class Ostatus_profile extends Memcached_DataObject
} else if ($feed->localName == 'rss') { // @fixme check namespace
$this->processRssFeed($feed, $source);
} else {
+ // TRANS: Exception.
throw new Exception(_m('Unknown feed format.'));
}
}
@@ -428,6 +391,7 @@ class Ostatus_profile extends Memcached_DataObject
$channels = $rss->getElementsByTagName('channel');
if ($channels->length == 0) {
+ // TRANS: Exception.
throw new Exception(_m('RSS feed without a channel.'));
} else if ($channels->length > 1) {
common_log(LOG_WARNING, __METHOD__ . ": more than one channel in an RSS feed");
@@ -555,7 +519,7 @@ class Ostatus_profile extends Memcached_DataObject
$sourceContent = $note->title;
} else {
// @fixme fetch from $sourceUrl?
- // TRANS: Client exception. %s is a source URL.
+ // TRANS: Client exception. %s is a source URI.
throw new ClientException(sprintf(_m('No content for notice %s.'),$sourceUri));
}
@@ -588,7 +552,9 @@ class Ostatus_profile extends Memcached_DataObject
// We mark up the attachment link specially for the HTML output
// so we can fold-out the full version inline.
- // TRANS: Shown when a notice is longer than supported and/or when attachments are present.
+ // @fixme I18N this tooltip will be saved with the site's default language
+ // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+ // TRANS: this will usually be replaced with localised text from StatusNet core messages.
$showMoreText = _m('Show more');
$attachUrl = common_local_url('attachment',
array('attachment' => $attachment->id));
@@ -839,7 +805,7 @@ class Ostatus_profile extends Memcached_DataObject
return self::ensureFeedURL($feedurl, $hints);
}
- // TRANS: Exception.
+ // TRANS: Exception. %s is a URL.
throw new Exception(sprintf(_m('Could not find a feed URL for profile page %s.'),$finalUrl));
}
@@ -977,6 +943,7 @@ class Ostatus_profile extends Memcached_DataObject
}
// XXX: make some educated guesses here
+ // TRANS: Feed sub exception.
throw new FeedSubException(_m('Can\'t find enough profile information to make a feed.'));
}
@@ -1036,6 +1003,7 @@ class Ostatus_profile extends Memcached_DataObject
return;
}
if (!common_valid_http_url($url)) {
+ // TRANS: Server exception. %s is a URL.
throw new ServerException(sprintf(_m("Invalid avatar URL %s."), $url));
}
@@ -1046,6 +1014,7 @@ class Ostatus_profile extends Memcached_DataObject
}
if (!$self) {
throw new ServerException(sprintf(
+ // TRANS: Server exception. %s is a URI.
_m("Tried to update avatar for unsaved remote profile %s."),
$this->uri));
}
@@ -1055,6 +1024,7 @@ class Ostatus_profile extends Memcached_DataObject
$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
try {
if (!copy($url, $temp_filename)) {
+ // TRANS: Server exception. %s is a URL.
throw new ServerException(sprintf(_m("Unable to fetch avatar from %s."), $url));
}
@@ -1337,7 +1307,7 @@ class Ostatus_profile extends Memcached_DataObject
$oprofile->profile_id = $profile->insert();
if (!$oprofile->profile_id) {
- // TRANS: Exception.
+ // TRANS: Server exception.
throw new ServerException(_m('Can\'t save local profile.'));
}
} else {
@@ -1348,7 +1318,7 @@ class Ostatus_profile extends Memcached_DataObject
$oprofile->group_id = $group->insert();
if (!$oprofile->group_id) {
- // TRANS: Exception.
+ // TRANS: Server exception.
throw new ServerException(_m('Can\'t save local profile.'));
}
}
@@ -1356,7 +1326,7 @@ class Ostatus_profile extends Memcached_DataObject
$ok = $oprofile->insert();
if (!$ok) {
- // TRANS: Exception.
+ // TRANS: Server exception.
throw new ServerException(_m('Can\'t save OStatus profile.'));
}
@@ -1795,6 +1765,7 @@ class Ostatus_profile extends Memcached_DataObject
if ($file_id === false) {
common_log_db_error($file, "INSERT", __FILE__);
+ // TRANS: Server exception.
throw new ServerException(_m('Could not store HTML content of long post as file.'));
}
diff --git a/plugins/OStatus/locale/OStatus.pot b/plugins/OStatus/locale/OStatus.pot
index 2473e730f..745a87c98 100644
--- a/plugins/OStatus/locale/OStatus.pot
+++ b/plugins/OStatus/locale/OStatus.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -119,13 +119,13 @@ msgstr ""
msgid "Attempting to end PuSH subscription for feed with no hub."
msgstr ""
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
msgstr ""
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -145,105 +145,113 @@ msgid ""
"Activity entry."
msgstr ""
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr ""
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr ""
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr ""
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr ""
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr ""
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr ""
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr ""
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr ""
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr ""
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr ""
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr ""
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr ""
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr ""
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr ""
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr ""
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr ""
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr ""
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr ""
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr ""
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr ""
diff --git a/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po
index b0ef34b6b..2d0a49c31 100644
--- a/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po
+++ b/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po
@@ -10,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - OStatus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:14+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
"Language-Team: French <http://translatewiki.net/wiki/Portal:fr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: fr\n"
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
@@ -131,7 +131,7 @@ msgstr ""
"Tente d’arrêter l’inscription PuSH à un flux d’information sans "
"concentrateur."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
@@ -139,7 +139,7 @@ msgstr ""
"État invalide du profil OStatus : identifiants à la fois de groupe et de "
"profil définis pour « %s »."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -163,111 +163,119 @@ msgstr ""
"Type invalide passé à la méthode « Ostatus_profile::notify ». Ce doit être "
"une chaîne XML ou une entrée « Activity »."
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr "Format de flux d’information inconnu."
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr "Flux RSS sans canal."
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr "Impossible de gérer cette sorte de publication."
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr "Aucun contenu dans l’avis « %s »."
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr "Voir davantage"
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr "Impossible d’atteindre la page de profil « %s »."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr ""
"Impossible de trouver une adresse URL de flux d’information pour la page de "
"profil « %s »."
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr ""
"Impossible de trouver assez d’informations de profil pour créer un flux "
"d’information."
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr "Adresse URL d’avatar « %s » invalide."
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr ""
"Tente de mettre à jour l’avatar associé au profil distant non sauvegardé « %s "
"»."
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr "Impossible de récupérer l’avatar depuis « %s »."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr "L’utilisateur local ne peut être référencé comme distant."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr "Le groupe local ne peut être référencé comme distant."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr "Impossible de sauvegarder le profil local."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr "Impossible de sauvegarder le profil OStatus."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr "Ce n’est pas une adresse « webfinger » valide."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr "Impossible de sauvegarder le profil pour « %s »."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr "Impossible d’enregistrer le profil OStatus pour « %s »."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr "Impossible de trouver un profil valide pour « %s »."
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr ""
"Impossible de stocker le contenu HTML d’une longue publication en un fichier."
diff --git a/plugins/OStatus/locale/ia/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/ia/LC_MESSAGES/OStatus.po
index fdeba50c2..f2f322de0 100644
--- a/plugins/OStatus/locale/ia/LC_MESSAGES/OStatus.po
+++ b/plugins/OStatus/locale/ia/LC_MESSAGES/OStatus.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - OStatus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:15+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
"Language-Team: Interlingua <http://translatewiki.net/wiki/Portal:ia>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ia\n"
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
@@ -126,14 +126,14 @@ msgstr "Tentativa de comenciar subscription PuSH pro syndication sin centro."
msgid "Attempting to end PuSH subscription for feed with no hub."
msgstr "Tentativa de terminar subscription PuSH pro syndication sin centro."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
msgstr ""
"Stato ostatus_profile invalide: IDs e de gruppo e de profilo definite pro %s."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -156,106 +156,114 @@ msgstr ""
"Typo invalide passate a Ostatos_profile::notify. Illo debe esser catena XML "
"o entrata Activity."
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr "Formato de syndication incognite."
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr "Syndication RSS sin canal."
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr "Non pote tractar iste typo de message."
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr "Nulle contento pro nota %s."
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr "Monstrar plus"
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr "Non poteva attinger pagina de profilo %s."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr "Non poteva trovar un URL de syndication pro pagina de profilo %s."
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr ""
"Non pote trovar satis de information de profilo pro facer un syndication."
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr "URL de avatar %s invalide."
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr "Tentava actualisar avatar pro profilo remote non salveguardate %s."
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr "Incapace de obtener avatar ab %s."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr "Usator local non pote esser referentiate como remote."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr "Gruppo local non pote esser referentiate como remote."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr "Non pote salveguardar profilo local."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr "Non pote salveguardar profilo OStatus."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr "Adresse webfinger invalide."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr "Non poteva salveguardar profilo pro \"%s\"."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr "Non poteva salveguardar osatus_profile pro %s."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr "Non poteva trovar un profilo valide pro \"%s\"."
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr "Non poteva immagazinar contento HTML de longe message como file."
diff --git a/plugins/OStatus/locale/mk/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/mk/LC_MESSAGES/OStatus.po
index 44baf29ee..fe2cb7e10 100644
--- a/plugins/OStatus/locale/mk/LC_MESSAGES/OStatus.po
+++ b/plugins/OStatus/locale/mk/LC_MESSAGES/OStatus.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - OStatus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:15+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
"Language-Team: Macedonian <http://translatewiki.net/wiki/Portal:mk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: mk\n"
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
@@ -127,7 +127,7 @@ msgid "Attempting to end PuSH subscription for feed with no hub."
msgstr ""
"Се обидувам да ставам крај на PuSH-претплатата за емитување без средиште."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
@@ -135,7 +135,7 @@ msgstr ""
"Неважечка ostatus_profile-состојба: назнаките (ID) на групата и профилот се "
"наместени за %s."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -159,106 +159,114 @@ msgstr ""
"На Ostatus_profile::notify е пренесен неважечки тип. Мора да биде XML-низа "
"или ставка во Activity."
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr "Непознат формат на каналско емитување."
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr "RSS-емитување без канал."
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr "Не можам да работам со таква објава."
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr "Нема содржина за забелешката %s."
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr "Повеќе"
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr "Не можев да ја добијам профилната страница %s."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr "Не можев да пронајдам каналска URL-адреса за профилната страница %s."
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr "Не можев да најдам доволно профилни податоци за да направам канал."
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr "Неважечка URL-адреса за аватарот: %s."
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr ""
"Се обидов да го подновам аватарот за незачуваниот далечински профил %s."
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr "Не можам да го добијам аватарот од %s."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr "Локалниот корисник не може да се наведе како далечински."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr "Локалната група не може да се наведе како далечинска."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr "Не можам да го зачувам локалниот профил."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr "Не можам да го зачувам профилот од OStatus."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr "Ова не е важечка Webfinger-адреса"
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr "Не можам да го зачувам профилот за „%s“."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr "Не можам да го зачувам ostatus_profile за „%s“."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr "Не можев да пронајдам важечки профил за „%s“."
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr ""
"Не можам да ја складирам HTML-содржината на долгата објава како податотека."
diff --git a/plugins/OStatus/locale/nl/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/nl/LC_MESSAGES/OStatus.po
index 7ffad8df9..d37b8cb01 100644
--- a/plugins/OStatus/locale/nl/LC_MESSAGES/OStatus.po
+++ b/plugins/OStatus/locale/nl/LC_MESSAGES/OStatus.po
@@ -10,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - OStatus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:16+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
"Language-Team: Dutch <http://translatewiki.net/wiki/Portal:nl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
@@ -133,7 +133,7 @@ msgid "Attempting to end PuSH subscription for feed with no hub."
msgstr ""
"Aan het proberen een PuSH-abonnement te verwijderen voor een feed zonder hub."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
@@ -141,7 +141,7 @@ msgstr ""
"Ongeldige ostatus_profile status: het ID voor zowel de groep als het profiel "
"voor %s is ingesteld."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -165,112 +165,120 @@ msgstr ""
"Ongeldig type doorgegeven aan Ostatus_profile::notify. Het moet een XML-"
"string of Activity zijn."
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr "Onbekend feedformaat"
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr "RSS-feed zonder kanaal."
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr "Dat type post kan niet verwerkt worden."
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr "Geen inhoud voor mededeling %s."
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr "Meer weergeven"
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr "Het was niet mogelijk de profielpagina %s te bereiken."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr "Het was niet mogelijk de feed-URL voor de profielpagina %s te vinden."
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr ""
"Het was niet mogelijk voldoende profielinformatie te vinden om een feed te "
"maken."
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr "Ongeldige avatar-URL %s."
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr ""
"Geprobeerd om een avatar bij te werken voor het niet opgeslagen profiel %s."
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr "Het was niet mogelijk de avatar op te halen van %s."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr ""
"Naar een lokale gebruiker kan niet verwezen worden alsof die zich bij een "
"andere dienst bevindt."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr ""
"Naar een lokale groep kan niet verwezen worden alsof die zich bij een andere "
"dienst bevindt."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr "Het was niet mogelijk het lokale profiel op te slaan."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr "Het was niet mogelijk het Ostatusprofiel op te slaan."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr "Geen geldig webfingeradres."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr "Het was niet mogelijk het profiel voor \"%s\" op te slaan."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr "Het was niet mogelijk het ostatus_profile voor \"%s\" op te slaan."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr "Er is geen geldig profiel voor \"%s\" gevonden."
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr ""
"Het was niet mogelijk de HTML-inhoud van het lange bericht als bestand op te "
diff --git a/plugins/OStatus/locale/uk/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/uk/LC_MESSAGES/OStatus.po
index 7d3ffb80f..88ba205b1 100644
--- a/plugins/OStatus/locale/uk/LC_MESSAGES/OStatus.po
+++ b/plugins/OStatus/locale/uk/LC_MESSAGES/OStatus.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - OStatus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:17+0000\n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
"Language-Team: Ukrainian <http://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
@@ -130,7 +130,7 @@ msgstr ""
"Спроба скасувати підписку за допомогою PuSH до веб-стрічки, котра не має "
"вузла."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:192
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
@@ -138,7 +138,7 @@ msgstr ""
"Невірний стан параметру ostatus_profile: як групові, так і персональні "
"ідентифікатори встановлено для %s."
-#. TRANS: Server exception.
+#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:195
#, php-format
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
@@ -162,106 +162,114 @@ msgstr ""
"До параметру Ostatus_profile::notify передано невірний тип. Це має бути або "
"рядок у форматі XML, або запис активності."
-#: classes/Ostatus_profile.php:408
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:409
msgid "Unknown feed format."
msgstr "Невідомий формат веб-стрічки."
-#: classes/Ostatus_profile.php:431
+#. TRANS: Exception.
+#: classes/Ostatus_profile.php:433
msgid "RSS feed without a channel."
msgstr "RSS-стрічка не має каналу."
#. TRANS: Client exception.
-#: classes/Ostatus_profile.php:476
+#: classes/Ostatus_profile.php:478
msgid "Can't handle that kind of post."
msgstr "Не вдається обробити такий тип допису."
-#. TRANS: Client exception. %s is a source URL.
-#: classes/Ostatus_profile.php:559
+#. TRANS: Client exception. %s is a source URI.
+#: classes/Ostatus_profile.php:561
#, php-format
msgid "No content for notice %s."
msgstr "Допис %s не має змісту."
-#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
-#: classes/Ostatus_profile.php:592
+#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
+#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
+#: classes/Ostatus_profile.php:596
msgid "Show more"
msgstr "Розгорнути"
#. TRANS: Exception. %s is a profile URL.
-#: classes/Ostatus_profile.php:785
+#: classes/Ostatus_profile.php:789
#, php-format
msgid "Could not reach profile page %s."
msgstr "Не вдалося досягти сторінки профілю %s."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:843
+#. TRANS: Exception. %s is a URL.
+#: classes/Ostatus_profile.php:847
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr "Не вдалося знайти URL веб-стрічки для сторінки профілю %s."
-#: classes/Ostatus_profile.php:980
+#. TRANS: Feed sub exception.
+#: classes/Ostatus_profile.php:985
msgid "Can't find enough profile information to make a feed."
msgstr ""
"Не можу знайти достатньо інформації про профіль, аби сформувати веб-стрічку."
-#: classes/Ostatus_profile.php:1039
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1045
#, php-format
msgid "Invalid avatar URL %s."
msgstr "Невірна URL-адреса аватари %s."
-#: classes/Ostatus_profile.php:1049
+#. TRANS: Server exception. %s is a URI.
+#: classes/Ostatus_profile.php:1056
#, php-format
msgid "Tried to update avatar for unsaved remote profile %s."
msgstr "Намагаюся оновити аватару для не збереженого віддаленого профілю %s."
-#: classes/Ostatus_profile.php:1058
+#. TRANS: Server exception. %s is a URL.
+#: classes/Ostatus_profile.php:1066
#, php-format
msgid "Unable to fetch avatar from %s."
msgstr "Неможливо завантажити аватару з %s."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1284
+#: classes/Ostatus_profile.php:1292
msgid "Local user can't be referenced as remote."
msgstr "Місцевий користувач не може бути зазначеним у якості віддаленого."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1289
+#: classes/Ostatus_profile.php:1297
msgid "Local group can't be referenced as remote."
msgstr "Локальну спільноту не можна зазначити у якості віддаленої."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
msgid "Can't save local profile."
msgstr "Не вдається зберегти місцевий профіль."
-#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1360
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1368
msgid "Can't save OStatus profile."
msgstr "Не вдається зберегти профіль OStatus."
#. TRANS: Exception.
-#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
+#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
msgid "Not a valid webfinger address."
msgstr "Це недійсна адреса для протоколу WebFinger."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1729
+#: classes/Ostatus_profile.php:1737
#, php-format
msgid "Couldn't save profile for \"%s\"."
msgstr "Не можу зберегти профіль для «%s»."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1748
+#: classes/Ostatus_profile.php:1756
#, php-format
msgid "Couldn't save ostatus_profile for \"%s\"."
msgstr "Не можу зберегти профіль OStatus для «%s»."
#. TRANS: Exception. %s is a webfinger address.
-#: classes/Ostatus_profile.php:1756
+#: classes/Ostatus_profile.php:1764
#, php-format
msgid "Couldn't find a valid profile for \"%s\"."
msgstr "не можу знайти відповідний й профіль для «%s»."
-#: classes/Ostatus_profile.php:1798
+#. TRANS: Server exception.
+#: classes/Ostatus_profile.php:1807
msgid "Could not store HTML content of long post as file."
msgstr "Не можу зберегти HTML місткого допису у якості файлу."
diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php
index bba0be515..7d9c39a67 100644
--- a/plugins/RSSCloud/RSSCloudPlugin.php
+++ b/plugins/RSSCloud/RSSCloudPlugin.php
@@ -209,19 +209,16 @@ class RSSCloudPlugin extends Plugin
{
$schema = Schema::get();
$schema->ensureTable('rsscloud_subscription',
- array(new ColumnDef('subscribed', 'integer',
- null, false, 'PRI'),
- new ColumnDef('url', 'varchar',
- '255', false, 'PRI'),
- new ColumnDef('failures', 'integer',
- null, false, null, 0),
- new ColumnDef('created', 'datetime',
- null, false),
- new ColumnDef('modified', 'timestamp',
- null, false, null,
- 'CURRENT_TIMESTAMP',
- 'on update CURRENT_TIMESTAMP')
- ));
+ array(
+ 'fields' => array(
+ 'subscribed' => array('type' => 'int', 'not null' => true),
+ 'url' => array('type' => 'varchar', 'length' => '255', 'not null' => true),
+ 'failures' => array('type' => 'int', 'not null' => true, 'default' => 0),
+ 'created' => array('type' => 'datetime', 'not null' => true),
+ 'modified' => array('type' => 'timestamp', 'not null' => true),
+ ),
+ 'primary key' => array('subscribed', 'url'),
+ ));
return true;
}
diff --git a/plugins/Realtime/README b/plugins/Realtime/README
index 99c79cfab..9b36d87f3 100644
--- a/plugins/Realtime/README
+++ b/plugins/Realtime/README
@@ -5,6 +5,6 @@
* Pause ~ retain up to 50-100 most recent notices
* Add geo data
* Make it work for Conversation page (perhaps a little tricky)
-* IE is updating the counter in document title all the time (Not sure if this is still an issue)
+* IE is updating the counter in document title all the time (Not sure if this
+ is still an issue)
* Reconsider the timestamp approach
-
diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php
index 352afcf78..99d1ff9c1 100644
--- a/plugins/Realtime/RealtimePlugin.php
+++ b/plugins/Realtime/RealtimePlugin.php
@@ -43,7 +43,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
-
class RealtimePlugin extends Plugin
{
protected $replyurl = null;
@@ -326,6 +325,32 @@ class RealtimePlugin extends Plugin
return array('plugins/Realtime/realtimeupdate.js');
}
+ /**
+ * Export any i18n messages that need to be loaded at runtime...
+ *
+ * @param Action $action
+ * @param array $messages
+ *
+ * @return boolean hook return value
+ */
+ function onEndScriptMessages($action, &$messages)
+ {
+ // TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+ $messages['realtime_play'] = _m('BUTTON', 'Play');
+ // TRANS: Tooltip for realtime view "play" button.
+ $messages['realtime_play_tooltip'] = _m('TOOLTIP', 'Play');
+ // TRANS: Text label for realtime view "pause" button
+ $messages['realtime_pause'] = _m('BUTTON', 'Pause');
+ // TRANS: Tooltip for realtime view "pause" button
+ $messages['realtime_pause_tooltip'] = _m('TOOLTIP', 'Pause');
+ // TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
+ $messages['realtime_popup'] = _m('BUTTON', 'Pop up');
+ // TRANS: Tooltip for realtime view "popup" button.
+ $messages['realtime_popup_tooltip'] = _m('TOOLTIP', 'Pop up in a window');
+
+ return true;
+ }
+
function _updateInitialize($timeline, $user_id)
{
return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->repeaturl\", \"$this->deleteurl\"); ";
diff --git a/plugins/Realtime/locale/Realtime.pot b/plugins/Realtime/locale/Realtime.pot
new file mode 100644
index 000000000..e491ce0c7
--- /dev/null
+++ b/plugins/Realtime/locale/Realtime.pot
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-02 19:46+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+#: RealtimePlugin.php:339
+msgctxt "BUTTON"
+msgid "Play"
+msgstr ""
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr ""
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr ""
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr ""
+
+#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
+#: RealtimePlugin.php:347
+msgctxt "BUTTON"
+msgid "Pop up"
+msgstr ""
+
+#. TRANS: Tooltip for realtime view "popup" button.
+#: RealtimePlugin.php:349
+msgctxt "TOOLTIP"
+msgid "Pop up in a window"
+msgstr ""
diff --git a/plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..94fdb4dcc
--- /dev/null
+++ b/plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Interlingua (Interlingua)
+# Expored from translatewiki.net
+#
+# Author: McDutchie
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - Realtime\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
+"Language-Team: Interlingua <http://translatewiki.net/wiki/Portal:ia>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: ia\n"
+"X-Message-Group: #out-statusnet-plugin-realtime\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+#: RealtimePlugin.php:339
+msgctxt "BUTTON"
+msgid "Play"
+msgstr "Reproducer"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Reproducer"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Pausar"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Pausar"
+
+#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
+#: RealtimePlugin.php:347
+msgctxt "BUTTON"
+msgid "Pop up"
+msgstr "Fenestra"
+
+#. TRANS: Tooltip for realtime view "popup" button.
+#: RealtimePlugin.php:349
+msgctxt "TOOLTIP"
+msgid "Pop up in a window"
+msgstr "Aperir le reproductor in un nove fenestra"
diff --git a/plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..0b4024a76
--- /dev/null
+++ b/plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Macedonian (Македонски)
+# Expored from translatewiki.net
+#
+# Author: Bjankuloski06
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - Realtime\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
+"Language-Team: Macedonian <http://translatewiki.net/wiki/Portal:mk>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: mk\n"
+"X-Message-Group: #out-statusnet-plugin-realtime\n"
+"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"
+
+#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+#: RealtimePlugin.php:339
+msgctxt "BUTTON"
+msgid "Play"
+msgstr "Пушти"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Пушти"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Паузирај"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Паузирај"
+
+#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
+#: RealtimePlugin.php:347
+msgctxt "BUTTON"
+msgid "Pop up"
+msgstr "Прозорче"
+
+#. TRANS: Tooltip for realtime view "popup" button.
+#: RealtimePlugin.php:349
+msgctxt "TOOLTIP"
+msgid "Pop up in a window"
+msgstr "Прикажи во прозорче"
diff --git a/plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..ac5b2f45c
--- /dev/null
+++ b/plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Dutch (Nederlands)
+# Expored from translatewiki.net
+#
+# Author: Siebrand
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - Realtime\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
+"Language-Team: Dutch <http://translatewiki.net/wiki/Portal:nl>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: nl\n"
+"X-Message-Group: #out-statusnet-plugin-realtime\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+#: RealtimePlugin.php:339
+msgctxt "BUTTON"
+msgid "Play"
+msgstr "Afspelen"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Afspelen"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Pauzeren"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Pauzeren"
+
+#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
+#: RealtimePlugin.php:347
+msgctxt "BUTTON"
+msgid "Pop up"
+msgstr "Pop-up"
+
+#. TRANS: Tooltip for realtime view "popup" button.
+#: RealtimePlugin.php:349
+msgctxt "TOOLTIP"
+msgid "Pop up in a window"
+msgstr "In nieuw venstertje weergeven"
diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css
index f43c97de5..b277b30a1 100644
--- a/plugins/Realtime/realtimeupdate.css
+++ b/plugins/Realtime/realtimeupdate.css
@@ -35,7 +35,6 @@ width:70%;
margin-left:1%;
}
-
#notices_primary {
position:relative;
}
@@ -75,4 +74,3 @@ line-height:1.2;
#showstream #notices_primary {
margin-top: 18px;
}
-
diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js
index 25dc12d58..f49cef95e 100644
--- a/plugins/Realtime/realtimeupdate.js
+++ b/plugins/Realtime/realtimeupdate.js
@@ -261,9 +261,10 @@ RealtimeUpdate = {
RealtimeUpdate.addNoticesHover();
$('#realtime_playpause').remove();
- $('#realtime_actions').prepend('<li id="realtime_playpause"><button id="realtime_pause" class="pause" title="Pause">Pause</button></li>');
-
- $('#realtime_pause').bind('click', function() {
+ $('#realtime_actions').prepend('<li id="realtime_playpause"><button id="realtime_pause" class="pause"></button></li>');
+ $('#realtime_pause').text(SN.msg('realtime_pause'))
+ .attr('title', SN.msg('realtime_pause_tooltip'))
+ .bind('click', function() {
RealtimeUpdate.removeNoticesHover();
RealtimeUpdate.showPlay();
return false;
@@ -274,9 +275,10 @@ RealtimeUpdate = {
{
RealtimeUpdate.setPause(true);
$('#realtime_playpause').remove();
- $('#realtime_actions').prepend('<li id="realtime_playpause"><span id="queued_counter"></span> <button id="realtime_play" class="play" title="Play">Play</button></li>');
-
- $('#realtime_play').bind('click', function() {
+ $('#realtime_actions').prepend('<li id="realtime_playpause"><span id="queued_counter"></span> <button id="realtime_play" class="play"></button></li>');
+ $('#realtime_play').text(SN.msg('realtime_play'))
+ .attr('title', SN.msg('realtime_play_tooltip'))
+ .bind('click', function() {
RealtimeUpdate.showPause();
return false;
});
@@ -334,10 +336,11 @@ RealtimeUpdate = {
initAddPopup: function(url, timeline, path)
{
- $('#realtime_timeline').append('<button id="realtime_popup" title="Pop up in a window">Pop up</button>');
-
- $('#realtime_popup').bind('click', function() {
- window.open(url,
+ $('#realtime_timeline').append('<button id="realtime_popup"></button>');
+ $('#realtime_popup').text(SN.msg('realtime_popup'))
+ .attr('title', SN.msg('realtime_popup_tooltip'))
+ .bind('click', function() {
+ window.open(url,
'',
'toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550');
diff --git a/plugins/Sample/SamplePlugin.php b/plugins/Sample/SamplePlugin.php
index ef69121a9..a0d1140f3 100644
--- a/plugins/Sample/SamplePlugin.php
+++ b/plugins/Sample/SamplePlugin.php
@@ -172,9 +172,20 @@ class SamplePlugin extends Plugin
// For storing user-submitted flags on profiles
$schema->ensureTable('user_greeting_count',
- array(new ColumnDef('user_id', 'integer', null,
- true, 'PRI'),
- new ColumnDef('greeting_count', 'integer')));
+ array(
+ 'fields' => array(
+ 'user_id' => array('type' => 'int', 'not null' => true),
+ 'greeting_count' => array('type' => 'int'),
+ ),
+ 'primary key' => array('user_id'),
+ 'foreign keys' => array(
+ // Not all databases will support foreign keys, but even
+ // when not enforced it's helpful to include these definitions
+ // as documentation.
+ 'user_greeting_count_user_id_fkey' => array('user', array('user_id' => 'id')),
+ ),
+ )
+ );
return true;
}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index 097d4486f..f5c361250 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -200,8 +200,15 @@ class TwitterBridgePlugin extends Plugin
return false;
case 'TwitterOAuthClient':
case 'TwitterQueueHandler':
+ case 'TwitterImport':
+ case 'JsonStreamReader':
+ case 'TwitterStreamReader':
include_once $dir . '/' . strtolower($cls) . '.php';
return false;
+ case 'TwitterSiteStream':
+ case 'TwitterUserStream':
+ include_once $dir . '/twitterstreamreader.php';
+ return false;
case 'Notice_to_status':
case 'Twitter_synch_status':
include_once $dir . '/' . $cls . '.php';
@@ -267,7 +274,11 @@ class TwitterBridgePlugin extends Plugin
function onEndInitializeQueueManager($manager)
{
if (self::hasKeys()) {
+ // Outgoing notices -> twitter
$manager->connect('twitter', 'TwitterQueueHandler');
+
+ // Incoming statuses <- twitter
+ $manager->connect('tweetin', 'TweetInQueueHandler');
}
return true;
}
diff --git a/plugins/TwitterBridge/daemons/twitterdaemon.php b/plugins/TwitterBridge/daemons/twitterdaemon.php
new file mode 100644
index 000000000..d313d2de9
--- /dev/null
+++ b/plugins/TwitterBridge/daemons/twitterdaemon.php
@@ -0,0 +1,314 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008-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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'fi::a';
+$longoptions = array('id::', 'foreground', 'all');
+
+$helptext = <<<END_OF_XMPP_HELP
+Daemon script for receiving new notices from Twitter users.
+
+ -i --id Identity (default none)
+ -a --all Handle Twitter for all local sites
+ (requires Stomp queue handler, status_network setup)
+ -f --foreground Stay in the foreground (default background)
+
+END_OF_XMPP_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/jabber.php';
+
+class TwitterDaemon extends SpawningDaemon
+{
+ protected $allsites = false;
+
+ function __construct($id=null, $daemonize=true, $threads=1, $allsites=false)
+ {
+ if ($threads != 1) {
+ // This should never happen. :)
+ throw new Exception("TwitterDaemon must run single-threaded");
+ }
+ parent::__construct($id, $daemonize, $threads);
+ $this->allsites = $allsites;
+ }
+
+ function runThread()
+ {
+ common_log(LOG_INFO, 'Waiting to listen to Twitter and queues');
+
+ $master = new TwitterMaster($this->get_id(), $this->processManager());
+ $master->init($this->allsites);
+ $master->service();
+
+ common_log(LOG_INFO, 'terminating normally');
+
+ return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN;
+ }
+
+}
+
+class TwitterMaster extends IoMaster
+{
+ protected $processManager;
+
+ function __construct($id, $processManager)
+ {
+ parent::__construct($id);
+ $this->processManager = $processManager;
+ }
+
+ /**
+ * Initialize IoManagers for the currently configured site
+ * which are appropriate to this instance.
+ */
+ function initManagers()
+ {
+ $qm = QueueManager::get();
+ $qm->setActiveGroup('twitter');
+ $this->instantiate($qm);
+ $this->instantiate(new TwitterManager());
+ $this->instantiate($this->processManager);
+ }
+}
+
+
+class TwitterManager extends IoManager
+{
+ // Recommended resource limits from http://dev.twitter.com/pages/site_streams
+ const MAX_STREAMS = 1000;
+ const USERS_PER_STREAM = 100;
+ const STREAMS_PER_SECOND = 20;
+
+ protected $streams;
+ protected $users;
+
+ /**
+ * Pull the site's active Twitter-importing users and start spawning
+ * some data streams for them!
+ *
+ * @fixme check their last-id and check whether we'll need to do a manual pull.
+ * @fixme abstract out the fetching so we can work over multiple sites.
+ */
+ protected function initStreams()
+ {
+ common_log(LOG_INFO, 'init...');
+ // Pull Twitter user IDs for all users we want to pull data for
+ $flink = new Foreign_link();
+ $flink->service = TWITTER_SERVICE;
+ // @fixme probably should do the bitfield check in a whereAdd but it's ugly :D
+ $flink->find();
+
+ $userIds = array();
+ while ($flink->fetch()) {
+ if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+ FOREIGN_NOTICE_RECV) {
+ $userIds[] = $flink->foreign_id;
+
+ if (count($userIds) >= self::USERS_PER_STREAM) {
+ $this->spawnStream($userIds);
+ $userIds = array();
+ }
+ }
+ }
+
+ if (count($userIds)) {
+ $this->spawnStream($userIds);
+ }
+ }
+
+ /**
+ * Prepare a Site Stream connection for the given chunk of users.
+ * The actual connection will be opened later.
+ *
+ * @param $userIds array of Twitter-side user IDs
+ */
+ protected function spawnStream($userIds)
+ {
+ $stream = $this->initSiteStream();
+ $stream->followUsers($userIds);
+
+ // Slip the stream reader into our list of active streams.
+ // We'll manage its actual connection on the next go-around.
+ $this->streams[] = $stream;
+
+ // Record the user->stream mappings; this makes it easier for us to know
+ // later if we need to kill something.
+ foreach ($userIds as $id) {
+ $this->users[$id] = $stream;
+ }
+ }
+
+ /**
+ * Initialize a generic site streams connection object.
+ * All our connections will look like this, then we'll add users to them.
+ *
+ * @return TwitterStreamReader
+ */
+ protected function initSiteStream()
+ {
+ $auth = $this->siteStreamAuth();
+ $stream = new TwitterSiteStream($auth);
+
+ // Add our event handler callbacks. Whee!
+ $this->setupEvents($stream);
+ return $stream;
+ }
+
+ /**
+ * Fetch the Twitter OAuth credentials to use to connect to the Site Streams API.
+ *
+ * This will use the locally-stored credentials for the applictation's owner account
+ * from the site configuration. These should be configured through the administration
+ * panels or manually in the config file.
+ *
+ * Will throw an exception if no credentials can be found -- but beware that invalid
+ * credentials won't cause breakage until later.
+ *
+ * @return TwitterOAuthClient
+ */
+ protected function siteStreamAuth()
+ {
+ $token = common_config('twitter', 'stream_token');
+ $secret = common_config('twitter', 'stream_secret');
+ if (empty($token) || empty($secret)) {
+ throw new ServerException('Twitter site streams have not been correctly configured. Configure the app owner account via the admin panel.');
+ }
+ return new TwitterOAuthClient($token, $secret);
+ }
+
+ /**
+ * Collect the sockets for all active connections for i/o monitoring.
+ *
+ * @return array of resources
+ */
+ public function getSockets()
+ {
+ $sockets = array();
+ foreach ($this->streams as $stream) {
+ foreach ($stream->getSockets() as $socket) {
+ $sockets[] = $socket;
+ }
+ }
+ return $sockets;
+ }
+
+ /**
+ * We're ready to process input from one of our data sources! Woooooo!
+ * @fixme is there an easier way to map from socket back to owning module? :(
+ *
+ * @param resource $socket
+ * @return boolean success
+ */
+ public function handleInput($socket)
+ {
+ foreach ($this->streams as $stream) {
+ foreach ($stream->getSockets() as $aSocket) {
+ if ($socket === $aSocket) {
+ $stream->handleInput($socket);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Start the i/o system up! Prepare our connections and start opening them.
+ *
+ * @fixme do some rate-limiting on the stream setup
+ * @fixme do some sensible backoff on failure etc
+ */
+ public function start()
+ {
+ $this->initStreams();
+ foreach ($this->streams as $stream) {
+ $stream->connect();
+ }
+ return true;
+ }
+
+ /**
+ * Close down our connections when the daemon wraps up for business.
+ */
+ public function finish()
+ {
+ foreach ($this->streams as $index => $stream) {
+ $stream->close();
+ unset($this->streams[$index]);
+ }
+ return true;
+ }
+
+ public static function get()
+ {
+ throw new Exception('not a singleton');
+ }
+
+ /**
+ * Set up event handlers on the streaming interface.
+ *
+ * @fixme add more event types as we add handling for them
+ */
+ protected function setupEvents(TwitterStreamReader $stream)
+ {
+ $handlers = array(
+ 'status',
+ );
+ foreach ($handlers as $event) {
+ $stream->hookEvent($event, array($this, 'onTwitter' . ucfirst($event)));
+ }
+ }
+
+ /**
+ * Event callback notifying that a user has a new message in their home timeline.
+ * We store the incoming message into the queues for processing, keeping our own
+ * daemon running as shiny-fast as possible.
+ *
+ * @param object $status JSON data: Twitter status update
+ * @fixme in all-sites mode we may need to route queue items into another site's
+ * destination queues, or multiple sites.
+ */
+ protected function onTwitterStatus($status, $context)
+ {
+ $data = array(
+ 'status' => $status,
+ 'for_user' => $context->for_user,
+ );
+ $qm = QueueManager::get();
+ $qm->enqueue($data, 'tweetin');
+ }
+}
+
+
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+$foreground = have_option('f', 'foreground');
+$all = have_option('a') || have_option('--all');
+
+$daemon = new TwitterDaemon($id, !$foreground, 1, $all);
+
+$daemon->runOnce();
diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
index cef67b180..9298d9e3a 100755
--- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
+++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
@@ -192,25 +192,12 @@ class TwitterStatusFetcher extends ParallelizingDaemon
common_debug(LOG_INFO, $this->name() . ' - Retrieved ' . sizeof($timeline) . ' statuses from Twitter.');
+ $importer = new TwitterImport();
+
// Reverse to preserve order
foreach (array_reverse($timeline) as $status) {
- // Hacktastic: filter out stuff coming from this StatusNet
- $source = mb_strtolower(common_config('integration', 'source'));
-
- if (preg_match("/$source/", mb_strtolower($status->source))) {
- common_debug($this->name() . ' - Skipping import of status ' .
- $status->id . ' with source ' . $source);
- continue;
- }
-
- // Don't save it if the user is protected
- // FIXME: save it but treat it as private
- if ($status->user->protected) {
- continue;
- }
-
- $notice = $this->saveStatus($status);
+ $notice = $importer->importStatus($status);
if (!empty($notice)) {
Inbox::insertNotice($flink->user_id, $notice->id);
@@ -226,578 +213,6 @@ class TwitterStatusFetcher extends ParallelizingDaemon
$flink->last_noticesync = common_sql_now();
$flink->update();
}
-
- function saveStatus($status)
- {
- $profile = $this->ensureProfile($status->user);
-
- if (empty($profile)) {
- common_log(LOG_ERR, $this->name() .
- ' - Problem saving notice. No associated Profile.');
- return null;
- }
-
- $statusUri = $this->makeStatusURI($status->user->screen_name, $status->id);
-
- // check to see if we've already imported the status
- $n2s = Notice_to_status::staticGet('status_id', $status->id);
-
- if (!empty($n2s)) {
- common_log(
- LOG_INFO,
- $this->name() .
- " - Ignoring duplicate import: {$status->id}"
- );
- return Notice::staticGet('id', $n2s->notice_id);
- }
-
- // If it's a retweet, save it as a repeat!
- if (!empty($status->retweeted_status)) {
- common_log(LOG_INFO, "Status {$status->id} is a retweet of {$status->retweeted_status->id}.");
- $original = $this->saveStatus($status->retweeted_status);
- if (empty($original)) {
- return null;
- } else {
- $author = $original->getProfile();
- // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
- // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
- $content = sprintf(_m('RT @%1$s %2$s'),
- $author->nickname,
- $original->content);
-
- if (Notice::contentTooLong($content)) {
- $contentlimit = Notice::maxContent();
- $content = mb_substr($content, 0, $contentlimit - 4) . ' ...';
- }
-
- $repeat = Notice::saveNew($profile->id,
- $content,
- 'twitter',
- array('repeat_of' => $original->id,
- 'uri' => $statusUri,
- 'is_local' => Notice::GATEWAY));
- common_log(LOG_INFO, "Saved {$repeat->id} as a repeat of {$original->id}");
- Notice_to_status::saveNew($repeat->id, $status->id);
- return $repeat;
- }
- }
-
- $notice = new Notice();
-
- $notice->profile_id = $profile->id;
- $notice->uri = $statusUri;
- $notice->url = $statusUri;
- $notice->created = strftime(
- '%Y-%m-%d %H:%M:%S',
- strtotime($status->created_at)
- );
-
- $notice->source = 'twitter';
-
- $notice->reply_to = null;
-
- if (!empty($status->in_reply_to_status_id)) {
- common_log(LOG_INFO, "Status {$status->id} is a reply to status {$status->in_reply_to_status_id}");
- $n2s = Notice_to_status::staticGet('status_id', $status->in_reply_to_status_id);
- if (empty($n2s)) {
- common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
- } else {
- $reply = Notice::staticGet('id', $n2s->notice_id);
- if (empty($reply)) {
- common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
- } else {
- common_log(LOG_INFO, "Found local notice {$reply->id} for status {$status->in_reply_to_status_id}");
- $notice->reply_to = $reply->id;
- $notice->conversation = $reply->conversation;
- }
- }
- }
-
- if (empty($notice->conversation)) {
- $conv = Conversation::create();
- $notice->conversation = $conv->id;
- common_log(LOG_INFO, "No known conversation for status {$status->id} so making a new one {$conv->id}.");
- }
-
- $notice->is_local = Notice::GATEWAY;
-
- $notice->content = html_entity_decode($status->text, ENT_QUOTES, 'UTF-8');
- $notice->rendered = $this->linkify($status);
-
- if (Event::handle('StartNoticeSave', array(&$notice))) {
-
- $id = $notice->insert();
-
- if (!$id) {
- common_log_db_error($notice, 'INSERT', __FILE__);
- common_log(LOG_ERR, $this->name() .
- ' - Problem saving notice.');
- }
-
- Event::handle('EndNoticeSave', array($notice));
- }
-
- Notice_to_status::saveNew($notice->id, $status->id);
-
- $this->saveStatusMentions($notice, $status);
-
- $notice->blowOnInsert();
-
- return $notice;
- }
-
- /**
- * Make an URI for a status.
- *
- * @param object $status status object
- *
- * @return string URI
- */
- function makeStatusURI($username, $id)
- {
- return 'http://twitter.com/'
- . $username
- . '/status/'
- . $id;
- }
-
- /**
- * Look up a Profile by profileurl field. Profile::staticGet() was
- * not working consistently.
- *
- * @param string $nickname local nickname of the Twitter user
- * @param string $profileurl the profile url
- *
- * @return mixed value the first Profile with that url, or null
- */
- function getProfileByUrl($nickname, $profileurl)
- {
- $profile = new Profile();
- $profile->nickname = $nickname;
- $profile->profileurl = $profileurl;
- $profile->limit(1);
-
- if ($profile->find()) {
- $profile->fetch();
- return $profile;
- }
-
- return null;
- }
-
- /**
- * Check to see if this Twitter status has already been imported
- *
- * @param Profile $profile Twitter user's local profile
- * @param string $statusUri URI of the status on Twitter
- *
- * @return mixed value a matching Notice or null
- */
- function checkDupe($profile, $statusUri)
- {
- $notice = new Notice();
- $notice->uri = $statusUri;
- $notice->profile_id = $profile->id;
- $notice->limit(1);
-
- if ($notice->find()) {
- $notice->fetch();
- return $notice;
- }
-
- return null;
- }
-
- function ensureProfile($user)
- {
- // check to see if there's already a profile for this user
- $profileurl = 'http://twitter.com/' . $user->screen_name;
- $profile = $this->getProfileByUrl($user->screen_name, $profileurl);
-
- if (!empty($profile)) {
- common_debug($this->name() .
- " - Profile for $profile->nickname found.");
-
- // Check to see if the user's Avatar has changed
-
- $this->checkAvatar($user, $profile);
- return $profile;
-
- } else {
- common_debug($this->name() . ' - Adding profile and remote profile ' .
- "for Twitter user: $profileurl.");
-
- $profile = new Profile();
- $profile->query("BEGIN");
-
- $profile->nickname = $user->screen_name;
- $profile->fullname = $user->name;
- $profile->homepage = $user->url;
- $profile->bio = $user->description;
- $profile->location = $user->location;
- $profile->profileurl = $profileurl;
- $profile->created = common_sql_now();
-
- try {
- $id = $profile->insert();
- } catch(Exception $e) {
- common_log(LOG_WARNING, $this->name . ' Couldn\'t insert profile - ' . $e->getMessage());
- }
-
- if (empty($id)) {
- common_log_db_error($profile, 'INSERT', __FILE__);
- $profile->query("ROLLBACK");
- return false;
- }
-
- // check for remote profile
-
- $remote_pro = Remote_profile::staticGet('uri', $profileurl);
-
- if (empty($remote_pro)) {
- $remote_pro = new Remote_profile();
-
- $remote_pro->id = $id;
- $remote_pro->uri = $profileurl;
- $remote_pro->created = common_sql_now();
-
- try {
- $rid = $remote_pro->insert();
- } catch (Exception $e) {
- common_log(LOG_WARNING, $this->name() . ' Couldn\'t save remote profile - ' . $e->getMessage());
- }
-
- if (empty($rid)) {
- common_log_db_error($profile, 'INSERT', __FILE__);
- $profile->query("ROLLBACK");
- return false;
- }
- }
-
- $profile->query("COMMIT");
-
- $this->saveAvatars($user, $id);
-
- return $profile;
- }
- }
-
- function checkAvatar($twitter_user, $profile)
- {
- global $config;
-
- $path_parts = pathinfo($twitter_user->profile_image_url);
-
- $newname = 'Twitter_' . $twitter_user->id . '_' .
- $path_parts['basename'];
-
- $oldname = $profile->getAvatar(48)->filename;
-
- if ($newname != $oldname) {
- common_debug($this->name() . ' - Avatar for Twitter user ' .
- "$profile->nickname has changed.");
- common_debug($this->name() . " - old: $oldname new: $newname");
-
- $this->updateAvatars($twitter_user, $profile);
- }
-
- if ($this->missingAvatarFile($profile)) {
- common_debug($this->name() . ' - Twitter user ' .
- $profile->nickname .
- ' is missing one or more local avatars.');
- common_debug($this->name() ." - old: $oldname new: $newname");
-
- $this->updateAvatars($twitter_user, $profile);
- }
- }
-
- function updateAvatars($twitter_user, $profile) {
-
- global $config;
-
- $path_parts = pathinfo($twitter_user->profile_image_url);
-
- $img_root = substr($path_parts['basename'], 0, -11);
- $ext = $path_parts['extension'];
- $mediatype = $this->getMediatype($ext);
-
- foreach (array('mini', 'normal', 'bigger') as $size) {
- $url = $path_parts['dirname'] . '/' .
- $img_root . '_' . $size . ".$ext";
- $filename = 'Twitter_' . $twitter_user->id . '_' .
- $img_root . "_$size.$ext";
-
- $this->updateAvatar($profile->id, $size, $mediatype, $filename);
- $this->fetchAvatar($url, $filename);
- }
- }
-
- function missingAvatarFile($profile) {
- foreach (array(24, 48, 73) as $size) {
- $filename = $profile->getAvatar($size)->filename;
- $avatarpath = Avatar::path($filename);
- if (file_exists($avatarpath) == FALSE) {
- return true;
- }
- }
- return false;
- }
-
- function getMediatype($ext)
- {
- $mediatype = null;
-
- switch (strtolower($ext)) {
- case 'jpg':
- $mediatype = 'image/jpg';
- break;
- case 'gif':
- $mediatype = 'image/gif';
- break;
- default:
- $mediatype = 'image/png';
- }
-
- return $mediatype;
- }
-
- function saveAvatars($user, $id)
- {
- global $config;
-
- $path_parts = pathinfo($user->profile_image_url);
- $ext = $path_parts['extension'];
- $end = strlen('_normal' . $ext);
- $img_root = substr($path_parts['basename'], 0, -($end+1));
- $mediatype = $this->getMediatype($ext);
-
- foreach (array('mini', 'normal', 'bigger') as $size) {
- $url = $path_parts['dirname'] . '/' .
- $img_root . '_' . $size . ".$ext";
- $filename = 'Twitter_' . $user->id . '_' .
- $img_root . "_$size.$ext";
-
- if ($this->fetchAvatar($url, $filename)) {
- $this->newAvatar($id, $size, $mediatype, $filename);
- } else {
- common_log(LOG_WARNING, $id() .
- " - Problem fetching Avatar: $url");
- }
- }
- }
-
- function updateAvatar($profile_id, $size, $mediatype, $filename) {
-
- common_debug($this->name() . " - Updating avatar: $size");
-
- $profile = Profile::staticGet($profile_id);
-
- if (empty($profile)) {
- common_debug($this->name() . " - Couldn't get profile: $profile_id!");
- return;
- }
-
- $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
- $avatar = $profile->getAvatar($sizes[$size]);
-
- // Delete the avatar, if present
- if ($avatar) {
- $avatar->delete();
- }
-
- $this->newAvatar($profile->id, $size, $mediatype, $filename);
- }
-
- function newAvatar($profile_id, $size, $mediatype, $filename)
- {
- global $config;
-
- $avatar = new Avatar();
- $avatar->profile_id = $profile_id;
-
- switch($size) {
- case 'mini':
- $avatar->width = 24;
- $avatar->height = 24;
- break;
- case 'normal':
- $avatar->width = 48;
- $avatar->height = 48;
- break;
- default:
- // Note: Twitter's big avatars are a different size than
- // StatusNet's (StatusNet's = 96)
- $avatar->width = 73;
- $avatar->height = 73;
- }
-
- $avatar->original = 0; // we don't have the original
- $avatar->mediatype = $mediatype;
- $avatar->filename = $filename;
- $avatar->url = Avatar::url($filename);
-
- $avatar->created = common_sql_now();
-
- try {
- $id = $avatar->insert();
- } catch (Exception $e) {
- common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert avatar - ' . $e->getMessage());
- }
-
- if (empty($id)) {
- common_log_db_error($avatar, 'INSERT', __FILE__);
- return null;
- }
-
- common_debug($this->name() .
- " - Saved new $size avatar for $profile_id.");
-
- return $id;
- }
-
- /**
- * Fetch a remote avatar image and save to local storage.
- *
- * @param string $url avatar source URL
- * @param string $filename bare local filename for download
- * @return bool true on success, false on failure
- */
- function fetchAvatar($url, $filename)
- {
- common_debug($this->name() . " - Fetching Twitter avatar: $url");
-
- $request = HTTPClient::start();
- $response = $request->get($url);
- if ($response->isOk()) {
- $avatarfile = Avatar::path($filename);
- $ok = file_put_contents($avatarfile, $response->getBody());
- if (!$ok) {
- common_log(LOG_WARNING, $this->name() .
- " - Couldn't open file $filename");
- return false;
- }
- } else {
- return false;
- }
-
- return true;
- }
-
- const URL = 1;
- const HASHTAG = 2;
- const MENTION = 3;
-
- function linkify($status)
- {
- $text = $status->text;
-
- if (empty($status->entities)) {
- common_log(LOG_WARNING, "No entities data for {$status->id}; trying to fake up links ourselves.");
- $text = common_replace_urls_callback($text, 'common_linkify');
- $text = preg_replace('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.TwitterStatusFetcher::tagLink('\\2')", $text);
- $text = preg_replace('/(^|\s+)@([a-z0-9A-Z_]{1,64})/e', "'\\1@'.TwitterStatusFetcher::atLink('\\2')", $text);
- return $text;
- }
-
- // Move all the entities into order so we can
- // replace them in reverse order and thus
- // not mess up their indices
-
- $toReplace = array();
-
- if (!empty($status->entities->urls)) {
- foreach ($status->entities->urls as $url) {
- $toReplace[$url->indices[0]] = array(self::URL, $url);
- }
- }
-
- if (!empty($status->entities->hashtags)) {
- foreach ($status->entities->hashtags as $hashtag) {
- $toReplace[$hashtag->indices[0]] = array(self::HASHTAG, $hashtag);
- }
- }
-
- if (!empty($status->entities->user_mentions)) {
- foreach ($status->entities->user_mentions as $mention) {
- $toReplace[$mention->indices[0]] = array(self::MENTION, $mention);
- }
- }
-
- // sort in reverse order by key
-
- krsort($toReplace);
-
- foreach ($toReplace as $part) {
- list($type, $object) = $part;
- switch($type) {
- case self::URL:
- $linkText = $this->makeUrlLink($object);
- break;
- case self::HASHTAG:
- $linkText = $this->makeHashtagLink($object);
- break;
- case self::MENTION:
- $linkText = $this->makeMentionLink($object);
- break;
- default:
- continue;
- }
- $text = mb_substr($text, 0, $object->indices[0]) . $linkText . mb_substr($text, $object->indices[1]);
- }
- return $text;
- }
-
- function makeUrlLink($object)
- {
- return "<a href='{$object->url}' class='extlink'>{$object->url}</a>";
- }
-
- function makeHashtagLink($object)
- {
- return "#" . self::tagLink($object->text);
- }
-
- function makeMentionLink($object)
- {
- return "@".self::atLink($object->screen_name, $object->name);
- }
-
- static function tagLink($tag)
- {
- return "<a href='https://twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
- }
-
- static function atLink($screenName, $fullName=null)
- {
- if (!empty($fullName)) {
- return "<a href='http://twitter.com/{$screenName}' title='{$fullName}'>{$screenName}</a>";
- } else {
- return "<a href='http://twitter.com/{$screenName}'>{$screenName}</a>";
- }
- }
-
- function saveStatusMentions($notice, $status)
- {
- $mentions = array();
-
- if (empty($status->entities) || empty($status->entities->user_mentions)) {
- return;
- }
-
- foreach ($status->entities->user_mentions as $mention) {
- $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE);
- if (!empty($flink)) {
- $user = User::staticGet('id', $flink->user_id);
- if (!empty($user)) {
- $reply = new Reply();
- $reply->notice_id = $notice->id;
- $reply->profile_id = $user->id;
- common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
- $id = $reply->insert();
- }
- }
- }
- }
}
$id = null;
diff --git a/plugins/TwitterBridge/jsonstreamreader.php b/plugins/TwitterBridge/jsonstreamreader.php
new file mode 100644
index 000000000..f6572c9ee
--- /dev/null
+++ b/plugins/TwitterBridge/jsonstreamreader.php
@@ -0,0 +1,265 @@
+<?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 Plugin
+ * @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/
+ */
+
+class OAuthData
+{
+ public $consumer_key, $consumer_secret, $token, $token_secret;
+}
+
+/**
+ *
+ */
+abstract class JsonStreamReader
+{
+ const CRLF = "\r\n";
+
+ public $id;
+ protected $socket = null;
+ protected $state = 'init'; // 'init', 'connecting', 'waiting', 'headers', 'active'
+
+ public function __construct()
+ {
+ $this->id = get_class($this) . '.' . substr(md5(mt_rand()), 0, 8);
+ }
+
+ /**
+ * Starts asynchronous connect operation...
+ *
+ * @fixme Can we do the open-socket fully async to? (need write select infrastructure)
+ *
+ * @param string $url
+ */
+ public function connect($url)
+ {
+ common_log(LOG_DEBUG, "$this->id opening connection to $url");
+
+ $scheme = parse_url($url, PHP_URL_SCHEME);
+ if ($scheme == 'http') {
+ $rawScheme = 'tcp';
+ } else if ($scheme == 'https') {
+ $rawScheme = 'ssl';
+ } else {
+ throw new ServerException('Invalid URL scheme for HTTP stream reader');
+ }
+
+ $host = parse_url($url, PHP_URL_HOST);
+ $port = parse_url($url, PHP_URL_PORT);
+ if (!$port) {
+ if ($scheme == 'https') {
+ $port = 443;
+ } else {
+ $port = 80;
+ }
+ }
+
+ $path = parse_url($url, PHP_URL_PATH);
+ $query = parse_url($url, PHP_URL_QUERY);
+ if ($query) {
+ $path .= '?' . $query;
+ }
+
+ $errno = $errstr = null;
+ $timeout = 5;
+ //$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
+ $flags = STREAM_CLIENT_CONNECT;
+ // @fixme add SSL params
+ $this->socket = stream_socket_client("$rawScheme://$host:$port", $errno, $errstr, $timeout, $flags);
+
+ $this->send($this->httpOpen($host, $path));
+
+ stream_set_blocking($this->socket, false);
+ $this->state = 'waiting';
+ }
+
+ /**
+ * Send some fun data off to the server.
+ *
+ * @param string $buffer
+ */
+ function send($buffer)
+ {
+ fwrite($this->socket, $buffer);
+ }
+
+ /**
+ * Read next packet of data from the socket.
+ *
+ * @return string
+ */
+ function read()
+ {
+ $buffer = fread($this->socket, 65536);
+ return $buffer;
+ }
+
+ /**
+ * Build HTTP request headers.
+ *
+ * @param string $host
+ * @param string $path
+ * @return string
+ */
+ protected function httpOpen($host, $path)
+ {
+ $lines = array(
+ "GET $path HTTP/1.1",
+ "Host: $host",
+ "User-Agent: StatusNet/" . STATUSNET_VERSION . " (TwitterBridgePlugin)",
+ "Connection: close",
+ "",
+ ""
+ );
+ return implode(self::CRLF, $lines);
+ }
+
+ /**
+ * Close the current connection, if open.
+ */
+ public function close()
+ {
+ if ($this->isConnected()) {
+ common_log(LOG_DEBUG, "$this->id closing connection.");
+ fclose($this->socket);
+ $this->socket = null;
+ }
+ }
+
+ /**
+ * Are we currently connected?
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return $this->socket !== null;
+ }
+
+ /**
+ * Send any sockets we're listening on to the IO manager
+ * to wait for input.
+ *
+ * @return array of resources
+ */
+ public function getSockets()
+ {
+ if ($this->isConnected()) {
+ return array($this->socket);
+ }
+ return array();
+ }
+
+ /**
+ * Take a chunk of input over the horn and go go go! :D
+ *
+ * @param string $buffer
+ */
+ public function handleInput($socket)
+ {
+ if ($this->socket !== $socket) {
+ throw new Exception('Got input from unexpected socket!');
+ }
+
+ try {
+ $buffer = $this->read();
+ $lines = explode(self::CRLF, $buffer);
+ foreach ($lines as $line) {
+ $this->handleLine($line);
+ }
+ } catch (Exception $e) {
+ common_log(LOG_ERR, "$this->id aborting connection due to error: " . $e->getMessage());
+ fclose($this->socket);
+ throw $e;
+ }
+ }
+
+ protected function handleLine($line)
+ {
+ switch ($this->state)
+ {
+ case 'waiting':
+ $this->handleLineWaiting($line);
+ break;
+ case 'headers':
+ $this->handleLineHeaders($line);
+ break;
+ case 'active':
+ $this->handleLineActive($line);
+ break;
+ default:
+ throw new Exception('Invalid state in handleLine: ' . $this->state);
+ }
+ }
+
+ /**
+ *
+ * @param <type> $line
+ */
+ protected function handleLineWaiting($line)
+ {
+ $bits = explode(' ', $line, 3);
+ if (count($bits) != 3) {
+ throw new Exception("Invalid HTTP response line: $line");
+ }
+
+ list($http, $status, $text) = $bits;
+ if (substr($http, 0, 5) != 'HTTP/') {
+ throw new Exception("Invalid HTTP response line chunk '$http': $line");
+ }
+ if ($status != '200') {
+ throw new Exception("Bad HTTP response code $status: $line");
+ }
+ common_log(LOG_DEBUG, "$this->id $line");
+ $this->state = 'headers';
+ }
+
+ protected function handleLineHeaders($line)
+ {
+ if ($line == '') {
+ $this->state = 'active';
+ common_log(LOG_DEBUG, "$this->id connection is active!");
+ } else {
+ common_log(LOG_DEBUG, "$this->id read HTTP header: $line");
+ $this->responseHeaders[] = $line;
+ }
+ }
+
+ protected function handleLineActive($line)
+ {
+ if ($line == "") {
+ // Server sends empty lines as keepalive.
+ return;
+ }
+ $data = json_decode($line);
+ if ($data) {
+ $this->handleJson($data);
+ } else {
+ common_log(LOG_ERR, "$this->id received bogus JSON data: " . var_export($line, true));
+ }
+ }
+
+ abstract protected function handleJson(stdClass $data);
+}
diff --git a/plugins/TwitterBridge/scripts/fakestream.php b/plugins/TwitterBridge/scripts/fakestream.php
new file mode 100644
index 000000000..369688816
--- /dev/null
+++ b/plugins/TwitterBridge/scripts/fakestream.php
@@ -0,0 +1,147 @@
+<?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 Plugin
+ * @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/
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'n:';
+$longoptions = array('nick=','import','all');
+
+$helptext = <<<ENDOFHELP
+USAGE: fakestream.php -n <username>
+
+ -n --nick=<username> Local user whose Twitter timeline to watch
+ --import Experimental: run incoming messages through import
+ --all Experimental: run multiuser; requires nick be the app owner
+
+Attempts a User Stream connection to Twitter as the given user, dumping
+data as it comes.
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (have_option('n')) {
+ $nickname = get_option_value('n');
+} else if (have_option('nick')) {
+ $nickname = get_option_value('nickname');
+} else if (have_option('all')) {
+ $nickname = null;
+} else {
+ show_help($helptext);
+ exit(0);
+}
+
+/**
+ *
+ * @param User $user
+ * @return TwitterOAuthClient
+ */
+function twitterAuthForUser(User $user)
+{
+ $flink = Foreign_link::getByUserID($user->id,
+ TWITTER_SERVICE);
+ if (!$flink) {
+ throw new ServerException("No Twitter config for this user.");
+ }
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ if (!$token) {
+ throw new ServerException("No Twitter OAuth credentials for this user.");
+ }
+
+ return new TwitterOAuthClient($token->key, $token->secret);
+}
+
+/**
+ * Emulate the line-by-line output...
+ *
+ * @param Foreign_link $flink
+ * @param mixed $data
+ */
+function dumpMessage($flink, $data)
+{
+ $msg = prepMessage($flink, $data);
+ print json_encode($msg) . "\r\n";
+}
+
+function prepMessage($flink, $data)
+{
+ $msg->for_user = $flink->foreign_id;
+ $msg->message = $data;
+ return $msg;
+}
+
+if (have_option('all')) {
+ $users = array();
+
+ $flink = new Foreign_link();
+ $flink->service = TWITTER_SERVICE;
+ $flink->find();
+
+ while ($flink->fetch()) {
+ if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+ FOREIGN_NOTICE_RECV) {
+ $users[] = $flink->user_id;
+ }
+ }
+} else {
+ $user = User::staticGet('nickname', $nickname);
+ $users = array($user->id);
+}
+
+$output = array();
+foreach ($users as $id) {
+ $user = User::staticGet('id', $id);
+ if (!$user) {
+ throw new Exception("No user for id $id");
+ }
+ $auth = twitterAuthForUser($user);
+ $flink = Foreign_link::getByUserID($user->id,
+ TWITTER_SERVICE);
+
+ $friends->friends = $auth->friendsIds();
+ dumpMessage($flink, $friends);
+
+ $timeline = $auth->statusesHomeTimeline();
+ foreach ($timeline as $status) {
+ $output[] = prepMessage($flink, $status);
+ }
+}
+
+usort($output, function($a, $b) {
+ if ($a->message->id < $b->message->id) {
+ return -1;
+ } else if ($a->message->id == $b->message->id) {
+ return 0;
+ } else {
+ return 1;
+ }
+});
+
+foreach ($output as $msg) {
+ print json_encode($msg) . "\r\n";
+}
diff --git a/plugins/TwitterBridge/scripts/streamtest.php b/plugins/TwitterBridge/scripts/streamtest.php
new file mode 100644
index 000000000..aad15fdea
--- /dev/null
+++ b/plugins/TwitterBridge/scripts/streamtest.php
@@ -0,0 +1,244 @@
+<?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 Plugin
+ * @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/
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'n:';
+$longoptions = array('nick=','import','all','apiroot=');
+
+$helptext = <<<ENDOFHELP
+USAGE: streamtest.php -n <username>
+
+ -n --nick=<username> Local user whose Twitter timeline to watch
+ --import Experimental: run incoming messages through import
+ --all Experimental: run multiuser; requires nick be the app owner
+ --apiroot=<url> Provide alternate streaming API root URL
+
+Attempts a User Stream connection to Twitter as the given user, dumping
+data as it comes.
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+require_once dirname(dirname(__FILE__)) . '/jsonstreamreader.php';
+require_once dirname(dirname(__FILE__)) . '/twitterstreamreader.php';
+
+if (have_option('n')) {
+ $nickname = get_option_value('n');
+} else if (have_option('nick')) {
+ $nickname = get_option_value('nickname');
+} else {
+ show_help($helptext);
+ exit(0);
+}
+
+/**
+ *
+ * @param User $user
+ * @return TwitterOAuthClient
+ */
+function twitterAuthForUser(User $user)
+{
+ $flink = Foreign_link::getByUserID($user->id,
+ TWITTER_SERVICE);
+ if (!$flink) {
+ throw new ServerException("No Twitter config for this user.");
+ }
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ if (!$token) {
+ throw new ServerException("No Twitter OAuth credentials for this user.");
+ }
+
+ return new TwitterOAuthClient($token->key, $token->secret);
+}
+
+function homeStreamForUser(User $user)
+{
+ $auth = twitterAuthForUser($user);
+ return new TwitterUserStream($auth);
+}
+
+function siteStreamForOwner(User $user)
+{
+ // The user we auth as must be the owner of the application.
+ $auth = twitterAuthForUser($user);
+
+ if (have_option('apiroot')) {
+ $stream = new TwitterSiteStream($auth, get_option_value('apiroot'));
+ } else {
+ $stream = new TwitterSiteStream($auth);
+ }
+
+ // Pull Twitter user IDs for all users we want to pull data for
+ $userIds = array();
+
+ $flink = new Foreign_link();
+ $flink->service = TWITTER_SERVICE;
+ $flink->find();
+
+ while ($flink->fetch()) {
+ if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+ FOREIGN_NOTICE_RECV) {
+ $userIds[] = $flink->foreign_id;
+ }
+ }
+
+ $stream->followUsers($userIds);
+ return $stream;
+}
+
+
+$user = User::staticGet('nickname', $nickname);
+global $myuser;
+$myuser = $user;
+
+if (have_option('all')) {
+ $stream = siteStreamForOwner($user);
+} else {
+ $stream = homeStreamForUser($user);
+}
+
+
+$stream->hookEvent('raw', function($data, $context) {
+ common_log(LOG_INFO, json_encode($data) . ' for ' . json_encode($context));
+});
+$stream->hookEvent('friends', function($data, $context) {
+ printf("Friend list: %s\n", implode(', ', $data->friends));
+});
+$stream->hookEvent('favorite', function($data, $context) {
+ printf("%s favorited %s's notice: %s\n",
+ $data->source->screen_name,
+ $data->target->screen_name,
+ $data->target_object->text);
+});
+$stream->hookEvent('unfavorite', function($data, $context) {
+ printf("%s unfavorited %s's notice: %s\n",
+ $data->source->screen_name,
+ $data->target->screen_name,
+ $data->target_object->text);
+});
+$stream->hookEvent('follow', function($data, $context) {
+ printf("%s friended %s\n",
+ $data->source->screen_name,
+ $data->target->screen_name);
+});
+$stream->hookEvent('unfollow', function($data, $context) {
+ printf("%s unfriended %s\n",
+ $data->source->screen_name,
+ $data->target->screen_name);
+});
+$stream->hookEvent('delete', function($data, $context) {
+ printf("Deleted status notification: %s\n",
+ $data->status->id);
+});
+$stream->hookEvent('scrub_geo', function($data, $context) {
+ printf("Req to scrub geo data for user id %s up to status ID %s\n",
+ $data->user_id,
+ $data->up_to_status_id);
+});
+$stream->hookEvent('status', function($data, $context) {
+ printf("Received status update from %s: %s\n",
+ $data->user->screen_name,
+ $data->text);
+
+ if (have_option('import')) {
+ $importer = new TwitterImport();
+ printf("\timporting...");
+ $notice = $importer->importStatus($data);
+ if ($notice) {
+ global $myuser;
+ Inbox::insertNotice($myuser->id, $notice->id);
+ printf(" %s\n", $notice->id);
+ } else {
+ printf(" FAIL\n");
+ }
+ }
+});
+$stream->hookEvent('direct_message', function($data) {
+ printf("Direct message from %s to %s: %s\n",
+ $data->sender->screen_name,
+ $data->recipient->screen_name,
+ $data->text);
+});
+
+class TwitterManager extends IoManager
+{
+ function __construct(TwitterStreamReader $stream)
+ {
+ $this->stream = $stream;
+ }
+
+ function getSockets()
+ {
+ return $this->stream->getSockets();
+ }
+
+ function handleInput($data)
+ {
+ $this->stream->handleInput($data);
+ return true;
+ }
+
+ function start()
+ {
+ $this->stream->connect();
+ return true;
+ }
+
+ function finish()
+ {
+ $this->stream->close();
+ return true;
+ }
+
+ public static function get()
+ {
+ throw new Exception('not a singleton');
+ }
+}
+
+class TwitterStreamMaster extends IoMaster
+{
+ function __construct($id, $ioManager)
+ {
+ parent::__construct($id);
+ $this->ioManager = $ioManager;
+ }
+
+ /**
+ * Initialize IoManagers which are appropriate to this instance.
+ */
+ function initManagers()
+ {
+ $this->instantiate($this->ioManager);
+ }
+}
+
+$master = new TwitterStreamMaster('TwitterStream', new TwitterManager($stream));
+$master->init();
+$master->service();
diff --git a/plugins/TwitterBridge/tweetctlqueuehandler.php b/plugins/TwitterBridge/tweetctlqueuehandler.php
new file mode 100644
index 000000000..4c8bef463
--- /dev/null
+++ b/plugins/TwitterBridge/tweetctlqueuehandler.php
@@ -0,0 +1,59 @@
+<?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/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Queue handler to deal with incoming Twitter status updates, as retrieved by
+ * TwitterDaemon (twitterdaemon.php).
+ *
+ * The queue handler passes the status through TwitterImporter for import into the
+ * local database (if necessary), then adds the imported notice to the local inbox
+ * of the attached Twitter user.
+ *
+ * Warning: the way we do inbox distribution manually means that realtime, XMPP, etc
+ * don't work on Twitter-borne messages. When TwitterImporter is changed to handle
+ * that correctly, we'll only need to do this once...?
+ */
+class TweetCtlQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'tweetctl';
+ }
+
+ function handle($data)
+ {
+ // A user has activated or deactivated their Twitter bridge
+ // import status.
+ $action = $data['action'];
+ $userId = $data['for_user'];
+
+ $tm = TwitterManager::get();
+ if ($action == 'start') {
+ $tm->startTwitterUser($userId);
+ } else if ($action == 'stop') {
+ $tm->stopTwitterUser($userId);
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/TwitterBridge/tweetinqueuehandler.php b/plugins/TwitterBridge/tweetinqueuehandler.php
new file mode 100644
index 000000000..ff6b2cc86
--- /dev/null
+++ b/plugins/TwitterBridge/tweetinqueuehandler.php
@@ -0,0 +1,63 @@
+<?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/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Queue handler to deal with incoming Twitter status updates, as retrieved by
+ * TwitterDaemon (twitterdaemon.php).
+ *
+ * The queue handler passes the status through TwitterImporter for import into the
+ * local database (if necessary), then adds the imported notice to the local inbox
+ * of the attached Twitter user.
+ *
+ * Warning: the way we do inbox distribution manually means that realtime, XMPP, etc
+ * don't work on Twitter-borne messages. When TwitterImporter is changed to handle
+ * that correctly, we'll only need to do this once...?
+ */
+class TweetInQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'tweetin';
+ }
+
+ function handle($data)
+ {
+ // JSON object with Twitter data
+ $status = $data['status'];
+
+ // Twitter user ID this incoming data belongs to.
+ $receiver = $data['for_user'];
+
+ $importer = new TwitterImport();
+ $notice = $importer->importStatus($status);
+ if ($notice) {
+ $flink = Foreign_link::getByForeignID(TWITTER_SERVICE, $receiver);
+ if ($flink) {
+ // @fixme this should go through more regular channels?
+ Inbox::insertNotice($flink->user_id, $notice->id);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php
new file mode 100644
index 000000000..07a9cf95f
--- /dev/null
+++ b/plugins/TwitterBridge/twitterimport.php
@@ -0,0 +1,651 @@
+<?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 Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @author Julien C <chaumond@gmail.com>
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2009-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')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Encapsulation of the Twitter status -> notice incoming bridge import.
+ * Is used by both the polling twitterstatusfetcher.php daemon, and the
+ * in-progress streaming import.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @author Julien C <chaumond@gmail.com>
+ * @author Brion Vibber <brion@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/
+ * @link http://twitter.com/
+ */
+class TwitterImport
+{
+ public function importStatus($status)
+ {
+ // Hacktastic: filter out stuff coming from this StatusNet
+ $source = mb_strtolower(common_config('integration', 'source'));
+
+ if (preg_match("/$source/", mb_strtolower($status->source))) {
+ common_debug($this->name() . ' - Skipping import of status ' .
+ $status->id . ' with source ' . $source);
+ return null;
+ }
+
+ // Don't save it if the user is protected
+ // FIXME: save it but treat it as private
+ if ($status->user->protected) {
+ return null;
+ }
+
+ $notice = $this->saveStatus($status);
+
+ return $notice;
+ }
+
+ function name()
+ {
+ return get_class($this);
+ }
+
+ function saveStatus($status)
+ {
+ $profile = $this->ensureProfile($status->user);
+
+ if (empty($profile)) {
+ common_log(LOG_ERR, $this->name() .
+ ' - Problem saving notice. No associated Profile.');
+ return null;
+ }
+
+ $statusUri = $this->makeStatusURI($status->user->screen_name, $status->id);
+
+ // check to see if we've already imported the status
+ $n2s = Notice_to_status::staticGet('status_id', $status->id);
+
+ if (!empty($n2s)) {
+ common_log(
+ LOG_INFO,
+ $this->name() .
+ " - Ignoring duplicate import: {$status->id}"
+ );
+ return Notice::staticGet('id', $n2s->notice_id);
+ }
+
+ // If it's a retweet, save it as a repeat!
+ if (!empty($status->retweeted_status)) {
+ common_log(LOG_INFO, "Status {$status->id} is a retweet of {$status->retweeted_status->id}.");
+ $original = $this->saveStatus($status->retweeted_status);
+ if (empty($original)) {
+ return null;
+ } else {
+ $author = $original->getProfile();
+ // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
+ // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
+ $content = sprintf(_m('RT @%1$s %2$s'),
+ $author->nickname,
+ $original->content);
+
+ if (Notice::contentTooLong($content)) {
+ $contentlimit = Notice::maxContent();
+ $content = mb_substr($content, 0, $contentlimit - 4) . ' ...';
+ }
+
+ $repeat = Notice::saveNew($profile->id,
+ $content,
+ 'twitter',
+ array('repeat_of' => $original->id,
+ 'uri' => $statusUri,
+ 'is_local' => Notice::GATEWAY));
+ common_log(LOG_INFO, "Saved {$repeat->id} as a repeat of {$original->id}");
+ Notice_to_status::saveNew($repeat->id, $status->id);
+ return $repeat;
+ }
+ }
+
+ $notice = new Notice();
+
+ $notice->profile_id = $profile->id;
+ $notice->uri = $statusUri;
+ $notice->url = $statusUri;
+ $notice->created = strftime(
+ '%Y-%m-%d %H:%M:%S',
+ strtotime($status->created_at)
+ );
+
+ $notice->source = 'twitter';
+
+ $notice->reply_to = null;
+
+ if (!empty($status->in_reply_to_status_id)) {
+ common_log(LOG_INFO, "Status {$status->id} is a reply to status {$status->in_reply_to_status_id}");
+ $n2s = Notice_to_status::staticGet('status_id', $status->in_reply_to_status_id);
+ if (empty($n2s)) {
+ common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
+ } else {
+ $reply = Notice::staticGet('id', $n2s->notice_id);
+ if (empty($reply)) {
+ common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
+ } else {
+ common_log(LOG_INFO, "Found local notice {$reply->id} for status {$status->in_reply_to_status_id}");
+ $notice->reply_to = $reply->id;
+ $notice->conversation = $reply->conversation;
+ }
+ }
+ }
+
+ if (empty($notice->conversation)) {
+ $conv = Conversation::create();
+ $notice->conversation = $conv->id;
+ common_log(LOG_INFO, "No known conversation for status {$status->id} so making a new one {$conv->id}.");
+ }
+
+ $notice->is_local = Notice::GATEWAY;
+
+ $notice->content = html_entity_decode($status->text, ENT_QUOTES, 'UTF-8');
+ $notice->rendered = $this->linkify($status);
+
+ if (Event::handle('StartNoticeSave', array(&$notice))) {
+
+ $id = $notice->insert();
+
+ if (!$id) {
+ common_log_db_error($notice, 'INSERT', __FILE__);
+ common_log(LOG_ERR, $this->name() .
+ ' - Problem saving notice.');
+ }
+
+ Event::handle('EndNoticeSave', array($notice));
+ }
+
+ Notice_to_status::saveNew($notice->id, $status->id);
+
+ $this->saveStatusMentions($notice, $status);
+
+ $notice->blowOnInsert();
+
+ return $notice;
+ }
+
+ /**
+ * Make an URI for a status.
+ *
+ * @param object $status status object
+ *
+ * @return string URI
+ */
+ function makeStatusURI($username, $id)
+ {
+ return 'http://twitter.com/'
+ . $username
+ . '/status/'
+ . $id;
+ }
+
+
+ /**
+ * Look up a Profile by profileurl field. Profile::staticGet() was
+ * not working consistently.
+ *
+ * @param string $nickname local nickname of the Twitter user
+ * @param string $profileurl the profile url
+ *
+ * @return mixed value the first Profile with that url, or null
+ */
+ function getProfileByUrl($nickname, $profileurl)
+ {
+ $profile = new Profile();
+ $profile->nickname = $nickname;
+ $profile->profileurl = $profileurl;
+ $profile->limit(1);
+
+ if ($profile->find()) {
+ $profile->fetch();
+ return $profile;
+ }
+
+ return null;
+ }
+
+ /**
+ * Check to see if this Twitter status has already been imported
+ *
+ * @param Profile $profile Twitter user's local profile
+ * @param string $statusUri URI of the status on Twitter
+ *
+ * @return mixed value a matching Notice or null
+ */
+ function checkDupe($profile, $statusUri)
+ {
+ $notice = new Notice();
+ $notice->uri = $statusUri;
+ $notice->profile_id = $profile->id;
+ $notice->limit(1);
+
+ if ($notice->find()) {
+ $notice->fetch();
+ return $notice;
+ }
+
+ return null;
+ }
+
+ function ensureProfile($user)
+ {
+ // check to see if there's already a profile for this user
+ $profileurl = 'http://twitter.com/' . $user->screen_name;
+ $profile = $this->getProfileByUrl($user->screen_name, $profileurl);
+
+ if (!empty($profile)) {
+ common_debug($this->name() .
+ " - Profile for $profile->nickname found.");
+
+ // Check to see if the user's Avatar has changed
+
+ $this->checkAvatar($user, $profile);
+ return $profile;
+
+ } else {
+ common_debug($this->name() . ' - Adding profile and remote profile ' .
+ "for Twitter user: $profileurl.");
+
+ $profile = new Profile();
+ $profile->query("BEGIN");
+
+ $profile->nickname = $user->screen_name;
+ $profile->fullname = $user->name;
+ $profile->homepage = $user->url;
+ $profile->bio = $user->description;
+ $profile->location = $user->location;
+ $profile->profileurl = $profileurl;
+ $profile->created = common_sql_now();
+
+ try {
+ $id = $profile->insert();
+ } catch(Exception $e) {
+ common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert profile - ' . $e->getMessage());
+ }
+
+ if (empty($id)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+
+ // check for remote profile
+
+ $remote_pro = Remote_profile::staticGet('uri', $profileurl);
+
+ if (empty($remote_pro)) {
+ $remote_pro = new Remote_profile();
+
+ $remote_pro->id = $id;
+ $remote_pro->uri = $profileurl;
+ $remote_pro->created = common_sql_now();
+
+ try {
+ $rid = $remote_pro->insert();
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $this->name() . ' Couldn\'t save remote profile - ' . $e->getMessage());
+ }
+
+ if (empty($rid)) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ $profile->query("ROLLBACK");
+ return false;
+ }
+ }
+
+ $profile->query("COMMIT");
+
+ $this->saveAvatars($user, $id);
+
+ return $profile;
+ }
+ }
+
+ function checkAvatar($twitter_user, $profile)
+ {
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $newname = 'Twitter_' . $twitter_user->id . '_' .
+ $path_parts['basename'];
+
+ $oldname = $profile->getAvatar(48)->filename;
+
+ if ($newname != $oldname) {
+ common_debug($this->name() . ' - Avatar for Twitter user ' .
+ "$profile->nickname has changed.");
+ common_debug($this->name() . " - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+
+ if ($this->missingAvatarFile($profile)) {
+ common_debug($this->name() . ' - Twitter user ' .
+ $profile->nickname .
+ ' is missing one or more local avatars.');
+ common_debug($this->name() ." - old: $oldname new: $newname");
+
+ $this->updateAvatars($twitter_user, $profile);
+ }
+ }
+
+ function updateAvatars($twitter_user, $profile) {
+
+ global $config;
+
+ $path_parts = pathinfo($twitter_user->profile_image_url);
+
+ $img_root = substr($path_parts['basename'], 0, -11);
+ $ext = $path_parts['extension'];
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $twitter_user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ $this->updateAvatar($profile->id, $size, $mediatype, $filename);
+ $this->fetchAvatar($url, $filename);
+ }
+ }
+
+ function missingAvatarFile($profile) {
+ foreach (array(24, 48, 73) as $size) {
+ $filename = $profile->getAvatar($size)->filename;
+ $avatarpath = Avatar::path($filename);
+ if (file_exists($avatarpath) == FALSE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function getMediatype($ext)
+ {
+ $mediatype = null;
+
+ switch (strtolower($ext)) {
+ case 'jpg':
+ $mediatype = 'image/jpg';
+ break;
+ case 'gif':
+ $mediatype = 'image/gif';
+ break;
+ default:
+ $mediatype = 'image/png';
+ }
+
+ return $mediatype;
+ }
+
+ function saveAvatars($user, $id)
+ {
+ global $config;
+
+ $path_parts = pathinfo($user->profile_image_url);
+ $ext = $path_parts['extension'];
+ $end = strlen('_normal' . $ext);
+ $img_root = substr($path_parts['basename'], 0, -($end+1));
+ $mediatype = $this->getMediatype($ext);
+
+ foreach (array('mini', 'normal', 'bigger') as $size) {
+ $url = $path_parts['dirname'] . '/' .
+ $img_root . '_' . $size . ".$ext";
+ $filename = 'Twitter_' . $user->id . '_' .
+ $img_root . "_$size.$ext";
+
+ if ($this->fetchAvatar($url, $filename)) {
+ $this->newAvatar($id, $size, $mediatype, $filename);
+ } else {
+ common_log(LOG_WARNING, $id() .
+ " - Problem fetching Avatar: $url");
+ }
+ }
+ }
+
+ function updateAvatar($profile_id, $size, $mediatype, $filename) {
+
+ common_debug($this->name() . " - Updating avatar: $size");
+
+ $profile = Profile::staticGet($profile_id);
+
+ if (empty($profile)) {
+ common_debug($this->name() . " - Couldn't get profile: $profile_id!");
+ return;
+ }
+
+ $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
+ $avatar = $profile->getAvatar($sizes[$size]);
+
+ // Delete the avatar, if present
+ if ($avatar) {
+ $avatar->delete();
+ }
+
+ $this->newAvatar($profile->id, $size, $mediatype, $filename);
+ }
+
+ function newAvatar($profile_id, $size, $mediatype, $filename)
+ {
+ global $config;
+
+ $avatar = new Avatar();
+ $avatar->profile_id = $profile_id;
+
+ switch($size) {
+ case 'mini':
+ $avatar->width = 24;
+ $avatar->height = 24;
+ break;
+ case 'normal':
+ $avatar->width = 48;
+ $avatar->height = 48;
+ break;
+ default:
+ // Note: Twitter's big avatars are a different size than
+ // StatusNet's (StatusNet's = 96)
+ $avatar->width = 73;
+ $avatar->height = 73;
+ }
+
+ $avatar->original = 0; // we don't have the original
+ $avatar->mediatype = $mediatype;
+ $avatar->filename = $filename;
+ $avatar->url = Avatar::url($filename);
+
+ $avatar->created = common_sql_now();
+
+ try {
+ $id = $avatar->insert();
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert avatar - ' . $e->getMessage());
+ }
+
+ if (empty($id)) {
+ common_log_db_error($avatar, 'INSERT', __FILE__);
+ return null;
+ }
+
+ common_debug($this->name() .
+ " - Saved new $size avatar for $profile_id.");
+
+ return $id;
+ }
+
+ /**
+ * Fetch a remote avatar image and save to local storage.
+ *
+ * @param string $url avatar source URL
+ * @param string $filename bare local filename for download
+ * @return bool true on success, false on failure
+ */
+ function fetchAvatar($url, $filename)
+ {
+ common_debug($this->name() . " - Fetching Twitter avatar: $url");
+
+ $request = HTTPClient::start();
+ $response = $request->get($url);
+ if ($response->isOk()) {
+ $avatarfile = Avatar::path($filename);
+ $ok = file_put_contents($avatarfile, $response->getBody());
+ if (!$ok) {
+ common_log(LOG_WARNING, $this->name() .
+ " - Couldn't open file $filename");
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ const URL = 1;
+ const HASHTAG = 2;
+ const MENTION = 3;
+
+ function linkify($status)
+ {
+ $text = $status->text;
+
+ if (empty($status->entities)) {
+ common_log(LOG_WARNING, "No entities data for {$status->id}; trying to fake up links ourselves.");
+ $text = common_replace_urls_callback($text, 'common_linkify');
+ $text = preg_replace('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.TwitterStatusFetcher::tagLink('\\2')", $text);
+ $text = preg_replace('/(^|\s+)@([a-z0-9A-Z_]{1,64})/e', "'\\1@'.TwitterStatusFetcher::atLink('\\2')", $text);
+ return $text;
+ }
+
+ // Move all the entities into order so we can
+ // replace them in reverse order and thus
+ // not mess up their indices
+
+ $toReplace = array();
+
+ if (!empty($status->entities->urls)) {
+ foreach ($status->entities->urls as $url) {
+ $toReplace[$url->indices[0]] = array(self::URL, $url);
+ }
+ }
+
+ if (!empty($status->entities->hashtags)) {
+ foreach ($status->entities->hashtags as $hashtag) {
+ $toReplace[$hashtag->indices[0]] = array(self::HASHTAG, $hashtag);
+ }
+ }
+
+ if (!empty($status->entities->user_mentions)) {
+ foreach ($status->entities->user_mentions as $mention) {
+ $toReplace[$mention->indices[0]] = array(self::MENTION, $mention);
+ }
+ }
+
+ // sort in reverse order by key
+
+ krsort($toReplace);
+
+ foreach ($toReplace as $part) {
+ list($type, $object) = $part;
+ switch($type) {
+ case self::URL:
+ $linkText = $this->makeUrlLink($object);
+ break;
+ case self::HASHTAG:
+ $linkText = $this->makeHashtagLink($object);
+ break;
+ case self::MENTION:
+ $linkText = $this->makeMentionLink($object);
+ break;
+ default:
+ continue;
+ }
+ $text = mb_substr($text, 0, $object->indices[0]) . $linkText . mb_substr($text, $object->indices[1]);
+ }
+ return $text;
+ }
+
+ function makeUrlLink($object)
+ {
+ return "<a href='{$object->url}' class='extlink'>{$object->url}</a>";
+ }
+
+ function makeHashtagLink($object)
+ {
+ return "#" . self::tagLink($object->text);
+ }
+
+ function makeMentionLink($object)
+ {
+ return "@".self::atLink($object->screen_name, $object->name);
+ }
+
+ static function tagLink($tag)
+ {
+ return "<a href='https://twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
+ }
+
+ static function atLink($screenName, $fullName=null)
+ {
+ if (!empty($fullName)) {
+ return "<a href='http://twitter.com/{$screenName}' title='{$fullName}'>{$screenName}</a>";
+ } else {
+ return "<a href='http://twitter.com/{$screenName}'>{$screenName}</a>";
+ }
+ }
+
+ function saveStatusMentions($notice, $status)
+ {
+ $mentions = array();
+
+ if (empty($status->entities) || empty($status->entities->user_mentions)) {
+ return;
+ }
+
+ foreach ($status->entities->user_mentions as $mention) {
+ $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE);
+ if (!empty($flink)) {
+ $user = User::staticGet('id', $flink->user_id);
+ if (!empty($user)) {
+ $reply = new Reply();
+ $reply->notice_id = $notice->id;
+ $reply->profile_id = $user->id;
+ common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
+ $id = $reply->insert();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
index 33c5eb65b..c169172b0 100644
--- a/plugins/TwitterBridge/twittersettings.php
+++ b/plugins/TwitterBridge/twittersettings.php
@@ -285,6 +285,7 @@ class TwittersettingsAction extends ConnectSettingsAction
}
$original = clone($flink);
+ $wasReceiving = (bool)($original->noticesync & FOREIGN_NOTICE_RECV);
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
$result = $flink->update($original);
@@ -294,6 +295,19 @@ class TwittersettingsAction extends ConnectSettingsAction
return;
}
+ if ($wasReceiving xor $noticerecv) {
+ $this->notifyDaemon($flink->foreign_id, $noticerecv);
+ }
+
$this->showForm(_m('Twitter preferences saved.'), true);
}
+
+ /**
+ * Tell the import daemon that we've updated a user's receive status.
+ */
+ function notifyDaemon($twitterUserId, $receiving)
+ {
+ // todo... should use control signals rather than queues
+ }
+
}
diff --git a/plugins/TwitterBridge/twitterstreamreader.php b/plugins/TwitterBridge/twitterstreamreader.php
new file mode 100644
index 000000000..5b0613bc4
--- /dev/null
+++ b/plugins/TwitterBridge/twitterstreamreader.php
@@ -0,0 +1,285 @@
+<?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 Plugin
+ * @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/
+ */
+
+/**
+ * Base class for reading Twitter's User Streams and Site Streams
+ * real-time streaming APIs.
+ *
+ * Caller can hook event callbacks for various types of messages;
+ * the data from the stream and some context info will be passed
+ * on to the callbacks.
+ */
+abstract class TwitterStreamReader extends JsonStreamReader
+{
+ protected $callbacks = array();
+
+ function __construct(TwitterOAuthClient $auth, $baseUrl)
+ {
+ $this->baseUrl = $baseUrl;
+ $this->oauth = $auth;
+ }
+
+ public function connect($method, $params=array())
+ {
+ $url = $this->oAuthUrl($this->baseUrl . '/' . $method, $params);
+ return parent::connect($url);
+ }
+
+ /**
+ * Sign our target URL with OAuth auth stuff.
+ *
+ * @param string $url
+ * @param array $params
+ * @return string
+ */
+ protected function oAuthUrl($url, $params=array())
+ {
+ // In an ideal world this would be better encapsulated. :)
+ $request = OAuthRequest::from_consumer_and_token($this->oauth->consumer,
+ $this->oauth->token, 'GET', $url, $params);
+ $request->sign_request($this->oauth->sha1_method,
+ $this->oauth->consumer, $this->oauth->token);
+
+ return $request->to_url();
+ }
+
+ /**
+ * Add an event callback to receive notifications when things come in
+ * over the wire.
+ *
+ * Callbacks should be in the form: function(object $data, array $context)
+ * where $context may list additional data on some streams, such as the
+ * user to whom the message should be routed.
+ *
+ * Available events:
+ *
+ * Messaging:
+ *
+ * 'status': $data contains a status update in standard Twitter JSON format.
+ * $data->user: sending user in standard Twitter JSON format.
+ * $data->text... etc
+ *
+ * 'direct_message': $data contains a direct message in standard Twitter JSON format.
+ * $data->sender: sending user in standard Twitter JSON format.
+ * $data->recipient: receiving user in standard Twitter JSON format.
+ * $data->text... etc
+ *
+ *
+ * Out of band events:
+ *
+ * 'follow': User has either started following someone, or is being followed.
+ * $data->source: following user in standard Twitter JSON format.
+ * $data->target: followed user in standard Twitter JSON format.
+ *
+ * 'favorite': Someone has favorited a status update.
+ * $data->source: user doing the favoriting, in standard Twitter JSON format.
+ * $data->target: user whose status was favorited, in standard Twitter JSON format.
+ * $data->target_object: the favorited status update in standard Twitter JSON format.
+ *
+ * 'unfavorite': Someone has unfavorited a status update.
+ * $data->source: user doing the unfavoriting, in standard Twitter JSON format.
+ * $data->target: user whose status was unfavorited, in standard Twitter JSON format.
+ * $data->target_object: the unfavorited status update in standard Twitter JSON format.
+ *
+ *
+ * Meta information:
+ *
+ * 'friends':
+ * $data->friends: array of user IDs of the current user's friends.
+ *
+ * 'delete': Advisory that a Twitter status has been deleted; nice clients
+ * should follow suit.
+ * $data->id: ID of status being deleted
+ * $data->user_id: ID of its owning user
+ *
+ * 'scrub_geo': Advisory that a user is clearing geo data from their status
+ * stream; nice clients should follow suit.
+ * $data->user_id: ID of user
+ * $data->up_to_status_id: any notice older than this should be scrubbed.
+ *
+ * 'limit': Advisory that tracking has hit a resource limit.
+ * $data->track
+ *
+ * 'raw': receives the full JSON data for all message types.
+ *
+ * @param string $event
+ * @param callable $callback
+ */
+ public function hookEvent($event, $callback)
+ {
+ $this->callbacks[$event][] = $callback;
+ }
+
+ /**
+ * Call event handler callbacks for the given event.
+ *
+ * @param string $event
+ * @param mixed $arg1 ... one or more params to pass on
+ */
+ protected function fireEvent($event, $arg1)
+ {
+ if (array_key_exists($event, $this->callbacks)) {
+ $args = array_slice(func_get_args(), 1);
+ foreach ($this->callbacks[$event] as $callback) {
+ call_user_func_array($callback, $args);
+ }
+ }
+ }
+
+ protected function handleJson(stdClass $data)
+ {
+ $this->routeMessage($data);
+ }
+
+ abstract protected function routeMessage(stdClass $data);
+
+ /**
+ * Send the decoded JSON object out to any event listeners.
+ *
+ * @param array $data
+ * @param array $context optional additional context data to pass on
+ */
+ protected function handleMessage(stdClass $data, array $context=array())
+ {
+ $this->fireEvent('raw', $data, $context);
+
+ if (isset($data->text)) {
+ $this->fireEvent('status', $data, $context);
+ return;
+ }
+ if (isset($data->event)) {
+ $this->fireEvent($data->event, $data, $context);
+ return;
+ }
+ if (isset($data->friends)) {
+ $this->fireEvent('friends', $data, $context);
+ }
+
+ $knownMeta = array('delete', 'scrub_geo', 'limit', 'direct_message');
+ foreach ($knownMeta as $key) {
+ if (isset($data->$key)) {
+ $this->fireEvent($key, $data->$key, $context);
+ return;
+ }
+ }
+ }
+}
+
+/**
+ * Multiuser stream listener for Twitter Site Streams API
+ * http://dev.twitter.com/pages/site_streams
+ *
+ * The site streams API allows listening to updates for multiple users.
+ * Pass in the user IDs to listen to in via followUser() -- note they
+ * must each have a valid OAuth token for the application ID we're
+ * connecting as.
+ *
+ * You'll need to be connecting with the auth keys for the user who
+ * owns the application registration.
+ *
+ * The user each message is destined for will be passed to event handlers
+ * in $context['for_user_id'].
+ */
+class TwitterSiteStream extends TwitterStreamReader
+{
+ protected $userIds;
+
+ public function __construct(TwitterOAuthClient $auth, $baseUrl='http://betastream.twitter.com')
+ {
+ parent::__construct($auth, $baseUrl);
+ }
+
+ public function connect($method='2b/site.json')
+ {
+ $params = array();
+ if ($this->userIds) {
+ $params['follow'] = implode(',', $this->userIds);
+ }
+ return parent::connect($method, $params);
+ }
+
+ /**
+ * Set the users whose home streams should be pulled.
+ * They all must have valid oauth tokens for this application.
+ *
+ * Must be called before connect().
+ *
+ * @param array $userIds
+ */
+ function followUsers($userIds)
+ {
+ $this->userIds = $userIds;
+ }
+
+ /**
+ * Each message in the site stream tells us which user ID it should be
+ * routed to; we'll need that to let the caller know what to do.
+ *
+ * @param array $data
+ */
+ function routeMessage(stdClass $data)
+ {
+ $context = array(
+ 'source' => 'sitestream',
+ 'for_user' => $data->for_user
+ );
+ parent::handleMessage($data->message, $context);
+ }
+}
+
+/**
+ * Stream listener for Twitter User Streams API
+ * http://dev.twitter.com/pages/user_streams
+ *
+ * This will pull the home stream and additional events just for the user
+ * we've authenticated as.
+ */
+class TwitterUserStream extends TwitterStreamReader
+{
+ public function __construct(TwitterOAuthClient $auth, $baseUrl='https://userstream.twitter.com')
+ {
+ parent::__construct($auth, $baseUrl);
+ }
+
+ public function connect($method='2/user.json')
+ {
+ return parent::connect($method);
+ }
+
+ /**
+ * Each message in the user stream is just ready to go.
+ *
+ * @param array $data
+ */
+ function routeMessage(stdClass $data)
+ {
+ $context = array(
+ 'source' => 'userstream'
+ );
+ parent::handleMessage($data, $context);
+ }
+}
diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php
index e6ad3e37d..fc7698841 100644
--- a/plugins/UserFlag/UserFlagPlugin.php
+++ b/plugins/UserFlag/UserFlagPlugin.php
@@ -128,25 +128,9 @@ class UserFlagPlugin extends Plugin
*/
function onEndProfilePageActionsElements(&$action, $profile)
{
- $user = common_current_user();
-
- if (!empty($user) && ($user->id != $profile->id)) {
-
- $action->elementStart('li', 'entity_flag');
-
- if (User_flag_profile::exists($profile->id, $user->id)) {
- // @todo FIXME: Add a title explaining what 'flagged' means?
- // TRANS: Message added to a profile if it has been flagged for review.
- $action->element('p', 'flagged', _('Flagged'));
- } else {
- $form = new FlagProfileForm($action, $profile,
- array('action' => 'showstream',
- 'nickname' => $profile->nickname));
- $form->show();
- }
-
- $action->elementEnd('li');
- }
+ $this->showFlagButton($action, $profile,
+ array('action' => 'showstream',
+ 'nickname' => $profile->nickname));
return true;
}
@@ -160,22 +144,40 @@ class UserFlagPlugin extends Plugin
*/
function onEndProfileListItemActionElements($item)
{
- $user = common_current_user();
+ list($action, $args) = $item->action->returnToArgs();
+ $args['action'] = $action;
+ $this->showFlagButton($item->action, $item->profile, $args);
+
+ return true;
+ }
- if (!empty($user)) {
+ /**
+ * Actually output a flag button. If the target profile has already been
+ * flagged by the current user, a null-action faux button is shown.
+ *
+ * @param Action $action
+ * @param Profile $profile
+ * @param array $returnToArgs
+ */
+ protected function showFlagButton($action, $profile, $returnToArgs)
+ {
+ $user = common_current_user();
- list($action, $args) = $item->action->returnToArgs();
+ if (!empty($user) && ($user->id != $profile->id)) {
- $args['action'] = $action;
+ $action->elementStart('li', 'entity_flag');
- $form = new FlagProfileForm($item->action, $item->profile, $args);
+ if (User_flag_profile::exists($profile->id, $user->id)) {
+ // @todo FIXME: Add a title explaining what 'flagged' means?
+ // TRANS: Message added to a profile if it has been flagged for review.
+ $action->element('p', 'flagged', _m('Flagged'));
+ } else {
+ $form = new FlagProfileForm($action, $profile, $returnToArgs);
+ $form->show();
+ }
- $item->action->elementStart('li', 'entity_flag');
- $form->show();
- $item->action->elementEnd('li');
+ $action->elementEnd('li');
}
-
- return true;
}
/**
diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php
index 69fe0f356..f4e9844df 100644
--- a/plugins/UserFlag/User_flag_profile.php
+++ b/plugins/UserFlag/User_flag_profile.php
@@ -79,21 +79,36 @@ class User_flag_profile extends Memcached_DataObject
/**
* return key definitions for DB_DataObject
*
- * @return array key definitions
+ * @return array of key names
*/
function keys()
{
- return array('profile_id' => 'K', 'user_id' => 'K');
+ return array_keys($this->keyTypes());
}
/**
* return key definitions for DB_DataObject
*
- * @return array key definitions
+ * @return array map of key definitions
*/
function keyTypes()
{
- return $this->keys();
+ return array('profile_id' => 'K', 'user_id' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+ function sequenceKey()
+ {
+ return array(false, false, false);
}
/**
diff --git a/plugins/UserFlag/flagprofile.php b/plugins/UserFlag/flagprofile.php
index 283eea40c..7096d3748 100644
--- a/plugins/UserFlag/flagprofile.php
+++ b/plugins/UserFlag/flagprofile.php
@@ -60,13 +60,6 @@ class FlagprofileAction extends ProfileFormAction
assert(!empty($user)); // checked above
assert(!empty($this->profile)); // checked above
- if (User_flag_profile::exists($this->profile->id,
- $user->id)) {
- // TRANS: Client error when setting flag that has already been set for a profile.
- $this->clientError(_m('Flag already exists.'));
- return false;
- }
-
return true;
}
@@ -104,7 +97,13 @@ class FlagprofileAction extends ProfileFormAction
// throws an exception on error
- User_flag_profile::create($user->id, $this->profile->id);
+ if (User_flag_profile::exists($this->profile->id,
+ $user->id)) {
+ // We'll return to the profile page (or return the updated AJAX form)
+ // showing the current state, so no harm done.
+ } else {
+ User_flag_profile::create($user->id, $this->profile->id);
+ }
if ($this->boolean('ajax')) {
$this->ajaxResults();
diff --git a/plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po b/plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po
new file mode 100644
index 000000000..5c768546a
--- /dev/null
+++ b/plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po
@@ -0,0 +1,26 @@
+# Translation of StatusNet - UserLimit to Finnish (Suomi)
+# Expored from translatewiki.net
+#
+# Author: Centerlink
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - UserLimit\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-02 22:51+0000\n"
+"PO-Revision-Date: 2010-11-02 22:55:20+0000\n"
+"Language-Team: Finnish <http://translatewiki.net/wiki/Portal:fi>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:14:14+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: fi\n"
+"X-Message-Group: #out-statusnet-plugin-userlimit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: UserLimitPlugin.php:89
+msgid "Limit the number of users who can register."
+msgstr "Rajoita niiden käyttäjien lukumäärää, jotka voivat rekisteröityä."
diff --git a/plugins/XCache/locale/fi/LC_MESSAGES/XCache.po b/plugins/XCache/locale/fi/LC_MESSAGES/XCache.po
new file mode 100644
index 000000000..c0ce345eb
--- /dev/null
+++ b/plugins/XCache/locale/fi/LC_MESSAGES/XCache.po
@@ -0,0 +1,30 @@
+# Translation of StatusNet - XCache to Finnish (Suomi)
+# Expored from translatewiki.net
+#
+# Author: Centerlink
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - XCache\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-10-30 23:18+0000\n"
+"PO-Revision-Date: 2010-10-30 23:23:06+0000\n"
+"Language-Team: Finnish <http://translatewiki.net/wiki/Portal:fi>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:14:46+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r75708); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: fi\n"
+"X-Message-Group: #out-statusnet-plugin-xcache\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: XCachePlugin.php:120
+msgid ""
+"Use the <a href=\"http://xcache.lighttpd.net/\">XCache</a> variable cache to "
+"cache query results."
+msgstr ""
+"Käytä <a href=\"http://xcache.lighttpd.net/\">XCache</a>-muuttujavälimuistia "
+"kyselyn tulosten tallentamiseksi välimuistiin."