diff options
35 files changed, 1937 insertions, 358 deletions
diff --git a/actions/newnotice.php b/actions/newnotice.php index 78480abab..ed0fa1b2b 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -294,6 +294,9 @@ class NewnoticeAction extends Action if ($profile) { $content = '@' . $profile->nickname . ' '; } + } else { + // @fixme most of these bits above aren't being passed on above + $inreplyto = null; } $notice_form = new NoticeForm($this, '', $content, null, $inreplyto); diff --git a/actions/postnotice.php b/actions/postnotice.php index fb0670376..b2f6f1bb9 100644 --- a/actions/postnotice.php +++ b/actions/postnotice.php @@ -54,7 +54,10 @@ class PostnoticeAction extends Action */ function prepare($argarray) { + StatusNet::setApi(true); // Send smaller error pages + parent::prepare($argarray); + try { $this->checkNotice(); } catch (Exception $e) { @@ -71,6 +74,14 @@ class PostnoticeAction extends Action $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), omb_oauth_server()); $srv->handlePostNotice(); + } catch (OMB_RemoteServiceException $rse) { + $msg = $rse->getMessage(); + if (preg_match('/Revoked accesstoken/', $msg) || + preg_match('/No subscriber/', $msg)) { + $this->clientError($msg, 403); + } else { + $this->clientError($msg); + } } catch (Exception $e) { $this->serverError($e->getMessage()); return; diff --git a/actions/updateprofile.php b/actions/updateprofile.php index e416a6fa9..bae6108cc 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -55,6 +55,8 @@ class UpdateprofileAction extends Action */ function prepare($argarray) { + StatusNet::setApi(true); // Send smaller error pages + parent::prepare($argarray); $license = $_POST['omb_listenee_license']; $site_license = common_config('license', 'url'); @@ -75,6 +77,14 @@ class UpdateprofileAction extends Action $srv = new OMB_Service_Provider(null, omb_oauth_datastore(), omb_oauth_server()); $srv->handleUpdateProfile(); + } catch (OMB_RemoteServiceException $rse) { + $msg = $rse->getMessage(); + if (preg_match('/Revoked accesstoken/', $msg) || + preg_match('/No subscriber/', $msg)) { + $this->clientError($msg, 403); + } else { + $this->clientError($msg); + } } catch (Exception $e) { $this->serverError($e->getMessage()); return; diff --git a/classes/Notice.php b/classes/Notice.php index 2d02a9a19..3702dbcfa 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -282,12 +282,6 @@ class Notice extends Memcached_DataObject $notice->content = $final; - if (!empty($rendered)) { - $notice->rendered = $rendered; - } else { - $notice->rendered = common_render_content($final, $notice); - } - $notice->source = $source; $notice->uri = $uri; $notice->url = $url; @@ -315,6 +309,12 @@ class Notice extends Memcached_DataObject $notice->location_ns = $location_ns; } + if (!empty($rendered)) { + $notice->rendered = $rendered; + } else { + $notice->rendered = common_render_content($final, $notice); + } + if (Event::handle('StartNoticeSave', array(&$notice))) { // XXX: some of these functions write to the DB @@ -973,7 +973,10 @@ class Notice extends Memcached_DataObject $sender = Profile::staticGet($this->profile_id); - $mentions = common_find_mentions($this->profile_id, $this->content); + // @todo ideally this parser information would only + // be calculated once. + + $mentions = common_find_mentions($this->content, $this); $replied = array(); diff --git a/classes/Subscription.php b/classes/Subscription.php index d6fb3fcbd..9cef2df1a 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -172,6 +172,28 @@ class Subscription extends Memcached_DataObject assert(!empty($sub)); + // @todo: move this block to EndSubscribe handler for + // OMB plugin when it exists. + + if (!empty($sub->token)) { + + $token = new Token(); + + $token->tok = $sub->token; + + if ($token->find(true)) { + + $result = $token->delete(); + + if (!$result) { + common_log_db_error($token, 'DELETE', __FILE__); + throw new Exception(_('Couldn\'t delete subscription OMB token.')); + } + } else { + common_log(LOG_ERR, "Couldn't find credentials with token {$token->tok}"); + } + } + $result = $sub->delete(); if (!$result) { diff --git a/db/08to09.sql b/db/08to09.sql index b10e47dbc..f30572154 100644 --- a/db/08to09.sql +++ b/db/08to09.sql @@ -110,3 +110,8 @@ insert into queue_item_new (frame,transport,created,claimed) alter table queue_item rename to queue_item_old; alter table queue_item_new rename to queue_item; +alter table file_to_post + add index post_id_idx (post_id); + +alter table group_inbox + add index group_inbox_notice_id_idx (notice_id); diff --git a/db/statusnet.sql b/db/statusnet.sql index 4158f0167..3f95948e1 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -458,7 +458,8 @@ create table group_inbox ( created datetime not null comment 'date the notice was created', constraint primary key (group_id, notice_id), - index group_inbox_created_idx (created) + index group_inbox_created_idx (created), + index group_inbox_notice_id_idx (notice_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; @@ -523,7 +524,8 @@ create table file_to_post ( post_id integer comment 'id of the notice it belongs to' references notice (id), modified timestamp comment 'date this record was modified', - constraint primary key (file_id, post_id) + constraint primary key (file_id, post_id), + index post_id_idx (post_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/action.php b/lib/action.php index a7e0eb33b..0918c6858 100644 --- a/lib/action.php +++ b/lib/action.php @@ -425,8 +425,6 @@ class Action extends HTMLOutputter // lawsuit $connect = 'imsettings'; } else if (common_config('sms', 'enabled')) { $connect = 'smssettings'; - } else if (common_config('twitter', 'enabled')) { - $connect = 'twittersettings'; } $this->elementStart('dl', array('id' => 'site_nav_global_primary')); diff --git a/lib/common.php b/lib/common.php index 2dbe3b3c5..546f6bbe4 100644 --- a/lib/common.php +++ b/lib/common.php @@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } //exit with 200 response, if this is checking fancy from the installer if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } -define('STATUSNET_VERSION', '0.9.0beta6'); +define('STATUSNET_VERSION', '0.9.0beta6+bugfix1'); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('STATUSNET_CODENAME', 'Stand'); diff --git a/lib/default.php b/lib/default.php index d849055c2..7b50242ae 100644 --- a/lib/default.php +++ b/lib/default.php @@ -177,8 +177,8 @@ $default = array('source' => 'StatusNet', # source attribute for Twitter 'taguri' => null), # base for tag URIs 'twitter' => - array('enabled' => true, - 'consumer_key' => null, + array('signin' => true, + 'consumer_key' => null, 'consumer_secret' => null), 'cache' => array('base' => null), diff --git a/lib/oauthstore.php b/lib/oauthstore.php index eabe37f9f..a6a6de750 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -390,7 +390,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore $sub->subscribed = $user->id; if (!$sub->find(true)) { - return 0; + return array(); } /* Since we do not use OMB_Service_Provider’s action methods, there diff --git a/lib/omb.php b/lib/omb.php index 17132a594..8bbe5e8aa 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -77,7 +77,7 @@ function omb_broadcast_notice($notice) /* Get remote users subscribed to this profile. */ $rp = new Remote_profile(); - $rp->query('SELECT postnoticeurl, token, secret ' . + $rp->query('SELECT remote_profile.*, secret, token ' . 'FROM subscription JOIN remote_profile ' . 'ON subscription.subscriber = remote_profile.id ' . 'WHERE subscription.subscribed = ' . $notice->profile_id . ' '); @@ -93,7 +93,8 @@ function omb_broadcast_notice($notice) /* Post notice. */ $service = new StatusNet_OMB_Service_Consumer( - array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl)); + array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl), + $rp->uri); try { $service->setToken($rp->token, $rp->secret); $service->postNotice($omb_notice); @@ -125,7 +126,7 @@ function omb_broadcast_profile($profile) /* Get remote users subscribed to this profile. */ $rp = new Remote_profile(); - $rp->query('SELECT updateprofileurl, token, secret ' . + $rp->query('SELECT remote_profile.*, secret, token ' . 'FROM subscription JOIN remote_profile ' . 'ON subscription.subscriber = remote_profile.id ' . 'WHERE subscription.subscribed = ' . $profile->id . ' '); @@ -141,7 +142,11 @@ function omb_broadcast_profile($profile) /* Update profile. */ $service = new StatusNet_OMB_Service_Consumer( - array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl)); + array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl), + $rp->uri); + + common_debug('service = ' . print_r($service, true)); + try { $service->setToken($rp->token, $rp->secret); $service->updateProfile($omb_profile); @@ -159,13 +164,14 @@ function omb_broadcast_profile($profile) } class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer { - public function __construct($urls) + public function __construct($urls, $listener_uri=null) { $this->services = $urls; $this->datastore = omb_oauth_datastore(); $this->oauth_consumer = omb_oauth_consumer(); $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); $this->fetcher->timeout = intval(common_config('omb', 'timeout')); + $this->listener_uri = $listener_uri; } } diff --git a/lib/util.php b/lib/util.php index 32061ec04..7a170a5f5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -426,14 +426,14 @@ function common_render_content($text, $notice) { $r = common_render_text($text); $id = $notice->profile_id; - $r = common_linkify_mentions($id, $r); + $r = common_linkify_mentions($r, $notice); $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); return $r; } -function common_linkify_mentions($profile_id, $text) +function common_linkify_mentions($text, $notice) { - $mentions = common_find_mentions($profile_id, $text); + $mentions = common_find_mentions($text, $notice); // We need to go through in reverse order by position, // so our positions stay valid despite our fudging with the @@ -487,11 +487,11 @@ function common_linkify_mention($mention) return $output; } -function common_find_mentions($profile_id, $text) +function common_find_mentions($text, $notice) { $mentions = array(); - $sender = Profile::staticGet('id', $profile_id); + $sender = Profile::staticGet('id', $notice->profile_id); if (empty($sender)) { return $mentions; @@ -499,6 +499,30 @@ function common_find_mentions($profile_id, $text) if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) { + // Get the context of the original notice, if any + + $originalAuthor = null; + $originalNotice = null; + $originalMentions = array(); + + // Is it a reply? + + if (!empty($notice) && !empty($notice->reply_to)) { + $originalNotice = Notice::staticGet('id', $notice->reply_to); + if (!empty($originalNotice)) { + $originalAuthor = Profile::staticGet('id', $originalNotice->profile_id); + + $ids = $originalNotice->getReplies(); + + foreach ($ids as $id) { + $repliedTo = Profile::staticGet('id', $id); + if (!empty($repliedTo)) { + $originalMentions[$repliedTo->nickname] = $repliedTo; + } + } + } + } + preg_match_all('/^T ([A-Z0-9]{1,64}) /', $text, $tmatches, @@ -514,7 +538,22 @@ function common_find_mentions($profile_id, $text) foreach ($matches as $match) { $nickname = common_canonical_nickname($match[0]); - $mentioned = common_relative_profile($sender, $nickname); + + // Try to get a profile for this nickname. + // Start with conversation context, then go to + // sender context. + + if (!empty($originalAuthor) && $originalAuthor->nickname == $nickname) { + + $mentioned = $originalAuthor; + + } else if (!empty($originalMentions) && + array_key_exists($nickname, $originalMentions)) { + + $mention = $originalMentions[$nickname]; + } else { + $mentioned = common_relative_profile($sender, $nickname); + } if (!empty($mentioned)) { @@ -770,8 +809,28 @@ function common_shorten_links($text) function common_xml_safe_str($str) { - // Neutralize control codes and surrogates - return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str); + // Replace common eol and extra whitespace input chars + $unWelcome = array( + "\t", // tab + "\n", // newline + "\r", // cr + "\0", // null byte eos + "\x0B" // vertical tab + ); + + $replacement = array( + ' ', // single space + ' ', + '', // nothing + '', + ' ' + ); + + $str = str_replace($unWelcome, $replacement, $str); + + // Neutralize any additional control codes and UTF-16 surrogates + // (Twitter uses '*') + return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str); } function common_tag_link($tag) diff --git a/locale/statusnet.po b/locale/statusnet.po index cf44e2d3c..8e1434497 100644 --- a/locale/statusnet.po +++ b/locale/statusnet.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-02-24 23:49+0000\n" +"POT-Creation-Date: 2010-03-01 14:08+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,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: actions/accessadminpanel.php:54 lib/adminpanelaction.php:326 +#: actions/accessadminpanel.php:54 lib/adminpanelaction.php:337 msgid "Access" msgstr "" @@ -88,14 +88,15 @@ msgstr "" #: actions/apitimelinehome.php:79 actions/apitimelinementions.php:79 #: actions/apitimelineuser.php:81 actions/avatarbynickname.php:75 #: actions/favoritesrss.php:74 actions/foaf.php:40 actions/foaf.php:58 -#: actions/microsummary.php:62 actions/newmessage.php:116 actions/otp.php:76 -#: actions/remotesubscribe.php:145 actions/remotesubscribe.php:154 -#: actions/replies.php:73 actions/repliesrss.php:38 actions/rsd.php:116 -#: actions/showfavorites.php:105 actions/userbyid.php:74 -#: actions/usergroups.php:91 actions/userrss.php:38 actions/xrds.php:71 -#: lib/command.php:163 lib/command.php:302 lib/command.php:355 -#: lib/command.php:401 lib/command.php:462 lib/command.php:518 -#: lib/galleryaction.php:59 lib/mailbox.php:82 lib/profileaction.php:77 +#: actions/hcard.php:67 actions/microsummary.php:62 actions/newmessage.php:116 +#: actions/otp.php:76 actions/remotesubscribe.php:145 +#: actions/remotesubscribe.php:154 actions/replies.php:73 +#: actions/repliesrss.php:38 actions/rsd.php:116 actions/showfavorites.php:105 +#: actions/userbyid.php:74 actions/usergroups.php:91 actions/userrss.php:38 +#: actions/xrds.php:71 lib/command.php:163 lib/command.php:302 +#: lib/command.php:355 lib/command.php:401 lib/command.php:462 +#: lib/command.php:518 lib/galleryaction.php:59 lib/mailbox.php:82 +#: lib/profileaction.php:77 msgid "No such user." msgstr "" @@ -171,20 +172,20 @@ msgstr "" #: actions/apiaccountverifycredentials.php:70 actions/apidirectmessage.php:156 #: actions/apifavoritecreate.php:99 actions/apifavoritedestroy.php:100 #: actions/apifriendshipscreate.php:100 actions/apifriendshipsdestroy.php:100 -#: actions/apifriendshipsshow.php:128 actions/apigroupcreate.php:136 +#: actions/apifriendshipsshow.php:128 actions/apigroupcreate.php:138 #: actions/apigroupismember.php:114 actions/apigroupjoin.php:155 #: actions/apigroupleave.php:141 actions/apigrouplist.php:132 #: actions/apigrouplistall.php:120 actions/apigroupmembership.php:106 -#: actions/apigroupshow.php:115 actions/apihelptest.php:88 +#: actions/apigroupshow.php:105 actions/apihelptest.php:88 #: actions/apistatusesdestroy.php:102 actions/apistatusesretweets.php:112 -#: actions/apistatusesshow.php:108 actions/apistatusnetconfig.php:137 +#: actions/apistatusesshow.php:108 actions/apistatusnetconfig.php:131 #: actions/apistatusnetversion.php:93 actions/apisubscriptions.php:111 #: actions/apitimelinefavorites.php:183 actions/apitimelinefriends.php:187 -#: actions/apitimelinegroup.php:195 actions/apitimelinehome.php:184 +#: actions/apitimelinegroup.php:185 actions/apitimelinehome.php:184 #: actions/apitimelinementions.php:175 actions/apitimelinepublic.php:152 #: actions/apitimelineretweetedtome.php:121 #: actions/apitimelineretweetsofme.php:152 actions/apitimelinetag.php:166 -#: actions/apitimelineuser.php:207 actions/apiusershow.php:101 +#: actions/apitimelineuser.php:196 actions/apiusershow.php:101 msgid "API method not found." msgstr "" @@ -216,8 +217,9 @@ msgstr "" #: actions/apiaccountupdateprofilebackgroundimage.php:194 #: actions/apiaccountupdateprofilecolors.php:185 #: actions/apiaccountupdateprofileimage.php:130 actions/apiusershow.php:108 -#: actions/avatarbynickname.php:80 actions/foaf.php:65 actions/replies.php:80 -#: actions/usergroups.php:98 lib/galleryaction.php:66 lib/profileaction.php:84 +#: actions/avatarbynickname.php:80 actions/foaf.php:65 actions/hcard.php:74 +#: actions/replies.php:80 actions/usergroups.php:98 lib/galleryaction.php:66 +#: lib/profileaction.php:84 msgid "User has no profile." msgstr "" @@ -241,7 +243,7 @@ msgstr "" #: actions/apiaccountupdateprofilebackgroundimage.php:146 #: actions/apiaccountupdateprofilecolors.php:164 #: actions/apiaccountupdateprofilecolors.php:174 -#: actions/groupdesignsettings.php:287 actions/groupdesignsettings.php:297 +#: actions/groupdesignsettings.php:290 actions/groupdesignsettings.php:300 #: actions/userdesignsettings.php:210 actions/userdesignsettings.php:220 #: actions/userdesignsettings.php:263 actions/userdesignsettings.php:273 msgid "Unable to save your design settings." @@ -351,87 +353,87 @@ msgstr "" msgid "Could not find target user." msgstr "" -#: actions/apigroupcreate.php:164 actions/editgroup.php:182 +#: actions/apigroupcreate.php:166 actions/editgroup.php:186 #: actions/newgroup.php:126 actions/profilesettings.php:215 #: actions/register.php:205 msgid "Nickname must have only lowercase letters and numbers and no spaces." msgstr "" -#: actions/apigroupcreate.php:173 actions/editgroup.php:186 +#: actions/apigroupcreate.php:175 actions/editgroup.php:190 #: actions/newgroup.php:130 actions/profilesettings.php:238 #: actions/register.php:208 msgid "Nickname already in use. Try another one." msgstr "" -#: actions/apigroupcreate.php:180 actions/editgroup.php:189 +#: actions/apigroupcreate.php:182 actions/editgroup.php:193 #: actions/newgroup.php:133 actions/profilesettings.php:218 #: actions/register.php:210 msgid "Not a valid nickname." msgstr "" -#: actions/apigroupcreate.php:196 actions/editapplication.php:215 -#: actions/editgroup.php:195 actions/newapplication.php:203 +#: actions/apigroupcreate.php:198 actions/editapplication.php:215 +#: actions/editgroup.php:199 actions/newapplication.php:203 #: actions/newgroup.php:139 actions/profilesettings.php:222 #: actions/register.php:217 msgid "Homepage is not a valid URL." msgstr "" -#: actions/apigroupcreate.php:205 actions/editgroup.php:198 +#: actions/apigroupcreate.php:207 actions/editgroup.php:202 #: actions/newgroup.php:142 actions/profilesettings.php:225 #: actions/register.php:220 msgid "Full name is too long (max 255 chars)." msgstr "" -#: actions/apigroupcreate.php:213 actions/editapplication.php:190 +#: actions/apigroupcreate.php:215 actions/editapplication.php:190 #: actions/newapplication.php:172 #, php-format msgid "Description is too long (max %d chars)." msgstr "" -#: actions/apigroupcreate.php:224 actions/editgroup.php:204 +#: actions/apigroupcreate.php:226 actions/editgroup.php:208 #: actions/newgroup.php:148 actions/profilesettings.php:232 #: actions/register.php:227 msgid "Location is too long (max 255 chars)." msgstr "" -#: actions/apigroupcreate.php:243 actions/editgroup.php:215 +#: actions/apigroupcreate.php:245 actions/editgroup.php:219 #: actions/newgroup.php:159 #, php-format msgid "Too many aliases! Maximum %d." msgstr "" -#: actions/apigroupcreate.php:264 actions/editgroup.php:224 +#: actions/apigroupcreate.php:266 actions/editgroup.php:228 #: actions/newgroup.php:168 #, php-format msgid "Invalid alias: \"%s\"" msgstr "" -#: actions/apigroupcreate.php:273 actions/editgroup.php:228 +#: actions/apigroupcreate.php:275 actions/editgroup.php:232 #: actions/newgroup.php:172 #, php-format msgid "Alias \"%s\" already in use. Try another one." msgstr "" -#: actions/apigroupcreate.php:286 actions/editgroup.php:234 +#: actions/apigroupcreate.php:288 actions/editgroup.php:238 #: actions/newgroup.php:178 msgid "Alias can't be the same as nickname." msgstr "" #: actions/apigroupismember.php:95 actions/apigroupjoin.php:104 #: actions/apigroupleave.php:104 actions/apigroupmembership.php:91 -#: actions/apigroupshow.php:82 actions/apitimelinegroup.php:91 +#: actions/apigroupshow.php:90 actions/apitimelinegroup.php:91 msgid "Group not found!" msgstr "" -#: actions/apigroupjoin.php:110 actions/joingroup.php:90 +#: actions/apigroupjoin.php:110 actions/joingroup.php:100 msgid "You are already a member of that group." msgstr "" -#: actions/apigroupjoin.php:119 actions/joingroup.php:95 lib/command.php:221 +#: actions/apigroupjoin.php:119 actions/joingroup.php:105 lib/command.php:221 msgid "You have been blocked from that group by the admin." msgstr "" -#: actions/apigroupjoin.php:138 actions/joingroup.php:124 +#: actions/apigroupjoin.php:138 actions/joingroup.php:134 #, php-format msgid "Could not join user %1$s to group %2$s." msgstr "" @@ -440,7 +442,7 @@ msgstr "" msgid "You are not a member of this group." msgstr "" -#: actions/apigroupleave.php:124 actions/leavegroup.php:119 +#: actions/apigroupleave.php:124 actions/leavegroup.php:129 #, php-format msgid "Could not remove user %1$s from group %2$s." msgstr "" @@ -471,7 +473,7 @@ msgstr "" #: actions/apioauthauthorize.php:123 actions/avatarsettings.php:268 #: actions/deletenotice.php:157 actions/disfavor.php:74 #: actions/emailsettings.php:238 actions/favor.php:75 actions/geocode.php:54 -#: actions/groupblock.php:66 actions/grouplogo.php:309 +#: actions/groupblock.php:66 actions/grouplogo.php:312 #: actions/groupunblock.php:66 actions/imsettings.php:206 #: actions/invite.php:56 actions/login.php:115 actions/makeadmin.php:66 #: actions/newmessage.php:135 actions/newnotice.php:103 actions/nudge.php:80 @@ -491,11 +493,11 @@ msgid "Invalid nickname / password!" msgstr "" #: actions/apioauthauthorize.php:159 -msgid "Database error deleting OAuth application user." +msgid "DB error deleting OAuth app user." msgstr "" #: actions/apioauthauthorize.php:185 -msgid "Database error inserting OAuth application user." +msgid "DB error inserting OAuth app user." msgstr "" #: actions/apioauthauthorize.php:214 @@ -512,7 +514,7 @@ msgstr "" #: actions/apioauthauthorize.php:232 actions/avatarsettings.php:281 #: actions/designadminpanel.php:103 actions/editapplication.php:139 -#: actions/emailsettings.php:256 actions/grouplogo.php:319 +#: actions/emailsettings.php:256 actions/grouplogo.php:322 #: actions/imsettings.php:220 actions/newapplication.php:121 #: actions/oauthconnectionssettings.php:147 actions/recoverpassword.php:44 #: actions/smssettings.php:248 lib/designsettings.php:304 @@ -541,7 +543,7 @@ msgstr "" #: actions/apioauthauthorize.php:313 actions/login.php:230 #: actions/profilesettings.php:106 actions/register.php:424 -#: actions/showgroup.php:236 actions/tagother.php:94 +#: actions/showgroup.php:244 actions/tagother.php:94 #: actions/userauthorization.php:145 lib/groupeditform.php:152 #: lib/userprofile.php:131 msgid "Nickname" @@ -623,12 +625,12 @@ msgid "%1$s updates favorited by %2$s / %2$s." msgstr "" #: actions/apitimelinegroup.php:109 actions/apitimelineuser.php:118 -#: actions/grouprss.php:131 actions/userrss.php:90 +#: actions/grouprss.php:138 actions/userrss.php:90 #, php-format msgid "%s timeline" msgstr "" -#: actions/apitimelinegroup.php:114 actions/apitimelineuser.php:126 +#: actions/apitimelinegroup.php:112 actions/apitimelineuser.php:124 #: actions/userrss.php:92 #, php-format msgid "Updates from %1$s on %2$s!" @@ -685,8 +687,7 @@ msgstr "" #: actions/avatarbynickname.php:59 actions/blockedfromgroup.php:73 #: actions/editgroup.php:84 actions/groupdesignsettings.php:84 #: actions/grouplogo.php:86 actions/groupmembers.php:76 -#: actions/grouprss.php:91 actions/joingroup.php:76 actions/leavegroup.php:76 -#: actions/showgroup.php:121 +#: actions/grouprss.php:91 actions/showgroup.php:121 msgid "No nickname." msgstr "" @@ -698,7 +699,7 @@ msgstr "" msgid "Invalid size." msgstr "" -#: actions/avatarsettings.php:67 actions/showgroup.php:221 +#: actions/avatarsettings.php:67 actions/showgroup.php:229 #: lib/accountsettingsaction.php:112 msgid "Avatar" msgstr "" @@ -715,30 +716,30 @@ msgid "User without matching profile" msgstr "" #: actions/avatarsettings.php:119 actions/avatarsettings.php:197 -#: actions/grouplogo.php:251 +#: actions/grouplogo.php:254 msgid "Avatar settings" msgstr "" #: actions/avatarsettings.php:127 actions/avatarsettings.php:205 -#: actions/grouplogo.php:199 actions/grouplogo.php:259 +#: actions/grouplogo.php:202 actions/grouplogo.php:262 msgid "Original" msgstr "" #: actions/avatarsettings.php:142 actions/avatarsettings.php:217 -#: actions/grouplogo.php:210 actions/grouplogo.php:271 +#: actions/grouplogo.php:213 actions/grouplogo.php:274 msgid "Preview" msgstr "" #: actions/avatarsettings.php:149 actions/showapplication.php:252 -#: lib/deleteuserform.php:66 lib/noticelist.php:637 +#: lib/deleteuserform.php:66 lib/noticelist.php:655 msgid "Delete" msgstr "" -#: actions/avatarsettings.php:166 actions/grouplogo.php:233 +#: actions/avatarsettings.php:166 actions/grouplogo.php:236 msgid "Upload" msgstr "" -#: actions/avatarsettings.php:231 actions/grouplogo.php:286 +#: actions/avatarsettings.php:231 actions/grouplogo.php:289 msgid "Crop" msgstr "" @@ -746,7 +747,7 @@ msgstr "" msgid "Pick a square area of the image to be your avatar" msgstr "" -#: actions/avatarsettings.php:343 actions/grouplogo.php:377 +#: actions/avatarsettings.php:343 actions/grouplogo.php:380 msgid "Lost our file data." msgstr "" @@ -778,22 +779,22 @@ msgid "" msgstr "" #: actions/block.php:143 actions/deleteapplication.php:153 -#: actions/deletenotice.php:145 actions/deleteuser.php:147 +#: actions/deletenotice.php:145 actions/deleteuser.php:150 #: actions/groupblock.php:178 msgid "No" msgstr "" -#: actions/block.php:143 actions/deleteuser.php:147 +#: actions/block.php:143 actions/deleteuser.php:150 msgid "Do not block this user" msgstr "" #: actions/block.php:144 actions/deleteapplication.php:158 -#: actions/deletenotice.php:146 actions/deleteuser.php:148 +#: actions/deletenotice.php:146 actions/deleteuser.php:151 #: actions/groupblock.php:179 lib/repeatform.php:132 msgid "Yes" msgstr "" -#: actions/block.php:144 actions/groupmembers.php:348 lib/blockform.php:80 +#: actions/block.php:144 actions/groupmembers.php:355 lib/blockform.php:80 msgid "Block this user" msgstr "" @@ -801,39 +802,43 @@ msgstr "" msgid "Failed to save block information." msgstr "" -#: actions/blockedfromgroup.php:80 actions/editgroup.php:96 -#: actions/foafgroup.php:44 actions/foafgroup.php:62 actions/groupblock.php:86 -#: actions/groupbyid.php:83 actions/groupdesignsettings.php:97 -#: actions/grouplogo.php:99 actions/groupmembers.php:83 -#: actions/grouprss.php:98 actions/groupunblock.php:86 -#: actions/joingroup.php:83 actions/leavegroup.php:83 actions/makeadmin.php:86 -#: actions/showgroup.php:137 lib/command.php:212 lib/command.php:260 +#: actions/blockedfromgroup.php:80 actions/blockedfromgroup.php:87 +#: actions/editgroup.php:100 actions/foafgroup.php:44 actions/foafgroup.php:62 +#: actions/foafgroup.php:69 actions/groupblock.php:86 actions/groupbyid.php:83 +#: actions/groupdesignsettings.php:100 actions/grouplogo.php:102 +#: actions/groupmembers.php:83 actions/groupmembers.php:90 +#: actions/grouprss.php:98 actions/grouprss.php:105 +#: actions/groupunblock.php:86 actions/joingroup.php:82 +#: actions/joingroup.php:93 actions/leavegroup.php:82 +#: actions/leavegroup.php:93 actions/makeadmin.php:86 +#: actions/showgroup.php:138 actions/showgroup.php:146 lib/command.php:212 +#: lib/command.php:260 msgid "No such group." msgstr "" -#: actions/blockedfromgroup.php:90 +#: actions/blockedfromgroup.php:97 #, php-format msgid "%s blocked profiles" msgstr "" -#: actions/blockedfromgroup.php:93 +#: actions/blockedfromgroup.php:100 #, php-format msgid "%1$s blocked profiles, page %2$d" msgstr "" -#: actions/blockedfromgroup.php:108 +#: actions/blockedfromgroup.php:115 msgid "A list of the users blocked from joining this group." msgstr "" -#: actions/blockedfromgroup.php:281 +#: actions/blockedfromgroup.php:288 msgid "Unblock user from group" msgstr "" -#: actions/blockedfromgroup.php:313 lib/unblockform.php:69 +#: actions/blockedfromgroup.php:320 lib/unblockform.php:69 msgid "Unblock" msgstr "" -#: actions/blockedfromgroup.php:313 lib/unblockform.php:80 +#: actions/blockedfromgroup.php:320 lib/unblockform.php:80 msgid "Unblock this user" msgstr "" @@ -876,7 +881,7 @@ msgid "Couldn't delete email confirmation." msgstr "" #: actions/confirmaddress.php:144 -msgid "Confirm address" +msgid "Confirm Address" msgstr "" #: actions/confirmaddress.php:159 @@ -963,7 +968,7 @@ msgstr "" msgid "Do not delete this notice" msgstr "" -#: actions/deletenotice.php:146 lib/noticelist.php:637 +#: actions/deletenotice.php:146 lib/noticelist.php:655 msgid "Delete this notice" msgstr "" @@ -979,18 +984,18 @@ msgstr "" msgid "Delete user" msgstr "" -#: actions/deleteuser.php:135 +#: actions/deleteuser.php:136 msgid "" "Are you sure you want to delete this user? This will clear all data about " "the user from the database, without a backup." msgstr "" -#: actions/deleteuser.php:148 lib/deleteuserform.php:77 +#: actions/deleteuser.php:151 lib/deleteuserform.php:77 msgid "Delete this user" msgstr "" #: actions/designadminpanel.php:62 lib/accountsettingsaction.php:124 -#: lib/adminpanelaction.php:316 lib/groupnav.php:119 +#: lib/adminpanelaction.php:327 lib/groupnav.php:119 msgid "Design" msgstr "" @@ -1182,29 +1187,29 @@ msgstr "" msgid "You must be logged in to create a group." msgstr "" -#: actions/editgroup.php:103 actions/editgroup.php:168 -#: actions/groupdesignsettings.php:104 actions/grouplogo.php:106 +#: actions/editgroup.php:107 actions/editgroup.php:172 +#: actions/groupdesignsettings.php:107 actions/grouplogo.php:109 msgid "You must be an admin to edit the group." msgstr "" -#: actions/editgroup.php:154 +#: actions/editgroup.php:158 msgid "Use this form to edit the group." msgstr "" -#: actions/editgroup.php:201 actions/newgroup.php:145 +#: actions/editgroup.php:205 actions/newgroup.php:145 #, php-format msgid "description is too long (max %d chars)." msgstr "" -#: actions/editgroup.php:253 +#: actions/editgroup.php:258 msgid "Could not update group." msgstr "" -#: actions/editgroup.php:259 classes/User_group.php:433 +#: actions/editgroup.php:264 classes/User_group.php:478 msgid "Could not create aliases." msgstr "" -#: actions/editgroup.php:269 +#: actions/editgroup.php:280 msgid "Options saved." msgstr "" @@ -1533,7 +1538,7 @@ msgstr "" msgid "User is not a member of group." msgstr "" -#: actions/groupblock.php:136 actions/groupmembers.php:316 +#: actions/groupblock.php:136 actions/groupmembers.php:323 msgid "Block user from group" msgstr "" @@ -1565,86 +1570,86 @@ msgstr "" msgid "You must be logged in to edit a group." msgstr "" -#: actions/groupdesignsettings.php:141 +#: actions/groupdesignsettings.php:144 msgid "Group design" msgstr "" -#: actions/groupdesignsettings.php:152 +#: actions/groupdesignsettings.php:155 msgid "" "Customize the way your group looks with a background image and a colour " "palette of your choice." msgstr "" -#: actions/groupdesignsettings.php:263 actions/userdesignsettings.php:186 +#: actions/groupdesignsettings.php:266 actions/userdesignsettings.php:186 #: lib/designsettings.php:391 lib/designsettings.php:413 msgid "Couldn't update your design." msgstr "" -#: actions/groupdesignsettings.php:308 actions/userdesignsettings.php:231 +#: actions/groupdesignsettings.php:311 actions/userdesignsettings.php:231 msgid "Design preferences saved." msgstr "" -#: actions/grouplogo.php:139 actions/grouplogo.php:192 +#: actions/grouplogo.php:142 actions/grouplogo.php:195 msgid "Group logo" msgstr "" -#: actions/grouplogo.php:150 +#: actions/grouplogo.php:153 #, php-format msgid "" "You can upload a logo image for your group. The maximum file size is %s." msgstr "" -#: actions/grouplogo.php:178 +#: actions/grouplogo.php:181 msgid "User without matching profile." msgstr "" -#: actions/grouplogo.php:362 +#: actions/grouplogo.php:365 msgid "Pick a square area of the image to be the logo." msgstr "" -#: actions/grouplogo.php:396 +#: actions/grouplogo.php:399 msgid "Logo updated." msgstr "" -#: actions/grouplogo.php:398 +#: actions/grouplogo.php:401 msgid "Failed updating logo." msgstr "" -#: actions/groupmembers.php:93 lib/groupnav.php:92 +#: actions/groupmembers.php:100 lib/groupnav.php:92 #, php-format msgid "%s group members" msgstr "" -#: actions/groupmembers.php:96 +#: actions/groupmembers.php:103 #, php-format msgid "%1$s group members, page %2$d" msgstr "" -#: actions/groupmembers.php:111 +#: actions/groupmembers.php:118 msgid "A list of the users in this group." msgstr "" -#: actions/groupmembers.php:175 lib/action.php:448 lib/groupnav.php:107 +#: actions/groupmembers.php:182 lib/action.php:448 lib/groupnav.php:107 msgid "Admin" msgstr "" -#: actions/groupmembers.php:348 lib/blockform.php:69 +#: actions/groupmembers.php:355 lib/blockform.php:69 msgid "Block" msgstr "" -#: actions/groupmembers.php:443 +#: actions/groupmembers.php:450 msgid "Make user an admin of the group" msgstr "" -#: actions/groupmembers.php:475 +#: actions/groupmembers.php:482 msgid "Make Admin" msgstr "" -#: actions/groupmembers.php:475 +#: actions/groupmembers.php:482 msgid "Make this user an admin" msgstr "" -#: actions/grouprss.php:133 +#: actions/grouprss.php:140 #, php-format msgid "Updates from members of %1$s on %2$s!" msgstr "" @@ -1924,7 +1929,11 @@ msgstr "" msgid "You must be logged in to join a group." msgstr "" -#: actions/joingroup.php:131 +#: actions/joingroup.php:88 actions/leavegroup.php:88 +msgid "No nickname or ID." +msgstr "" + +#: actions/joingroup.php:141 #, php-format msgid "%1$s joined group %2$s" msgstr "" @@ -1933,11 +1942,11 @@ msgstr "" msgid "You must be logged in to leave a group." msgstr "" -#: actions/leavegroup.php:90 lib/command.php:265 +#: actions/leavegroup.php:100 lib/command.php:265 msgid "You are not a member of that group." msgstr "" -#: actions/leavegroup.php:127 +#: actions/leavegroup.php:137 #, php-format msgid "%1$s left group %2$s" msgstr "" @@ -2194,8 +2203,8 @@ msgstr "" msgid "Only " msgstr "" -#: actions/oembed.php:181 actions/oembed.php:200 lib/api.php:1040 -#: lib/api.php:1068 lib/api.php:1177 +#: actions/oembed.php:181 actions/oembed.php:200 lib/apiaction.php:1039 +#: lib/apiaction.php:1067 lib/apiaction.php:1176 msgid "Not a supported data format." msgstr "" @@ -2208,7 +2217,7 @@ msgid "Notice Search" msgstr "" #: actions/othersettings.php:60 -msgid "Other settings" +msgid "Other Settings" msgstr "" #: actions/othersettings.php:71 @@ -2334,7 +2343,7 @@ msgstr "" msgid "Password saved." msgstr "" -#: actions/pathsadminpanel.php:59 lib/adminpanelaction.php:331 +#: actions/pathsadminpanel.php:59 lib/adminpanelaction.php:342 msgid "Paths" msgstr "" @@ -2367,7 +2376,7 @@ msgid "Invalid SSL server. The maximum length is 255 characters." msgstr "" #: actions/pathsadminpanel.php:234 actions/siteadminpanel.php:58 -#: lib/adminpanelaction.php:311 +#: lib/adminpanelaction.php:322 msgid "Site" msgstr "" @@ -2535,7 +2544,7 @@ msgid "1-64 lowercase letters or numbers, no punctuation or spaces" msgstr "" #: actions/profilesettings.php:111 actions/register.php:448 -#: actions/showgroup.php:247 actions/tagother.php:104 +#: actions/showgroup.php:255 actions/tagother.php:104 #: lib/groupeditform.php:157 lib/userprofile.php:149 msgid "Full name" msgstr "" @@ -2563,7 +2572,7 @@ msgid "Bio" msgstr "" #: actions/profilesettings.php:132 actions/register.php:471 -#: actions/showgroup.php:256 actions/tagother.php:112 +#: actions/showgroup.php:264 actions/tagother.php:112 #: actions/userauthorization.php:166 lib/groupeditform.php:177 #: lib/userprofile.php:164 msgid "Location" @@ -3032,7 +3041,7 @@ msgstr "" msgid "You already repeated that notice." msgstr "" -#: actions/repeat.php:114 lib/noticelist.php:656 +#: actions/repeat.php:114 lib/noticelist.php:674 msgid "Repeated" msgstr "" @@ -3105,7 +3114,7 @@ msgid "User is already sandboxed." msgstr "" #: actions/sessionsadminpanel.php:54 actions/sessionsadminpanel.php:170 -#: lib/adminpanelaction.php:336 +#: lib/adminpanelaction.php:347 msgid "Sessions" msgstr "" @@ -3160,7 +3169,7 @@ msgstr "" msgid "Description" msgstr "" -#: actions/showapplication.php:192 actions/showgroup.php:429 +#: actions/showapplication.php:192 actions/showgroup.php:437 #: lib/profileaction.php:174 msgid "Statistics" msgstr "" @@ -3271,67 +3280,67 @@ msgstr "" msgid "%1$s group, page %2$d" msgstr "" -#: actions/showgroup.php:218 +#: actions/showgroup.php:226 msgid "Group profile" msgstr "" -#: actions/showgroup.php:263 actions/tagother.php:118 +#: actions/showgroup.php:271 actions/tagother.php:118 #: actions/userauthorization.php:175 lib/userprofile.php:177 msgid "URL" msgstr "" -#: actions/showgroup.php:274 actions/tagother.php:128 +#: actions/showgroup.php:282 actions/tagother.php:128 #: actions/userauthorization.php:187 lib/userprofile.php:194 msgid "Note" msgstr "" -#: actions/showgroup.php:284 lib/groupeditform.php:184 +#: actions/showgroup.php:292 lib/groupeditform.php:184 msgid "Aliases" msgstr "" -#: actions/showgroup.php:293 +#: actions/showgroup.php:301 msgid "Group actions" msgstr "" -#: actions/showgroup.php:328 +#: actions/showgroup.php:336 #, php-format msgid "Notice feed for %s group (RSS 1.0)" msgstr "" -#: actions/showgroup.php:334 +#: actions/showgroup.php:342 #, php-format msgid "Notice feed for %s group (RSS 2.0)" msgstr "" -#: actions/showgroup.php:340 +#: actions/showgroup.php:348 #, php-format msgid "Notice feed for %s group (Atom)" msgstr "" -#: actions/showgroup.php:345 +#: actions/showgroup.php:353 #, php-format msgid "FOAF for %s group" msgstr "" -#: actions/showgroup.php:381 actions/showgroup.php:438 lib/groupnav.php:91 +#: actions/showgroup.php:389 actions/showgroup.php:446 lib/groupnav.php:91 msgid "Members" msgstr "" -#: actions/showgroup.php:386 lib/profileaction.php:117 +#: actions/showgroup.php:394 lib/profileaction.php:117 #: lib/profileaction.php:148 lib/profileaction.php:236 lib/section.php:95 #: lib/subscriptionlist.php:126 lib/tagcloudsection.php:71 msgid "(None)" msgstr "" -#: actions/showgroup.php:392 +#: actions/showgroup.php:400 msgid "All members" msgstr "" -#: actions/showgroup.php:432 +#: actions/showgroup.php:440 msgid "Created" msgstr "" -#: actions/showgroup.php:448 +#: actions/showgroup.php:456 #, php-format msgid "" "**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en." @@ -3341,7 +3350,7 @@ msgid "" "of this group and many more! ([Read more](%%%%doc.help%%%%))" msgstr "" -#: actions/showgroup.php:454 +#: actions/showgroup.php:462 #, php-format msgid "" "**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en." @@ -3350,7 +3359,7 @@ msgid "" "their life and interests. " msgstr "" -#: actions/showgroup.php:482 +#: actions/showgroup.php:490 msgid "Admins" msgstr "" @@ -3861,7 +3870,7 @@ msgstr "" msgid "No such tag." msgstr "" -#: actions/twitapitrends.php:87 +#: actions/twitapitrends.php:85 msgid "API method under construction." msgstr "" @@ -3891,7 +3900,7 @@ msgid "" "Listenee stream license ‘%1$s’ is not compatible with site license ‘%2$s’." msgstr "" -#: actions/useradminpanel.php:58 lib/adminpanelaction.php:321 +#: actions/useradminpanel.php:58 lib/adminpanelaction.php:332 #: lib/personalgroupnav.php:115 msgid "User" msgstr "" @@ -4164,6 +4173,10 @@ msgstr "" msgid "Group leave failed." msgstr "" +#: classes/Local_group.php:41 +msgid "Could not update local group." +msgstr "" + #: classes/Login_token.php:76 #, php-format msgid "Could not create login token for %s" @@ -4181,43 +4194,43 @@ msgstr "" msgid "Could not update message with new URI." msgstr "" -#: classes/Notice.php:157 +#: classes/Notice.php:172 #, php-format msgid "DB error inserting hashtag: %s" msgstr "" -#: classes/Notice.php:222 +#: classes/Notice.php:239 msgid "Problem saving notice. Too long." msgstr "" -#: classes/Notice.php:226 +#: classes/Notice.php:243 msgid "Problem saving notice. Unknown user." msgstr "" -#: classes/Notice.php:231 +#: classes/Notice.php:248 msgid "" "Too many notices too fast; take a breather and post again in a few minutes." msgstr "" -#: classes/Notice.php:237 +#: classes/Notice.php:254 msgid "" "Too many duplicate messages too quickly; take a breather and post again in a " "few minutes." msgstr "" -#: classes/Notice.php:243 +#: classes/Notice.php:260 msgid "You are banned from posting notices on this site." msgstr "" -#: classes/Notice.php:309 classes/Notice.php:335 +#: classes/Notice.php:326 classes/Notice.php:352 msgid "Problem saving notice." msgstr "" -#: classes/Notice.php:882 +#: classes/Notice.php:911 msgid "Problem saving group inbox." msgstr "" -#: classes/Notice.php:1407 +#: classes/Notice.php:1442 #, php-format msgid "RT @%1$s %2$s" msgstr "" @@ -4242,7 +4255,7 @@ msgstr "" msgid "Couldn't delete self-subscription." msgstr "" -#: classes/Subscription.php:179 lib/subs.php:69 +#: classes/Subscription.php:179 msgid "Couldn't delete subscription." msgstr "" @@ -4251,14 +4264,22 @@ msgstr "" msgid "Welcome to %1$s, @%2$s!" msgstr "" -#: classes/User_group.php:423 +#: classes/User_group.php:462 msgid "Could not create group." msgstr "" -#: classes/User_group.php:452 +#: classes/User_group.php:471 +msgid "Could not set group uri." +msgstr "" + +#: classes/User_group.php:492 msgid "Could not set group membership." msgstr "" +#: classes/User_group.php:506 +msgid "Could not save local group info." +msgstr "" + #: lib/accountsettingsaction.php:108 msgid "Change your profile settings" msgstr "" @@ -4471,15 +4492,15 @@ msgstr "" msgid "Before" msgstr "" -#: lib/activity.php:382 +#: lib/activity.php:449 msgid "Can't handle remote content yet." msgstr "" -#: lib/activity.php:410 +#: lib/activity.php:477 msgid "Can't handle embedded XML content yet." msgstr "" -#: lib/activity.php:414 +#: lib/activity.php:481 msgid "Can't handle embedded Base64 content yet." msgstr "" @@ -4503,35 +4524,35 @@ msgstr "" msgid "Unable to delete design setting." msgstr "" -#: lib/adminpanelaction.php:312 +#: lib/adminpanelaction.php:323 msgid "Basic site configuration" msgstr "" -#: lib/adminpanelaction.php:317 +#: lib/adminpanelaction.php:328 msgid "Design configuration" msgstr "" -#: lib/adminpanelaction.php:322 +#: lib/adminpanelaction.php:333 msgid "User configuration" msgstr "" -#: lib/adminpanelaction.php:327 +#: lib/adminpanelaction.php:338 msgid "Access configuration" msgstr "" -#: lib/adminpanelaction.php:332 +#: lib/adminpanelaction.php:343 msgid "Paths configuration" msgstr "" -#: lib/adminpanelaction.php:337 +#: lib/adminpanelaction.php:348 msgid "Sessions configuration" msgstr "" -#: lib/apiauth.php:95 +#: lib/apiauth.php:94 msgid "API resource requires read-write access, but you only have read access." msgstr "" -#: lib/apiauth.php:273 +#: lib/apiauth.php:272 #, php-format msgid "Failed API auth attempt, nickname = %1$s, proxy = %2$s, ip = %3$s" msgstr "" @@ -4621,11 +4642,11 @@ msgstr "" msgid "Tags for this attachment" msgstr "" -#: lib/authenticationplugin.php:218 lib/authenticationplugin.php:223 +#: lib/authenticationplugin.php:182 lib/authenticationplugin.php:187 msgid "Password changing failed" msgstr "" -#: lib/authenticationplugin.php:233 +#: lib/authenticationplugin.php:197 msgid "Password changing is not allowed" msgstr "" @@ -4782,7 +4803,7 @@ msgstr "" msgid "Subscribed to %s" msgstr "" -#: lib/command.php:582 lib/command.php:685 +#: lib/command.php:582 msgid "Specify the name of the user to unsubscribe from" msgstr "" @@ -4820,42 +4841,37 @@ msgstr "" msgid "This link is useable only once, and is good for only 2 minutes: %s" msgstr "" -#: lib/command.php:692 -#, php-format -msgid "Unsubscribed %s" -msgstr "" - -#: lib/command.php:709 +#: lib/command.php:681 msgid "You are not subscribed to anyone." msgstr "" -#: lib/command.php:711 +#: lib/command.php:683 msgid "You are subscribed to this person:" msgid_plural "You are subscribed to these people:" msgstr[0] "" msgstr[1] "" -#: lib/command.php:731 +#: lib/command.php:703 msgid "No one is subscribed to you." msgstr "" -#: lib/command.php:733 +#: lib/command.php:705 msgid "This person is subscribed to you:" msgid_plural "These people are subscribed to you:" msgstr[0] "" msgstr[1] "" -#: lib/command.php:753 +#: lib/command.php:725 msgid "You are not a member of any groups." msgstr "" -#: lib/command.php:755 +#: lib/command.php:727 msgid "You are a member of this group:" msgid_plural "You are a member of these groups:" msgstr[0] "" msgstr[1] "" -#: lib/command.php:769 +#: lib/command.php:741 msgid "" "Commands:\n" "on - turn on notifications\n" @@ -4869,7 +4885,6 @@ msgid "" "d <nickname> <text> - direct message to user\n" "get <nickname> - get last notice from user\n" "whois <nickname> - get profile info on user\n" -"lose <nickname> - force user to stop following you\n" "fav <nickname> - add user's last notice as a 'fave'\n" "fav #<notice_id> - add notice with the given id as a 'fave'\n" "repeat #<notice_id> - repeat a notice with a given id\n" @@ -5460,23 +5475,23 @@ msgstr "" msgid "at" msgstr "" -#: lib/noticelist.php:558 +#: lib/noticelist.php:566 msgid "in context" msgstr "" -#: lib/noticelist.php:583 +#: lib/noticelist.php:601 msgid "Repeated by" msgstr "" -#: lib/noticelist.php:610 +#: lib/noticelist.php:628 msgid "Reply to this notice" msgstr "" -#: lib/noticelist.php:611 +#: lib/noticelist.php:629 msgid "Reply" msgstr "" -#: lib/noticelist.php:655 +#: lib/noticelist.php:673 msgid "Notice repeated" msgstr "" @@ -5613,7 +5628,7 @@ msgstr "" msgid "Repeat this notice" msgstr "" -#: lib/router.php:665 +#: lib/router.php:668 msgid "No single user defined for single-user mode." msgstr "" @@ -5754,47 +5769,47 @@ msgstr "" msgid "Moderate" msgstr "" -#: lib/util.php:952 +#: lib/util.php:1000 msgid "a few seconds ago" msgstr "" -#: lib/util.php:954 +#: lib/util.php:1002 msgid "about a minute ago" msgstr "" -#: lib/util.php:956 +#: lib/util.php:1004 #, php-format msgid "about %d minutes ago" msgstr "" -#: lib/util.php:958 +#: lib/util.php:1006 msgid "about an hour ago" msgstr "" -#: lib/util.php:960 +#: lib/util.php:1008 #, php-format msgid "about %d hours ago" msgstr "" -#: lib/util.php:962 +#: lib/util.php:1010 msgid "about a day ago" msgstr "" -#: lib/util.php:964 +#: lib/util.php:1012 #, php-format msgid "about %d days ago" msgstr "" -#: lib/util.php:966 +#: lib/util.php:1014 msgid "about a month ago" msgstr "" -#: lib/util.php:968 +#: lib/util.php:1016 #, php-format msgid "about %d months ago" msgstr "" -#: lib/util.php:970 +#: lib/util.php:1018 msgid "about a year ago" msgstr "" diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index 4266b886d..014d0d197 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -22,7 +22,7 @@ * @category Plugin * @package StatusNet * @author Zach Copley <zach@status.net> - * @copyright 2009 StatusNet, Inc. + * @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/ */ @@ -32,12 +32,12 @@ if (!defined('STATUSNET')) { } define("FACEBOOK_CONNECT_SERVICE", 3); -define('FACEBOOKPLUGIN_VERSION', '0.9'); require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; /** - * Facebook plugin to add a StatusNet Facebook application + * Facebook plugin to add a StatusNet Facebook canvas application + * and allow registration and authentication via Facebook Connect * * @category Plugin * @package StatusNet @@ -49,6 +49,36 @@ require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; class FacebookPlugin extends Plugin { + const VERSION = STATUSNET_VERSION; + + /** + * Initializer for the plugin. + */ + + function initialize() + { + // Allow the key and secret to be passed in + // Control panel will override + + if (isset($this->apikey)) { + $key = common_config('facebook', 'apikey'); + if (empty($key)) { + Config::save('facebook', 'apikey', $this->apikey); + } + } + + if (isset($this->secret)) { + $secret = common_config('facebook', 'secret'); + if (empty($secret)) { + Config::save( + 'facebook', + 'secret', + $this->secret + ); + } + } + } + /** * Add Facebook app actions to the router table * @@ -70,6 +100,7 @@ class FacebookPlugin extends Plugin array('action' => 'facebooksettings')); $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); $m->connect('facebook/app/remove', array('action' => 'facebookremove')); + $m->connect('admin/facebook', array('action' => 'facebookadminpanel')); // Facebook Connect stuff @@ -98,6 +129,7 @@ class FacebookPlugin extends Plugin case 'FacebookinviteAction': case 'FacebookremoveAction': case 'FacebooksettingsAction': + case 'FacebookadminpanelAction': include_once INSTALLDIR . '/plugins/Facebook/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; @@ -123,6 +155,32 @@ class FacebookPlugin extends Plugin } /** + * 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'), + _m('Facebook'), + _m('Facebook integration configuration'), + $action_name == 'facebookadminpanel', + 'nav_facebook_admin_panel' + ); + } + + return true; + } + + /** * Override normal HTML output to force the content type to * text/html and add in xmlns:fb * @@ -359,8 +417,6 @@ class FacebookPlugin extends Plugin $connect = 'imsettings'; } else if (common_config('sms', 'enabled')) { $connect = 'smssettings'; - } else if (common_config('twitter', 'enabled')) { - $connect = 'twittersettings'; } if (!empty($user)) { @@ -525,15 +581,18 @@ class FacebookPlugin extends Plugin function onPluginVersion(&$versions) { - $versions[] = array('name' => 'Facebook', - 'version' => FACEBOOKPLUGIN_VERSION, - 'author' => 'Zach Copley', - 'homepage' => 'http://status.net/wiki/Plugin:Facebook', - 'rawdescription' => - _m('The Facebook plugin allows you to integrate ' . - 'your StatusNet instance with ' . - '<a href="http://facebook.com/">Facebook</a> ' . - 'and Facebook Connect.')); + $versions[] = array( + 'name' => 'Facebook', + 'version' => self::VERSION, + 'author' => 'Zach Copley', + 'homepage' => 'http://status.net/wiki/Plugin:Facebook', + 'rawdescription' => _m( + 'The Facebook plugin allows you to integrate ' . + 'your StatusNet instance with ' . + '<a href="http://facebook.com/">Facebook</a> ' . + 'and Facebook Connect.' + ) + ); return true; } diff --git a/plugins/Facebook/facebookadminpanel.php b/plugins/Facebook/facebookadminpanel.php new file mode 100644 index 000000000..ae1c7302f --- /dev/null +++ b/plugins/Facebook/facebookadminpanel.php @@ -0,0 +1,223 @@ +<?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('apikey', '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) + { + // Validate consumer key and secret (can't be too long) + + if (mb_strlen($values['facebook']['apikey']) > 255) { + $this->clientError( + _m("Invalid Facebook API key. Max length is 255 characters.") + ); + } + + if (mb_strlen($values['facebook']['secret']) > 255) { + $this->clientError( + _m("Invalid Facebook API 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( + 'apikey', + _m('API key'), + _m('API key provided by Facebook'), + 'facebook' + ); + $this->unli(); + + $this->li(); + $this->input( + 'secret', + _m('Secret'), + _m('API secret provided by Facebook'), + 'facebook' + ); + $this->unli(); + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save'), 'submit', null, _('Save Facebook settings')); + } +} diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index cd2531fa7..f788639ae 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -312,8 +312,6 @@ class MobileProfilePlugin extends WAP20Plugin $connect = 'imsettings'; } else if (common_config('sms', 'enabled')) { $connect = 'smssettings'; - } else if (common_config('twitter', 'enabled')) { - $connect = 'twittersettings'; } $action->elementStart('ul', array('id' => 'site_nav_global_primary')); diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 720dedd0a..4ffbba45b 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -222,31 +222,62 @@ class OStatusPlugin extends Plugin } /** - * + * Find any explicit remote mentions. Accepted forms: + * Webfinger: @user@example.com + * Profile link: @example.com/mublog/user + * @param Profile $sender (os user?) + * @param string $text input markup text + * @param array &$mention in/out param: set of found mentions + * @return boolean hook return value */ function onEndFindMentions($sender, $text, &$mentions) { - preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/', + preg_match_all('!(?:^|\s+) + @( # Webfinger: + (?:\w+\.)*\w+ # user + @ # @ + (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain + | # Profile: + (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain + (?:/\w+)+ # /path1(/path2...) + )!x', $text, $wmatches, PREG_OFFSET_CAPTURE); foreach ($wmatches[1] as $wmatch) { - - $webfinger = $wmatch[0]; - - $this->log(LOG_INFO, "Checking Webfinger for address '$webfinger'"); - - $oprofile = Ostatus_profile::ensureWebfinger($webfinger); + $target = $wmatch[0]; + $oprofile = null; + + if (strpos($target, '/') === false) { + $this->log(LOG_INFO, "Checking Webfinger for address '$target'"); + try { + $oprofile = Ostatus_profile::ensureWebfinger($target); + } catch (Exception $e) { + $this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage()); + } + } else { + $schemes = array('https', 'http'); + foreach ($schemes as $scheme) { + $url = "$scheme://$target"; + $this->log(LOG_INFO, "Checking profile address '$url'"); + try { + $oprofile = Ostatus_profile::ensureProfile($url); + if ($oprofile) { + continue; + } + } catch (Exception $e) { + $this->log(LOG_ERR, "Profile check failed: " . $e->getMessage()); + } + } + } if (empty($oprofile)) { - - $this->log(LOG_INFO, "No Ostatus_profile found for address '$webfinger'"); - + $this->log(LOG_INFO, "No Ostatus_profile found for address '$target'"); } else { - $this->log(LOG_INFO, "Ostatus_profile found for address '$webfinger'"); + $this->log(LOG_INFO, "Ostatus_profile found for address '$target'"); if ($oprofile->isGroup()) { continue; @@ -261,7 +292,7 @@ class OStatusPlugin extends Plugin } } $mentions[] = array('mentioned' => array($profile), - 'text' => $wmatch[0], + 'text' => $target, 'position' => $pos, 'url' => $profile->profileurl); } diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index aae22f868..f45e6a8d1 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -332,6 +332,7 @@ class OStatusSubAction extends Action if ($this->oprofile->isGroup()) { $group = $this->oprofile->localGroup(); if ($user->isMember($group)) { + // TRANS: OStatus remote group subscription dialog error. $this->showForm(_m('Already a member!')); return; } @@ -341,18 +342,22 @@ class OStatusSubAction extends Action Event::handle('EndJoinGroup', array($group, $user)); $this->successGroup(); } else { + // TRANS: OStatus remote group subscription dialog error. $this->showForm(_m('Remote group join failed!')); } } else { + // TRANS: OStatus remote group subscription dialog error. $this->showForm(_m('Remote group join aborted!')); } } else { $local = $this->oprofile->localProfile(); if ($user->isSubscribed($local)) { + // TRANS: OStatus remote subscription dialog error. $this->showForm(_m('Already subscribed!')); } elseif ($this->oprofile->subscribeLocalToRemote($user)) { $this->successUser(); } else { + // TRANS: OStatus remote subscription dialog error. $this->showForm(_m('Remote subscription failed!')); } } @@ -450,6 +455,7 @@ class OStatusSubAction extends Action function title() { + // TRANS: Page title for OStatus remote subscription form return _m('Authorize subscription'); } diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php index f33690bc4..842d65e7d 100644 --- a/plugins/OStatus/actions/pushhub.php +++ b/plugins/OStatus/actions/pushhub.php @@ -104,7 +104,7 @@ class PushHubAction extends Action throw new ClientException("Invalid hub.secret $secret; must be under 200 bytes."); } - $sub = HubSub::staticGet($sub->topic, $sub->callback); + $sub = HubSub::staticGet($topic, $callback); if (!$sub) { // Creating a new one! $sub = new HubSub(); diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index e599d83a9..3120a70f9 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -260,9 +260,15 @@ class HubSub extends Memcached_DataObject $retries = intval(common_config('ostatus', 'hub_retries')); } - $data = array('sub' => clone($this), + // We dare not clone() as when the clone is discarded it'll + // destroy the result data for the parent query. + // @fixme use clone() again when it's safe to copy an + // individual item from a multi-item query again. + $sub = HubSub::staticGet($this->topic, $this->callback); + $data = array('sub' => $sub, 'atom' => $atom, 'retries' => $retries); + common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback"); $qm = QueueManager::get(); $qm->enqueue($data, 'hubout'); } diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 96900d876..5a46aeeb6 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -146,8 +146,10 @@ class Magicsig extends Memcached_DataObject $mod = base64_url_decode($matches[1]); $exp = base64_url_decode($matches[2]); - if ($matches[4]) { + if (!empty($matches[4])) { $private_exp = base64_url_decode($matches[4]); + } else { + $private_exp = false; } $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public'); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 7b1aec76b..a33e95d93 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -698,7 +698,7 @@ class Ostatus_profile extends Memcached_DataObject { // Get the canonical feed URI and check it $discover = new FeedDiscovery(); - if ($hints['feedurl']) { + if (isset($hints['feedurl'])) { $feeduri = $hints['feedurl']; $feeduri = $discover->discoverFromFeedURL($feeduri); } else { @@ -1145,7 +1145,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($poco)) { $url = $poco->getPrimaryURL(); - if ($url->type == 'homepage') { + if ($url && $url->type == 'homepage') { $homepage = $url->value; } } diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index 388df0a28..f8449b309 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -94,7 +94,7 @@ class Discovery $links = call_user_func(array($class, 'discover'), $uri); if ($link = Discovery::getService($links, Discovery::LRDD_REL)) { // Load the LRDD XRD - if ($link['template']) { + if (!empty($link['template'])) { $xrd_uri = Discovery::applyTemplate($link['template'], $uri); } else { $xrd_uri = $link['href']; diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php index 16d27f8eb..85df26c54 100644 --- a/plugins/OStatus/lib/xrd.php +++ b/plugins/OStatus/lib/xrd.php @@ -53,17 +53,22 @@ class XRD $xrd = new XRD(); $dom = new DOMDocument(); - $dom->loadXML($xml); + if (!$dom->loadXML($xml)) { + throw new Exception("Invalid XML"); + } $xrd_element = $dom->getElementsByTagName('XRD')->item(0); // Check for host-meta host - $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; + $host = $xrd_element->getElementsByTagName('Host')->item(0); if ($host) { - $xrd->host = $host; + $xrd->host = $host->nodeValue; } // Loop through other elements foreach ($xrd_element->childNodes as $node) { + if (!($node instanceof DOMElement)) { + continue; + } switch ($node->tagName) { case 'Expires': $xrd->expires = $node->nodeValue; @@ -156,20 +161,20 @@ class XRD function saveLink($doc, $link) { $link_element = $doc->createElement('Link'); - if ($link['rel']) { + if (!empty($link['rel'])) { $link_element->setAttribute('rel', $link['rel']); } - if ($link['type']) { + if (!empty($link['type'])) { $link_element->setAttribute('type', $link['type']); } - if ($link['href']) { + if (!empty($link['href'])) { $link_element->setAttribute('href', $link['href']); } - if ($link['template']) { + if (!empty($link['template'])) { $link_element->setAttribute('template', $link['template']); } - if (is_array($link['title'])) { + if (!empty($link['title']) && is_array($link['title'])) { foreach($link['title'] as $title) { $title = $doc->createElement('Title', $title); $link_element->appendChild($title); diff --git a/plugins/OStatus/locale/OStatus.po b/plugins/OStatus/locale/OStatus.po index dedc018e3..ee19cf3db 100644 --- a/plugins/OStatus/locale/OStatus.po +++ b/plugins/OStatus/locale/OStatus.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-12-07 20:38-0800\n" +"POT-Creation-Date: 2010-03-01 14:08-0800\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,89 +16,297 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: tests/gettext-speedtest.php:57 FeedSubPlugin.php:76 -msgid "Feeds" +#: actions/groupsalmon.php:51 +msgid "Can't accept remote posts for a remote group." +msgstr "" + +#: actions/groupsalmon.php:123 +msgid "Can't read profile to set up group membership." msgstr "" -#: FeedSubPlugin.php:77 -msgid "Feed subscription options" +#: actions/groupsalmon.php:126 actions/groupsalmon.php:169 +msgid "Groups can't join groups." msgstr "" -#: feedmunger.php:215 +#: actions/groupsalmon.php:153 #, php-format -msgid "New post: \"%1$s\" %2$s" +msgid "Could not join remote user %1$s to group %2$s." msgstr "" -#: actions/feedsubsettings.php:41 -msgid "Feed subscriptions" +#: actions/groupsalmon.php:166 +msgid "Can't read profile to cancel group membership." msgstr "" -#: actions/feedsubsettings.php:52 -msgid "" -"You can subscribe to feeds from other sites; updates will appear in your " -"personal timeline." +#: actions/groupsalmon.php:182 +#, php-format +msgid "Could not remove remote user %1$s from group %2$s." +msgstr "" + +#: actions/ostatusinit.php:40 +msgid "You can use the local subscription!" +msgstr "" + +#: actions/ostatusinit.php:61 +msgid "There was a problem with your session token. Try again, please." +msgstr "" + +#: actions/ostatusinit.php:79 actions/ostatussub.php:439 +msgid "Subscribe to user" +msgstr "" + +#: actions/ostatusinit.php:97 +#, php-format +msgid "Subscribe to %s" msgstr "" -#: actions/feedsubsettings.php:96 +#: actions/ostatusinit.php:102 +msgid "User nickname" +msgstr "" + +#: actions/ostatusinit.php:103 +msgid "Nickname of the user you want to follow" +msgstr "" + +#: actions/ostatusinit.php:106 +msgid "Profile Account" +msgstr "" + +#: actions/ostatusinit.php:107 +msgid "Your account id (i.e. user@identi.ca)" +msgstr "" + +#: actions/ostatusinit.php:110 actions/ostatussub.php:115 +#: OStatusPlugin.php:205 msgid "Subscribe" msgstr "" -#: actions/feedsubsettings.php:98 +#: actions/ostatusinit.php:128 +msgid "Must provide a remote profile." +msgstr "" + +#: actions/ostatusinit.php:138 +msgid "Couldn't look up OStatus account profile." +msgstr "" + +#: actions/ostatusinit.php:153 +msgid "Couldn't confirm remote profile address." +msgstr "" + +#: actions/ostatusinit.php:171 +msgid "OStatus Connect" +msgstr "" + +#: actions/ostatussub.php:68 +msgid "Address or profile URL" +msgstr "" + +#: actions/ostatussub.php:70 +msgid "Enter the profile URL of a PubSubHubbub-enabled feed" +msgstr "" + +#: actions/ostatussub.php:74 msgid "Continue" msgstr "" -#: actions/feedsubsettings.php:151 -msgid "Empty feed URL!" +#: actions/ostatussub.php:112 OStatusPlugin.php:503 +msgid "Join" +msgstr "" + +#: actions/ostatussub.php:113 +msgid "Join this group" +msgstr "" + +#: actions/ostatussub.php:116 +msgid "Subscribe to this user" +msgstr "" + +#: actions/ostatussub.php:137 +msgid "You are already subscribed to this user." +msgstr "" + +#: actions/ostatussub.php:165 +msgid "You are already a member of this group." msgstr "" -#: actions/feedsubsettings.php:161 +#: actions/ostatussub.php:286 +msgid "Empty remote profile URL!" +msgstr "" + +#: actions/ostatussub.php:297 +msgid "Invalid address format." +msgstr "" + +#: actions/ostatussub.php:302 msgid "Invalid URL or could not reach server." msgstr "" -#: actions/feedsubsettings.php:164 +#: actions/ostatussub.php:304 msgid "Cannot read feed; server returned error." msgstr "" -#: actions/feedsubsettings.php:167 +#: actions/ostatussub.php:306 msgid "Cannot read feed; server returned an empty page." msgstr "" -#: actions/feedsubsettings.php:170 +#: actions/ostatussub.php:308 msgid "Bad HTML, could not find feed link." msgstr "" -#: actions/feedsubsettings.php:173 +#: actions/ostatussub.php:310 msgid "Could not find a feed linked from this URL." msgstr "" -#: actions/feedsubsettings.php:176 +#: actions/ostatussub.php:312 msgid "Not a recognized feed type." msgstr "" -#: actions/feedsubsettings.php:180 -msgid "Bad feed URL." +#: actions/ostatussub.php:315 +#, php-format +msgid "Bad feed URL: %s %s" +msgstr "" + +#. TRANS: OStatus remote group subscription dialog error. +#: actions/ostatussub.php:336 +msgid "Already a member!" msgstr "" -#: actions/feedsubsettings.php:188 -msgid "Feed is not PuSH-enabled; cannot subscribe." +#. TRANS: OStatus remote group subscription dialog error. +#: actions/ostatussub.php:346 +msgid "Remote group join failed!" msgstr "" -#: actions/feedsubsettings.php:208 -msgid "Feed subscription failed! Bad response from hub." +#. TRANS: OStatus remote group subscription dialog error. +#: actions/ostatussub.php:350 +msgid "Remote group join aborted!" msgstr "" -#: actions/feedsubsettings.php:218 +#. TRANS: OStatus remote subscription dialog error. +#: actions/ostatussub.php:356 msgid "Already subscribed!" msgstr "" -#: actions/feedsubsettings.php:220 -msgid "Feed subscribed!" +#. TRANS: OStatus remote subscription dialog error. +#: actions/ostatussub.php:361 +msgid "Remote subscription failed!" msgstr "" -#: actions/feedsubsettings.php:222 -msgid "Feed subscription failed!" +#. TRANS: Page title for OStatus remote subscription form +#: actions/ostatussub.php:459 +msgid "Authorize subscription" +msgstr "" + +#: actions/ostatussub.php:470 +msgid "" +"You can subscribe to users from other supported sites. Paste their address " +"or profile URI below:" +msgstr "" + +#: classes/Ostatus_profile.php:789 +#, php-format +msgid "Tried to update avatar for unsaved remote profile %s" msgstr "" -#: actions/feedsubsettings.php:231 -msgid "Previewing feed:" +#: classes/Ostatus_profile.php:797 +#, php-format +msgid "Unable to fetch avatar from %s" +msgstr "" + +#: lib/salmonaction.php:41 +msgid "This method requires a POST." +msgstr "" + +#: lib/salmonaction.php:45 +msgid "Salmon requires application/magic-envelope+xml" +msgstr "" + +#: lib/salmonaction.php:55 +msgid "Salmon signature verification failed." +msgstr "" + +#: lib/salmonaction.php:66 +msgid "Salmon post must be an Atom entry." +msgstr "" + +#: lib/salmonaction.php:114 +msgid "Unrecognized activity type." +msgstr "" + +#: lib/salmonaction.php:122 +msgid "This target doesn't understand posts." +msgstr "" + +#: lib/salmonaction.php:127 +msgid "This target doesn't understand follows." +msgstr "" + +#: lib/salmonaction.php:132 +msgid "This target doesn't understand unfollows." +msgstr "" + +#: lib/salmonaction.php:137 +msgid "This target doesn't understand favorites." +msgstr "" + +#: lib/salmonaction.php:142 +msgid "This target doesn't understand unfavorites." +msgstr "" + +#: lib/salmonaction.php:147 +msgid "This target doesn't understand share events." +msgstr "" + +#: lib/salmonaction.php:152 +msgid "This target doesn't understand joins." +msgstr "" + +#: lib/salmonaction.php:157 +msgid "This target doesn't understand leave events." +msgstr "" + +#: OStatusPlugin.php:319 +#, php-format +msgid "Sent from %s via OStatus" +msgstr "" + +#: OStatusPlugin.php:371 +msgid "Could not set up remote subscription." +msgstr "" + +#: OStatusPlugin.php:487 +msgid "Could not set up remote group membership." +msgstr "" + +#: OStatusPlugin.php:504 +#, php-format +msgid "%s has joined group %s." +msgstr "" + +#: OStatusPlugin.php:512 +msgid "Failed joining remote group." +msgstr "" + +#: OStatusPlugin.php:553 +msgid "Leave" +msgstr "" + +#: OStatusPlugin.php:554 +#, php-format +msgid "%s has left group %s." +msgstr "" + +#: OStatusPlugin.php:685 +msgid "Subscribe to remote user" +msgstr "" + +#: OStatusPlugin.php:726 +msgid "Profile update" +msgstr "" + +#: OStatusPlugin.php:727 +#, php-format +msgid "%s has updated their profile page." +msgstr "" + +#: tests/gettext-speedtest.php:57 +msgid "Feeds" msgstr "" diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php new file mode 100644 index 000000000..d553a7d62 --- /dev/null +++ b/plugins/OStatus/scripts/updateostatus.php @@ -0,0 +1,127 @@ +#!/usr/bin/env php +<?php +/* + * StatusNet - a distributed open-source microblogging tool + * Copyright (C) 2008, 2009, 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_UPDATEOSTATUS_HELP +updateostatus.php [options] +update the OMB subscriptions of a user to use OStatus if possible + + -i --id ID of user to update + -n --nickname nickname of the user to update + -a --all update all + +END_OF_UPDATEOSTATUS_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +try { + $user = null; + + if (have_option('i', 'id')) { + $id = get_option_value('i', 'id'); + $user = User::staticGet('id', $id); + if (empty($user)) { + throw new Exception("Can't find user with id '$id'."); + } + updateProfileURL($user); + } else if (have_option('n', 'nickname')) { + $nickname = get_option_value('n', 'nickname'); + $user = User::staticGet('nickname', $nickname); + if (empty($user)) { + throw new Exception("Can't find user with nickname '$nickname'"); + } + updateProfileURL($user); + } else if (have_option('a', 'all')) { + $user = new User(); + if ($user->find()) { + while ($user->fetch()) { + updateOStatus($user); + } + } + } else { + show_help(); + exit(1); + } +} catch (Exception $e) { + print $e->getMessage()."\n"; + exit(1); +} + +function updateOStatus($user) +{ + if (!have_option('q', 'quiet')) { + echo "{$user->nickname}..."; + } + + $up = $user->getProfile(); + + $sp = $user->getSubscriptions(); + + $rps = array(); + + while ($sp->fetch()) { + $remote = Remote_profile::staticGet('id', $sp->id); + + if (!empty($remote)) { + $rps[] = clone($sp); + } + } + + if (!have_option('q', 'quiet')) { + echo count($rps) . "\n"; + } + + foreach ($rps as $rp) { + try { + if (!have_option('q', 'quiet')) { + echo "Checking {$rp->nickname}..."; + } + + $op = Ostatus_profile::ensureProfile($rp->profileurl); + + if (empty($op)) { + echo "can't convert.\n"; + continue; + } else { + if (!have_option('q', 'quiet')) { + echo "Converting..."; + } + Subscription::cancel($up, $rp); + Subscription::start($up, $op->localProfile()); + if (!have_option('q', 'quiet')) { + echo "done.\n"; + } + } + + } catch (Exception $e) { + if (!have_option('q', 'quiet')) { + echo "fail.\n"; + } + continue; + common_log(LOG_WARNING, "Couldn't convert OMB subscription (" . $up->nickname . ", " . $rp->nickname . + ") to OStatus: " . $e->getMessage()); + continue; + } + } +} diff --git a/plugins/RegisterThrottle/RegisterThrottlePlugin.php b/plugins/RegisterThrottle/RegisterThrottlePlugin.php new file mode 100644 index 000000000..05709b780 --- /dev/null +++ b/plugins/RegisterThrottle/RegisterThrottlePlugin.php @@ -0,0 +1,249 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * Throttle registration by IP address + * + * 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 Spam + * @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); +} + +/** + * Throttle registration by IP address + * + * We a) record IP address of registrants and b) throttle registrations. + * + * @category Spam + * @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 RegisterThrottlePlugin extends Plugin +{ + /** + * Array of time spans in seconds to limits. + * + * Default is 3 registrations per hour, 5 per day, 10 per week. + */ + + public $regLimits = array(604800 => 10, // per week + 86400 => 5, // per day + 3600 => 3); // per hour + + /** + * Database schema setup + * + * We store user registrations in a table registration_ip. + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing user-submitted flags on profiles + + $schema->ensureTable('registration_ip', + array(new ColumnDef('user_id', 'integer', null, + false, 'PRI'), + new ColumnDef('ipaddress', 'varchar', 15, false, 'MUL'), + new ColumnDef('created', 'timestamp', null, false, 'MUL'))); + + 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 'Registration_ip': + include_once $dir . '/'.$cls.'.php'; + return false; + default: + return true; + } + } + + /** + * Called when someone tries to register. + * + * We check the IP here to determine if it goes over any of our + * configured limits. + * + * @param Action $action Action that is being executed + * + * @return boolean hook value + * + */ + + function onStartRegistrationTry($action) + { + $ipaddress = $this->_getIpAddress(); + + if (empty($ipaddress)) { + throw new ServerException(_m('Cannot find IP address.')); + } + + foreach ($this->regLimits as $seconds => $limit) { + + $this->debug("Checking $seconds ($limit)"); + + $reg = $this->_getNthReg($ipaddress, $limit); + + if (!empty($reg)) { + $this->debug("Got a {$limit}th registration."); + $regtime = strtotime($reg->created); + $now = time(); + $this->debug("Comparing {$regtime} to {$now}"); + if ($now - $regtime < $seconds) { + throw new Exception(_("Too many registrations. Take a break and try again later.")); + } + } + } + + return true; + } + + /** + * Called after someone registers. + * + * We record the successful registration and IP address. + * + * @param Action $action Action that is being executed + * + * @return boolean hook value + * + */ + + function onEndRegistrationTry($action) + { + $ipaddress = $this->_getIpAddress(); + + if (empty($ipaddress)) { + throw new ServerException(_m('Cannot find IP address.')); + } + + $user = common_current_user(); + + if (empty($user)) { + throw new ServerException(_m('Cannot find user after successful registration.')); + } + + $reg = new Registration_ip(); + + $reg->user_id = $user->id; + $reg->ipaddress = $ipaddress; + + $result = $reg->insert(); + + if (!$result) { + common_log_db_error($reg, 'INSERT', __FILE__); + // @todo throw an exception? + } + + return true; + } + + /** + * Check the version of the plugin. + * + * @param array &$versions Version array. + * + * @return boolean hook value + */ + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'RegisterThrottle', + 'version' => STATUSNET_VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:RegisterThrottle', + 'description' => + _m('Throttles excessive registration from a single IP.')); + return true; + } + + /** + * Gets the current IP address. + * + * @return string IP address or null if not found. + */ + + private function _getIpAddress() + { + $keys = array('HTTP_X_FORWARDED_FOR', + 'CLIENT-IP', + 'REMOTE_ADDR'); + + foreach ($keys as $k) { + if (!empty($_SERVER[$k])) { + return $_SERVER[$k]; + } + } + + return null; + } + + /** + * Gets the Nth registration with the given IP address. + * + * @param string $ipaddress Address to key on + * @param integer $n Nth address + * + * @return Registration_ip nth registration or null if not found. + */ + + private function _getNthReg($ipaddress, $n) + { + $reg = new Registration_ip(); + + $reg->ipaddress = $ipaddress; + + $reg->orderBy('created DESC'); + $reg->limit($n - 1, 1); + + if ($reg->find(true)) { + return $reg; + } else { + return null; + } + } +} diff --git a/plugins/RegisterThrottle/Registration_ip.php b/plugins/RegisterThrottle/Registration_ip.php new file mode 100644 index 000000000..7e61d089e --- /dev/null +++ b/plugins/RegisterThrottle/Registration_ip.php @@ -0,0 +1,124 @@ +<?php +/** + * Data class for storing IP addresses of new registrants. + * + * 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 storing IP addresses of new registrants. + * + * @category Spam + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class Registration_ip extends Memcached_DataObject +{ + public $__table = 'registration_ip'; // table name + public $user_id; // int(4) primary_key not_null + public $ipaddress; // varchar(15) + public $created; // timestamp + + /** + * 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 User_greeting_count object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Registration_ip', $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * @return array array of column definitions + */ + + function table() + { + return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'ipaddress' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL); + } + + /** + * return key definitions for DB_DataObject + * + * DB_DataObject needs to know about keys that the table has; this function + * defines them. + * + * @return array key definitions + */ + + function keys() + { + return array('user_id' => 'K'); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. + * + * @return array key definitions + */ + + function keyTypes() + { + return $this->keys(); + } + + /** + * 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/TwitterBridge/README b/plugins/TwitterBridge/README index d3bcda598..72278b32e 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -1,47 +1,89 @@ +Twitter Bridge Plugin +===================== + This Twitter "bridge" plugin allows you to integrate your StatusNet instance with Twitter. Installing it will allow your users to: - - automatically post notices to thier Twitter accounts + - automatically post notices to their Twitter accounts - automatically subscribe to other Twitter users who are also using your StatusNet install, if possible (requires running a daemon) - import their Twitter friends' tweets (requires running a daemon) + - allow users to authenticate using Twitter ('Sign in with Twitter') Installation ------------ -To enable the plugin, add the following to your config.php: - - addPlugin("TwitterBridge"); - -OAuth is used to to access protected resources on Twitter (as opposed to -HTTP Basic Auth)*. To use Twitter bridging you will need to register -your instance of StatusNet as an application on Twitter -(http://twitter.com/apps), and update the following variables in your -config.php with the consumer key and secret Twitter generates for you: - - $config['twitter']['consumer_key'] = 'YOURKEY'; - $config['twitter']['consumer_secret'] = 'YOURSECRET'; +OAuth 1.0a (http://oauth.net) is used to to access protected resources +on Twitter (as opposed to HTTP Basic Auth)*. To use Twitter bridging +you will need to register your instance of StatusNet as an application +on Twitter (http://twitter.com/apps). During the application +registration process your application will be assigned a "consumer" key +and secret, which the plugin will use to make OAuth requests to Twitter. +You can either pass the consumer key and secret in when you enable the +plugin, or set it using the Twitter administration panel. When registering your application with Twitter set the type to "Browser" and your Callback URL to: http://example.org/mublog/twitter/authorization -The default access type should be, "Read & Write". +(Change "example.org" to your site domain and "mublog" to your site +path.) + +The default access type should be "Read & Write". + +To enable the plugin, add the following to your config.php: + + addPlugin( + 'TwitterBridge', + array( + 'consumer_key' => 'YOUR_CONSUMER_KEY', + 'consumer_secret' => 'YOUR_CONSUMER_SECRET' + ) + ); * Note: The plugin will still push notices to Twitter for users who - have previously setup the Twitter bridge using their Twitter name and - password under an older versions of StatusNet, but all new Twitter + have previously set up the Twitter bridge using their Twitter name and + password under an older version of StatusNet, but all new Twitter bridge connections will use OAuth. -Deamons +Administration panel +-------------------- + +As of StatusNet 0.9.0 there is a new administration panel that allows +you to configure Twitter bridge settings within StatusNet itself, +instead of having to specify them manually in your config.php. To enable +the administration panel, you will need to add it to the list of active +administration panels. You can do this via your config.php. E.g.: + + $config['admin']['panels'][] = 'twitter'; + +And to access it, you'll need to use a user with the "administrator" +role (see: scripts/userrole.php). + +Sign in with Twitter +-------------------- + +With 0.9.0, StatusNet optionally allows users to register and +authenticate using their Twitter credentials via the "Sign in with +Twitter" pattern described here: + + http://apiwiki.twitter.com/Sign-in-with-Twitter + +The option is _on_ by default when you install the plugin, but it can +disabled via the Twitter bridge administration panel, or by adding the +following line to your config.php: + + $config['twitter']['signin'] = false; + +Daemons ------- -For friend syncing and importing notices running two additional daemon -scripts is necessary (synctwitterfriends.php and -twitterstatusfetcher.php). +For friend syncing and importing Twitter tweets, running two +additional daemon scripts is necessary: synctwitterfriends.php and +twitterstatusfetcher.php. -In the daemons subidrectory of the plugin are three scripts: +In the daemons subdirectory of the plugin are three scripts: * Twitter Friends Syncing (daemons/synctwitterfriends.php) @@ -51,13 +93,13 @@ subscribe to "friends" (people they "follow") on Twitter who also have accounts on your StatusNet system, and who have previously set up a link for automatically posting notices to Twitter. -The plugin will try to start this daemon when you run -scripts/startdaemons.sh. +The plugin will start this daemon when you run scripts/startdaemons.sh. * Importing statuses from Twitter (daemons/twitterstatusfetcher.php) -To allow your users to import their friends' Twitter statuses, you will -need to enable the bidirectional Twitter bridge in your config.php: +You can allow uses to enable importing of your friends' Twitter +timelines either in the Twitter bridge administration panel or in your +config.php using the following configuration line: $config['twitterimport']['enabled'] = true; @@ -66,8 +108,9 @@ other daemons when you run scripts/startdaemons.sh. Additionally, you will want to set the integration source variable, which will keep notices posted to Twitter via StatusNet from looping -back. The integration source should be set to the name of your -application, exactly as you specified it on the settings page for your +back. You can do this in the Twitter bridge administration panel, or +via config.php. The integration source should be set to the name of your +application _exactly_ as you specified it on the settings page for your StatusNet application on Twitter, e.g.: $config['integration']['source'] = 'YourApp'; @@ -79,7 +122,9 @@ set up Twitter bridging. It's not strictly necessary to run this queue handler, and sites that haven't enabled queuing are still able to push notices to Twitter, but -for larger sites and sites that wish to improve performance, this -script allows notices to be sent "offline" via a separate process. +for larger sites and sites that wish to improve performance the script +allows notices to be sent "offline" via a separate process. -The plugin will start this script when you run scripts/startdaemons.sh. +StatusNet will automatically use the TwitterQueueHandler if you have +enabled the queuing subsystem. See the "Queues and daemons" section of +the main README file for more information about how to do that. diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index c7f57ffc7..6ce69d5e2 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -23,7 +23,7 @@ * @author Julien C <chaumond@gmail.com> * @copyright 2009-2010 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ + * @link http://status.net/ */ if (!defined('STATUSNET')) { @@ -32,8 +32,6 @@ if (!defined('STATUSNET')) { require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; -define('TWITTERBRIDGEPLUGIN_VERSION', '0.9'); - /** * Plugin for sending and importing Twitter statuses * @@ -44,19 +42,41 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9'); * @author Zach Copley <zach@status.net> * @author Julien C <chaumond@gmail.com> * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ + * @link http://status.net/ * @link http://twitter.com/ */ class TwitterBridgePlugin extends Plugin { + + const VERSION = STATUSNET_VERSION; + /** * Initializer for the plugin. */ - function __construct() + function initialize() { - parent::__construct(); + // Allow the key and secret to be passed in + // Control panel will override + + if (isset($this->consumer_key)) { + $key = common_config('twitter', 'consumer_key'); + if (empty($key)) { + Config::save('twitter', 'consumer_key', $this->consumer_key); + } + } + + if (isset($this->consumer_secret)) { + $secret = common_config('twitter', 'consumer_secret'); + if (empty($secret)) { + Config::save( + 'twitter', + 'consumer_secret', + $this->consumer_secret + ); + } + } } /** @@ -71,10 +91,17 @@ class TwitterBridgePlugin extends Plugin function onRouterInitialized($m) { - $m->connect('twitter/authorization', - array('action' => 'twitterauthorization')); + $m->connect( + 'twitter/authorization', + array('action' => 'twitterauthorization') + ); $m->connect('settings/twitter', array('action' => 'twittersettings')); - $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); + + if (common_config('twitter', 'signin')) { + $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); + } + + $m->connect('admin/twitter', array('action' => 'twitteradminpanel')); return true; } @@ -88,13 +115,16 @@ class TwitterBridgePlugin extends Plugin */ function onEndLoginGroupNav(&$action) { - $action_name = $action->trimmed('action'); - $action->menuItem(common_local_url('twitterlogin'), - _('Twitter'), - _('Login or register using Twitter'), - 'twitterlogin' === $action_name); + if (common_config('twitter', 'signin')) { + $action->menuItem( + common_local_url('twitterlogin'), + _m('Twitter'), + _m('Login or register using Twitter'), + 'twitterlogin' === $action_name + ); + } return true; } @@ -110,10 +140,12 @@ class TwitterBridgePlugin extends Plugin { $action_name = $action->trimmed('action'); - $action->menuItem(common_local_url('twittersettings'), - _m('Twitter'), - _m('Twitter integration options'), - $action_name === 'twittersettings'); + $action->menuItem( + common_local_url('twittersettings'), + _m('Twitter'), + _m('Twitter integration options'), + $action_name === 'twittersettings' + ); return true; } @@ -132,6 +164,7 @@ class TwitterBridgePlugin extends Plugin case 'TwittersettingsAction': case 'TwitterauthorizationAction': case 'TwitterloginAction': + case 'TwitteradminpanelAction': include_once INSTALLDIR . '/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; @@ -173,12 +206,18 @@ class TwitterBridgePlugin extends Plugin */ function onGetValidDaemons($daemons) { - array_push($daemons, INSTALLDIR . - '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); + array_push( + $daemons, + INSTALLDIR + . '/plugins/TwitterBridge/daemons/synctwitterfriends.php' + ); if (common_config('twitterimport', 'enabled')) { - array_push($daemons, INSTALLDIR - . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); + array_push( + $daemons, + INSTALLDIR + . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php' + ); } return true; @@ -197,17 +236,55 @@ class TwitterBridgePlugin extends Plugin return true; } + /** + * Add a Twitter tab to the admin panel + * + * @param Widget $nav Admin panel nav + * + * @return boolean hook value + */ + + function onEndAdminPanelNav($nav) + { + if (AdminPanelAction::canAdmin('twitter')) { + + $action_name = $nav->action->trimmed('action'); + + $nav->out->menuItem( + common_local_url('twitteradminpanel'), + _m('Twitter'), + _m('Twitter bridge configuration'), + $action_name == 'twitteradminpanel', + 'nav_twitter_admin_panel' + ); + } + + return true; + } + + /** + * Plugin version data + * + * @param array &$versions array of version blocks + * + * @return boolean hook value + */ + function onPluginVersion(&$versions) { - $versions[] = array('name' => 'TwitterBridge', - 'version' => TWITTERBRIDGEPLUGIN_VERSION, - 'author' => 'Zach Copley', - 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge', - 'rawdescription' => - _m('The Twitter "bridge" plugin allows you to integrate ' . - 'your StatusNet instance with ' . - '<a href="http://twitter.com/">Twitter</a>.')); + $versions[] = array( + 'name' => 'TwitterBridge', + 'version' => self::VERSION, + 'author' => 'Zach Copley, Julien C', + 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge', + 'rawdescription' => _m( + 'The Twitter "bridge" plugin allows you to integrate ' . + 'your StatusNet instance with ' . + '<a href="http://twitter.com/">Twitter</a>.' + ) + ); return true; } } + diff --git a/plugins/TwitterBridge/twitteradminpanel.php b/plugins/TwitterBridge/twitteradminpanel.php new file mode 100644 index 000000000..b22e6d99f --- /dev/null +++ b/plugins/TwitterBridge/twitteradminpanel.php @@ -0,0 +1,280 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Twitter bridge 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 Twitter bridge 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 TwitteradminpanelAction extends AdminPanelAction +{ + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + return _m('Twitter'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + return _m('Twitter bridge settings'); + } + + /** + * Show the Twitter admin panel form + * + * @return void + */ + + function showForm() + { + $form = new TwitterAdminPanelForm($this); + $form->show(); + return; + } + + /** + * Save settings from the form + * + * @return void + */ + + function saveSettings() + { + static $settings = array( + 'twitter' => array('consumer_key', 'consumer_secret'), + 'integration' => array('source') + ); + + static $booleans = array( + 'twitter' => array('signin'), + 'twitterimport' => array('enabled') + ); + + $values = array(); + + foreach ($settings as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] + = $this->trimmed($setting); + } + } + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] + = ($this->boolean($setting)) ? 1 : 0; + } + } + + // 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]); + } + } + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); + + return; + } + + function validate(&$values) + { + // Validate consumer key and secret (can't be too long) + + if (mb_strlen($values['twitter']['consumer_key']) > 255) { + $this->clientError( + _m("Invalid consumer key. Max length is 255 characters.") + ); + } + + if (mb_strlen($values['twitter']['consumer_secret']) > 255) { + $this->clientError( + _m("Invalid consumer secret. Max length is 255 characters.") + ); + } + } +} + +class TwitterAdminPanelForm extends AdminForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'twitteradminpanel'; + } + + /** + * 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('twitteradminpanel'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart( + 'fieldset', + array('id' => 'settings_twitter-application') + ); + $this->out->element('legend', null, _m('Twitter application settings')); + $this->out->elementStart('ul', 'form_data'); + + $this->li(); + $this->input( + 'consumer_key', + _m('Consumer key'), + _m('Consumer key assigned by Twitter'), + 'twitter' + ); + $this->unli(); + + $this->li(); + $this->input( + 'consumer_secret', + _m('Consumer secret'), + _m('Consumer secret assigned by Twitter'), + 'twitter' + ); + $this->unli(); + + $this->li(); + $this->input( + 'source', + _m('Integration source'), + _m('Name of your Twitter application'), + 'integration' + ); + $this->unli(); + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart( + 'fieldset', + array('id' => 'settings_twitter-options') + ); + $this->out->element('legend', null, _m('Options')); + + $this->out->elementStart('ul', 'form_data'); + + $this->li(); + + $this->out->checkbox( + 'signin', _m('Enable "Sign-in with Twitter"'), + (bool) $this->value('signin', 'twitter'), + _m('Allow users to login with their Twitter credentials') + ); + $this->unli(); + + $this->li(); + $this->out->checkbox( + 'enabled', _m('Enable Twitter import'), + (bool) $this->value('enabled', 'twitterimport'), + _m('Allow users to import their Twitter friends\' timelines') + ); + $this->unli(); + + $this->out->elementEnd('ul'); + + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save'), 'submit', null, _('Save Twitter settings')); + } +} diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index cabf69d7a..c93f6666b 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -47,7 +47,7 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * @author Zach Copley <zach@status.net> * @author Julien C <chaumond@gmail.com> * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ + * @link http://status.net/ * */ class TwitterauthorizationAction extends Action diff --git a/scripts/update_po_templates.php b/scripts/update_po_templates.php index 61a6ac783..0bfa62a22 100755 --- a/scripts/update_po_templates.php +++ b/scripts/update_po_templates.php @@ -36,7 +36,11 @@ xgettext \ --default-domain=$domain \ --output=locale/$domain.po \ --language=PHP \ - --keyword="_m:1" \ + --add-comments=TRANS \ + --keyword="_m:1,1t" \ + --keyword="_m:1c,2,2t" \ + --keyword="_m:1,2,3t" \ + --keyword="_m:1c,2,3,4t" \ --keyword="pgettext:1c,2" \ --keyword="npgettext:1c,2,3" \ actions/*.php \ @@ -62,6 +66,7 @@ xgettext \ --default-domain=$domain \ --output=locale/$domain.po \ --language=PHP \ + --add-comments=TRANS \ --keyword='' \ --keyword="_m:1,1t" \ --keyword="_m:1c,2,2t" \ diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 52f97f6b1..f32c57ea4 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -799,8 +799,8 @@ list-style-type:none; display:inline; } .entity_tags li { -float:left; -margin-right:11px; +display:inline; +margin-right:7px; } .aside .section { |