summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Adsense/AdsensePlugin.php2
-rw-r--r--plugins/Adsense/locale/Adsense.pot4
-rw-r--r--plugins/Adsense/locale/br/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/de/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/es/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/fr/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/gl/LC_MESSAGES/Adsense.po10
-rw-r--r--plugins/Adsense/locale/ia/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/it/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/ka/LC_MESSAGES/Adsense.po10
-rw-r--r--plugins/Adsense/locale/mk/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/nl/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po13
-rw-r--r--plugins/Adsense/locale/ru/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/sv/LC_MESSAGES/Adsense.po10
-rw-r--r--plugins/Adsense/locale/tl/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/uk/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/Adsense/locale/zh_CN/LC_MESSAGES/Adsense.po11
-rw-r--r--plugins/BitlyUrl/locale/nb/LC_MESSAGES/BitlyUrl.po10
-rw-r--r--plugins/Comet/locale/br/LC_MESSAGES/Comet.po28
-rw-r--r--plugins/Disqus/locale/br/LC_MESSAGES/Disqus.po12
-rw-r--r--plugins/EmailSummary/EmailSummaryPlugin.php202
-rw-r--r--plugins/EmailSummary/Email_summary_status.php167
-rw-r--r--plugins/EmailSummary/sendemailsummary.php47
-rw-r--r--plugins/EmailSummary/siteemailsummaryhandler.php96
-rw-r--r--plugins/EmailSummary/useremailsummaryhandler.php226
-rw-r--r--plugins/Facebook/locale/nl/LC_MESSAGES/Facebook.po10
-rw-r--r--plugins/FacebookBridge/FacebookBridgePlugin.php552
-rw-r--r--plugins/FacebookBridge/actions/facebookadminpanel.php212
-rw-r--r--plugins/FacebookBridge/actions/facebookdeauthorize.php144
-rw-r--r--plugins/FacebookBridge/actions/facebookfinishlogin.php688
-rw-r--r--plugins/FacebookBridge/actions/facebooklogin.php123
-rw-r--r--plugins/FacebookBridge/actions/facebooksettings.php271
-rw-r--r--plugins/FacebookBridge/classes/Notice_to_item.php190
-rw-r--r--plugins/FacebookBridge/extlib/facebook.php963
-rw-r--r--plugins/FacebookBridge/extlib/fb_ca_chain_bundle.crt121
-rw-r--r--plugins/FacebookBridge/images/login-button.pngbin0 -> 1661 bytes
-rw-r--r--plugins/FacebookBridge/lib/facebookclient.php1121
-rw-r--r--plugins/FacebookBridge/lib/facebookqueuehandler.php61
-rw-r--r--plugins/GroupFavorited/groupfavoritedaction.php7
-rw-r--r--plugins/GroupFavorited/locale/GroupFavorited.pot6
-rw-r--r--plugins/GroupFavorited/locale/br/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/de/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/es/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/fr/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/ia/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/mk/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/nl/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/ru/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/tl/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/GroupFavorited/locale/uk/LC_MESSAGES/GroupFavorited.po12
-rw-r--r--plugins/LinkPreview/LinkPreviewPlugin.php101
-rw-r--r--plugins/LinkPreview/linkpreview.js194
-rw-r--r--plugins/LinkPreview/oembedproxyaction.php84
-rw-r--r--plugins/Mapstraction/allmap.php7
-rw-r--r--plugins/Mapstraction/locale/br/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/de/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/fi/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/fr/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/gl/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/ia/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/mk/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/nb/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/nl/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/ru/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/ta/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/tl/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/uk/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/locale/zh_CN/LC_MESSAGES/Mapstraction.po14
-rw-r--r--plugins/Mapstraction/usermap.php7
-rw-r--r--plugins/Memcached/locale/ja/LC_MESSAGES/Memcached.po29
-rw-r--r--plugins/Meteor/MeteorPlugin.php2
-rw-r--r--plugins/Meteor/meteorupdater.min.js1
-rw-r--r--plugins/NoticeTitle/locale/pl/LC_MESSAGES/NoticeTitle.po33
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php25
-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/OpenX/locale/br/LC_MESSAGES/OpenX.po109
-rw-r--r--plugins/Realtime/README4
-rw-r--r--plugins/Realtime/RealtimePlugin.php29
-rw-r--r--plugins/Realtime/locale/Realtime.pot53
-rw-r--r--plugins/Realtime/locale/af/LC_MESSAGES/Realtime.po58
-rw-r--r--plugins/Realtime/locale/br/LC_MESSAGES/Realtime.po58
-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/locale/tr/LC_MESSAGES/Realtime.po58
-rw-r--r--plugins/Realtime/locale/uk/LC_MESSAGES/Realtime.po59
-rw-r--r--plugins/Realtime/realtimeupdate.css2
-rw-r--r--plugins/Realtime/realtimeupdate.js23
-rw-r--r--plugins/Realtime/realtimeupdate.min.js1
-rw-r--r--plugins/Sample/locale/br/LC_MESSAGES/Sample.po20
-rw-r--r--plugins/Sitemap/locale/br/LC_MESSAGES/Sitemap.po11
-rw-r--r--plugins/TabFocus/locale/br/LC_MESSAGES/TabFocus.po32
-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/locale/TwitterBridge.pot26
-rw-r--r--plugins/TwitterBridge/locale/fr/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/ia/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/mk/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/nl/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/tr/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/uk/LC_MESSAGES/TwitterBridge.po32
-rw-r--r--plugins/TwitterBridge/locale/zh_CN/LC_MESSAGES/TwitterBridge.po32
-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.php670
-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/UserFlag/locale/UserFlag.pot16
-rw-r--r--plugins/UserFlag/locale/fr/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserFlag/locale/ia/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserFlag/locale/mk/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserFlag/locale/nl/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserFlag/locale/pt/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserFlag/locale/uk/LC_MESSAGES/UserFlag.po22
-rw-r--r--plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po26
-rw-r--r--plugins/XCache/locale/fi/LC_MESSAGES/XCache.po30
129 files changed, 9215 insertions, 1342 deletions
diff --git a/plugins/Adsense/AdsensePlugin.php b/plugins/Adsense/AdsensePlugin.php
index 3d733e150..1965f95ea 100644
--- a/plugins/Adsense/AdsensePlugin.php
+++ b/plugins/Adsense/AdsensePlugin.php
@@ -206,7 +206,7 @@ class AdsensePlugin extends UAPPlugin
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:Adsense',
'rawdescription' =>
- _m('Plugin to add Google Adsense to StatusNet sites.'));
+ _m('Plugin to add Google AdSense to StatusNet sites.'));
return true;
}
}
diff --git a/plugins/Adsense/locale/Adsense.pot b/plugins/Adsense/locale/Adsense.pot
index 2f5e4af2f..77db7e7d8 100644
--- a/plugins/Adsense/locale/Adsense.pot
+++ b/plugins/Adsense/locale/Adsense.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-07 20:25+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"
@@ -27,7 +27,7 @@ msgid "AdSense"
msgstr ""
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr ""
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/br/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/br/LC_MESSAGES/Adsense.po
index c572f9b27..87c864a69 100644
--- a/plugins/Adsense/locale/br/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/br/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal: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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plugin evit ouzhpennañ Google Adsense da lec'hiennoù StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/de/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/de/LC_MESSAGES/Adsense.po
index e7ddaa32e..e6c880a6e 100644
--- a/plugins/Adsense/locale/de/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/de/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: German <http://translatewiki.net/wiki/Portal:de>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plugin, das Google Adsense auf StatusNet-Websites hinzufügt."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/es/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/es/LC_MESSAGES/Adsense.po
index 5a2fcfb25..1232b8293 100644
--- a/plugins/Adsense/locale/es/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/es/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Spanish <http://translatewiki.net/wiki/Portal:es>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: es\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Extensión para añadir Google Adsense a sitios StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/fr/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/fr/LC_MESSAGES/Adsense.po
index 17b56a00a..e73c99a46 100644
--- a/plugins/Adsense/locale/fr/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/fr/LC_MESSAGES/Adsense.po
@@ -10,13 +10,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+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-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-adsense\n"
@@ -33,7 +33,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Greffon pour ajouter Google Adsense aux sites StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/gl/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/gl/LC_MESSAGES/Adsense.po
index e906670ea..2efd29f96 100644
--- a/plugins/Adsense/locale/gl/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/gl/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Galician <http://translatewiki.net/wiki/Portal:gl>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,7 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr ""
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/ia/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/ia/LC_MESSAGES/Adsense.po
index 1f8a73ea2..ef9250d00 100644
--- a/plugins/Adsense/locale/ia/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/ia/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+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-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plug-in pro adder Google Adsense a sitos StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/it/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/it/LC_MESSAGES/Adsense.po
index e8addce83..004fe0559 100644
--- a/plugins/Adsense/locale/it/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/it/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Italian <http://translatewiki.net/wiki/Portal:it>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: it\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plugin per aggiungere Google Adsense ai siti StatusNet"
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/ka/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/ka/LC_MESSAGES/Adsense.po
index d3ccee66e..ee971e6d1 100644
--- a/plugins/Adsense/locale/ka/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/ka/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Georgian <http://translatewiki.net/wiki/Portal:ka>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ka\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,7 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr ""
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/mk/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/mk/LC_MESSAGES/Adsense.po
index 44fe432aa..ef9b949e4 100644
--- a/plugins/Adsense/locale/mk/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/mk/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+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-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Приклучок за додавање на Google AdSense во мреж. места со StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/nl/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/nl/LC_MESSAGES/Adsense.po
index c4854ad3f..9c722cc95 100644
--- a/plugins/Adsense/locale/nl/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/nl/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+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-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plug-in om Google AdSense toe te voegen aan Statusnetsites."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/pt_BR/LC_MESSAGES/Adsense.po
index cbe5e0554..649287ceb 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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"
@@ -34,7 +34,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Plugin para adicionar Google Adsense aos sites StatusNet."
#: adsenseadminpanel.php:52
@@ -80,7 +81,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/Adsense/locale/ru/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/ru/LC_MESSAGES/Adsense.po
index 85bb316a6..3a359a3a9 100644
--- a/plugins/Adsense/locale/ru/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/ru/LC_MESSAGES/Adsense.po
@@ -10,13 +10,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Russian <http://translatewiki.net/wiki/Portal:ru>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -34,7 +34,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Плагин для добавления Google Adsense на сайты StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/sv/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/sv/LC_MESSAGES/Adsense.po
index 031b79503..f8e3d83bc 100644
--- a/plugins/Adsense/locale/sv/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/sv/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Swedish <http://translatewiki.net/wiki/Portal:sv>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: sv\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,7 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr ""
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/tl/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/tl/LC_MESSAGES/Adsense.po
index ad5ed1ad2..db5d3445b 100644
--- a/plugins/Adsense/locale/tl/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/tl/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:50+0000\n"
"Language-Team: Tagalog <http://translatewiki.net/wiki/Portal:tl>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -32,7 +32,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr ""
"Pampasak upang maidagdag ang Adsense ng Google sa mga sityo ng StatusNet."
diff --git a/plugins/Adsense/locale/uk/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/uk/LC_MESSAGES/Adsense.po
index f15de57c9..fe9ee4984 100644
--- a/plugins/Adsense/locale/uk/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/uk/LC_MESSAGES/Adsense.po
@@ -9,13 +9,13 @@ 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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27: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-20 17:58:21+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-adsense\n"
@@ -33,7 +33,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "Додаток для відображення Google Adsense на сторінці сайту StatusNet."
#: adsenseadminpanel.php:52
diff --git a/plugins/Adsense/locale/zh_CN/LC_MESSAGES/Adsense.po b/plugins/Adsense/locale/zh_CN/LC_MESSAGES/Adsense.po
index 9bc76e416..0bdb9880d 100644
--- a/plugins/Adsense/locale/zh_CN/LC_MESSAGES/Adsense.po
+++ b/plugins/Adsense/locale/zh_CN/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-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:51+0000\n"
"Language-Team: Simplified Chinese <http://translatewiki.net/wiki/Portal:zh-"
"hans>\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-30 23:43:40+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-adsense\n"
@@ -34,7 +34,8 @@ msgid "AdSense"
msgstr "AdSense"
#: AdsensePlugin.php:209
-msgid "Plugin to add Google Adsense to StatusNet sites."
+#, fuzzy
+msgid "Plugin to add Google AdSense to StatusNet sites."
msgstr "添加 Google Adsense 到 StatusNet 网站的插件。"
#: adsenseadminpanel.php:52
diff --git a/plugins/BitlyUrl/locale/nb/LC_MESSAGES/BitlyUrl.po b/plugins/BitlyUrl/locale/nb/LC_MESSAGES/BitlyUrl.po
index de00fc1ca..4298ae85e 100644
--- a/plugins/BitlyUrl/locale/nb/LC_MESSAGES/BitlyUrl.po
+++ b/plugins/BitlyUrl/locale/nb/LC_MESSAGES/BitlyUrl.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - BitlyUrl\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:16+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:27:56+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-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:53+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-bitlyurl\n"
@@ -23,7 +23,7 @@ msgstr ""
#: BitlyUrlPlugin.php:48
msgid "You must specify a serviceUrl for bit.ly shortening."
-msgstr ""
+msgstr "Du må angi en serviceUrl for bit.ly-forkortelse."
#: BitlyUrlPlugin.php:171
#, php-format
diff --git a/plugins/Comet/locale/br/LC_MESSAGES/Comet.po b/plugins/Comet/locale/br/LC_MESSAGES/Comet.po
new file mode 100644
index 000000000..70093d613
--- /dev/null
+++ b/plugins/Comet/locale/br/LC_MESSAGES/Comet.po
@@ -0,0 +1,28 @@
+# Translation of StatusNet - Comet to Breton (Brezhoneg)
+# Expored from translatewiki.net
+#
+# Author: Y-M D
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - Comet\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:04+0000\n"
+"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:12:39+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: br\n"
+"X-Message-Group: #out-statusnet-plugin-comet\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: CometPlugin.php:114
+msgid "Plugin to do \"real time\" updates using Comet/Bayeux."
+msgstr ""
+"Un astenn evit ober hizivadennoù \"war ar prim\" en ur implijout Comet/"
+"Bayeux."
diff --git a/plugins/Disqus/locale/br/LC_MESSAGES/Disqus.po b/plugins/Disqus/locale/br/LC_MESSAGES/Disqus.po
index caafa7ba8..d647fe3f6 100644
--- a/plugins/Disqus/locale/br/LC_MESSAGES/Disqus.po
+++ b/plugins/Disqus/locale/br/LC_MESSAGES/Disqus.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Disqus\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:26+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:06+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:07+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:43+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-disqus\n"
@@ -27,10 +27,12 @@ msgid ""
"Please enable JavaScript to view the [comments powered by Disqus](http://"
"disqus.com/?ref_noscript=%s)."
msgstr ""
+"Mar plij gweredekait JavaScript evit gwelet an [evezhiadennoù enlusket gant "
+"Disqus] (http://disqus.com/?ref_noscript=%s)."
#: DisqusPlugin.php:149
msgid "Comments powered by "
-msgstr ""
+msgstr "Evezhiadennoù enlusket gant "
#: DisqusPlugin.php:201
msgid "Comments"
diff --git a/plugins/EmailSummary/EmailSummaryPlugin.php b/plugins/EmailSummary/EmailSummaryPlugin.php
new file mode 100644
index 000000000..58c40e43c
--- /dev/null
+++ b/plugins/EmailSummary/EmailSummaryPlugin.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Sends an email summary of the inbox to users in the network
+ *
+ * PHP version 5
+ *
+ * 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 Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Plugin for sending email summaries to users
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Brion Vibber <brionv@status.net>
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class EmailSummaryPlugin extends Plugin
+{
+ /**
+ * Database schema setup
+ *
+ * @return boolean hook value
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+
+ // For storing user-submitted flags on profiles
+
+ $schema->ensureTable('email_summary_status',
+ array(new ColumnDef('user_id', 'integer', null,
+ false, 'PRI'),
+ new ColumnDef('send_summary', 'tinyint', null,
+ false, null, 1),
+ new ColumnDef('last_summary_id', 'integer', null,
+ true),
+ new ColumnDef('created', 'datetime', null,
+ false),
+ new ColumnDef('modified', 'datetime', null,
+ false),
+ )
+ );
+ return true;
+ }
+
+ /**
+ * Load related modules when needed
+ *
+ * @param string $cls Name of the class to be loaded
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ *
+ */
+
+ function onAutoload($cls)
+ {
+ $dir = dirname(__FILE__);
+
+ switch ($cls)
+ {
+ case 'SiteEmailSummaryHandler':
+ case 'UserEmailSummaryHandler':
+ include_once $dir . '/'.strtolower($cls).'.php';
+ return false;
+ case 'Email_summary_status':
+ include_once $dir . '/'.$cls.'.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Version info for this plugin
+ *
+ * @param array &$versions array of version data
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ *
+ */
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'EmailSummary',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:EmailSummary',
+ 'rawdescription' =>
+ _m('Send an email summary of the inbox to users.'));
+ return true;
+ }
+
+ /**
+ * Register our queue handlers
+ *
+ * @param QueueManager $qm Current queue manager
+ *
+ * @return boolean hook value
+ */
+
+ function onEndInitializeQueueManager($qm)
+ {
+ $qm->connect('sitesum', 'SiteEmailSummaryHandler');
+ $qm->connect('usersum', 'UserEmailSummaryHandler');
+ return true;
+ }
+
+ /**
+ * Add a checkbox to turn off email summaries
+ *
+ * @param Action $action Action being executed (emailsettings)
+ *
+ * @return boolean hook value
+ */
+
+ function onEndEmailFormData($action)
+ {
+ $user = common_current_user();
+
+ $action->elementStart('li');
+ $action->checkbox('emailsummary',
+ // TRANS: Checkbox label in e-mail preferences form.
+ _('Send me a periodic summary of updates from my network.'),
+ Email_summary_status::getSendSummary($user->id));
+ $action->elementEnd('li');
+ return true;
+ }
+
+ /**
+ * Add a checkbox to turn off email summaries
+ *
+ * @param Action $action Action being executed (emailsettings)
+ *
+ * @return boolean hook value
+ */
+
+ function onEndEmailSaveForm($action)
+ {
+ $sendSummary = $action->boolean('emailsummary');
+
+ $user = common_current_user();
+
+ if (!empty($user)) {
+
+ $ess = Email_summary_status::staticGet('user_id', $user->id);
+
+ if (empty($ess)) {
+
+ $ess = new Email_summary_status();
+
+ $ess->user_id = $user->id;
+ $ess->send_summary = $sendSummary;
+ $ess->created = common_sql_now();
+ $ess->modified = common_sql_now();
+
+ $ess->insert();
+
+ } else {
+
+ $orig = clone($ess);
+
+ $ess->send_summary = $sendSummary;
+ $ess->modified = common_sql_now();
+
+ $ess->update($orig);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/EmailSummary/Email_summary_status.php b/plugins/EmailSummary/Email_summary_status.php
new file mode 100644
index 000000000..5b5b231e3
--- /dev/null
+++ b/plugins/EmailSummary/Email_summary_status.php
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Data class for email summary status
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * 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')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for email summaries
+ *
+ * Email summary information for users
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * @see DB_DataObject
+ */
+
+class Email_summary_status extends Memcached_DataObject
+{
+ public $__table = 'email_summary_status'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $send_summary; // tinyint not_null
+ public $last_summary_id; // int(4) null
+ public $created; // datetime not_null
+ public $modified; // datetime not_null
+
+ /**
+ * Get an instance by key
+ *
+ * @param string $k Key to use to lookup (usually 'user_id' for this class)
+ * @param mixed $v Value to lookup
+ *
+ * @return Email_summary_status object found, or null for no hits
+ *
+ */
+ function staticGet($k, $v=null)
+ {
+ return Memcached_DataObject::staticGet('email_summary_status', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'send_summary' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'last_summary_id' => DB_DATAOBJECT_INT,
+ 'created' => DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * @return array list of key field names
+ */
+
+ 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. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+ function keyTypes()
+ {
+ return array('user_id' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+
+ function sequenceKey()
+ {
+ return array(false, false, false);
+ }
+
+ /**
+ * Helper function
+ *
+ * @param integer $user_id ID of the user to get a count for
+ *
+ * @return int flag for whether to send this user a summary email
+ */
+
+ static function getSendSummary($user_id)
+ {
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess)) {
+ return $ess->send_summary;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Get email summary status for a user
+ *
+ * @param integer $user_id ID of the user to get a count for
+ *
+ * @return Email_summary_status instance for this user, with count already incremented.
+ */
+
+ static function getLastSummaryID($user_id)
+ {
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess)) {
+ return $ess->last_summary_id;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/plugins/EmailSummary/sendemailsummary.php b/plugins/EmailSummary/sendemailsummary.php
new file mode 100644
index 000000000..37bfdcfbd
--- /dev/null
+++ b/plugins/EmailSummary/sendemailsummary.php
@@ -0,0 +1,47 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+$shortoptions = 'i:n:a';
+$longoptions = array('id=', 'nickname=', 'all');
+
+$helptext = <<<END_OF_SENDEMAILSUMMARY_HELP
+sendemailsummary.php [options]
+Send an email summary of the inbox to users
+
+ -i --id ID of user to send summary to
+ -n --nickname nickname of the user to send summary to
+ -a --all send summary to all users
+
+END_OF_SENDEMAILSUMMARY_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$qm = QueueManager::get();
+
+// enqueue summary for user or all users
+
+try {
+ $user = getUser();
+ $qm->enqueue($user->id, 'usersum');
+} catch (NoUserArgumentException $nuae) {
+ $qm->enqueue(null, 'sitesum');
+}
diff --git a/plugins/EmailSummary/siteemailsummaryhandler.php b/plugins/EmailSummary/siteemailsummaryhandler.php
new file mode 100644
index 000000000..595c3267a
--- /dev/null
+++ b/plugins/EmailSummary/siteemailsummaryhandler.php
@@ -0,0 +1,96 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * Handler for queue items of type 'sitesum', sends email summaries
+ * to all users on the site.
+ *
+ * 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 Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ *
+ * Handler for queue items of type 'sitesum', sends email summaries
+ * to all users on the site.
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class SiteEmailSummaryHandler extends QueueHandler
+{
+
+ /**
+ * Return transport keyword which identifies items this queue handler
+ * services; must be defined for all subclasses.
+ *
+ * Must be 8 characters or less to fit in the queue_item database.
+ * ex "email", "jabber", "sms", "irc", ...
+ *
+ * @return string
+ */
+
+ function transport()
+ {
+ return 'sitesum';
+ }
+
+ /**
+ * Handle the site
+ *
+ * @param mixed $object
+ * @return boolean true on success, false on failure
+ */
+
+ function handle($object)
+ {
+ $qm = QueueManager::get();
+
+ try {
+ // Enqueue a summary for all users
+
+ $user = new User();
+ $user->find();
+
+ while ($user->fetch()) {
+ try {
+ $qm->enqueue($user->id, 'usersum');
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $e->getMessage());
+ continue;
+ }
+ }
+ } catch (Exception $e) {
+ common_log(LOG_WARNING, $e->getMessage());
+ }
+
+ return true;
+ }
+}
+
diff --git a/plugins/EmailSummary/useremailsummaryhandler.php b/plugins/EmailSummary/useremailsummaryhandler.php
new file mode 100644
index 000000000..b1ebd0c42
--- /dev/null
+++ b/plugins/EmailSummary/useremailsummaryhandler.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ *
+ * Handler for queue items of type 'usersum', sends an email summaries
+ * to a particular user.
+ *
+ * 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 Sample
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Handler for queue items of type 'usersum', sends an email summaries
+ * to a particular user.
+ *
+ * @category Email
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class UserEmailSummaryHandler extends QueueHandler
+{
+ // Maximum number of notices to include by default. This is probably too much.
+
+ const MAX_NOTICES = 200;
+
+ /**
+ * Return transport keyword which identifies items this queue handler
+ * services; must be defined for all subclasses.
+ *
+ * Must be 8 characters or less to fit in the queue_item database.
+ * ex "email", "jabber", "sms", "irc", ...
+ *
+ * @return string
+ */
+
+ function transport()
+ {
+ return 'sitesum';
+ }
+
+ /**
+ * Send a summary email to the user
+ *
+ * @param mixed $object
+ * @return boolean true on success, false on failure
+ */
+
+ function handle($user_id)
+ {
+ // Skip if they've asked not to get summaries
+
+ $ess = Email_summary_status::staticGet('user_id', $user_id);
+
+ if (!empty($ess) && !$ess->send_summary) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s by request.', $user_id));
+ return true;
+ }
+
+ $since_id = null;
+
+ if (!empty($ess)) {
+ $since_id = $ess->last_summary_id;
+ }
+
+ $user = User::staticGet('id', $user_id);
+
+ if (empty($user)) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no such user.', $user_id));
+ return true;
+ }
+
+ if (empty($user->email)) {
+ common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no email address.', $user_id));
+ return true;
+ }
+
+ $profile = $user->getProfile();
+
+ if (empty($profile)) {
+ common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no profile.', $user_id));
+ return true;
+ }
+
+ $notice = $user->ownFriendsTimeline(0, self::MAX_NOTICES, $since_id);
+
+ if (empty($notice) || $notice->N == 0) {
+ common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no notices.', $user_id));
+ return true;
+ }
+
+ // XXX: This is risky fingerpoken in der objektvars, but I didn't feel like
+ // figuring out a better way. -ESP
+
+ $new_top = null;
+
+ if ($notice instanceof ArrayWrapper) {
+ $new_top = $notice->_items[0]->id;
+ }
+
+ $out = new XMLStringer();
+
+ $out->raw(sprintf(_('<p>Recent updates from %1s for %2s:</p>'),
+ common_config('site', 'name'),
+ $profile->getBestName()));
+
+
+ $out->elementStart('table', array('width' => '541px', 'style' => 'border: none'));
+
+ while ($notice->fetch()) {
+
+ $profile = Profile::staticGet('id', $notice->profile_id);
+
+ if (empty($profile)) {
+ continue;
+ }
+
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ $out->elementStart('tr');
+ $out->elementStart('td', array('width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'align' => 'left',
+ 'valign' => 'top'));
+ $out->element('img', array('src' => ($avatar) ?
+ $avatar->displayUrl() :
+ Avatar::defaultImage($avatar_size),
+ 'class' => 'avatar photo',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' => $profile->getBestName()));
+ $out->elementEnd('td');
+ $out->elementStart('td', array('align' => 'left',
+ 'valign' => 'top'));
+ $out->element('a', array('href' => $profile->profileurl),
+ $profile->nickname);
+ $out->text(' ');
+ $out->raw($notice->rendered);
+ $out->element('br'); // yeah, you know it. I just wrote a <br> in the middle of my table layout.
+ $noticeurl = $notice->bestUrl();
+ // above should always return an URL
+ assert(!empty($noticeurl));
+ $out->elementStart('a', array('rel' => 'bookmark',
+ 'class' => 'timestamp',
+ 'href' => $noticeurl));
+ $dt = common_date_iso8601($notice->created);
+ $out->element('abbr', array('class' => 'published',
+ 'title' => $dt),
+ common_date_string($notice->created));
+ $out->elementEnd('a');
+ if ($notice->hasConversation()) {
+ $conv = Conversation::staticGet('id', $notice->conversation);
+ $convurl = $conv->uri;
+ if (!empty($convurl)) {
+ $out->text(' ');
+ $out->element('a',
+ array('href' => $convurl.'#notice-'.$notice->id,
+ 'class' => 'response'),
+ _('in context'));
+ }
+ }
+ $out->elementEnd('td');
+ $out->elementEnd('tr');
+ }
+
+ $out->elementEnd('table');
+
+ $out->raw(sprintf(_('<p><a href="%1s">change your email settings for %2s</a></p>'),
+ common_local_url('emailsettings'),
+ common_config('site', 'name')));
+
+ $body = $out->getString();
+
+ // FIXME: do something for people who don't like HTML email
+
+ mail_to_user($user, _('Updates from your network'), $body,
+ array('Content-Type' => 'text/html; charset=UTF-8'));
+
+ if (empty($ess)) {
+
+ $ess = new Email_summary_status();
+
+ $ess->user_id = $user_id;
+ $ess->created = common_sql_now();
+ $ess->last_summary_id = $new_top;
+ $ess->modified = common_sql_now();
+
+ $ess->insert();
+
+ } else {
+
+ $orig = clone($ess);
+
+ $ess->last_summary_id = $new_top;
+ $ess->modified = common_sql_now();
+
+ $ess->update($orig);
+ }
+
+ return true;
+ }
+}
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/FacebookBridge/FacebookBridgePlugin.php b/plugins/FacebookBridge/FacebookBridgePlugin.php
new file mode 100644
index 000000000..8b5d05e98
--- /dev/null
+++ b/plugins/FacebookBridge/FacebookBridgePlugin.php
@@ -0,0 +1,552 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * A plugin for integrating Facebook with StatusNet. Includes single-sign-on
+ * and publishing notices to Facebook using Facebook's Graph API.
+ *
+ * PHP version 5
+ *
+ * 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 Pugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+define("FACEBOOK_SERVICE", 2);
+
+/**
+ * Main class for Facebook Bridge plugin
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+class FacebookBridgePlugin extends Plugin
+{
+ public $appId = null; // Facebook application ID
+ public $secret = null; // Facebook application secret
+ public $facebook = null; // Facebook application instance
+ public $dir = null; // Facebook plugin dir
+
+ /**
+ * Initializer for this plugin
+ *
+ * Gets an instance of the Facebook API client object
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+ function initialize()
+ {
+ $this->facebook = Facebookclient::getFacebook(
+ $this->appId,
+ $this->secret
+ );
+
+ return true;
+ }
+
+ /**
+ * Load related modules when needed
+ *
+ * @param string $cls Name of the class to be loaded
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+ function onAutoload($cls)
+ {
+
+ $dir = dirname(__FILE__);
+
+ //common_debug("class = " . $cls);
+
+ switch ($cls)
+ {
+ case 'Facebook': // Facebook PHP SDK
+ include_once $dir . '/extlib/facebook.php';
+ return false;
+ case 'FacebookloginAction':
+ case 'FacebookfinishloginAction':
+ case 'FacebookadminpanelAction':
+ case 'FacebooksettingsAction':
+ case 'FacebookdeauthorizeAction':
+ include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+ return false;
+ case 'Facebookclient':
+ case 'FacebookQueueHandler':
+ include_once $dir . '/lib/' . strtolower($cls) . '.php';
+ return false;
+ case 'Notice_to_item':
+ include_once $dir . '/classes/' . $cls . '.php';
+ return false;
+ default:
+ return true;
+ }
+
+ }
+
+ /**
+ * Database schema setup
+ *
+ * We maintain a table mapping StatusNet notices to Facebook items
+ *
+ * @see Schema
+ * @see ColumnDef
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+ $schema->ensureTable('notice_to_item', Notice_to_item::schemaDef());
+ return true;
+ }
+
+ /*
+ * Does this $action need the Facebook JavaScripts?
+ */
+ function needsScripts($action)
+ {
+ static $needy = array(
+ 'FacebookloginAction',
+ 'FacebookfinishloginAction',
+ 'FacebookadminpanelAction',
+ 'FacebooksettingsAction'
+ );
+
+ if (in_array(get_class($action), $needy)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Map URLs to actions
+ *
+ * @param Net_URL_Mapper $m path-to-action mapper
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+ function onRouterInitialized($m)
+ {
+ // Always add the admin panel route
+ $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
+
+ // Only add these routes if an application has been setup on
+ // Facebook for the plugin to use.
+ if ($this->hasApplication()) {
+
+ $m->connect(
+ 'main/facebooklogin',
+ array('action' => 'facebooklogin')
+ );
+ $m->connect(
+ 'main/facebookfinishlogin',
+ array('action' => 'facebookfinishlogin')
+ );
+ $m->connect(
+ 'settings/facebook',
+ array('action' => 'facebooksettings')
+ );
+ $m->connect(
+ 'facebook/deauthorize',
+ array('action' => 'facebookdeauthorize')
+ );
+
+ }
+
+ return true;
+ }
+
+ /*
+ * Add a login tab for Facebook, but only if there's a Facebook
+ * application defined for the plugin to use.
+ *
+ * @param Action &action the current action
+ *
+ * @return void
+ */
+ function onEndLoginGroupNav(&$action)
+ {
+ $action_name = $action->trimmed('action');
+
+ if ($this->hasApplication()) {
+
+ $action->menuItem(
+ common_local_url('facebooklogin'),
+ _m('MENU', 'Facebook'),
+ // TRANS: Tooltip for menu item "Facebook".
+ _m('Login or register using Facebook'),
+ 'facebooklogin' === $action_name
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * If the plugin's installed, this should be accessible to admins
+ */
+ function onAdminPanelCheck($name, &$isOK)
+ {
+ if ($name == 'facebook') {
+ $isOK = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a Facebook tab to the admin panels
+ *
+ * @param Widget $nav Admin panel nav
+ *
+ * @return boolean hook value
+ */
+ function onEndAdminPanelNav($nav)
+ {
+ if (AdminPanelAction::canAdmin('facebook')) {
+
+ $action_name = $nav->action->trimmed('action');
+
+ $nav->out->menuItem(
+ common_local_url('facebookadminpanel'),
+ // TRANS: Menu item.
+ _m('MENU','Facebook'),
+ // TRANS: Tooltip for menu item "Facebook".
+ _m('Facebook integration configuration'),
+ $action_name == 'facebookadminpanel',
+ 'nav_facebook_admin_panel'
+ );
+ }
+
+ return true;
+ }
+
+ /*
+ * Add a tab for user-level Facebook settings if the user
+ * has a link to Facebook
+ *
+ * @param Action &action the current action
+ *
+ * @return void
+ */
+ function onEndConnectSettingsNav(&$action)
+ {
+ if ($this->hasApplication()) {
+ $action_name = $action->trimmed('action');
+
+ // CurrentUserDesignAction stores the current user in $cur
+ $user = $action->getCurrentUser();
+
+ $flink = null;
+
+ if (!empty($user)) {
+ $flink = Foreign_link::getByUserID(
+ $user->id,
+ FACEBOOK_SERVICE
+ );
+ }
+
+ if (!empty($flink)) {
+
+ $action->menuItem(
+ common_local_url('facebooksettings'),
+ // TRANS: Menu item tab.
+ _m('MENU','Facebook'),
+ // TRANS: Tooltip for menu item "Facebook".
+ _m('Facebook settings'),
+ $action_name === 'facebooksettings'
+ );
+
+ }
+ }
+
+ }
+
+ /*
+ * Is there a Facebook application for the plugin to use?
+ *
+ * Checks to see if a Facebook application ID and secret
+ * have been configured and a valid Facebook API client
+ * object exists.
+ *
+ */
+ function hasApplication()
+ {
+ if (!empty($this->facebook)) {
+
+ $appId = $this->facebook->getAppId();
+ $secret = $this->facebook->getApiSecret();
+
+ if (!empty($appId) && !empty($secret)) {
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ /*
+ * Output a Facebook div for the Facebook JavaSsript SDK to use
+ *
+ * @param Action $action the current action
+ *
+ */
+ function onStartShowHeader($action)
+ {
+ // output <div id="fb-root"></div> as close to <body> as possible
+ $action->element('div', array('id' => 'fb-root'));
+ return true;
+ }
+
+ /*
+ * Load the Facebook JavaScript SDK on pages that need them.
+ *
+ * @param Action $action the current action
+ *
+ */
+ function onEndShowScripts($action)
+ {
+ if ($this->needsScripts($action)) {
+
+ $action->script('https://connect.facebook.net/en_US/all.js');
+
+ $script = <<<ENDOFSCRIPT
+FB.init({appId: %1\$s, session: %2\$s, status: true, cookie: true, xfbml: true});
+
+$('#facebook_button').bind('click', function(event) {
+
+ event.preventDefault();
+
+ FB.login(function(response) {
+ if (response.session && response.perms) {
+ window.location.href = '%3\$s';
+ } else {
+ // NOP (user cancelled login)
+ }
+ }, {perms:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
+});
+ENDOFSCRIPT;
+
+ $action->inlineScript(
+ sprintf($script,
+ json_encode($this->facebook->getAppId()),
+ json_encode($this->facebook->getSession()),
+ common_local_url('facebookfinishlogin')
+ )
+ );
+ }
+ }
+
+ /*
+ * Log the user out of Facebook, per the Facebook authentication guide
+ *
+ * @param Action action the current action
+ */
+ function onEndLogout($action)
+ {
+ if ($this->hasApplication()) {
+ $session = $this->facebook->getSession();
+ $fbuser = null;
+ $fbuid = null;
+
+ if ($session) {
+ try {
+ $fbuid = $this->facebook->getUser();
+ $fbuser = $this->facebook->api('/me');
+ } catch (FacebookApiException $e) {
+ common_log(LOG_ERROR, $e, __FILE__);
+ }
+ }
+
+ if (!empty($fbuser)) {
+
+ $logoutUrl = $this->facebook->getLogoutUrl(
+ array('next' => common_local_url('public'))
+ );
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ "Logging user out of Facebook (fbuid = %s)",
+ $fbuid
+ ),
+ __FILE__
+ );
+ common_debug("LOGOUT URL = $logoutUrl");
+ common_redirect($logoutUrl, 303);
+ }
+
+ }
+ }
+
+ /*
+ * Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
+ * and render XFBML tags
+ *
+ * @param Action $action the current action
+ * @param array $attrs array of attributes for the HTML tag
+ *
+ * @return nothing
+ */
+ function onStartHtmlElement($action, $attrs) {
+
+ if ($this->needsScripts($action)) {
+ $attrs = array_merge(
+ $attrs,
+ array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a Facebook queue item for each notice
+ *
+ * @param Notice $notice the notice
+ * @param array &$transports the list of transports (queues)
+ *
+ * @return boolean hook return
+ */
+ function onStartEnqueueNotice($notice, &$transports)
+ {
+ if (self::hasApplication() && $notice->isLocal()) {
+ array_push($transports, 'facebook');
+ }
+ return true;
+ }
+
+ /**
+ * Register Facebook notice queue handler
+ *
+ * @param QueueManager $manager
+ *
+ * @return boolean hook return
+ */
+ function onEndInitializeQueueManager($manager)
+ {
+ if (self::hasApplication()) {
+ $manager->connect('facebook', 'FacebookQueueHandler');
+ }
+ return true;
+ }
+
+ /*
+ * Use SSL for Facebook stuff
+ *
+ * @param string $action name
+ * @param boolean $ssl outval to force SSL
+ * @return mixed hook return value
+ */
+ function onSensitiveAction($action, &$ssl)
+ {
+ $sensitive = array(
+ 'facebookadminpanel',
+ 'facebooksettings',
+ 'facebooklogin',
+ 'facebookfinishlogin'
+ );
+
+ if (in_array($action, $sensitive)) {
+ $ssl = true;
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * If a notice gets deleted, remove the Notice_to_item mapping and
+ * delete the item on Facebook
+ *
+ * @param User $user The user doing the deleting
+ * @param Notice $notice The notice getting deleted
+ *
+ * @return boolean hook value
+ */
+ function onStartDeleteOwnNotice(User $user, Notice $notice)
+ {
+ $client = new Facebookclient($notice);
+ $client->streamRemove();
+
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get favorited.
+ *
+ * @param Profile or User $profile of local user doing the faving
+ * @param Notice $notice being favored
+ * @return hook return value
+ */
+ function onEndFavorNotice(Profile $profile, Notice $notice)
+ {
+ $client = new Facebookclient($notice);
+ $client->like();
+
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get de-favorited.
+ *
+ * @param Profile $profile Profile person doing the de-faving
+ * @param Notice $notice Notice being favored
+ *
+ * @return hook return value
+ */
+ function onEndDisfavorNotice(Profile $profile, Notice $notice)
+ {
+ $client = new Facebookclient($notice);
+ $client->unLike();
+
+ return true;
+ }
+
+ /*
+ * Add version info for this plugin
+ *
+ * @param array &$versions plugin version descriptions
+ */
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array(
+ 'name' => 'Facebook Bridge',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews, Zach Copley',
+ 'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge',
+ 'rawdescription' =>
+ _m('A plugin for integrating StatusNet with Facebook.')
+ );
+
+ return true;
+ }
+}
diff --git a/plugins/FacebookBridge/actions/facebookadminpanel.php b/plugins/FacebookBridge/actions/facebookadminpanel.php
new file mode 100644
index 000000000..61b544184
--- /dev/null
+++ b/plugins/FacebookBridge/actions/facebookadminpanel.php
@@ -0,0 +1,212 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Facebook integration administration panel
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Settings
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Administer global Facebook integration settings
+ *
+ * @category Admin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class FacebookadminpanelAction extends AdminPanelAction
+{
+ /**
+ * Returns the page title
+ *
+ * @return string page title
+ */
+ function title()
+ {
+ return _m('Facebook');
+ }
+
+ /**
+ * Instructions for using this form.
+ *
+ * @return string instructions
+ */
+ function getInstructions()
+ {
+ return _m('Facebook integration settings');
+ }
+
+ /**
+ * Show the Facebook admin panel form
+ *
+ * @return void
+ */
+ function showForm()
+ {
+ $form = new FacebookAdminPanelForm($this);
+ $form->show();
+ return;
+ }
+
+ /**
+ * Save settings from the form
+ *
+ * @return void
+ */
+ function saveSettings()
+ {
+ static $settings = array(
+ 'facebook' => array('appid', 'secret'),
+ );
+
+ $values = array();
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ $values[$section][$setting]
+ = $this->trimmed($setting);
+ }
+ }
+
+ // This throws an exception on validation errors
+ $this->validate($values);
+
+ // assert(all values are valid);
+
+ $config = new Config();
+
+ $config->query('BEGIN');
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ Config::save($section, $setting, $values[$section][$setting]);
+ }
+ }
+
+ $config->query('COMMIT');
+
+ return;
+ }
+
+ function validate(&$values)
+ {
+ // appId, key and secret (can't be too long)
+
+ if (mb_strlen($values['facebook']['appid']) > 255) {
+ $this->clientError(
+ _m("Invalid Facebook ID. Max length is 255 characters.")
+ );
+ }
+
+ if (mb_strlen($values['facebook']['secret']) > 255) {
+ $this->clientError(
+ _m("Invalid Facebook secret. Max length is 255 characters.")
+ );
+ }
+ }
+}
+
+class FacebookAdminPanelForm extends AdminForm
+{
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+ function id()
+ {
+ return 'facebookadminpanel';
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+ function formClass()
+ {
+ return 'form_settings';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+ function action()
+ {
+ return common_local_url('facebookadminpanel');
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+ function formData()
+ {
+ $this->out->elementStart(
+ 'fieldset',
+ array('id' => 'settings_facebook-application')
+ );
+ $this->out->element('legend', null, _m('Facebook application settings'));
+ $this->out->elementStart('ul', 'form_data');
+
+ $this->li();
+ $this->input(
+ 'appid',
+ _m('Application ID'),
+ _m('ID of your Facebook application'),
+ 'facebook'
+ );
+ $this->unli();
+
+ $this->li();
+ $this->input(
+ 'secret',
+ _m('Secret'),
+ _m('Application secret'),
+ 'facebook'
+ );
+ $this->unli();
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('fieldset');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+ function formActions()
+ {
+ $this->out->submit('submit', _m('Save'), 'submit', null, _m('Save Facebook settings'));
+ }
+}
diff --git a/plugins/FacebookBridge/actions/facebookdeauthorize.php b/plugins/FacebookBridge/actions/facebookdeauthorize.php
new file mode 100644
index 000000000..6813ccf1d
--- /dev/null
+++ b/plugins/FacebookBridge/actions/facebookdeauthorize.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * An action that handles deauthorize callbacks from Facebook
+ *
+ * PHP version 5
+ *
+ * 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>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/*
+ * Action class for handling deauthorize callbacks from Facebook. If the user
+ * doesn't have a password let her know she'll need to contact the site
+ * admin to get back into her account (if possible).
+ */
+class FacebookdeauthorizeAction extends Action
+{
+ private $facebook;
+
+ /**
+ * For initializing members of the class.
+ *
+ * @param array $args misc. arguments
+ *
+ * @return boolean true
+ */
+ function prepare($args)
+ {
+ $this->facebook = Facebookclient::getFacebook();
+
+ return true;
+ }
+
+ /**
+ * Handler method
+ *
+ * @param array $args is ignored since it's now passed in in prepare()
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+
+ $data = $this->facebook->getSignedRequest();
+
+ if (isset($data['user_id'])) {
+
+ $fbuid = $data['user_id'];
+
+ $flink = Foreign_link::getByForeignID($fbuid, FACEBOOK_SERVICE);
+ $user = $flink->getUser();
+
+ // Remove the link to Facebook
+ $result = $flink->delete();
+
+ if (!$result) {
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Unable to delete Facebook foreign link '
+ . 'for %s (%d), fbuid %d',
+ $user->nickname,
+ $user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ return;
+ }
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Facebook callback: %s (%d), fbuid %d has deauthorized '
+ . 'the Facebook application.',
+ $user->nickname,
+ $user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ // Warn the user about being locked out of their account
+ // if we can.
+ if (empty($user->password) && !empty($user->email)) {
+ Facebookclient::emailWarn($user);
+ } else {
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ '%s (%d), fbuid %d has deauthorized his/her Facebook '
+ . 'connection but hasn\'t set a password so s/he '
+ . 'is locked out.',
+ $user->nickname,
+ $user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ }
+
+ } else {
+ if (!empty($data)) {
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Facebook called the deauthorize callback '
+ . ' but didn\'t provide a user ID.'
+ ),
+ __FILE__
+ );
+ } else {
+ // It probably wasn't Facebook that hit this action,
+ // so redirect to the public timeline
+ common_redirect(common_local_url('public'), 303);
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/FacebookBridge/actions/facebookfinishlogin.php b/plugins/FacebookBridge/actions/facebookfinishlogin.php
new file mode 100644
index 000000000..2174c5ad4
--- /dev/null
+++ b/plugins/FacebookBridge/actions/facebookfinishlogin.php
@@ -0,0 +1,688 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Login or register a local user based on a Facebook user
+ *
+ * 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>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class FacebookfinishloginAction extends Action
+{
+ private $facebook = null; // Facebook client
+ private $fbuid = null; // Facebook user ID
+ private $fbuser = null; // Facebook user object (JSON)
+
+ function prepare($args) {
+
+ parent::prepare($args);
+
+ $this->facebook = new Facebook(
+ array(
+ 'appId' => common_config('facebook', 'appid'),
+ 'secret' => common_config('facebook', 'secret'),
+ 'cookie' => true,
+ )
+ );
+
+ // Check for a Facebook user session
+
+ $session = $this->facebook->getSession();
+ $me = null;
+
+ if ($session) {
+ try {
+ $this->fbuid = $this->facebook->getUser();
+ $this->fbuser = $this->facebook->api('/me');
+ } catch (FacebookApiException $e) {
+ common_log(LOG_ERROR, $e, __FILE__);
+ }
+ }
+
+ if (!empty($this->fbuser)) {
+
+ // OKAY, all is well... proceed to register
+
+ common_debug("Found a valid Facebook user.", __FILE__);
+ } else {
+
+ // This shouldn't happen in the regular course of things
+
+ list($proxy, $ip) = common_client_ip();
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Failed Facebook authentication attempt, proxy = %s, ip = %s.',
+ $proxy,
+ $ip
+ ),
+ __FILE__
+ );
+
+ $this->clientError(
+ _m('You must be logged into Facebook to register a local account using Facebook.')
+ );
+ }
+
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (common_is_real_login()) {
+
+ // User is already logged in, are her accounts already linked?
+
+ $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
+
+ if (!empty($flink)) {
+
+ // User already has a linked Facebook account and shouldn't be here!
+
+ common_debug(
+ sprintf(
+ 'There\'s already a local user %d linked with Facebook user %s.',
+ $flink->user_id,
+ $this->fbuid
+ )
+ );
+
+ $this->clientError(
+ _m('There is already a local account linked with that Facebook account.')
+ );
+
+ } else {
+
+ // Possibly reconnect an existing account
+
+ $this->connectUser();
+ }
+
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->handlePost();
+ } else {
+ $this->tryLogin();
+ }
+ }
+
+ function handlePost()
+ {
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(
+ _m('There was a problem with your session token. Try again, please.')
+ );
+ return;
+ }
+
+ if ($this->arg('create')) {
+
+ if (!$this->boolean('license')) {
+ $this->showForm(
+ _m('You can\'t register if you don\'t agree to the license.'),
+ $this->trimmed('newname')
+ );
+ return;
+ }
+
+ // We has a valid Facebook session and the Facebook user has
+ // agreed to the SN license, so create a new user
+ $this->createNewUser();
+
+ } else if ($this->arg('connect')) {
+
+ $this->connectNewUser();
+
+ } else {
+
+ $this->showForm(
+ _m('An unknown error has occured.'),
+ $this->trimmed('newname')
+ );
+ }
+ }
+
+ function showPageNotice()
+ {
+ if ($this->error) {
+
+ $this->element('div', array('class' => 'error'), $this->error);
+
+ } else {
+
+ $this->element(
+ 'div', 'instructions',
+ // TRANS: %s is the site name.
+ sprintf(
+ _m('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new local account, or connect with an existing local account.'),
+ common_config('site', 'name')
+ )
+ );
+ }
+ }
+
+ function title()
+ {
+ // TRANS: Page title.
+ return _m('Facebook Setup');
+ }
+
+ function showForm($error=null, $username=null)
+ {
+ $this->error = $error;
+ $this->username = $username;
+
+ $this->showPage();
+ }
+
+ function showPage()
+ {
+ parent::showPage();
+ }
+
+ /**
+ * @fixme much of this duplicates core code, which is very fragile.
+ * Should probably be replaced with an extensible mini version of
+ * the core registration form.
+ */
+ function showContent()
+ {
+ if (!empty($this->message_text)) {
+ $this->element('p', null, $this->message);
+ return;
+ }
+
+ $this->elementStart('form', array('method' => 'post',
+ 'id' => 'form_settings_facebook_connect',
+ 'class' => 'form_settings',
+ 'action' => common_local_url('facebookfinishlogin')));
+ $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
+ // TRANS: Legend.
+ $this->element('legend', null, _m('Connection options'));
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ $this->element('input', array('type' => 'checkbox',
+ 'id' => 'license',
+ 'class' => 'checkbox',
+ 'name' => 'license',
+ 'value' => 'true'));
+ $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
+ // TRANS: %s is the name of the license used by the user for their status updates.
+ $message = _m('My text and files are available under %s ' .
+ 'except this private data: password, ' .
+ 'email address, IM address, and phone number.');
+ $link = '<a href="' .
+ htmlspecialchars(common_config('license', 'url')) .
+ '">' .
+ htmlspecialchars(common_config('license', 'title')) .
+ '</a>';
+ $this->raw(sprintf(htmlspecialchars($message), $link));
+ $this->elementEnd('label');
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+
+ $this->elementStart('fieldset');
+ $this->hidden('token', common_session_token());
+ $this->element('legend', null,
+ // TRANS: Legend.
+ _m('Create new account'));
+ $this->element('p', null,
+ _m('Create a new user with this nickname.'));
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ // TRANS: Field label.
+ $this->input('newname', _m('New nickname'),
+ ($this->username) ? $this->username : '',
+ _m('1-64 lowercase letters or numbers, no punctuation or spaces'));
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+ // TRANS: Submit button.
+ $this->submit('create', _m('BUTTON','Create'));
+ $this->elementEnd('fieldset');
+
+ $this->elementStart('fieldset');
+ // TRANS: Legend.
+ $this->element('legend', null,
+ _m('Connect existing account'));
+ $this->element('p', null,
+ _m('If you already have an account, login with your username and password to connect it to your Facebook.'));
+ $this->elementStart('ul', 'form_data');
+ $this->elementStart('li');
+ // TRANS: Field label.
+ $this->input('nickname', _m('Existing nickname'));
+ $this->elementEnd('li');
+ $this->elementStart('li');
+ $this->password('password', _m('Password'));
+ $this->elementEnd('li');
+ $this->elementEnd('ul');
+ // TRANS: Submit button.
+ $this->submit('connect', _m('BUTTON','Connect'));
+ $this->elementEnd('fieldset');
+
+ $this->elementEnd('fieldset');
+ $this->elementEnd('form');
+ }
+
+ function message($msg)
+ {
+ $this->message_text = $msg;
+ $this->showPage();
+ }
+
+ function createNewUser()
+ {
+ if (common_config('site', 'closed')) {
+ // TRANS: Client error trying to register with registrations not allowed.
+ $this->clientError(_m('Registration not allowed.'));
+ return;
+ }
+
+ $invite = null;
+
+ if (common_config('site', 'inviteonly')) {
+ $code = $_SESSION['invitecode'];
+ if (empty($code)) {
+ // TRANS: Client error trying to register with registrations 'invite only'.
+ $this->clientError(_m('Registration not allowed.'));
+ return;
+ }
+
+ $invite = Invitation::staticGet($code);
+
+ if (empty($invite)) {
+ // TRANS: Client error trying to register with an invalid invitation code.
+ $this->clientError(_m('Not a valid invitation code.'));
+ return;
+ }
+ }
+
+ $nickname = $this->trimmed('newname');
+
+ if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => NICKNAME_FMT))) {
+ $this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
+ return;
+ }
+
+ if (!User::allowed_nickname($nickname)) {
+ $this->showForm(_m('Nickname not allowed.'));
+ return;
+ }
+
+ if (User::staticGet('nickname', $nickname)) {
+ $this->showForm(_m('Nickname already in use. Try another one.'));
+ return;
+ }
+
+ $args = array(
+ 'nickname' => $nickname,
+ 'fullname' => $this->fbuser['first_name']
+ . ' ' . $this->fbuser['last_name'],
+ 'homepage' => $this->fbuser['website'],
+ 'bio' => $this->fbuser['about'],
+ 'location' => $this->fbuser['location']['name']
+ );
+
+ // It's possible that the email address is already in our
+ // DB. It's a unique key, so we need to check
+ if ($this->isNewEmail($this->fbuser['email'])) {
+ $args['email'] = $this->fbuser['email'];
+ $args['email_confirmed'] = true;
+ }
+
+ if (!empty($invite)) {
+ $args['code'] = $invite->code;
+ }
+
+ $user = User::register($args);
+ $result = $this->flinkUser($user->id, $this->fbuid);
+
+ if (!$result) {
+ $this->serverError(_m('Error connecting user to Facebook.'));
+ return;
+ }
+
+ // Add a Foreign_user record
+ Facebookclient::addFacebookUser($this->fbuser);
+
+ $this->setAvatar($user);
+
+ common_set_user($user);
+ common_real_login(true);
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Registered new user %s (%d) from Facebook user %s, (fbuid %d)',
+ $user->nickname,
+ $user->id,
+ $this->fbuser['name'],
+ $this->fbuid
+ ),
+ __FILE__
+ );
+
+ $this->goHome($user->nickname);
+ }
+
+ /*
+ * Attempt to download the user's Facebook picture and create a
+ * StatusNet avatar for the new user.
+ */
+ function setAvatar($user)
+ {
+ $picUrl = sprintf(
+ 'http://graph.facebook.com/%s/picture?type=large',
+ $this->fbuid
+ );
+
+ // fetch the picture from Facebook
+ $client = new HTTPClient();
+
+ // fetch the actual picture
+ $response = $client->get($picUrl);
+
+ if ($response->isOk()) {
+
+ $finalUrl = $client->getUrl();
+
+ // Make sure the filename is unique becuase it's possible for a user
+ // to deauthorize our app, and then come back in as a new user but
+ // have the same Facebook picture (avatar URLs have a unique index
+ // and their URLs are based on the filenames).
+ $filename = 'facebook-' . common_good_rand(4) . '-'
+ . substr(strrchr($finalUrl, '/'), 1);
+
+ $ok = file_put_contents(
+ Avatar::path($filename),
+ $response->getBody()
+ );
+
+ if (!$ok) {
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Couldn\'t save Facebook avatar %s',
+ $tmp
+ ),
+ __FILE__
+ );
+
+ } else {
+
+ // save it as an avatar
+ $profile = $user->getProfile();
+
+ if ($profile->setOriginal($filename)) {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Saved avatar for %s (%d) from Facebook picture for '
+ . '%s (fbuid %d), filename = %s',
+ $user->nickname,
+ $user->id,
+ $this->fbuser['name'],
+ $this->fbuid,
+ $filename
+ ),
+ __FILE__
+ );
+ }
+ }
+ }
+ }
+
+ function connectNewUser()
+ {
+ $nickname = $this->trimmed('nickname');
+ $password = $this->trimmed('password');
+
+ if (!common_check_user($nickname, $password)) {
+ $this->showForm(_m('Invalid username or password.'));
+ return;
+ }
+
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!empty($user)) {
+ common_debug(
+ sprintf(
+ 'Found a legit user to connect to Facebook: %s (%d)',
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
+ }
+
+ $this->tryLinkUser($user);
+
+ common_set_user($user);
+ common_real_login(true);
+
+ $this->goHome($user->nickname);
+ }
+
+ function connectUser()
+ {
+ $user = common_current_user();
+ $this->tryLinkUser($user);
+ common_redirect(common_local_url('facebookfinishlogin'), 303);
+ }
+
+ function tryLinkUser($user)
+ {
+ $result = $this->flinkUser($user->id, $this->fbuid);
+
+ if (empty($result)) {
+ $this->serverError(_m('Error connecting user to Facebook.'));
+ return;
+ }
+
+ common_debug(
+ sprintf(
+ 'Connected Facebook user %s (fbuid %d) to local user %s (%d)',
+ $this->fbuser['name'],
+ $this->fbuid,
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
+ }
+
+ function tryLogin()
+ {
+ common_debug(
+ sprintf(
+ 'Trying login for Facebook user %s',
+ $this->fbuid
+ ),
+ __FILE__
+ );
+
+ $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
+
+ if (!empty($flink)) {
+ $user = $flink->getUser();
+
+ if (!empty($user)) {
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Logged in Facebook user %s as user %d (%s)',
+ $this->fbuid,
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
+
+ common_set_user($user);
+ common_real_login(true);
+ $this->goHome($user->nickname);
+ }
+
+ } else {
+
+ common_debug(
+ sprintf(
+ 'No flink found for fbuid: %s - new user',
+ $this->fbuid
+ ),
+ __FILE__
+ );
+
+ $this->showForm(null, $this->bestNewNickname());
+ }
+ }
+
+ function goHome($nickname)
+ {
+ $url = common_get_returnto();
+ if ($url) {
+ // We don't have to return to it again
+ common_set_returnto(null);
+ } else {
+ $url = common_local_url('all',
+ array('nickname' =>
+ $nickname));
+ }
+
+ common_redirect($url, 303);
+ }
+
+ function flinkUser($user_id, $fbuid)
+ {
+ $flink = new Foreign_link();
+ $flink->user_id = $user_id;
+ $flink->foreign_id = $fbuid;
+ $flink->service = FACEBOOK_SERVICE;
+
+ // Pull the access token from the Facebook cookies
+ $flink->credentials = $this->facebook->getAccessToken();
+
+ $flink->created = common_sql_now();
+
+ $flink_id = $flink->insert();
+
+ return $flink_id;
+ }
+
+ function bestNewNickname()
+ {
+ if (!empty($this->fbuser['name'])) {
+ $nickname = $this->nicknamize($this->fbuser['name']);
+ if ($this->isNewNickname($nickname)) {
+ return $nickname;
+ }
+ }
+
+ // Try the full name
+
+ $fullname = trim($this->fbuser['first_name'] .
+ ' ' . $this->fbuser['last_name']);
+
+ if (!empty($fullname)) {
+ $fullname = $this->nicknamize($fullname);
+ if ($this->isNewNickname($fullname)) {
+ return $fullname;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a string, try to make it work as a nickname
+ */
+ function nicknamize($str)
+ {
+ $str = preg_replace('/\W/', '', $str);
+ return strtolower($str);
+ }
+
+ /*
+ * Is the desired nickname already taken?
+ *
+ * @return boolean result
+ */
+ function isNewNickname($str)
+ {
+ if (
+ !Validate::string(
+ $str,
+ array(
+ 'min_length' => 1,
+ 'max_length' => 64,
+ 'format' => NICKNAME_FMT
+ )
+ )
+ ) {
+ return false;
+ }
+
+ if (!User::allowed_nickname($str)) {
+ return false;
+ }
+
+ if (User::staticGet('nickname', $str)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * Do we already have a user record with this email?
+ * (emails have to be unique but they can change)
+ *
+ * @param string $email the email address to check
+ *
+ * @return boolean result
+ */
+ function isNewEmail($email)
+ {
+ // we shouldn't have to validate the format
+ $result = User::staticGet('email', $email);
+
+ if (empty($result)) {
+ common_debug("XXXXXXXXXXXXXXXXXX We've never seen this email before!!!");
+ return true;
+ }
+ common_debug("XXXXXXXXXXXXXXXXXX dupe email address!!!!");
+
+ return false;
+ }
+
+}
diff --git a/plugins/FacebookBridge/actions/facebooklogin.php b/plugins/FacebookBridge/actions/facebooklogin.php
new file mode 100644
index 000000000..f8a45c41b
--- /dev/null
+++ b/plugins/FacebookBridge/actions/facebooklogin.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * An action for logging in with Facebook
+ *
+ * PHP version 5
+ *
+ * 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>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class FacebookloginAction extends Action
+{
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (common_is_real_login()) {
+ $this->clientError(_m('Already logged in.'));
+ } else {
+ $this->showPage();
+ }
+ }
+
+ function getInstructions()
+ {
+ // TRANS: Instructions.
+ return _m('Login with your Facebook Account');
+ }
+
+ function showPageNotice()
+ {
+ $instr = $this->getInstructions();
+ $output = common_markup_to_html($instr);
+ $this->elementStart('div', 'instructions');
+ $this->raw($output);
+ $this->elementEnd('div');
+ }
+
+ function title()
+ {
+ // TRANS: Page title.
+ return _m('Login with Facebook');
+ }
+
+ function showContent() {
+
+ $this->elementStart('fieldset');
+
+ $facebook = Facebookclient::getFacebook();
+
+ // Degrade to plain link if JavaScript is not available
+ $this->elementStart(
+ 'a',
+ array(
+ 'href' => $facebook->getLoginUrl(
+ array(
+ 'next' => common_local_url('facebookfinishlogin'),
+ 'cancel' => common_local_url('facebooklogin'),
+ 'req_perms' => 'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'
+ )
+ ),
+ 'id' => 'facebook_button'
+ )
+ );
+
+ $attrs = array(
+ 'src' => common_path(
+ 'plugins/FacebookBridge/images/login-button.png',
+ true
+ ),
+ 'alt' => 'Login with Facebook',
+ 'title' => 'Login with Facebook'
+ );
+
+ $this->element('img', $attrs);
+
+ $this->elementEnd('a');
+
+ /*
+ $this->element('div', array('id' => 'fb-root'));
+ $this->script(
+ sprintf(
+ 'http://connect.facebook.net/en_US/all.js#appId=%s&xfbml=1',
+ common_config('facebook', 'appid')
+ )
+ );
+ $this->element('fb:facepile', array('max-rows' => '2', 'width' =>'300'));
+ */
+ $this->elementEnd('fieldset');
+ }
+
+ function showLocalNav()
+ {
+ $nav = new LoginGroupNav($this);
+ $nav->show();
+ }
+}
+
diff --git a/plugins/FacebookBridge/actions/facebooksettings.php b/plugins/FacebookBridge/actions/facebooksettings.php
new file mode 100644
index 000000000..b9fa7ba2a
--- /dev/null
+++ b/plugins/FacebookBridge/actions/facebooksettings.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Edit user settings for Facebook
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Settings
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Edit user settings for Facebook
+ *
+ * @category Settings
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ *
+ * @see SettingsAction
+ */
+class FacebooksettingsAction extends ConnectSettingsAction {
+
+ private $facebook; // Facebook PHP-SDK client obj
+ private $flink;
+ private $user;
+
+ /**
+ * For initializing members of the class.
+ *
+ * @param array $argarray misc. arguments
+ *
+ * @return boolean true
+ */
+ function prepare($args) {
+ parent::prepare($args);
+
+ $this->facebook = new Facebook(
+ array(
+ 'appId' => common_config('facebook', 'appid'),
+ 'secret' => common_config('facebook', 'secret'),
+ 'cookie' => true,
+ )
+ );
+
+ $this->user = common_current_user();
+
+ $this->flink = Foreign_link::getByUserID(
+ $this->user->id,
+ FACEBOOK_SERVICE
+ );
+
+ return true;
+ }
+
+ /*
+ * Check the sessions token and dispatch
+ */
+ function handlePost($args) {
+ // CSRF protection
+
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->showForm(
+ _m('There was a problem with your session token. Try again, please.')
+ );
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->saveSettings();
+ } else if ($this->arg('disconnect')) {
+ $this->disconnect();
+ }
+ }
+
+ /**
+ * Returns the page title
+ *
+ * @return string page title
+ */
+ function title() {
+ // TRANS: Page title for Facebook settings.
+ return _m('Facebook settings');
+ }
+
+ /**
+ * Instructions for use
+ *
+ * @return instructions for use
+ */
+ function getInstructions() {
+ return _('Facebook settings');
+ }
+
+ /*
+ * Show the settings form if he/she has a link to Facebook
+ *
+ * @return void
+ */
+ function showContent() {
+
+ if (!empty($this->flink)) {
+
+ $this->elementStart(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'id' => 'form_settings_facebook',
+ 'class' => 'form_settings',
+ 'action' => common_local_url('facebooksettings')
+ )
+ );
+
+ $this->hidden('token', common_session_token());
+
+ $this->element('p', 'form_note', _m('Connected Facebook user'));
+
+ $this->elementStart('p', array('class' => 'facebook-user-display'));
+
+ $this->element(
+ 'fb:profile-pic',
+ array(
+ 'uid' => $this->flink->foreign_id,
+ 'size' => 'small',
+ 'linked' => 'true',
+ 'facebook-logo' => 'true'
+ )
+ );
+
+ $this->element(
+ 'fb:name',
+ array('uid' => $this->flink->foreign_id, 'useyou' => 'false')
+ );
+
+ $this->elementEnd('p');
+
+ $this->elementStart('ul', 'form_data');
+
+ $this->elementStart('li');
+
+ $this->checkbox(
+ 'noticesync',
+ _m('Publish my notices to Facebook.'),
+ ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND) : true
+ );
+
+ $this->elementEnd('li');
+
+ $this->elementStart('li');
+
+ $this->checkbox(
+ 'replysync',
+ _m('Send "@" replies to Facebook.'),
+ ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true
+ );
+
+ $this->elementEnd('li');
+
+ $this->elementStart('li');
+
+ // TRANS: Submit button to save synchronisation settings.
+ $this->submit('save', _m('BUTTON', 'Save'));
+
+ $this->elementEnd('li');
+
+ $this->elementEnd('ul');
+
+ $this->elementStart('fieldset');
+
+ // TRANS: Legend.
+ $this->element('legend', null, _m('Disconnect my account from Facebook'));
+
+ if (empty($this->user->password)) {
+
+ $this->elementStart('p', array('class' => 'form_guide'));
+
+ $msg = sprintf(
+ _m(
+ 'Disconnecting your Faceboook would make it impossible to '
+ . 'log in! Please [set a password](%s) first.'
+ ),
+ common_local_url('passwordsettings')
+ );
+
+ $this->raw(common_markup_to_html($msg));
+ $this->elementEnd('p');
+
+ } else {
+
+ $msg = sprintf(
+ _m(
+ 'Keep your %1$s account but disconnect from Facebook. ' .
+ 'You\'ll use your 1%$s password to log in.'
+ ),
+ common_config('site', 'name')
+ );
+
+ // TRANS: Submit button.
+ $this->submit('disconnect', _m('BUTTON', 'Disconnect'));
+ }
+
+ $this->elementEnd('fieldset');
+
+ $this->elementEnd('form');
+ }
+ }
+
+ /*
+ * Save the user's Facebook settings
+ *
+ * @return void
+ */
+ function saveSettings() {
+
+ $noticesync = $this->boolean('noticesync');
+ $replysync = $this->boolean('replysync');
+
+ $original = clone($this->flink);
+ $this->flink->set_flags($noticesync, false, $replysync, false);
+ $result = $this->flink->update($original);
+
+ if ($result === false) {
+ $this->showForm(_m('There was a problem saving your sync preferences.'));
+ } else {
+ // TRANS: Confirmation that synchronisation settings have been saved into the system.
+ $this->showForm(_m('Sync preferences saved.'), true);
+ }
+ }
+
+ /*
+ * Disconnect the user's Facebook account - deletes the Foreign_link
+ * and shows the user a success message if all goes well.
+ */
+ function disconnect() {
+
+ $result = $this->flink->delete();
+ $this->flink = null;
+
+ if ($result === false) {
+ common_log_db_error($user, 'DELETE', __FILE__);
+ $this->serverError(_m('Couldn\'t delete link to Facebook.'));
+ return;
+ }
+
+ $this->showForm(_m('You have disconnected from Facebook.'), true);
+ }
+
+}
diff --git a/plugins/FacebookBridge/classes/Notice_to_item.php b/plugins/FacebookBridge/classes/Notice_to_item.php
new file mode 100644
index 000000000..a6a803034
--- /dev/null
+++ b/plugins/FacebookBridge/classes/Notice_to_item.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Data class for storing notice-to-Facebook-item mappings
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * 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')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for mapping notices to Facebook stream items
+ *
+ * Note that notice_id is unique only within a single database; if you
+ * want to share this data for some reason, get the notice's URI and use
+ * that instead, since it's universally unique.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * @see DB_DataObject
+ */
+
+class Notice_to_item extends Memcached_DataObject
+{
+ public $__table = 'notice_to_item'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $item_id; // varchar(255) not null
+ public $created; // datetime
+
+ /**
+ * Get an instance by key
+ *
+ * This is a utility method to get a single instance with a given key value.
+ *
+ * @param string $k Key to use to lookup
+ * @param mixed $v Value to lookup
+ *
+ * @return Notice_to_item object found, or null for no hits
+ *
+ */
+
+ function staticGet($k, $v=null)
+ {
+ return Memcached_DataObject::staticGet('Notice_to_item', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array(
+ 'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'item_id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL
+ );
+ }
+
+ static function schemaDef()
+ {
+ return array(
+ new ColumnDef('notice_id', 'integer', null, false, 'PRI'),
+ new ColumnDef('item_id', 'varchar', 255, false, 'UNI'),
+ new ColumnDef('created', 'datetime', null, false)
+ );
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has, since it
+ * won't appear in StatusNet's own keys list. In most cases, this will
+ * simply reference your keyTypes() function.
+ *
+ * @return array list of key field names
+ */
+
+ 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. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+
+ function keyTypes()
+ {
+ return array('notice_id' => 'K', 'item_id' => 'U');
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Save a mapping between a notice and a Facebook item
+ *
+ * @param integer $notice_id ID of the notice in StatusNet
+ * @param integer $item_id ID of the stream item on Facebook
+ *
+ * @return Notice_to_item new object for this value
+ */
+
+ static function saveNew($notice_id, $item_id)
+ {
+ $n2i = Notice_to_item::staticGet('notice_id', $notice_id);
+
+ if (!empty($n2i)) {
+ return $n2i;
+ }
+
+ $n2i = Notice_to_item::staticGet('item_id', $item_id);
+
+ if (!empty($n2i)) {
+ return $n2i;
+ }
+
+ common_debug(
+ "Mapping notice {$notice_id} to Facebook item {$item_id}",
+ __FILE__
+ );
+
+ $n2i = new Notice_to_item();
+
+ $n2i->notice_id = $notice_id;
+ $n2i->item_id = $item_id;
+ $n2i->created = common_sql_now();
+
+ $n2i->insert();
+
+ return $n2i;
+ }
+}
diff --git a/plugins/FacebookBridge/extlib/facebook.php b/plugins/FacebookBridge/extlib/facebook.php
new file mode 100644
index 000000000..d2d2e866b
--- /dev/null
+++ b/plugins/FacebookBridge/extlib/facebook.php
@@ -0,0 +1,963 @@
+<?php
+
+if (!function_exists('curl_init')) {
+ throw new Exception('Facebook needs the CURL PHP extension.');
+}
+if (!function_exists('json_decode')) {
+ throw new Exception('Facebook needs the JSON PHP extension.');
+}
+
+/**
+ * Thrown when an API call returns an exception.
+ *
+ * @author Naitik Shah <naitik@facebook.com>
+ */
+class FacebookApiException extends Exception
+{
+ /**
+ * The result from the API server that represents the exception information.
+ */
+ protected $result;
+
+ /**
+ * Make a new API Exception with the given result.
+ *
+ * @param Array $result the result from the API server
+ */
+ public function __construct($result) {
+ $this->result = $result;
+
+ $code = isset($result['error_code']) ? $result['error_code'] : 0;
+
+ if (isset($result['error_description'])) {
+ // OAuth 2.0 Draft 10 style
+ $msg = $result['error_description'];
+ } else if (isset($result['error']) && is_array($result['error'])) {
+ // OAuth 2.0 Draft 00 style
+ $msg = $result['error']['message'];
+ } else if (isset($result['error_msg'])) {
+ // Rest server style
+ $msg = $result['error_msg'];
+ } else {
+ $msg = 'Unknown Error. Check getResult()';
+ }
+
+ parent::__construct($msg, $code);
+ }
+
+ /**
+ * Return the associated result object returned by the API server.
+ *
+ * @returns Array the result from the API server
+ */
+ public function getResult() {
+ return $this->result;
+ }
+
+ /**
+ * Returns the associated type for the error. This will default to
+ * 'Exception' when a type is not available.
+ *
+ * @return String
+ */
+ public function getType() {
+ if (isset($this->result['error'])) {
+ $error = $this->result['error'];
+ if (is_string($error)) {
+ // OAuth 2.0 Draft 10 style
+ return $error;
+ } else if (is_array($error)) {
+ // OAuth 2.0 Draft 00 style
+ if (isset($error['type'])) {
+ return $error['type'];
+ }
+ }
+ }
+ return 'Exception';
+ }
+
+ /**
+ * To make debugging easier.
+ *
+ * @returns String the string representation of the error
+ */
+ public function __toString() {
+ $str = $this->getType() . ': ';
+ if ($this->code != 0) {
+ $str .= $this->code . ': ';
+ }
+ return $str . $this->message;
+ }
+}
+
+/**
+ * Provides access to the Facebook Platform.
+ *
+ * @author Naitik Shah <naitik@facebook.com>
+ */
+class Facebook
+{
+ /**
+ * Version.
+ */
+ const VERSION = '2.1.2';
+
+ /**
+ * Default options for curl.
+ */
+ public static $CURL_OPTS = array(
+ CURLOPT_CONNECTTIMEOUT => 10,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_TIMEOUT => 60,
+ CURLOPT_USERAGENT => 'facebook-php-2.0',
+ );
+
+ /**
+ * List of query parameters that get automatically dropped when rebuilding
+ * the current URL.
+ */
+ protected static $DROP_QUERY_PARAMS = array(
+ 'session',
+ 'signed_request',
+ );
+
+ /**
+ * Maps aliases to Facebook domains.
+ */
+ public static $DOMAIN_MAP = array(
+ 'api' => 'https://api.facebook.com/',
+ 'api_read' => 'https://api-read.facebook.com/',
+ 'graph' => 'https://graph.facebook.com/',
+ 'www' => 'https://www.facebook.com/',
+ );
+
+ /**
+ * The Application ID.
+ */
+ protected $appId;
+
+ /**
+ * The Application API Secret.
+ */
+ protected $apiSecret;
+
+ /**
+ * The active user session, if one is available.
+ */
+ protected $session;
+
+ /**
+ * The data from the signed_request token.
+ */
+ protected $signedRequest;
+
+ /**
+ * Indicates that we already loaded the session as best as we could.
+ */
+ protected $sessionLoaded = false;
+
+ /**
+ * Indicates if Cookie support should be enabled.
+ */
+ protected $cookieSupport = false;
+
+ /**
+ * Base domain for the Cookie.
+ */
+ protected $baseDomain = '';
+
+ /**
+ * Indicates if the CURL based @ syntax for file uploads is enabled.
+ */
+ protected $fileUploadSupport = false;
+
+ /**
+ * Initialize a Facebook Application.
+ *
+ * The configuration:
+ * - appId: the application ID
+ * - secret: the application secret
+ * - cookie: (optional) boolean true to enable cookie support
+ * - domain: (optional) domain for the cookie
+ * - fileUpload: (optional) boolean indicating if file uploads are enabled
+ *
+ * @param Array $config the application configuration
+ */
+ public function __construct($config) {
+ $this->setAppId($config['appId']);
+ $this->setApiSecret($config['secret']);
+ if (isset($config['cookie'])) {
+ $this->setCookieSupport($config['cookie']);
+ }
+ if (isset($config['domain'])) {
+ $this->setBaseDomain($config['domain']);
+ }
+ if (isset($config['fileUpload'])) {
+ $this->setFileUploadSupport($config['fileUpload']);
+ }
+ }
+
+ /**
+ * Set the Application ID.
+ *
+ * @param String $appId the Application ID
+ */
+ public function setAppId($appId) {
+ $this->appId = $appId;
+ return $this;
+ }
+
+ /**
+ * Get the Application ID.
+ *
+ * @return String the Application ID
+ */
+ public function getAppId() {
+ return $this->appId;
+ }
+
+ /**
+ * Set the API Secret.
+ *
+ * @param String $appId the API Secret
+ */
+ public function setApiSecret($apiSecret) {
+ $this->apiSecret = $apiSecret;
+ return $this;
+ }
+
+ /**
+ * Get the API Secret.
+ *
+ * @return String the API Secret
+ */
+ public function getApiSecret() {
+ return $this->apiSecret;
+ }
+
+ /**
+ * Set the Cookie Support status.
+ *
+ * @param Boolean $cookieSupport the Cookie Support status
+ */
+ public function setCookieSupport($cookieSupport) {
+ $this->cookieSupport = $cookieSupport;
+ return $this;
+ }
+
+ /**
+ * Get the Cookie Support status.
+ *
+ * @return Boolean the Cookie Support status
+ */
+ public function useCookieSupport() {
+ return $this->cookieSupport;
+ }
+
+ /**
+ * Set the base domain for the Cookie.
+ *
+ * @param String $domain the base domain
+ */
+ public function setBaseDomain($domain) {
+ $this->baseDomain = $domain;
+ return $this;
+ }
+
+ /**
+ * Get the base domain for the Cookie.
+ *
+ * @return String the base domain
+ */
+ public function getBaseDomain() {
+ return $this->baseDomain;
+ }
+
+ /**
+ * Set the file upload support status.
+ *
+ * @param String $domain the base domain
+ */
+ public function setFileUploadSupport($fileUploadSupport) {
+ $this->fileUploadSupport = $fileUploadSupport;
+ return $this;
+ }
+
+ /**
+ * Get the file upload support status.
+ *
+ * @return String the base domain
+ */
+ public function useFileUploadSupport() {
+ return $this->fileUploadSupport;
+ }
+
+ /**
+ * Get the data from a signed_request token
+ *
+ * @return String the base domain
+ */
+ public function getSignedRequest() {
+ if (!$this->signedRequest) {
+ if (isset($_REQUEST['signed_request'])) {
+ $this->signedRequest = $this->parseSignedRequest(
+ $_REQUEST['signed_request']);
+ }
+ }
+ return $this->signedRequest;
+ }
+
+ /**
+ * Set the Session.
+ *
+ * @param Array $session the session
+ * @param Boolean $write_cookie indicate if a cookie should be written. this
+ * value is ignored if cookie support has been disabled.
+ */
+ public function setSession($session=null, $write_cookie=true) {
+ $session = $this->validateSessionObject($session);
+ $this->sessionLoaded = true;
+ $this->session = $session;
+ if ($write_cookie) {
+ $this->setCookieFromSession($session);
+ }
+ return $this;
+ }
+
+ /**
+ * Get the session object. This will automatically look for a signed session
+ * sent via the signed_request, Cookie or Query Parameters if needed.
+ *
+ * @return Array the session
+ */
+ public function getSession() {
+ if (!$this->sessionLoaded) {
+ $session = null;
+ $write_cookie = true;
+
+ // try loading session from signed_request in $_REQUEST
+ $signedRequest = $this->getSignedRequest();
+ if ($signedRequest) {
+ // sig is good, use the signedRequest
+ $session = $this->createSessionFromSignedRequest($signedRequest);
+ }
+
+ // try loading session from $_REQUEST
+ if (!$session && isset($_REQUEST['session'])) {
+ $session = json_decode(
+ get_magic_quotes_gpc()
+ ? stripslashes($_REQUEST['session'])
+ : $_REQUEST['session'],
+ true
+ );
+ $session = $this->validateSessionObject($session);
+ }
+
+ // try loading session from cookie if necessary
+ if (!$session && $this->useCookieSupport()) {
+ $cookieName = $this->getSessionCookieName();
+ if (isset($_COOKIE[$cookieName])) {
+ $session = array();
+ parse_str(trim(
+ get_magic_quotes_gpc()
+ ? stripslashes($_COOKIE[$cookieName])
+ : $_COOKIE[$cookieName],
+ '"'
+ ), $session);
+ $session = $this->validateSessionObject($session);
+ // write only if we need to delete a invalid session cookie
+ $write_cookie = empty($session);
+ }
+ }
+
+ $this->setSession($session, $write_cookie);
+ }
+
+ return $this->session;
+ }
+
+ /**
+ * Get the UID from the session.
+ *
+ * @return String the UID if available
+ */
+ public function getUser() {
+ $session = $this->getSession();
+ return $session ? $session['uid'] : null;
+ }
+
+ /**
+ * Gets a OAuth access token.
+ *
+ * @return String the access token
+ */
+ public function getAccessToken() {
+ $session = $this->getSession();
+ // either user session signed, or app signed
+ if ($session) {
+ return $session['access_token'];
+ } else {
+ return $this->getAppId() .'|'. $this->getApiSecret();
+ }
+ }
+
+ /**
+ * Get a Login URL for use with redirects. By default, full page redirect is
+ * assumed. If you are using the generated URL with a window.open() call in
+ * JavaScript, you can pass in display=popup as part of the $params.
+ *
+ * The parameters:
+ * - next: the url to go to after a successful login
+ * - cancel_url: the url to go to after the user cancels
+ * - req_perms: comma separated list of requested extended perms
+ * - display: can be "page" (default, full page) or "popup"
+ *
+ * @param Array $params provide custom parameters
+ * @return String the URL for the login flow
+ */
+ public function getLoginUrl($params=array()) {
+ $currentUrl = $this->getCurrentUrl();
+ return $this->getUrl(
+ 'www',
+ 'login.php',
+ array_merge(array(
+ 'api_key' => $this->getAppId(),
+ 'cancel_url' => $currentUrl,
+ 'display' => 'page',
+ 'fbconnect' => 1,
+ 'next' => $currentUrl,
+ 'return_session' => 1,
+ 'session_version' => 3,
+ 'v' => '1.0',
+ ), $params)
+ );
+ }
+
+ /**
+ * Get a Logout URL suitable for use with redirects.
+ *
+ * The parameters:
+ * - next: the url to go to after a successful logout
+ *
+ * @param Array $params provide custom parameters
+ * @return String the URL for the logout flow
+ */
+ public function getLogoutUrl($params=array()) {
+ return $this->getUrl(
+ 'www',
+ 'logout.php',
+ array_merge(array(
+ 'next' => $this->getCurrentUrl(),
+ 'access_token' => $this->getAccessToken(),
+ ), $params)
+ );
+ }
+
+ /**
+ * Get a login status URL to fetch the status from facebook.
+ *
+ * The parameters:
+ * - ok_session: the URL to go to if a session is found
+ * - no_session: the URL to go to if the user is not connected
+ * - no_user: the URL to go to if the user is not signed into facebook
+ *
+ * @param Array $params provide custom parameters
+ * @return String the URL for the logout flow
+ */
+ public function getLoginStatusUrl($params=array()) {
+ return $this->getUrl(
+ 'www',
+ 'extern/login_status.php',
+ array_merge(array(
+ 'api_key' => $this->getAppId(),
+ 'no_session' => $this->getCurrentUrl(),
+ 'no_user' => $this->getCurrentUrl(),
+ 'ok_session' => $this->getCurrentUrl(),
+ 'session_version' => 3,
+ ), $params)
+ );
+ }
+
+ /**
+ * Make an API call.
+ *
+ * @param Array $params the API call parameters
+ * @return the decoded response
+ */
+ public function api(/* polymorphic */) {
+ $args = func_get_args();
+ if (is_array($args[0])) {
+ return $this->_restserver($args[0]);
+ } else {
+ return call_user_func_array(array($this, '_graph'), $args);
+ }
+ }
+
+ /**
+ * Invoke the old restserver.php endpoint.
+ *
+ * @param Array $params method call object
+ * @return the decoded response object
+ * @throws FacebookApiException
+ */
+ protected function _restserver($params) {
+ // generic application level parameters
+ $params['api_key'] = $this->getAppId();
+ $params['format'] = 'json-strings';
+
+ $result = json_decode($this->_oauthRequest(
+ $this->getApiUrl($params['method']),
+ $params
+ ), true);
+
+ // results are returned, errors are thrown
+ if (is_array($result) && isset($result['error_code'])) {
+ throw new FacebookApiException($result);
+ }
+ return $result;
+ }
+
+ /**
+ * Invoke the Graph API.
+ *
+ * @param String $path the path (required)
+ * @param String $method the http method (default 'GET')
+ * @param Array $params the query/post data
+ * @return the decoded response object
+ * @throws FacebookApiException
+ */
+ protected function _graph($path, $method='GET', $params=array()) {
+ if (is_array($method) && empty($params)) {
+ $params = $method;
+ $method = 'GET';
+ }
+ $params['method'] = $method; // method override as we always do a POST
+
+ $result = json_decode($this->_oauthRequest(
+ $this->getUrl('graph', $path),
+ $params
+ ), true);
+
+ // results are returned, errors are thrown
+ if (is_array($result) && isset($result['error'])) {
+ $e = new FacebookApiException($result);
+ switch ($e->getType()) {
+ // OAuth 2.0 Draft 00 style
+ case 'OAuthException':
+ // OAuth 2.0 Draft 10 style
+ case 'invalid_token':
+ $this->setSession(null);
+ }
+ throw $e;
+ }
+ return $result;
+ }
+
+ /**
+ * Make a OAuth Request
+ *
+ * @param String $path the path (required)
+ * @param Array $params the query/post data
+ * @return the decoded response object
+ * @throws FacebookApiException
+ */
+ protected function _oauthRequest($url, $params) {
+ if (!isset($params['access_token'])) {
+ $params['access_token'] = $this->getAccessToken();
+ }
+
+ // json_encode all params values that are not strings
+ foreach ($params as $key => $value) {
+ if (!is_string($value)) {
+ $params[$key] = json_encode($value);
+ }
+ }
+ return $this->makeRequest($url, $params);
+ }
+
+ /**
+ * Makes an HTTP request. This method can be overriden by subclasses if
+ * developers want to do fancier things or use something other than curl to
+ * make the request.
+ *
+ * @param String $url the URL to make the request to
+ * @param Array $params the parameters to use for the POST body
+ * @param CurlHandler $ch optional initialized curl handle
+ * @return String the response text
+ */
+ protected function makeRequest($url, $params, $ch=null) {
+ if (!$ch) {
+ $ch = curl_init();
+ }
+
+ $opts = self::$CURL_OPTS;
+ if ($this->useFileUploadSupport()) {
+ $opts[CURLOPT_POSTFIELDS] = $params;
+ } else {
+ $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
+ }
+ $opts[CURLOPT_URL] = $url;
+
+ // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
+ // for 2 seconds if the server does not support this header.
+ if (isset($opts[CURLOPT_HTTPHEADER])) {
+ $existing_headers = $opts[CURLOPT_HTTPHEADER];
+ $existing_headers[] = 'Expect:';
+ $opts[CURLOPT_HTTPHEADER] = $existing_headers;
+ } else {
+ $opts[CURLOPT_HTTPHEADER] = array('Expect:');
+ }
+
+ curl_setopt_array($ch, $opts);
+ $result = curl_exec($ch);
+
+ if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
+ self::errorLog('Invalid or no certificate authority found, using bundled information');
+ curl_setopt($ch, CURLOPT_CAINFO,
+ dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
+ $result = curl_exec($ch);
+ }
+
+ if ($result === false) {
+ $e = new FacebookApiException(array(
+ 'error_code' => curl_errno($ch),
+ 'error' => array(
+ 'message' => curl_error($ch),
+ 'type' => 'CurlException',
+ ),
+ ));
+ curl_close($ch);
+ throw $e;
+ }
+ curl_close($ch);
+ return $result;
+ }
+
+ /**
+ * The name of the Cookie that contains the session.
+ *
+ * @return String the cookie name
+ */
+ protected function getSessionCookieName() {
+ return 'fbs_' . $this->getAppId();
+ }
+
+ /**
+ * Set a JS Cookie based on the _passed in_ session. It does not use the
+ * currently stored session -- you need to explicitly pass it in.
+ *
+ * @param Array $session the session to use for setting the cookie
+ */
+ protected function setCookieFromSession($session=null) {
+ if (!$this->useCookieSupport()) {
+ return;
+ }
+
+ $cookieName = $this->getSessionCookieName();
+ $value = 'deleted';
+ $expires = time() - 3600;
+ $domain = $this->getBaseDomain();
+ if ($session) {
+ $value = '"' . http_build_query($session, null, '&') . '"';
+ if (isset($session['base_domain'])) {
+ $domain = $session['base_domain'];
+ }
+ $expires = $session['expires'];
+ }
+
+ // prepend dot if a domain is found
+ if ($domain) {
+ $domain = '.' . $domain;
+ }
+
+ // if an existing cookie is not set, we dont need to delete it
+ if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
+ return;
+ }
+
+ if (headers_sent()) {
+ self::errorLog('Could not set cookie. Headers already sent.');
+
+ // ignore for code coverage as we will never be able to setcookie in a CLI
+ // environment
+ // @codeCoverageIgnoreStart
+ } else {
+ setcookie($cookieName, $value, $expires, '/', $domain);
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Validates a session_version=3 style session object.
+ *
+ * @param Array $session the session object
+ * @return Array the session object if it validates, null otherwise
+ */
+ protected function validateSessionObject($session) {
+ // make sure some essential fields exist
+ if (is_array($session) &&
+ isset($session['uid']) &&
+ isset($session['access_token']) &&
+ isset($session['sig'])) {
+ // validate the signature
+ $session_without_sig = $session;
+ unset($session_without_sig['sig']);
+ $expected_sig = self::generateSignature(
+ $session_without_sig,
+ $this->getApiSecret()
+ );
+ if ($session['sig'] != $expected_sig) {
+ self::errorLog('Got invalid session signature in cookie.');
+ $session = null;
+ }
+ // check expiry time
+ } else {
+ $session = null;
+ }
+ return $session;
+ }
+
+ /**
+ * Returns something that looks like our JS session object from the
+ * signed token's data
+ *
+ * TODO: Nuke this once the login flow uses OAuth2
+ *
+ * @param Array the output of getSignedRequest
+ * @return Array Something that will work as a session
+ */
+ protected function createSessionFromSignedRequest($data) {
+ if (!isset($data['oauth_token'])) {
+ return null;
+ }
+
+ $session = array(
+ 'uid' => $data['user_id'],
+ 'access_token' => $data['oauth_token'],
+ 'expires' => $data['expires'],
+ );
+
+ // put a real sig, so that validateSignature works
+ $session['sig'] = self::generateSignature(
+ $session,
+ $this->getApiSecret()
+ );
+
+ return $session;
+ }
+
+ /**
+ * Parses a signed_request and validates the signature.
+ * Then saves it in $this->signed_data
+ *
+ * @param String A signed token
+ * @param Boolean Should we remove the parts of the payload that
+ * are used by the algorithm?
+ * @return Array the payload inside it or null if the sig is wrong
+ */
+ protected function parseSignedRequest($signed_request) {
+ list($encoded_sig, $payload) = explode('.', $signed_request, 2);
+
+ // decode the data
+ $sig = self::base64UrlDecode($encoded_sig);
+ $data = json_decode(self::base64UrlDecode($payload), true);
+
+ if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
+ self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
+ return null;
+ }
+
+ // check sig
+ $expected_sig = hash_hmac('sha256', $payload,
+ $this->getApiSecret(), $raw = true);
+ if ($sig !== $expected_sig) {
+ self::errorLog('Bad Signed JSON signature!');
+ return null;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Build the URL for api given parameters.
+ *
+ * @param $method String the method name.
+ * @return String the URL for the given parameters
+ */
+ protected function getApiUrl($method) {
+ static $READ_ONLY_CALLS =
+ array('admin.getallocation' => 1,
+ 'admin.getappproperties' => 1,
+ 'admin.getbannedusers' => 1,
+ 'admin.getlivestreamvialink' => 1,
+ 'admin.getmetrics' => 1,
+ 'admin.getrestrictioninfo' => 1,
+ 'application.getpublicinfo' => 1,
+ 'auth.getapppublickey' => 1,
+ 'auth.getsession' => 1,
+ 'auth.getsignedpublicsessiondata' => 1,
+ 'comments.get' => 1,
+ 'connect.getunconnectedfriendscount' => 1,
+ 'dashboard.getactivity' => 1,
+ 'dashboard.getcount' => 1,
+ 'dashboard.getglobalnews' => 1,
+ 'dashboard.getnews' => 1,
+ 'dashboard.multigetcount' => 1,
+ 'dashboard.multigetnews' => 1,
+ 'data.getcookies' => 1,
+ 'events.get' => 1,
+ 'events.getmembers' => 1,
+ 'fbml.getcustomtags' => 1,
+ 'feed.getappfriendstories' => 1,
+ 'feed.getregisteredtemplatebundlebyid' => 1,
+ 'feed.getregisteredtemplatebundles' => 1,
+ 'fql.multiquery' => 1,
+ 'fql.query' => 1,
+ 'friends.arefriends' => 1,
+ 'friends.get' => 1,
+ 'friends.getappusers' => 1,
+ 'friends.getlists' => 1,
+ 'friends.getmutualfriends' => 1,
+ 'gifts.get' => 1,
+ 'groups.get' => 1,
+ 'groups.getmembers' => 1,
+ 'intl.gettranslations' => 1,
+ 'links.get' => 1,
+ 'notes.get' => 1,
+ 'notifications.get' => 1,
+ 'pages.getinfo' => 1,
+ 'pages.isadmin' => 1,
+ 'pages.isappadded' => 1,
+ 'pages.isfan' => 1,
+ 'permissions.checkavailableapiaccess' => 1,
+ 'permissions.checkgrantedapiaccess' => 1,
+ 'photos.get' => 1,
+ 'photos.getalbums' => 1,
+ 'photos.gettags' => 1,
+ 'profile.getinfo' => 1,
+ 'profile.getinfooptions' => 1,
+ 'stream.get' => 1,
+ 'stream.getcomments' => 1,
+ 'stream.getfilters' => 1,
+ 'users.getinfo' => 1,
+ 'users.getloggedinuser' => 1,
+ 'users.getstandardinfo' => 1,
+ 'users.hasapppermission' => 1,
+ 'users.isappuser' => 1,
+ 'users.isverified' => 1,
+ 'video.getuploadlimits' => 1);
+ $name = 'api';
+ if (isset($READ_ONLY_CALLS[strtolower($method)])) {
+ $name = 'api_read';
+ }
+ return self::getUrl($name, 'restserver.php');
+ }
+
+ /**
+ * Build the URL for given domain alias, path and parameters.
+ *
+ * @param $name String the name of the domain
+ * @param $path String optional path (without a leading slash)
+ * @param $params Array optional query parameters
+ * @return String the URL for the given parameters
+ */
+ protected function getUrl($name, $path='', $params=array()) {
+ $url = self::$DOMAIN_MAP[$name];
+ if ($path) {
+ if ($path[0] === '/') {
+ $path = substr($path, 1);
+ }
+ $url .= $path;
+ }
+ if ($params) {
+ $url .= '?' . http_build_query($params, null, '&');
+ }
+ return $url;
+ }
+
+ /**
+ * Returns the Current URL, stripping it of known FB parameters that should
+ * not persist.
+ *
+ * @return String the current URL
+ */
+ protected function getCurrentUrl() {
+ $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
+ ? 'https://'
+ : 'http://';
+ $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ $parts = parse_url($currentUrl);
+
+ // drop known fb params
+ $query = '';
+ if (!empty($parts['query'])) {
+ $params = array();
+ parse_str($parts['query'], $params);
+ foreach(self::$DROP_QUERY_PARAMS as $key) {
+ unset($params[$key]);
+ }
+ if (!empty($params)) {
+ $query = '?' . http_build_query($params, null, '&');
+ }
+ }
+
+ // use port if non default
+ $port =
+ isset($parts['port']) &&
+ (($protocol === 'http://' && $parts['port'] !== 80) ||
+ ($protocol === 'https://' && $parts['port'] !== 443))
+ ? ':' . $parts['port'] : '';
+
+ // rebuild
+ return $protocol . $parts['host'] . $port . $parts['path'] . $query;
+ }
+
+ /**
+ * Generate a signature for the given params and secret.
+ *
+ * @param Array $params the parameters to sign
+ * @param String $secret the secret to sign with
+ * @return String the generated signature
+ */
+ protected static function generateSignature($params, $secret) {
+ // work with sorted data
+ ksort($params);
+
+ // generate the base string
+ $base_string = '';
+ foreach($params as $key => $value) {
+ $base_string .= $key . '=' . $value;
+ }
+ $base_string .= $secret;
+
+ return md5($base_string);
+ }
+
+ /**
+ * Prints to the error log if you aren't in command line mode.
+ *
+ * @param String log message
+ */
+ protected static function errorLog($msg) {
+ // disable error log if we are running in a CLI environment
+ // @codeCoverageIgnoreStart
+ if (php_sapi_name() != 'cli') {
+ error_log($msg);
+ }
+ // uncomment this if you want to see the errors on the page
+ // print 'error_log: '.$msg."\n";
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Base64 encoding that doesn't need to be urlencode()ed.
+ * Exactly the same as base64_encode except it uses
+ * - instead of +
+ * _ instead of /
+ *
+ * @param String base64UrlEncodeded string
+ */
+ protected static function base64UrlDecode($input) {
+ return base64_decode(strtr($input, '-_', '+/'));
+ }
+}
diff --git a/plugins/FacebookBridge/extlib/fb_ca_chain_bundle.crt b/plugins/FacebookBridge/extlib/fb_ca_chain_bundle.crt
new file mode 100644
index 000000000..b92d7190e
--- /dev/null
+++ b/plugins/FacebookBridge/extlib/fb_ca_chain_bundle.crt
@@ -0,0 +1,121 @@
+-----BEGIN CERTIFICATE-----
+MIIFgjCCBGqgAwIBAgIQDKKbZcnESGaLDuEaVk6fQjANBgkqhkiG9w0BAQUFADBm
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBDQS0zMB4XDTEwMDExMzAwMDAwMFoXDTEzMDQxMTIzNTk1OVowaDELMAkGA1UE
+BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEX
+MBUGA1UEChMORmFjZWJvb2ssIEluYy4xFzAVBgNVBAMUDiouZmFjZWJvb2suY29t
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9rzj7QIuLM3sdHu1HcI1VcR3g
+b5FExKNV646agxSle1aQ/sJev1mh/u91ynwqd2BQmM0brZ1Hc3QrfYyAaiGGgEkp
+xbhezyfeYhAyO0TKAYxPnm2cTjB5HICzk6xEIwFbA7SBJ2fSyW1CFhYZyo3tIBjj
+19VjKyBfpRaPkzLmRwIDAQABo4ICrDCCAqgwHwYDVR0jBBgwFoAUUOpzidsp+xCP
+nuUBINTeeZlIg/cwHQYDVR0OBBYEFPp+tsFBozkjrHlEnZ9J4cFj2eM0MA4GA1Ud
+DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMF8GA1UdHwRYMFYwKaAnoCWGI2h0dHA6
+Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9jYTMtZmIuY3JsMCmgJ6AlhiNodHRwOi8vY3Js
+NC5kaWdpY2VydC5jb20vY2EzLWZiLmNybDCCAcYGA1UdIASCAb0wggG5MIIBtQYL
+YIZIAYb9bAEDAAEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0
+LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIB
+UgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkA
+YwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEA
+bgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMA
+UABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkA
+IABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwA
+aQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8A
+cgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMA
+ZQAuMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF
+AAOCAQEACOkTIdxMy11+CKrbGNLBSg5xHaTvu/v1wbyn3dO/mf68pPfJnX6ShPYy
+4XM4Vk0x4uaFaU4wAGke+nCKGi5dyg0Esg7nemLNKEJaFAJZ9enxZm334lSCeARy
+wlDtxULGOFRyGIZZPmbV2eNq5xdU/g3IuBEhL722mTpAye9FU/J8Wsnw54/gANyO
+Gzkewigua8ip8Lbs9Cht399yAfbfhUP1DrAm/xEcnHrzPr3cdCtOyJaM6SRPpRqH
+ITK5Nc06tat9lXVosSinT3KqydzxBYua9gCFFiR3x3DgZfvXkC6KDdUlDrNcJUub
+a1BHnLLP4mxTHL6faAXYd05IxNn/IA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
+CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
+KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
+BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
+1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
+zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
+32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
+ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
+AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
+AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
+AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
+AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
+AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
+AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
+AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG
+CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB
+hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz
+c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
+Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe
+eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1
+rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv
+XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk
+1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF
+EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU
+9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQjCCA6ugAwIBAgIEQoclDjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEy
+MjIxNTI3MjdaFw0xNDA3MjIxNTU3MjdaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
+EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
+BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD
+1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt
+cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46
+OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd
+HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm
+t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET
+MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr
+BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo
+dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v
+Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU
+mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7
+UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF
+BQADgYEAUuVY7HCc/9EvhaYzC1rAIo348LtGIiMduEl5Xa24G8tmJnDioD2GU06r
+1kjLX/ktCdpdBgXadbjtdrZXTP59uN0AXlsdaTiFufsqVLPvkp5yMnqnuI3E2o6p
+NpAkoQSbB6kUCNnXcW26valgOjDLZFOnr241QiwdBAJAAE/rRa8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
diff --git a/plugins/FacebookBridge/images/login-button.png b/plugins/FacebookBridge/images/login-button.png
new file mode 100644
index 000000000..4e7766bca
--- /dev/null
+++ b/plugins/FacebookBridge/images/login-button.png
Binary files differ
diff --git a/plugins/FacebookBridge/lib/facebookclient.php b/plugins/FacebookBridge/lib/facebookclient.php
new file mode 100644
index 000000000..907c7537e
--- /dev/null
+++ b/plugins/FacebookBridge/lib/facebookclient.php
@@ -0,0 +1,1121 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for communicating with Facebook
+ *
+ * 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 Craig Andrews <candrews@integralblue.com>
+ * @author Zach Copley <zach@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);
+}
+
+/**
+ * Class for communication with Facebook
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class Facebookclient
+{
+ protected $facebook = null; // Facebook Graph client obj
+ protected $flink = null; // Foreign_link StatusNet -> Facebook
+ protected $notice = null; // The user's notice
+ protected $user = null; // Sender of the notice
+
+ function __construct($notice)
+ {
+ $this->facebook = self::getFacebook();
+ $this->notice = $notice;
+
+ $this->flink = Foreign_link::getByUserID(
+ $notice->profile_id,
+ FACEBOOK_SERVICE
+ );
+
+ $this->user = $this->flink->getUser();
+ }
+
+ /*
+ * Get an instance of the Facebook Graph SDK object
+ *
+ * @param string $appId Application
+ * @param string $secret Facebook API secret
+ *
+ * @return Facebook A Facebook SDK obj
+ */
+ static function getFacebook($appId = null, $secret = null)
+ {
+ // Check defaults and configuration for application ID and secret
+ if (empty($appId)) {
+ $appId = common_config('facebook', 'appid');
+ }
+
+ if (empty($secret)) {
+ $secret = common_config('facebook', 'secret');
+ }
+
+ // If there's no app ID and secret set in the local config, look
+ // for a global one
+ if (empty($appId) || empty($secret)) {
+ $appId = common_config('facebook', 'global_appid');
+ $secret = common_config('facebook', 'global_secret');
+ }
+
+ return new Facebook(
+ array(
+ 'appId' => $appId,
+ 'secret' => $secret,
+ 'cookie' => true
+ )
+ );
+ }
+
+ /*
+ * Broadcast a notice to Facebook
+ *
+ * @param Notice $notice the notice to send
+ */
+ static function facebookBroadcastNotice($notice)
+ {
+ $client = new Facebookclient($notice);
+ return $client->sendNotice();
+ }
+
+ /*
+ * Should the notice go to Facebook?
+ */
+ function isFacebookBound() {
+
+ if (empty($this->flink)) {
+ common_log(
+ LOG_WARN,
+ sprintf(
+ "No Foreign_link to Facebook for the author of notice %d.",
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // Avoid a loop
+ if ($this->notice->source == 'Facebook') {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Skipping notice %d because its source is Facebook.',
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // If the user does not want to broadcast to Facebook, move along
+ if (!($this->flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Skipping notice %d because user has FOREIGN_NOTICE_SEND bit off.',
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // If it's not a reply, or if the user WANTS to send @-replies,
+ // then, yeah, it can go to Facebook.
+ if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $this->notice->content) ||
+ ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * Determine whether we should send this notice using the Graph API or the
+ * old REST API and then dispatch
+ */
+ function sendNotice()
+ {
+ // If there's nothing in the credentials field try to send via
+ // the Old Rest API
+
+ if ($this->isFacebookBound()) {
+ common_debug("notice is facebook bound", __FILE__);
+ if (empty($this->flink->credentials)) {
+ return $this->sendOldRest();
+ } else {
+
+ // Otherwise we most likely have an access token
+ return $this->sendGraph();
+ }
+
+ } else {
+ common_debug(
+ sprintf(
+ "Skipping notice %d - not bound for Facebook",
+ $this->notice->id,
+ __FILE__
+ )
+ );
+ }
+ }
+
+ /*
+ * Send a notice to Facebook using the Graph API
+ */
+ function sendGraph()
+ {
+ try {
+
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ "Attempting use Graph API to post notice %d as a stream item for %s (%d), fbuid %d",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $params = array(
+ 'access_token' => $this->flink->credentials,
+ // XXX: Need to worrry about length of the message?
+ 'message' => $this->notice->content
+ );
+
+ $attachments = $this->notice->attachments();
+
+ if (!empty($attachments)) {
+
+ // We can only send one attachment with the Graph API :(
+
+ $first = array_shift($attachments);
+
+ if (substr($first->mimetype, 0, 6) == 'image/'
+ || in_array(
+ $first->mimetype,
+ array('application/x-shockwave-flash', 'audio/mpeg' ))) {
+
+ $params['picture'] = $first->url;
+ $params['caption'] = 'Click for full size';
+ $params['source'] = $first->url;
+ }
+
+ }
+
+ $result = $this->facebook->api(
+ sprintf('/%s/feed', $fbuid), 'post', $params
+ );
+
+ // Save a mapping
+ Notice_to_item::saveNew($this->notice->id, $result['id']);
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ "Posted notice %d as a stream item for %s (%d), fbuid %d",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ } catch (FacebookApiException $e) {
+ return $this->handleFacebookError($e);
+ }
+
+ return true;
+ }
+
+ /*
+ * Send a notice to Facebook using the deprecated Old REST API. We need this
+ * for backwards compatibility. Users who signed up for Facebook bridging
+ * using the old Facebook Canvas application do not have an OAuth 2.0
+ * access token.
+ */
+ function sendOldRest()
+ {
+ try {
+
+ $canPublish = $this->checkPermission('publish_stream');
+ $canUpdate = $this->checkPermission('status_update');
+
+ // We prefer to use stream.publish, because it can handle
+ // attachments and returns the ID of the published item
+
+ if ($canPublish == 1) {
+ $this->restPublishStream();
+ } else if ($canUpdate == 1) {
+ // as a last resort we can just update the user's "status"
+ $this->restStatusUpdate();
+ } else {
+
+ $msg = 'Not sending notice %d to Facebook because user %s '
+ . '(%d), fbuid %d, does not have \'status_update\' '
+ . 'or \'publish_stream\' permission.';
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ $msg,
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+ }
+
+ } catch (FacebookApiException $e) {
+ return $this->handleFacebookError($e);
+ }
+
+ return true;
+ }
+
+ /*
+ * Query Facebook to to see if a user has permission
+ *
+ *
+ *
+ * @param $permission the permission to check for - must be either
+ * public_stream or status_update
+ *
+ * @return boolean result
+ */
+ function checkPermission($permission)
+ {
+ if (!in_array($permission, array('publish_stream', 'status_update'))) {
+ throw new ServerException("No such permission!");
+ }
+
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ 'Checking for %s permission for user %s (%d), fbuid %d',
+ $permission,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $hasPermission = $this->facebook->api(
+ array(
+ 'method' => 'users.hasAppPermission',
+ 'ext_perm' => $permission,
+ 'uid' => $fbuid
+ )
+ );
+
+ if ($hasPermission == 1) {
+
+ common_debug(
+ sprintf(
+ '%s (%d), fbuid %d has %s permission',
+ $permission,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ return true;
+
+ } else {
+
+ $logMsg = '%s (%d), fbuid $fbuid does NOT have %s permission.'
+ . 'Facebook returned: %s';
+
+ common_debug(
+ sprintf(
+ $logMsg,
+ $this->user->nickname,
+ $this->user->id,
+ $permission,
+ $fbuid,
+ var_export($result, true)
+ ),
+ __FILE__
+ );
+
+ return false;
+
+ }
+ }
+
+ /*
+ * Handle a Facebook API Exception
+ *
+ * @param FacebookApiException $e the exception
+ *
+ */
+ function handleFacebookError($e)
+ {
+ $fbuid = $this->flink->foreign_id;
+ $errmsg = $e->getMessage();
+ $code = $e->getCode();
+
+ // The Facebook PHP SDK seems to always set the code attribute
+ // of the Exception to 0; they put the real error code in
+ // the message. Gar!
+ if ($code == 0) {
+ preg_match('/^\(#(?<code>\d+)\)/', $errmsg, $matches);
+ $code = $matches['code'];
+ }
+
+ // XXX: Check for any others?
+ switch($code) {
+ case 100: // Invalid parameter
+ $msg = 'Facebook claims notice %d was posted with an invalid '
+ . 'parameter (error code 100 - %s) Notice details: '
+ . '[nickname=%s, user id=%d, fbuid=%d, content="%s"]. '
+ . 'Dequeing.';
+ common_log(
+ LOG_ERR, sprintf(
+ $msg,
+ $this->notice->id,
+ $errmsg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid,
+ $this->notice->content
+ ),
+ __FILE__
+ );
+ return true;
+ break;
+
+ // @fixme: Facebook returns these 2xx permission errors sometimes
+ // FOR NO GOOD REASON AT ALL! It would be better to retry a few times
+ // over an extended period of time to instead of immediately
+ // disconnecting.
+
+ case 200: // Permissions error
+ case 250: // Updating status requires the extended permission status_update
+ $this->disconnect();
+ return true; // dequeue
+ break;
+ case 341: // Feed action request limit reached
+ $msg = '%s (userid=%d, fbuid=%d) has exceeded his/her limit '
+ . 'for posting notices to Facebook today. Dequeuing '
+ . 'notice %d';
+ common_log(
+ LOG_INFO, sprintf(
+ $msg,
+ $user->nickname,
+ $user->id,
+ $fbuid,
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ // @fixme: We want to rety at a later time when the throttling has expired
+ // instead of just giving up.
+ return true;
+ break;
+ default:
+ $msg = 'Facebook returned an error we don\'t know how to deal with '
+ . 'when posting notice %d. Error code: %d, error message: "%s"'
+ . ' Notice details: [nickname=%s, user id=%d, fbuid=%d, '
+ . 'notice content="%s"]. Dequeing.';
+ common_log(
+ LOG_ERR, sprintf(
+ $msg,
+ $this->notice->id,
+ $code,
+ $errmsg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid,
+ $this->notice->content
+ ),
+ __FILE__
+ );
+ return true; // dequeue
+ break;
+ }
+ }
+
+ /*
+ * Publish a notice to Facebook as a status update
+ *
+ * This is the least preferable way to send a notice to Facebook because
+ * it doesn't support attachments and the API method doesn't return
+ * the ID of the post on Facebook.
+ *
+ */
+ function restStatusUpdate()
+ {
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ "Attempting to post notice %d as a status update for %s (%d), fbuid %d",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $result = $this->facebook->api(
+ array(
+ 'method' => 'users.setStatus',
+ 'status' => $this->formatMessage(),
+ 'status_includes_verb' => true,
+ 'uid' => $fbuid
+ )
+ );
+
+ if ($result == 1) { // 1 is success
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ "Posted notice %s as a status update for %s (%d), fbuid %d",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ // There is no item ID returned for status update so we can't
+ // save a Notice_to_item mapping
+
+ } else {
+
+ $msg = sprintf(
+ "Error posting notice %s as a status update for %s (%d), fbuid %d - error code: %s",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid,
+ $result // will contain 0, or an error
+ );
+
+ throw new FacebookApiException($msg, $result);
+ }
+ }
+
+ /*
+ * Publish a notice to a Facebook user's stream using the old REST API
+ */
+ function restPublishStream()
+ {
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ 'Attempting to post notice %d as stream item for %s (%d) fbuid %d',
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $fbattachment = $this->formatAttachments();
+
+ $result = $this->facebook->api(
+ array(
+ 'method' => 'stream.publish',
+ 'message' => $this->formatMessage(),
+ 'attachment' => $fbattachment,
+ 'uid' => $fbuid
+ )
+ );
+
+ if (!empty($result)) { // result will contain the item ID
+
+ // Save a mapping
+ Notice_to_item::saveNew($this->notice->id, $result);
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Posted notice %d as a %s for %s (%d), fbuid %d',
+ $this->notice->id,
+ empty($fbattachment) ? 'stream item' : 'stream item with attachment',
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ } else {
+
+ $msg = sprintf(
+ 'Could not post notice %d as a %s for %s (%d), fbuid %d - error code: %s',
+ $this->notice->id,
+ empty($fbattachment) ? 'stream item' : 'stream item with attachment',
+ $this->user->nickname,
+ $this->user->id,
+ $result, // result will contain an error code
+ $fbuid
+ );
+
+ throw new FacebookApiException($msg, $result);
+ }
+ }
+
+ /*
+ * Format the text message of a stream item so it's appropriate for
+ * sending to Facebook. If the notice is too long, truncate it, and
+ * add a linkback to the original notice at the end.
+ *
+ * @return String $txt the formated message
+ */
+ function formatMessage()
+ {
+ // Start with the plaintext source of this notice...
+ $txt = $this->notice->content;
+
+ // Facebook has a 420-char hardcoded max.
+ if (mb_strlen($statustxt) > 420) {
+ $noticeUrl = common_shorten_url($this->notice->uri);
+ $urlLen = mb_strlen($noticeUrl);
+ $txt = mb_substr($statustxt, 0, 420 - ($urlLen + 3)) . ' … ' . $noticeUrl;
+ }
+
+ return $txt;
+ }
+
+ /*
+ * Format attachments for the old REST API stream.publish method
+ *
+ * Note: Old REST API supports multiple attachments per post
+ *
+ */
+ function formatAttachments()
+ {
+ $attachments = $this->notice->attachments();
+
+ $fbattachment = array();
+ $fbattachment['media'] = array();
+
+ foreach($attachments as $attachment)
+ {
+ if($enclosure = $attachment->getEnclosure()){
+ $fbmedia = $this->getFacebookMedia($enclosure);
+ }else{
+ $fbmedia = $this->getFacebookMedia($attachment);
+ }
+ if($fbmedia){
+ $fbattachment['media'][]=$fbmedia;
+ }else{
+ $fbattachment['name'] = ($attachment->title ?
+ $attachment->title : $attachment->url);
+ $fbattachment['href'] = $attachment->url;
+ }
+ }
+ if(count($fbattachment['media'])>0){
+ unset($fbattachment['name']);
+ unset($fbattachment['href']);
+ }
+ return $fbattachment;
+ }
+
+ /**
+ * given a File objects, returns an associative array suitable for Facebook media
+ */
+ function getFacebookMedia($attachment)
+ {
+ $fbmedia = array();
+
+ if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) {
+ $fbmedia['type'] = 'image';
+ $fbmedia['src'] = $attachment->url;
+ $fbmedia['href'] = $attachment->url;
+ } else if ($attachment->mimetype == 'audio/mpeg') {
+ $fbmedia['type'] = 'mp3';
+ $fbmedia['src'] = $attachment->url;
+ }else if ($attachment->mimetype == 'application/x-shockwave-flash') {
+ $fbmedia['type'] = 'flash';
+
+ // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
+ // says that imgsrc is required... but we have no value to put in it
+ // $fbmedia['imgsrc']='';
+
+ $fbmedia['swfsrc'] = $attachment->url;
+ }else{
+ return false;
+ }
+ return $fbmedia;
+ }
+
+ /*
+ * Disconnect a user from Facebook by deleting his Foreign_link.
+ * Notifies the user his account has been disconnected by email.
+ */
+ function disconnect()
+ {
+ $fbuid = $this->flink->foreign_id;
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Removing Facebook link for %s (%d), fbuid %d',
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $result = $this->flink->delete();
+
+ if (empty($result)) {
+ common_log(
+ LOG_ERR,
+ sprintf(
+ 'Could not remove Facebook link for %s (%d), fbuid %d',
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that we are removing their Facebook link
+ if (!empty($this->user->email)) {
+ $result = $this->mailFacebookDisconnect();
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify %s (%d), fbuid %d '
+ . 'about his/her Facebook link being removed.';
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ $msg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ }
+
+ } else {
+
+ $msg = 'Unable to send email to notify %s (%d), fbuid %d '
+ . 'about his/her Facebook link being removed because the '
+ . 'user has not set an email address.';
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ $msg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ }
+ }
+
+ /**
+ * Send a mail message to notify a user that her Facebook link
+ * has been terminated.
+ *
+ * @return boolean success flag
+ */
+ function mailFacebookDisconnect()
+ {
+ $profile = $this->user->getProfile();
+
+ $siteName = common_config('site', 'name');
+
+ common_switch_locale($this->user->language);
+
+ $subject = _m('Your Facebook connection has been removed');
+
+ $msg = <<<BODY
+Hi %1$s,
+
+We're sorry to inform you we are unable to publish your notice to
+Facebook, and have removed the connection between your %2$s account and
+Facebook.
+
+This may have happened because you have removed permission for %2$s
+to post on your behalf, or perhaps you have deactivated your Facebook
+account. You can reconnect your %s account to Facebook at any time by
+logging in with Facebook again.
+
+Sincerely,
+
+%2$s
+BODY;
+ $body = sprintf(
+ _m($msg),
+ $this->user->nickname,
+ $siteName
+ );
+
+ common_switch_locale();
+
+ $result = mail_to_user($this->user, $subject, $body);
+
+ if (empty($this->user->password)) {
+ $result = self::emailWarn($this->user);
+ }
+
+ return $result;
+ }
+
+ /*
+ * Send the user an email warning that their account has been
+ * disconnected and he/she has no way to login and must contact
+ * the site administrator for help.
+ *
+ * @param User $user the deauthorizing user
+ *
+ */
+ static function emailWarn($user)
+ {
+ $profile = $user->getProfile();
+
+ $siteName = common_config('site', 'name');
+ $siteEmail = common_config('site', 'email');
+
+ if (empty($siteEmail)) {
+ common_log(
+ LOG_WARNING,
+ "No site email address configured. Please set one."
+ );
+ }
+
+ common_switch_locale($user->language);
+
+ $subject = _m('Contact the %s administrator to retrieve your account');
+
+ $msg = <<<BODY
+Hi %1$s,
+
+We've noticed you have deauthorized the Facebook connection for your
+%2$s account. You have not set a password for your %2$s account yet, so
+you will not be able to login. If you wish to continue using your %2$s
+account, please contact the site administrator (%3$s) to set a password.
+
+Sincerely,
+
+%2$s
+BODY;
+ $body = sprintf(
+ _m($msg),
+ $user->nickname,
+ $siteName,
+ $siteEmail
+ );
+
+ common_switch_locale();
+
+ if (mail_to_user($user, $subject, $body)) {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Sent account lockout warning to %s (%d)',
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
+ } else {
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Unable to send account lockout warning to %s (%d)',
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
+ }
+ }
+
+ /*
+ * Check to see if we have a mapping to a copy of this notice
+ * on Facebook
+ *
+ * @param Notice $notice the notice to check
+ *
+ * @return mixed null if it can't find one, or the id of the Facebook
+ * stream item
+ */
+ static function facebookStatusId($notice)
+ {
+ $n2i = Notice_to_item::staticGet('notice_id', $notice->id);
+
+ if (empty($n2i)) {
+ return null;
+ } else {
+ return $n2i->item_id;
+ }
+ }
+
+ /*
+ * Save a Foreign_user record of a Facebook user
+ *
+ * @param object $fbuser a Facebook Graph API user obj
+ * See: http://developers.facebook.com/docs/reference/api/user
+ * @return mixed $result Id or key
+ *
+ */
+ static function addFacebookUser($fbuser)
+ {
+ // remove any existing, possibly outdated, record
+ $luser = Foreign_user::getForeignUser($fbuser['id'], FACEBOOK_SERVICE);
+
+ if (!empty($luser)) {
+
+ $result = $luser->delete();
+
+ if ($result != false) {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Removed old Facebook user: %s, fbuid %d',
+ $fbuid['name'],
+ $fbuid['id']
+ ),
+ __FILE__
+ );
+ }
+ }
+
+ $fuser = new Foreign_user();
+
+ $fuser->nickname = $fbuser['name'];
+ $fuser->uri = $fbuser['link'];
+ $fuser->id = $fbuser['id'];
+ $fuser->service = FACEBOOK_SERVICE;
+ $fuser->created = common_sql_now();
+
+ $result = $fuser->insert();
+
+ if (empty($result)) {
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Failed to add new Facebook user: %s, fbuid %d',
+ $fbuser['name'],
+ $fbuser['id']
+ ),
+ __FILE__
+ );
+
+ common_log_db_error($fuser, 'INSERT', __FILE__);
+ } else {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Added new Facebook user: %s, fbuid %d',
+ $fbuser['name'],
+ $fbuser['id']
+ ),
+ __FILE__
+ );
+ }
+
+ return $result;
+ }
+
+ /*
+ * Remove an item from a Facebook user's feed if we have a mapping
+ * for it.
+ */
+ function streamRemove()
+ {
+ $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
+
+ if (!empty($this->flink) && !empty($n2i)) {
+
+ $result = $this->facebook->api(
+ array(
+ 'method' => 'stream.remove',
+ 'post_id' => $n2i->item_id,
+ 'uid' => $this->flink->foreign_id
+ )
+ );
+
+ if (!empty($result) && result == true) {
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Deleted Facebook item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+
+ $n2i->delete();
+
+ } else {
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Could not deleted Facebook item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+ }
+ }
+ }
+
+ /*
+ * Like an item in a Facebook user's feed if we have a mapping
+ * for it.
+ */
+ function like()
+ {
+ $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
+
+ if (!empty($this->flink) && !empty($n2i)) {
+
+ $result = $this->facebook->api(
+ array(
+ 'method' => 'stream.addlike',
+ 'post_id' => $n2i->item_id,
+ 'uid' => $this->flink->foreign_id
+ )
+ );
+
+ if (!empty($result) && result == true) {
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Added like for item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+
+ } else {
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Could not like Facebook item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+ }
+ }
+ }
+
+ /*
+ * Unlike an item in a Facebook user's feed if we have a mapping
+ * for it.
+ */
+ function unLike()
+ {
+ $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
+
+ if (!empty($this->flink) && !empty($n2i)) {
+
+ $result = $this->facebook->api(
+ array(
+ 'method' => 'stream.removeLike',
+ 'post_id' => $n2i->item_id,
+ 'uid' => $this->flink->foreign_id
+ )
+ );
+
+ if (!empty($result) && result == true) {
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Removed like for item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+
+ } else {
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ 'Could not remove like for Facebook item: %s for %s (%d), fbuid %d',
+ $n2i->item_id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+ }
+ }
+ }
+
+}
diff --git a/plugins/FacebookBridge/lib/facebookqueuehandler.php b/plugins/FacebookBridge/lib/facebookqueuehandler.php
new file mode 100644
index 000000000..1e82ff01b
--- /dev/null
+++ b/plugins/FacebookBridge/lib/facebookqueuehandler.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Queuehandler for Facebook transport
+ *
+ * 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>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class FacebookQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'facebook';
+ }
+
+ function handle($notice)
+ {
+ if ($this->_isLocal($notice)) {
+ return Facebookclient::facebookBroadcastNotice($notice);
+ }
+ return true;
+ }
+
+ /**
+ * Determine whether the notice was locally created
+ *
+ * @param Notice $notice the notice
+ *
+ * @return boolean locality
+ */
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
+ }
+}
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/GroupFavorited/locale/GroupFavorited.pot b/plugins/GroupFavorited/locale/GroupFavorited.pot
index c6c2f1114..041126fad 100644
--- a/plugins/GroupFavorited/locale/GroupFavorited.pot
+++ b/plugins/GroupFavorited/locale/GroupFavorited.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-04 18:25+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"
@@ -17,13 +17,13 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr ""
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr ""
diff --git a/plugins/GroupFavorited/locale/br/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/br/LC_MESSAGES/GroupFavorited.po
index 7362f0505..6ca70c83f 100644
--- a/plugins/GroupFavorited/locale/br/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/br/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr ""
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr ""
diff --git a/plugins/GroupFavorited/locale/de/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/de/LC_MESSAGES/GroupFavorited.po
index c820c89e1..cc8988658 100644
--- a/plugins/GroupFavorited/locale/de/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/de/LC_MESSAGES/GroupFavorited.po
@@ -10,26 +10,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+0000\n"
"Language-Team: German <http://translatewiki.net/wiki/Portal:de>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Beliebte Beiträge in der %s-Gruppe"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Beliebte Beiträge in der %1$s-Gruppe, Seite %2$d"
diff --git a/plugins/GroupFavorited/locale/es/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/es/LC_MESSAGES/GroupFavorited.po
index 6af511ae6..3b3419d5a 100644
--- a/plugins/GroupFavorited/locale/es/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/es/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+0000\n"
"Language-Team: Spanish <http://translatewiki.net/wiki/Portal:es>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: es\n"
"X-Message-Group: #out-statusnet-plugin-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Mensajes populares en el grupo %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Mensajes populares en el grupo %1$s, página %2$d"
diff --git a/plugins/GroupFavorited/locale/fr/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/fr/LC_MESSAGES/GroupFavorited.po
index 9cfb10efc..3facce573 100644
--- a/plugins/GroupFavorited/locale/fr/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/fr/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+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-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Messages populaires dans le groupe %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Messages populaires dans le groupe %1$s, page %2$d"
diff --git a/plugins/GroupFavorited/locale/ia/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/ia/LC_MESSAGES/GroupFavorited.po
index 03eb3e3da..f1e424cd1 100644
--- a/plugins/GroupFavorited/locale/ia/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/ia/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+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-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Messages popular in gruppo %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Messages popular in gruppo %1$s, pagina %2$d"
diff --git a/plugins/GroupFavorited/locale/mk/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/mk/LC_MESSAGES/GroupFavorited.po
index 1d1a43a0d..5aada9eca 100644
--- a/plugins/GroupFavorited/locale/mk/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/mk/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+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-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Популарни објави во групата %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Популарни објави во групата %1$s, страница %2$d"
diff --git a/plugins/GroupFavorited/locale/nl/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/nl/LC_MESSAGES/GroupFavorited.po
index 1d8a2b57e..bf5669877 100644
--- a/plugins/GroupFavorited/locale/nl/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/nl/LC_MESSAGES/GroupFavorited.po
@@ -10,26 +10,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+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-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Populaire berichten in de groep %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Populaire berichten in de groep %1$s, pagina %2$d"
diff --git a/plugins/GroupFavorited/locale/ru/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/ru/LC_MESSAGES/GroupFavorited.po
index 63e84c453..1cb4433d8 100644
--- a/plugins/GroupFavorited/locale/ru/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/ru/LC_MESSAGES/GroupFavorited.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+0000\n"
"Language-Team: Russian <http://translatewiki.net/wiki/Portal:ru>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-groupfavorited\n"
@@ -23,13 +23,13 @@ msgstr ""
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Популярные сообщения в группе %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr ""
diff --git a/plugins/GroupFavorited/locale/tl/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/tl/LC_MESSAGES/GroupFavorited.po
index 31a1077bd..4091155de 100644
--- a/plugins/GroupFavorited/locale/tl/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/tl/LC_MESSAGES/GroupFavorited.po
@@ -9,26 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+0000\n"
"Language-Team: Tagalog <http://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-groupfavorited\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Tanyag na mga pagpapaskila sa loob ng pangkat na %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Tanyag na mga pagpapaskila sa loob ng pangkat na %1$s, pahina %2$d"
diff --git a/plugins/GroupFavorited/locale/uk/LC_MESSAGES/GroupFavorited.po b/plugins/GroupFavorited/locale/uk/LC_MESSAGES/GroupFavorited.po
index d0a44340a..143c0cae8 100644
--- a/plugins/GroupFavorited/locale/uk/LC_MESSAGES/GroupFavorited.po
+++ b/plugins/GroupFavorited/locale/uk/LC_MESSAGES/GroupFavorited.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - GroupFavorited\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:41+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:28+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-18 20:29:53+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:48+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-groupfavorited\n"
@@ -23,13 +23,13 @@ msgstr ""
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"
#. TRANS: %s is a group name.
-#: groupfavoritedaction.php:53
+#: groupfavoritedaction.php:48
#, php-format
msgid "Popular posts in %s group"
msgstr "Популярні повідомлення спільноти %s"
#. TRANS: %1$s is a group name, %2$s is a group number.
-#: groupfavoritedaction.php:56
+#: groupfavoritedaction.php:51
#, php-format
msgid "Popular posts in %1$s group, page %2$d"
msgstr "Популярні повідомлення спільноти %1$s, сторінка %2$d"
diff --git a/plugins/LinkPreview/LinkPreviewPlugin.php b/plugins/LinkPreview/LinkPreviewPlugin.php
new file mode 100644
index 000000000..39d2c9bf3
--- /dev/null
+++ b/plugins/LinkPreview/LinkPreviewPlugin.php
@@ -0,0 +1,101 @@
+<?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')) {
+ exit(1);
+}
+
+/**
+ * Some UI extras for now...
+ *
+ * @package LinkPreviewPlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+class LinkPreviewPlugin extends Plugin
+{
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'LinkPreview',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Brion Vibber',
+ 'homepage' => 'http://status.net/wiki/Plugin:LinkPreview',
+ 'rawdescription' =>
+ _m('UI extensions previewing thumbnails from links.'));
+
+ return true;
+ }
+
+ /**
+ * Load JS at runtime if we're logged in.
+ *
+ * @param Action $action
+ * @return boolean hook result
+ */
+ function onEndShowScripts($action)
+ {
+ $user = common_current_user();
+ if ($user && common_config('attachments', 'process_links')) {
+ $action->script('plugins/LinkPreview/linkpreview.js');
+ $data = json_encode(array(
+ 'api' => common_local_url('oembedproxy'),
+ 'width' => common_config('attachments', 'thumbwidth'),
+ 'height' => common_config('attachments', 'thumbheight'),
+ ));
+ $action->inlineScript('$(function() {SN.Init.LinkPreview && SN.Init.LinkPreview('.$data.');})');
+ }
+ return true;
+ }
+
+ /**
+ * Autoloader
+ *
+ * Loads our classes if they're requested.
+ *
+ * @param string $cls Class requested
+ *
+ * @return boolean hook return
+ */
+ function onAutoload($cls)
+ {
+ $lower = strtolower($cls);
+ switch ($lower)
+ {
+ case 'oembedproxyaction':
+ require_once dirname(__FILE__) . '/' . $lower . '.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Hook for RouterInitialized event.
+ *
+ * @param Net_URL_Mapper $m URL mapper
+ *
+ * @return boolean hook return
+ */
+ function onStartInitializeRouter($m)
+ {
+ $m->connect('main/oembed/proxy',
+ array('action' => 'oembedproxy'));
+
+ return true;
+ }
+}
diff --git a/plugins/LinkPreview/linkpreview.js b/plugins/LinkPreview/linkpreview.js
new file mode 100644
index 000000000..0c0eb734e
--- /dev/null
+++ b/plugins/LinkPreview/linkpreview.js
@@ -0,0 +1,194 @@
+/**
+ * (c) 2010 StatusNet, Inc.
+ */
+
+(function() {
+ var oEmbed = {
+ api: 'http://oohembed.com/oohembed',
+ width: 100,
+ height: 75,
+ cache: {},
+ callbacks: {},
+
+ /**
+ * Do a cached oEmbed lookup for the given URL.
+ *
+ * @param {String} url
+ * @param {function} callback
+ */
+ lookup: function(url, callback)
+ {
+ if (typeof oEmbed.cache[url] == "object") {
+ // We already have a successful lookup.
+ callback(oEmbed.cache[url]);
+ } else if (typeof oEmbed.callbacks[url] == "undefined") {
+ // No lookup yet... Start it!
+ oEmbed.callbacks[url] = [callback];
+
+ oEmbed.rawLookup(url, function(data) {
+ oEmbed.cache[url] = data;
+ var callbacks = oEmbed.callbacks[url];
+ oEmbed.callbacks[url] = undefined;
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](data);
+ }
+ });
+ } else {
+ // A lookup is in progress.
+ oEmbed.callbacks[url].push(callback);
+ }
+ },
+
+ /**
+ * Do an oEmbed lookup for the given URL.
+ *
+ * @fixme proxy through ourselves if possible?
+ * @fixme use the global thumbnail size settings
+ *
+ * @param {String} url
+ * @param {function} callback
+ */
+ rawLookup: function(url, callback)
+ {
+ var params = {
+ url: url,
+ format: 'json',
+ maxwidth: oEmbed.width,
+ maxheight: oEmbed.height,
+ token: $('#token').val()
+ };
+ $.get(oEmbed.api, params, function(data, xhr) {
+ callback(data);
+ }, 'json');
+ }
+ };
+
+ var LinkPreview = {
+ links: [],
+
+ /**
+ * Find URL links from the source text that may be interesting.
+ *
+ * @param {String} text
+ * @return {Array} list of URLs
+ */
+ findLinks: function (text)
+ {
+ // @fixme match this to core code
+ var re = /(?:^| )(https?:\/\/.+?\/.+?)(?= |$)/mg;
+ var links = [];
+ var matches;
+ while ((matches = re.exec(text)) !== null) {
+ links.push(matches[1]);
+ }
+ return links;
+ },
+
+ /**
+ * Start looking up info for a link preview...
+ * May start async data loads.
+ *
+ * @param {String} id
+ * @param {String} url
+ */
+ prepLinkPreview: function(id, url)
+ {
+ oEmbed.lookup(url, function(data) {
+ var thumb = null;
+ var width = 100;
+ if (typeof data.thumbnail_url == "string") {
+ thumb = data.thumbnail_url;
+ if (typeof data.thumbnail_width !== "undefined") {
+ if (data.thumbnail_width < width) {
+ width = data.thumbnail_width;
+ }
+ }
+ } else if (data.type == 'photo' && typeof data.url == "string") {
+ thumb = data.url;
+ if (typeof data.width !== "undefined") {
+ if (data.width < width) {
+ width = data.width;
+ }
+ }
+ }
+ if (thumb) {
+ var link = $('<span class="inline-attachment"><a><img/></a></span>');
+ link.find('a')
+ .attr('href', url)
+ .attr('target', '_blank')
+ .last()
+ .find('img')
+ .attr('src', thumb)
+ .attr('width', width)
+ .attr('title', data.title || data.url || url);
+ $('#' + id).append(link);
+ }
+ });
+ },
+
+ /**
+ * Update the live preview section with links found in the given text.
+ * May start async data loads.
+ *
+ * @param {String} text: free-form input text
+ */
+ previewLinks: function(text)
+ {
+ var old = LinkPreview.links;
+ var links = LinkPreview.findLinks(text);
+
+ // Check for existing common elements...
+ for (var i = 0; i < old.length && i < links.length; i++) {
+ if (links[i] != old[i]) {
+ // Change an existing entry!
+ var id = 'link-preview-' + i;
+ $('#' + id).html('');
+ LinkPreview.prepLinkPreview(id, links[i]);
+ }
+ }
+ if (links.length > old.length) {
+ // Adding new entries, whee!
+ for (var i = old.length; i < links.length; i++) {
+ var id = 'link-preview-' + i;
+ $('#link-preview').append('<span id="' + id + '"></span>');
+ LinkPreview.prepLinkPreview(id, links[i]);
+ }
+ } else if (old.length > links.length) {
+ // Remove preview entries for links that have been removed.
+ for (var i = links.length; i < old.length; i++) {
+ var id = 'link-preview-' + i;
+ $('#' + id).remove();
+ }
+ }
+
+ LinkPreview.links = links;
+ },
+
+ /**
+ * Clear out any link preview data.
+ */
+ clear: function() {
+ LinkPreview.links = [];
+ $('#link-preview').empty();
+ }
+ };
+
+ SN.Init.LinkPreview = function(params) {
+ if (params.api) oEmbed.api = params.api;
+ if (params.width) oEmbed.width = params.width;
+ if (params.height) oEmbed.height = params.height;
+
+ $('#form_notice')
+ .append('<div id="link-preview" class="thumbnails"></div>')
+ .bind('reset', function() {
+ LinkPreview.clear();
+ });
+
+ // Piggyback on the counter update...
+ var origCounter = SN.U.Counter;
+ SN.U.Counter = function(form) {
+ LinkPreview.previewLinks($('#notice_data-text').val());
+ return origCounter(form);
+ }
+ }
+})();
diff --git a/plugins/LinkPreview/oembedproxyaction.php b/plugins/LinkPreview/oembedproxyaction.php
new file mode 100644
index 000000000..470f78073
--- /dev/null
+++ b/plugins/LinkPreview/oembedproxyaction.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * StatusNet-only extensions to the Twitter-like API
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Oembed proxy implementation
+ *
+ * This class provides an interface for our JS-side code to pull info on
+ * links from other sites, using either native oEmbed, our own custom
+ * handlers, or the oohEmbed.com offsite proxy service as configured.
+ *
+ * @category oEmbed
+ * @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 OembedproxyAction extends OembedAction
+{
+
+ function handle($args)
+ {
+ // We're not a general oEmbed proxy service; limit to valid sessions.
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->clientError(_('There was a problem with your session token. '.
+ 'Try again, please.'));
+ }
+
+ $format = $this->arg('format');
+ if ($format && $format != 'json') {
+ throw new ClientException('Invalid format; only JSON supported.');
+ }
+
+ $url = $this->arg('url');
+ if (!common_valid_http_url($url)) {
+ throw new ClientException('Invalid URL.');
+ }
+
+ $params = array();
+ if ($this->arg('maxwidth')) {
+ $params['maxwidth'] = $this->arg('maxwidth');
+ }
+ if ($this->arg('maxheight')) {
+ $params['maxheight'] = $this->arg('maxheight');
+ }
+
+ $data = oEmbedHelper::getObject($url, $params);
+
+ $this->init_document('json');
+ print json_encode($data);
+ }
+
+}
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/locale/br/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/br/LC_MESSAGES/Mapstraction.po
index 6552915b9..6dd726870 100644
--- a/plugins/Mapstraction/locale/br/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/br/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:46+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -46,19 +46,19 @@ msgstr "An implijer-mañ n'eus profil ebet dezhañ."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Kartenn mignoned %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, fuzzy, php-format
msgid "%1$s friends map, page %2$d"
msgstr "%s gartenn, pajenn %d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "%s gartenn, pajenn %d"
diff --git a/plugins/Mapstraction/locale/de/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/de/LC_MESSAGES/Mapstraction.po
index 0299a5d15..22c3e84ee 100644
--- a/plugins/Mapstraction/locale/de/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/de/LC_MESSAGES/Mapstraction.po
@@ -10,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:46+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: German <http://translatewiki.net/wiki/Portal:de>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -49,19 +49,19 @@ msgstr "Benutzer hat kein Profil."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Karte der Freunde von %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Karte der Freunde von %1$s, Seite %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "%s-Karte, Seite %d"
diff --git a/plugins/Mapstraction/locale/fi/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/fi/LC_MESSAGES/Mapstraction.po
index 8e6007152..9d84ece5a 100644
--- a/plugins/Mapstraction/locale/fi/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/fi/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -46,19 +46,19 @@ msgstr "Käyttäjällä ei ole profiilia."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Kartta käyttäjän %s ystävistä"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, fuzzy, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Kartta käyttäjän %s ystävistä"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr ""
diff --git a/plugins/Mapstraction/locale/fr/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/fr/LC_MESSAGES/Mapstraction.po
index a75da34ec..399917055 100644
--- a/plugins/Mapstraction/locale/fr/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/fr/LC_MESSAGES/Mapstraction.po
@@ -10,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -49,19 +49,19 @@ msgstr "Aucun profil ne correspond à cet utilisateur."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Carte des amis de %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Carte des amis de %1$s, page %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "Carte %s, page %d"
diff --git a/plugins/Mapstraction/locale/gl/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/gl/LC_MESSAGES/Mapstraction.po
index 5df0d1404..ec66764bc 100644
--- a/plugins/Mapstraction/locale/gl/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/gl/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Galician <http://translatewiki.net/wiki/Portal:gl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -46,19 +46,19 @@ msgstr "O usuario non ten perfil."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr ""
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr ""
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr ""
diff --git a/plugins/Mapstraction/locale/ia/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/ia/LC_MESSAGES/Mapstraction.po
index 72249a654..7deba8963 100644
--- a/plugins/Mapstraction/locale/ia/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/ia/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -48,19 +48,19 @@ msgstr "Le usator non ha un profilo."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Mappa del amicos de %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Mappa de amicos de %1$s, pagina %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "Mappa de %s, pagina %d"
diff --git a/plugins/Mapstraction/locale/mk/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/mk/LC_MESSAGES/Mapstraction.po
index cf7be70ab..33562d597 100644
--- a/plugins/Mapstraction/locale/mk/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/mk/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -48,19 +48,19 @@ msgstr "Корисникот нема профил."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Карта на пријатели на %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Карта на пријатели на %1$s, страница %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "Карта на %s, стр. %d"
diff --git a/plugins/Mapstraction/locale/nb/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/nb/LC_MESSAGES/Mapstraction.po
index b478de888..68b2d02ec 100644
--- a/plugins/Mapstraction/locale/nb/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/nb/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -48,19 +48,19 @@ msgstr "Bruker har ingen profil."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "%s vennekart"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "%1$s vennekart, side %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "%s kart, side %d"
diff --git a/plugins/Mapstraction/locale/nl/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/nl/LC_MESSAGES/Mapstraction.po
index fdc68490e..f68e24ea9 100644
--- a/plugins/Mapstraction/locale/nl/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/nl/LC_MESSAGES/Mapstraction.po
@@ -10,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -49,19 +49,19 @@ msgstr "Deze gebruiker heeft geen profiel."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Kaart van %s en vrienden"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Kaart van vrienden van %1$s, pagina %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "Kaart van %s, pagina %d"
diff --git a/plugins/Mapstraction/locale/ru/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/ru/LC_MESSAGES/Mapstraction.po
index 93ad72cc3..97dbedf6e 100644
--- a/plugins/Mapstraction/locale/ru/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/ru/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Russian <http://translatewiki.net/wiki/Portal:ru>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -47,19 +47,19 @@ msgstr "У пользователя нет профиля."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Карта друзей: %s"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, fuzzy, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Карта друзей: %s"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr ""
diff --git a/plugins/Mapstraction/locale/ta/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/ta/LC_MESSAGES/Mapstraction.po
index 301117348..fbeb8efac 100644
--- a/plugins/Mapstraction/locale/ta/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/ta/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Tamil <http://translatewiki.net/wiki/Portal:ta>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ta\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -46,19 +46,19 @@ msgstr ""
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr ""
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr ""
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr ""
diff --git a/plugins/Mapstraction/locale/tl/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/tl/LC_MESSAGES/Mapstraction.po
index 49d27836b..bec287a2a 100644
--- a/plugins/Mapstraction/locale/tl/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/tl/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Tagalog <http://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -48,19 +48,19 @@ msgstr "Walang balangkas ang tagagamit."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "%s na mapa ng mga kaibigan"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr " %1$s mapa ng mga kaibigan, pahina %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "%s na mapa, pahina %d"
diff --git a/plugins/Mapstraction/locale/uk/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/uk/LC_MESSAGES/Mapstraction.po
index 9a43d791e..9a928759a 100644
--- a/plugins/Mapstraction/locale/uk/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/uk/LC_MESSAGES/Mapstraction.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+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-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-mapstraction\n"
@@ -49,19 +49,19 @@ msgstr "Користувач не має профілю."
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "Мапа друзів %s."
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "Мапа друзів %1$s, сторінка %2$d"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "Мапа друзів %s, сторінка %d"
diff --git a/plugins/Mapstraction/locale/zh_CN/LC_MESSAGES/Mapstraction.po b/plugins/Mapstraction/locale/zh_CN/LC_MESSAGES/Mapstraction.po
index 47f07d159..15358a1aa 100644
--- a/plugins/Mapstraction/locale/zh_CN/LC_MESSAGES/Mapstraction.po
+++ b/plugins/Mapstraction/locale/zh_CN/LC_MESSAGES/Mapstraction.po
@@ -9,14 +9,14 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Mapstraction\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:46:47+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:33+0000\n"
"Language-Team: Simplified Chinese <http://translatewiki.net/wiki/Portal:zh-"
"hans>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:29:55+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75590); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:12:52+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-mapstraction\n"
@@ -49,19 +49,19 @@ msgstr "用户没有个人信息。"
#. TRANS: Page title.
#. TRANS: %s is a user nickname.
-#: allmap.php:74
+#: allmap.php:69
#, php-format
msgid "%s friends map"
msgstr "%s 好友地图"
#. TRANS: Page title.
#. TRANS: %1$s is a user nickname, %2$d is a page number.
-#: allmap.php:80
+#: allmap.php:75
#, php-format
msgid "%1$s friends map, page %2$d"
msgstr "%1$s 好友地图,第 %2$d 页。"
-#: usermap.php:73
+#: usermap.php:68
#, php-format
msgid "%s map, page %d"
msgstr "%s 地图,第 %d 页"
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/Memcached/locale/ja/LC_MESSAGES/Memcached.po b/plugins/Memcached/locale/ja/LC_MESSAGES/Memcached.po
new file mode 100644
index 000000000..fea6080bc
--- /dev/null
+++ b/plugins/Memcached/locale/ja/LC_MESSAGES/Memcached.po
@@ -0,0 +1,29 @@
+# Translation of StatusNet - Memcached to Japanese (日本語)
+# Expored from translatewiki.net
+#
+# Author: Iwai.masaharu
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - Memcached\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:31+0000\n"
+"Language-Team: Japanese <http://translatewiki.net/wiki/Portal:ja>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:12:53+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: ja\n"
+"X-Message-Group: #out-statusnet-plugin-memcached\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: MemcachedPlugin.php:218
+msgid ""
+"Use <a href=\"http://memcached.org/\">Memcached</a> to cache query results."
+msgstr ""
+"クエリー結果のキャッシュに <a href=\"http://memcached.org/\">Memcached</a> を"
+"使う"
diff --git a/plugins/Meteor/MeteorPlugin.php b/plugins/Meteor/MeteorPlugin.php
index a48c52b56..1bdccae7a 100644
--- a/plugins/Meteor/MeteorPlugin.php
+++ b/plugins/Meteor/MeteorPlugin.php
@@ -89,7 +89,7 @@ class MeteorPlugin extends RealtimePlugin
{
$scripts = parent::_getScripts();
$scripts[] = 'http://'.$this->webserver.(($this->webport == 80) ? '':':'.$this->webport).'/meteor.js';
- $scripts[] = common_path('plugins/Meteor/meteorupdater.js');
+ $scripts[] = common_path('plugins/Meteor/meteorupdater.min.js');
return $scripts;
}
diff --git a/plugins/Meteor/meteorupdater.min.js b/plugins/Meteor/meteorupdater.min.js
new file mode 100644
index 000000000..61928ab4f
--- /dev/null
+++ b/plugins/Meteor/meteorupdater.min.js
@@ -0,0 +1 @@
+var MeteorUpdater=function(){return{init:function(c,a,b){Meteor.callbacks.process=function(d){RealtimeUpdate.receive(JSON.parse(d))};Meteor.host=c;Meteor.port=a;Meteor.joinChannel(b,0);Meteor.connect()}}}(); \ No newline at end of file
diff --git a/plugins/NoticeTitle/locale/pl/LC_MESSAGES/NoticeTitle.po b/plugins/NoticeTitle/locale/pl/LC_MESSAGES/NoticeTitle.po
new file mode 100644
index 000000000..64b208fa5
--- /dev/null
+++ b/plugins/NoticeTitle/locale/pl/LC_MESSAGES/NoticeTitle.po
@@ -0,0 +1,33 @@
+# Translation of StatusNet - NoticeTitle to Polish (Polski)
+# Expored from translatewiki.net
+#
+# Author: Raven
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - NoticeTitle\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:28:38+0000\n"
+"Language-Team: Polish <http://translatewiki.net/wiki/Portal:pl>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:13:51+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: pl\n"
+"X-Message-Group: #out-statusnet-plugin-noticetitle\n"
+"Plural-Forms: nplurals=3; plural=(n == 1) ? 0 : ( (n%10 >= 2 && n%10 <= 4 && "
+"(n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"
+
+#: NoticeTitlePlugin.php:132
+msgid "Adds optional titles to notices."
+msgstr ""
+
+#. TRANS: Page title. %1$s is the title, %2$s is the site name.
+#: NoticeTitlePlugin.php:309
+#, php-format
+msgid "%1$s - %2$s"
+msgstr "%1$s - %2$s"
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 03fcb71df..b43a2b5f1 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -188,10 +188,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 +405,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 +429,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 +557,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 +590,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 +843,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 +981,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 +1041,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 +1052,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 +1062,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 +1345,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 +1356,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 +1364,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 +1803,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/OpenX/locale/br/LC_MESSAGES/OpenX.po b/plugins/OpenX/locale/br/LC_MESSAGES/OpenX.po
new file mode 100644
index 000000000..c0f138fa0
--- /dev/null
+++ b/plugins/OpenX/locale/br/LC_MESSAGES/OpenX.po
@@ -0,0 +1,109 @@
+# Translation of StatusNet - OpenX to Breton (Brezhoneg)
+# Expored from translatewiki.net
+#
+# Author: Y-M D
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - OpenX\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:45+0000\n"
+"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:13:54+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: br\n"
+"X-Message-Group: #out-statusnet-plugin-openx\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANS: Menu item title/tooltip
+#: OpenXPlugin.php:201
+msgid "OpenX configuration"
+msgstr "Kefluniadur OpenX"
+
+#. TRANS: Menu item for site administration
+#: OpenXPlugin.php:203
+msgid "OpenX"
+msgstr "OpenX"
+
+#. TRANS: Plugin description.
+#: OpenXPlugin.php:224
+msgid "Plugin for <a href=\"http://www.openx.org/\">OpenX Ad Server</a>."
+msgstr ""
+
+#. TRANS: Page title for OpenX admin panel.
+#: openxadminpanel.php:53
+msgctxt "TITLE"
+msgid "OpenX"
+msgstr "OpenX"
+
+#. TRANS: Instructions for OpenX admin panel.
+#: openxadminpanel.php:64
+msgid "OpenX settings for this StatusNet site"
+msgstr "Arventennoù OpenX evit al lec'hienn StatusNet-mañ."
+
+#. TRANS: Form label in OpenX admin panel.
+#: openxadminpanel.php:167
+msgid "Ad script URL"
+msgstr ""
+
+#. TRANS: Tooltip for form label in OpenX admin panel.
+#: openxadminpanel.php:169
+msgid "Script URL"
+msgstr ""
+
+#. TRANS: Form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:175
+msgid "Medium rectangle"
+msgstr ""
+
+#. TRANS: Tooltip for form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:177
+msgid "Medium rectangle zone"
+msgstr ""
+
+#. TRANS: Form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:183
+msgid "Rectangle"
+msgstr "Skouergornek"
+
+#. TRANS: Tooltip for form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:185
+msgid "Rectangle zone"
+msgstr ""
+
+#. TRANS: Form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:191
+msgid "Leaderboard"
+msgstr ""
+
+#. TRANS: Tooltip for form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:193
+msgid "Leaderboard zone"
+msgstr ""
+
+#. TRANS: Form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:199
+msgid "Skyscraper"
+msgstr "Giton a-serzh"
+
+#. TRANS: Tooltip for form label in OpenX admin panel. Refers to advertisement format.
+#: openxadminpanel.php:201
+msgid "Wide skyscraper zone"
+msgstr ""
+
+#. TRANS: Submit button text in OpenX admin panel.
+#: openxadminpanel.php:216
+msgctxt "BUTTON"
+msgid "Save"
+msgstr "Enrollañ"
+
+#. TRANS: Submit button title in OpenX admin panel.
+#: openxadminpanel.php:220
+msgid "Save OpenX settings"
+msgstr ""
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..113187e1e 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;
@@ -323,7 +322,33 @@ class RealtimePlugin extends Plugin
function _getScripts()
{
- return array('plugins/Realtime/realtimeupdate.js');
+ return array('plugins/Realtime/realtimeupdate.min.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)
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/af/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/af/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..81614a28e
--- /dev/null
+++ b/plugins/Realtime/locale/af/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Afrikaans (Afrikaans)
+# Expored from translatewiki.net
+#
+# Author: Naudefj
+# --
+# 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-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:59+0000\n"
+"Language-Team: Afrikaans <http://translatewiki.net/wiki/Portal:af>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-05 00:29:27+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: af\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 "Speel"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Speel"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Wag"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Wag"
+
+#. 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 "Wys in 'n venstertjie"
diff --git a/plugins/Realtime/locale/br/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/br/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..d30b98ec5
--- /dev/null
+++ b/plugins/Realtime/locale/br/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Breton (Brezhoneg)
+# Expored from translatewiki.net
+#
+# Author: Y-M D
+# --
+# 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-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:59+0000\n"
+"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-05 00:29:27+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: br\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 "Lenn"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Lenn"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Ehan"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Ehan"
+
+#. 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/locale/tr/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/tr/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..fb235468a
--- /dev/null
+++ b/plugins/Realtime/locale/tr/LC_MESSAGES/Realtime.po
@@ -0,0 +1,58 @@
+# Translation of StatusNet - Realtime to Turkish (Türkçe)
+# Expored from translatewiki.net
+#
+# Author: Maidis
+# --
+# 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-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:28:59+0000\n"
+"Language-Team: Turkish <http://translatewiki.net/wiki/Portal:tr>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-11-05 00:29:27+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: tr\n"
+"X-Message-Group: #out-statusnet-plugin-realtime\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
+#: RealtimePlugin.php:339
+msgctxt "BUTTON"
+msgid "Play"
+msgstr "Oynat"
+
+#. TRANS: Tooltip for realtime view "play" button.
+#: RealtimePlugin.php:341
+msgctxt "TOOLTIP"
+msgid "Play"
+msgstr "Oynat"
+
+#. TRANS: Text label for realtime view "pause" button
+#: RealtimePlugin.php:343
+msgctxt "BUTTON"
+msgid "Pause"
+msgstr "Duraklat"
+
+#. TRANS: Tooltip for realtime view "pause" button
+#: RealtimePlugin.php:345
+msgctxt "TOOLTIP"
+msgid "Pause"
+msgstr "Duraklat"
+
+#. 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/uk/LC_MESSAGES/Realtime.po b/plugins/Realtime/locale/uk/LC_MESSAGES/Realtime.po
new file mode 100644
index 000000000..f15b29127
--- /dev/null
+++ b/plugins/Realtime/locale/uk/LC_MESSAGES/Realtime.po
@@ -0,0 +1,59 @@
+# Translation of StatusNet - Realtime to Ukrainian (Українська)
+# Expored from translatewiki.net
+#
+# Author: Boogie
+# --
+# 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-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:00+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-11-02 23:07:29+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-realtime\n"
+"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
+"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\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/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/Realtime/realtimeupdate.min.js b/plugins/Realtime/realtimeupdate.min.js
new file mode 100644
index 000000000..a06c03c4e
--- /dev/null
+++ b/plugins/Realtime/realtimeupdate.min.js
@@ -0,0 +1 @@
+RealtimeUpdate={_userid:0,_replyurl:"",_favorurl:"",_repeaturl:"",_deleteurl:"",_updatecounter:0,_maxnotices:50,_windowhasfocus:true,_documenttitle:"",_paused:false,_queuedNotices:[],init:function(c,b,d,e,a){RealtimeUpdate._userid=c;RealtimeUpdate._replyurl=b;RealtimeUpdate._favorurl=d;RealtimeUpdate._repeaturl=e;RealtimeUpdate._deleteurl=a;RealtimeUpdate._documenttitle=document.title;$(window).bind("focus",function(){RealtimeUpdate._windowhasfocus=true});$(window).bind("blur",function(){$("#notices_primary .notice").removeClass("mark-top");$("#notices_primary .notice:first").addClass("mark-top");RealtimeUpdate._updatecounter=0;document.title=RealtimeUpdate._documenttitle;RealtimeUpdate._windowhasfocus=false;return false})},receive:function(a){if(RealtimeUpdate._paused===false){RealtimeUpdate.purgeLastNoticeItem();RealtimeUpdate.insertNoticeItem(a)}else{RealtimeUpdate._queuedNotices.push(a);RealtimeUpdate.updateQueuedCounter()}RealtimeUpdate.updateWindowCounter()},insertNoticeItem:function(a){if($("#notice-"+a.id).length>0){return}var b=RealtimeUpdate.makeNoticeItem(a);var c=$(b).attr("id");$("#notices_primary .notices").prepend(b);$("#notices_primary .notice:first").css({display:"none"});$("#notices_primary .notice:first").fadeIn(1000);SN.U.NoticeReplyTo($("#"+c));SN.U.NoticeWithAttachment($("#"+c))},purgeLastNoticeItem:function(){if($("#notices_primary .notice").length>RealtimeUpdate._maxnotices){$("#notices_primary .notice:last").remove()}},updateWindowCounter:function(){if(RealtimeUpdate._windowhasfocus===false){RealtimeUpdate._updatecounter+=1;document.title="("+RealtimeUpdate._updatecounter+") "+RealtimeUpdate._documenttitle}},makeNoticeItem:function(c){if(c.hasOwnProperty("retweeted_status")){original=c.retweeted_status;repeat=c;c=original;unique=repeat.id;responsible=repeat.user}else{original=null;repeat=null;unique=c.id;responsible=c.user}user=c.user;html=c.html.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&amp;/g,"&");source=c.source.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&amp;/g,"&");ni='<li class="hentry notice" id="notice-'+unique+'"><div class="entry-title"><span class="vcard author"><a href="'+user.profile_url+'" class="url" title="'+user.name+'"><img src="'+user.profile_image_url+'" class="avatar photo" width="48" height="48" alt="'+user.screen_name+'"/><span class="nickname fn">'+user.screen_name+'</span></a></span><p class="entry-content">'+html+'</p></div><div class="entry-content"><a class="timestamp" rel="bookmark" href="'+c.url+'" ><abbr class="published" title="'+c.created_at+'">a few seconds ago</abbr></a> <span class="source">from <span class="device">'+source+"</span></span>";if(c.conversation_url){ni=ni+' <a class="response" href="'+c.conversation_url+'">in context</a>'}if(repeat){ru=repeat.user;ni=ni+'<span class="repeat vcard">Repeated by <a href="'+ru.profile_url+'" class="url"><span class="nickname">'+ru.screen_name+"</span></a></span>"}ni=ni+"</div>";ni=ni+'<div class="notice-options">';if(RealtimeUpdate._userid!=0){var a=$("form#form_notice fieldset input#token");var b=a.val();ni=ni+RealtimeUpdate.makeFavoriteForm(c.id,b);ni=ni+RealtimeUpdate.makeReplyLink(c.id,c.user["screen_name"]);if(RealtimeUpdate._userid==responsible.id){ni=ni+RealtimeUpdate.makeDeleteLink(c.id)}else{if(RealtimeUpdate._userid!=user.id){ni=ni+RealtimeUpdate.makeRepeatForm(c.id,b)}}}ni=ni+"</div>";ni=ni+"</li>";return ni},makeFavoriteForm:function(c,b){var a;a='<form id="favor-'+c+'" class="form_favor" method="post" action="'+RealtimeUpdate._favorurl+'"><fieldset><legend>Favor this notice</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-n'+c+'" value="'+c+'"/><input type="submit" id="favor-submit-'+c+'" name="favor-submit-'+c+'" class="submit" value="Favor" title="Favor this notice"/></fieldset></form>';return a},makeReplyLink:function(c,a){var b;b='<a class="notice_reply" href="'+RealtimeUpdate._replyurl+"?replyto="+a+'" title="Reply to this notice">Reply <span class="notice_id">'+c+"</span></a>";return b},makeRepeatForm:function(c,b){var a;a='<form id="repeat-'+c+'" class="form_repeat" method="post" action="'+RealtimeUpdate._repeaturl+'"><fieldset><legend>Repeat this notice?</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-'+c+'" value="'+c+'"/><input type="submit" id="repeat-submit-'+c+'" name="repeat-submit-'+c+'" class="submit" value="Yes" title="Repeat this notice"/></fieldset></form>';return a},makeDeleteLink:function(c){var b,a;a=RealtimeUpdate._deleteurl.replace("0000000000",c);b='<a class="notice_delete" href="'+a+'" title="Delete this notice">Delete</a>';return b},initActions:function(a,b,c){$("#notices_primary").prepend('<ul id="realtime_actions"><li id="realtime_playpause"></li><li id="realtime_timeline"></li></ul>');RealtimeUpdate._pluginPath=c;RealtimeUpdate.initPlayPause();RealtimeUpdate.initAddPopup(a,b,RealtimeUpdate._pluginPath)},initPlayPause:function(){if(typeof(localStorage)=="undefined"){RealtimeUpdate.showPause()}else{if(localStorage.getItem("RealtimeUpdate_paused")==="true"){RealtimeUpdate.showPlay()}else{RealtimeUpdate.showPause()}}},showPause:function(){RealtimeUpdate.setPause(false);RealtimeUpdate.showQueuedNotices();RealtimeUpdate.addNoticesHover();$("#realtime_playpause").remove();$("#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})},showPlay:function(){RealtimeUpdate.setPause(true);$("#realtime_playpause").remove();$("#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})},setPause:function(a){RealtimeUpdate._paused=a;if(typeof(localStorage)!="undefined"){localStorage.setItem("RealtimeUpdate_paused",RealtimeUpdate._paused)}},showQueuedNotices:function(){$.each(RealtimeUpdate._queuedNotices,function(a,b){RealtimeUpdate.insertNoticeItem(b)});RealtimeUpdate._queuedNotices=[];RealtimeUpdate.removeQueuedCounter()},updateQueuedCounter:function(){$("#realtime_playpause #queued_counter").html("("+RealtimeUpdate._queuedNotices.length+")")},removeQueuedCounter:function(){$("#realtime_playpause #queued_counter").empty()},addNoticesHover:function(){$("#notices_primary .notices").hover(function(){if(RealtimeUpdate._paused===false){RealtimeUpdate.showPlay()}},function(){if(RealtimeUpdate._paused===true){RealtimeUpdate.showPause()}})},removeNoticesHover:function(){$("#notices_primary .notices").unbind()},initAddPopup:function(a,b,c){$("#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(a,"","toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550");return false})},initPopupWindow:function(){$(".notices .entry-title a, .notices .entry-content a").bind("click",function(){window.open(this.href,"");return false});$("#showstream .entity_profile").css({width:"69%"})}}; \ No newline at end of file
diff --git a/plugins/Sample/locale/br/LC_MESSAGES/Sample.po b/plugins/Sample/locale/br/LC_MESSAGES/Sample.po
index 2ac1405de..0702b07c3 100644
--- a/plugins/Sample/locale/br/LC_MESSAGES/Sample.po
+++ b/plugins/Sample/locale/br/LC_MESSAGES/Sample.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Sample\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:31+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:05+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-20 17:58:22+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:01+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-sample\n"
@@ -26,14 +26,14 @@ msgstr ""
#: User_greeting_count.php:164
#, php-format
msgid "Could not save new greeting count for %d."
-msgstr ""
+msgstr "Dibosupl eo enrollañ ar gont degemer nevez evit an implijer %d."
#. TRANS: Exception thrown when the user greeting count could not be saved in the database.
#. TRANS: %d is a user ID (number).
#: User_greeting_count.php:177
#, php-format
msgid "Could not increment greeting count for %d."
-msgstr ""
+msgstr "Dibosupl eo inkremantañ ar gont degemer nevez evit an implijer %d."
#: SamplePlugin.php:259 hello.php:111
msgid "Hello"
@@ -41,11 +41,13 @@ msgstr "Demat"
#: SamplePlugin.php:259
msgid "A warm greeting"
-msgstr ""
+msgstr "Un degemer tomm"
#: SamplePlugin.php:270
msgid "A sample plugin to show basics of development for new hackers."
msgstr ""
+"Ur skouer a lugant evit diskouez an diazezoù diorren evit ar c'hoderien "
+"nevez."
#: hello.php:113
#, php-format
@@ -65,5 +67,5 @@ msgstr "Demat, %s"
#, php-format
msgid "I have greeted you %d time."
msgid_plural "I have greeted you %d times."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "ur"
+msgstr[1] "%d"
diff --git a/plugins/Sitemap/locale/br/LC_MESSAGES/Sitemap.po b/plugins/Sitemap/locale/br/LC_MESSAGES/Sitemap.po
index 02dea72ee..73ebd322f 100644
--- a/plugins/Sitemap/locale/br/LC_MESSAGES/Sitemap.po
+++ b/plugins/Sitemap/locale/br/LC_MESSAGES/Sitemap.po
@@ -2,6 +2,7 @@
# Expored from translatewiki.net
#
# Author: Fulup
+# Author: Y-M D
# --
# This file is distributed under the same license as the StatusNet package.
#
@@ -9,13 +10,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Sitemap\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:40+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:07+0000\n"
"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-18 20:34:11+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:04+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-sitemap\n"
@@ -54,7 +55,7 @@ msgstr ""
#. TRANS: Field label.
#: sitemapadminpanel.php:183
msgid "Bing key"
-msgstr ""
+msgstr "Alc'hwez Bing"
#. TRANS: Title for field label.
#: sitemapadminpanel.php:185
diff --git a/plugins/TabFocus/locale/br/LC_MESSAGES/TabFocus.po b/plugins/TabFocus/locale/br/LC_MESSAGES/TabFocus.po
new file mode 100644
index 000000000..b9b454898
--- /dev/null
+++ b/plugins/TabFocus/locale/br/LC_MESSAGES/TabFocus.po
@@ -0,0 +1,32 @@
+# Translation of StatusNet - TabFocus to Breton (Brezhoneg)
+# Expored from translatewiki.net
+#
+# Author: Y-M D
+# --
+# This file is distributed under the same license as the StatusNet package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: StatusNet - TabFocus\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:11+0000\n"
+"Language-Team: Breton <http://translatewiki.net/wiki/Portal:br>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-POT-Import-Date: 2010-10-29 16:14:08+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
+"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
+"X-Language-Code: br\n"
+"X-Message-Group: #out-statusnet-plugin-tabfocus\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: TabFocusPlugin.php:54
+msgid ""
+"TabFocus changes the notice form behavior so that, while in the text area, "
+"pressing the tab key focuses the \"Send\" button, matching the behavior of "
+"Twitter."
+msgstr ""
+"TabFocus a gemm emzalc'h ar furmskrid kemennoù evit ma vefe kaset ar fokus "
+"war ar bouton \"Kas\" pa bouezer war a stokell taolennata adalek ar zonenn "
+"testenn, ar pezh a glot gant emzalc'h Twitter."
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index b520a4544..b2dce6f1c 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/locale/TwitterBridge.pot b/plugins/TwitterBridge/locale/TwitterBridge.pot
index 2fc96dfe2..61a1b6fb1 100644
--- a/plugins/TwitterBridge/locale/TwitterBridge.pot
+++ b/plugins/TwitterBridge/locale/TwitterBridge.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-04 18:25+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"
@@ -16,6 +16,13 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr ""
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr ""
@@ -37,7 +44,7 @@ msgid ""
msgstr ""
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr ""
@@ -49,11 +56,11 @@ msgstr ""
msgid "Twitter integration options"
msgstr ""
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr ""
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -354,17 +361,10 @@ msgstr ""
msgid "Twitter account disconnected."
msgstr ""
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr ""
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr ""
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr ""
diff --git a/plugins/TwitterBridge/locale/fr/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/fr/LC_MESSAGES/TwitterBridge.po
index 988b2577d..0192705db 100644
--- a/plugins/TwitterBridge/locale/fr/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/fr/LC_MESSAGES/TwitterBridge.po
@@ -10,18 +10,25 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:18+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:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-twitterbridge\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "Votre passerelle Twitter a été désactivée."
@@ -55,7 +62,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -67,11 +74,11 @@ msgstr "Se connecter ou s’inscrire via Twitter"
msgid "Twitter integration options"
msgstr "Options d’intégration de Twitter"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Configuration de la passerelle Twitter"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -399,17 +406,10 @@ msgstr "Impossible de supprimer l’utilisateur Twitter."
msgid "Twitter account disconnected."
msgstr "Compte Twitter déconnecté."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Impossible de sauvegarder les préférences Twitter."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "Préférences Twitter enregistrées."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
diff --git a/plugins/TwitterBridge/locale/ia/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/ia/LC_MESSAGES/TwitterBridge.po
index c655e8e9c..de5b00c85 100644
--- a/plugins/TwitterBridge/locale/ia/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/ia/LC_MESSAGES/TwitterBridge.po
@@ -9,18 +9,25 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:18+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:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-twitterbridge\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "Tu ponte a Twitter ha essite disactivate."
@@ -53,7 +60,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -65,11 +72,11 @@ msgstr "Aperir session o crear conto usante Twitter"
msgid "Twitter integration options"
msgstr "Optiones de integration de Twitter"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Configuration del ponte a Twitter"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -388,17 +395,10 @@ msgstr "Non poteva remover le usator de Twitter."
msgid "Twitter account disconnected."
msgstr "Conto de Twitter disconnectite."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Non poteva salveguardar le preferentias de Twitter."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "Preferentias de Twitter salveguardate."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
diff --git a/plugins/TwitterBridge/locale/mk/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/mk/LC_MESSAGES/TwitterBridge.po
index 88f3a2eb2..31f166286 100644
--- a/plugins/TwitterBridge/locale/mk/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/mk/LC_MESSAGES/TwitterBridge.po
@@ -9,18 +9,25 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:21+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:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-twitterbridge\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "Вашиот мост до Twitter е оневозможен."
@@ -53,7 +60,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -65,11 +72,11 @@ msgstr "Најава или регистрација со Twitter"
msgid "Twitter integration options"
msgstr "Нагодувања за обединување со Twitter"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Нагодувања за мостот до Twitter"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -391,17 +398,10 @@ msgstr "Не можев да го отстранам корисникот на T
msgid "Twitter account disconnected."
msgstr "Врската со сметката на Twitter е прекината."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Не можев да ги зачувам нагодувањата за Twitter."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "Нагодувањата за Twitter се зачувани."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
diff --git a/plugins/TwitterBridge/locale/nl/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/nl/LC_MESSAGES/TwitterBridge.po
index 8e152f405..38d1e5472 100644
--- a/plugins/TwitterBridge/locale/nl/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/nl/LC_MESSAGES/TwitterBridge.po
@@ -9,18 +9,25 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:21+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:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-twitterbridge\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "Uw koppeling naar Twitter is uitgeschakeld."
@@ -55,7 +62,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -67,11 +74,11 @@ msgstr "Aanmelden of registreren via Twitter"
msgid "Twitter integration options"
msgstr "Opties voor Twitterintegratie"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Instellingen voor Twitterkoppeling"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -398,17 +405,10 @@ msgstr "Het was niet mogelijk de Twittergebruiker te verwijderen."
msgid "Twitter account disconnected."
msgstr "De Twittergebruiker is ontkoppeld."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Het was niet mogelijk de Twittervoorkeuren op te slaan."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "De Twitterinstellingen zijn opgeslagen."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
diff --git a/plugins/TwitterBridge/locale/tr/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/tr/LC_MESSAGES/TwitterBridge.po
index 76e7e8ecb..3d25b3715 100644
--- a/plugins/TwitterBridge/locale/tr/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/tr/LC_MESSAGES/TwitterBridge.po
@@ -9,18 +9,25 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:21+0000\n"
"Language-Team: Turkish <http://translatewiki.net/wiki/Portal:tr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: tr\n"
"X-Message-Group: #out-statusnet-plugin-twitterbridge\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr ""
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr ""
@@ -42,7 +49,7 @@ msgid ""
msgstr ""
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -54,11 +61,11 @@ msgstr ""
msgid "Twitter integration options"
msgstr "Twitter entegrasyon seçenekleri"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Twitter köprü yapılandırması"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -372,17 +379,10 @@ msgstr "Twitter kullanıcısı silinemedi."
msgid "Twitter account disconnected."
msgstr "Twitter hesabı bağlantısı kesildi."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Twitter tercihleri kaydedilemedi."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "Twitter tercihleriniz kaydedildi."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr ""
diff --git a/plugins/TwitterBridge/locale/uk/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/uk/LC_MESSAGES/TwitterBridge.po
index 2ee15e69b..2eb66a514 100644
--- a/plugins/TwitterBridge/locale/uk/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/uk/LC_MESSAGES/TwitterBridge.po
@@ -9,19 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:52+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:21+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:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); 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-twitterbridge\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "Ваш місток до Twitter було відключено."
@@ -55,7 +62,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -67,11 +74,11 @@ msgstr "Увійти або зареєструватись з Twitter"
msgid "Twitter integration options"
msgstr "Параметри інтеграції з Twitter"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Налаштування містка з Twitter"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -394,17 +401,10 @@ msgstr "Не вдається видалити користувача Twitter."
msgid "Twitter account disconnected."
msgstr "Акаунт Twitter від’єднано."
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "Не можу зберегти налаштування Twitter."
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "Налаштування Twitter збережено."
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
diff --git a/plugins/TwitterBridge/locale/zh_CN/LC_MESSAGES/TwitterBridge.po b/plugins/TwitterBridge/locale/zh_CN/LC_MESSAGES/TwitterBridge.po
index 54804a728..9208a4954 100644
--- a/plugins/TwitterBridge/locale/zh_CN/LC_MESSAGES/TwitterBridge.po
+++ b/plugins/TwitterBridge/locale/zh_CN/LC_MESSAGES/TwitterBridge.po
@@ -9,19 +9,26 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - TwitterBridge\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:53+0000\n"
+"POT-Creation-Date: 2010-11-04 18:25+0000\n"
+"PO-Revision-Date: 2010-11-04 18:29:22+0000\n"
"Language-Team: Simplified Chinese <http://translatewiki.net/wiki/Portal:zh-"
"hans>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:01:06+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-10-29 16:14:11+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76004); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-twitterbridge\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+#. 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.
+#: twitterimport.php:113
+#, php-format
+msgid "RT @%1$s %2$s"
+msgstr "RT @%1$s %2$s"
+
#: twitter.php:349
msgid "Your Twitter bridge has been disabled."
msgstr "你的 Twitter bridge 已被禁用。"
@@ -52,7 +59,7 @@ msgstr ""
"%3$s"
#: TwitterBridgePlugin.php:151 TwitterBridgePlugin.php:174
-#: TwitterBridgePlugin.php:291 twitteradminpanel.php:52
+#: TwitterBridgePlugin.php:302 twitteradminpanel.php:52
msgid "Twitter"
msgstr "Twitter"
@@ -64,11 +71,11 @@ msgstr "使用 Twitter 登录或注册"
msgid "Twitter integration options"
msgstr "Twitter 整合选项"
-#: TwitterBridgePlugin.php:292
+#: TwitterBridgePlugin.php:303
msgid "Twitter bridge configuration"
msgstr "Twitter bridge 设置"
-#: TwitterBridgePlugin.php:316
+#: TwitterBridgePlugin.php:327
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with <a href=\"http://twitter.com/\">Twitter</a>."
@@ -376,17 +383,10 @@ msgstr "无法删除 Twitter 用户。"
msgid "Twitter account disconnected."
msgstr "已取消 Twitter 帐号关联。"
-#: twittersettings.php:283 twittersettings.php:293
+#: twittersettings.php:283 twittersettings.php:294
msgid "Couldn't save Twitter preferences."
msgstr "无法保存 Twitter 参数设置。"
-#: twittersettings.php:297
+#: twittersettings.php:302
msgid "Twitter preferences saved."
msgstr "已保存 Twitter 参数设置。"
-
-#. 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.
-#: daemons/twitterstatusfetcher.php:264
-#, php-format
-msgid "RT @%1$s %2$s"
-msgstr "RT @%1$s %2$s"
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..9e53849d8
--- /dev/null
+++ b/plugins/TwitterBridge/twitterimport.php
@@ -0,0 +1,670 @@
+<?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);
+ $this->saveStatusAttachments($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();
+ }
+ }
+ }
+ }
+
+ /**
+ * Record URL links from the notice. Needed to get thumbnail records
+ * for referenced photo and video posts, etc.
+ *
+ * @param Notice $notice
+ * @param object $status
+ */
+ function saveStatusAttachments($notice, $status)
+ {
+ if (common_config('attachments', 'process_links')) {
+ if (!empty($status->entities) && !empty($status->entities->urls)) {
+ foreach ($status->entities->urls as $url) {
+ File::processNew($url->url, $notice->id);
+ }
+ }
+ }
+ }
+} \ 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/UserFlag/locale/UserFlag.pot b/plugins/UserFlag/locale/UserFlag.pot
index decb06d04..c70c7988e 100644
--- a/plugins/UserFlag/locale/UserFlag.pot
+++ b/plugins/UserFlag/locale/UserFlag.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-04 18:25+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"
@@ -44,23 +44,19 @@ msgstr[1] ""
msgid "Flagged by %s"
msgstr ""
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr ""
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr ""
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr ""
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -93,7 +89,7 @@ msgid "Flag profile for review."
msgstr ""
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr ""
diff --git a/plugins/UserFlag/locale/fr/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/fr/LC_MESSAGES/UserFlag.po
index 277ce5650..f2c84969a 100644
--- a/plugins/UserFlag/locale/fr/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/fr/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+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:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-userflag\n"
@@ -48,23 +48,19 @@ msgstr[1] "Marqué par %1$s et %2$d autres"
msgid "Flagged by %s"
msgstr "Marqué par %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "Déjà marqué."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Marqué pour vérification"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Marqué"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -99,7 +95,7 @@ msgid "Flag profile for review."
msgstr "Marquer le profil pour vérification."
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Impossible de marquer le profil « %d » pour vérification."
diff --git a/plugins/UserFlag/locale/ia/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/ia/LC_MESSAGES/UserFlag.po
index d58458b05..2917da732 100644
--- a/plugins/UserFlag/locale/ia/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/ia/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+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:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-userflag\n"
@@ -48,23 +48,19 @@ msgstr[1] "Marcate per %1$s e %2$d alteres"
msgid "Flagged by %s"
msgstr "Marcate per %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "Le marca ja existe."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Marcate pro revision"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Marcate"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -98,7 +94,7 @@ msgid "Flag profile for review."
msgstr "Marcar profilo pro revision."
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Non poteva marcar profilo \"%d\" pro revision."
diff --git a/plugins/UserFlag/locale/mk/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/mk/LC_MESSAGES/UserFlag.po
index ab9c1b2e4..8d934141e 100644
--- a/plugins/UserFlag/locale/mk/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/mk/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+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:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-userflag\n"
@@ -48,23 +48,19 @@ msgstr[1] "Означено од %1$s и уште %2$d други"
msgid "Flagged by %s"
msgstr "Означено од %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "Ознаката веќе постои."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Означено за преглед"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Означено"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -99,7 +95,7 @@ msgid "Flag profile for review."
msgstr "Означи профил за преглед."
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Не можев да го означам профилот „%d“ за преглед."
diff --git a/plugins/UserFlag/locale/nl/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/nl/LC_MESSAGES/UserFlag.po
index 115b63433..766776a9e 100644
--- a/plugins/UserFlag/locale/nl/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/nl/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+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:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-userflag\n"
@@ -48,23 +48,19 @@ msgstr[1] "Gemarkeerd door %1$s en %2$d anderen"
msgid "Flagged by %s"
msgstr "Gemarkeerd door %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "De markering bestaat al."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Gemarkeerd voor controle"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Gemarkeerd"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -100,7 +96,7 @@ msgid "Flag profile for review."
msgstr "Profiel voor controle markeren"
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Het was niet mogelijk het profiel \"%d\" voor controle te markeren."
diff --git a/plugins/UserFlag/locale/pt/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/pt/LC_MESSAGES/UserFlag.po
index c476cf2f7..ea71be24e 100644
--- a/plugins/UserFlag/locale/pt/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/pt/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+0000\n"
"Language-Team: Portuguese <http://translatewiki.net/wiki/Portal:pt>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-POT-Import-Date: 2010-10-23 19:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: pt\n"
"X-Message-Group: #out-statusnet-plugin-userflag\n"
@@ -48,23 +48,19 @@ msgstr[1] "Sinalizado por %1$s e mais %2$d pessoas"
msgid "Flagged by %s"
msgstr "Sinalizado por %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "Já existe uma sinalização."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Sinalizado para análise"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Sinalizado"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -99,7 +95,7 @@ msgid "Flag profile for review."
msgstr "Sinalizar perfil para análise."
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Não foi possível sinalizar o perfil \"%d\" para análise."
diff --git a/plugins/UserFlag/locale/uk/LC_MESSAGES/UserFlag.po b/plugins/UserFlag/locale/uk/LC_MESSAGES/UserFlag.po
index c6dd6e40d..ae0e9b03f 100644
--- a/plugins/UserFlag/locale/uk/LC_MESSAGES/UserFlag.po
+++ b/plugins/UserFlag/locale/uk/LC_MESSAGES/UserFlag.po
@@ -9,13 +9,13 @@ msgid ""
msgstr ""
"Project-Id-Version: StatusNet - UserFlag\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-10-27 23:43+0000\n"
-"PO-Revision-Date: 2010-10-27 23:47:54+0000\n"
+"POT-Creation-Date: 2010-11-07 20:25+0000\n"
+"PO-Revision-Date: 2010-11-07 20:29:20+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:01:50+0000\n"
-"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
+"X-POT-Import-Date: 2010-11-05 00:29:34+0000\n"
+"X-Generator: MediaWiki 1.17alpha (r76266); 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-userflag\n"
@@ -50,23 +50,19 @@ msgstr[2] "Відмічено %1$s та ще %2$d користувачами"
msgid "Flagged by %s"
msgstr "Відмічено %s"
-#. TRANS: Client error when setting flag that has already been set for a profile.
-#: flagprofile.php:66
-msgid "Flag already exists."
-msgstr "Відмітка вже стоїть."
-
#. TRANS: AJAX form title for a flagged profile.
-#: flagprofile.php:126
+#: flagprofile.php:125
msgid "Flagged for review"
msgstr "Відмічені для розгляду"
#. TRANS: Body text for AJAX form when a profile has been flagged for review.
-#: flagprofile.php:130
+#. TRANS: Message added to a profile if it has been flagged for review.
+#: flagprofile.php:129 UserFlagPlugin.php:173
msgid "Flagged"
msgstr "Відмічені"
#. TRANS: Plugin description.
-#: UserFlagPlugin.php:292
+#: UserFlagPlugin.php:294
msgid ""
"This plugin allows flagging of profiles for review and reviewing flagged "
"profiles."
@@ -101,7 +97,7 @@ msgid "Flag profile for review."
msgstr "Відмітити профіль для розгляду."
#. TRANS: Server exception.
-#: User_flag_profile.php:145
+#: User_flag_profile.php:160
#, php-format
msgid "Couldn't flag profile \"%d\" for review."
msgstr "Не вдалося відмітити профіль «%d» для розгляду."
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."