From 206229875511d24a97a0aac79605f6868d21ee7f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 21 May 2010 14:07:59 -0700 Subject: [PATCH 01/17] Add $config['queue']['stomp_enqueue_to'] override for which queue server to send to. Must be set to a value that matches one of the entries in $config['queue']['stomp_server'] array, otherwise ignored. --- lib/stompqueuemanager.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 5d5c7ccfbd..de4ba7f01f 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -122,7 +122,19 @@ class StompQueueManager extends QueueManager public function enqueue($object, $queue) { $this->_connect(); - return $this->_doEnqueue($object, $queue, $this->defaultIdx); + if (common_config('queue', 'stomp_enqueue_on')) { + // We're trying to force all writes to a single server. + // WARNING: this might do odd things if that server connection dies. + $idx = array_search(common_config('queue', 'stomp_enqueue_on'), + $this->servers); + if ($idx === false) { + common_log(LOG_ERR, 'queue stomp_enqueue_on setting does not match our server list.'); + $idx = $this->defaultIdx; + } + } else { + $idx = $this->defaultIdx; + } + return $this->_doEnqueue($object, $queue, $idx); } /** From dc22ed84807555f6a16c041c16b3bc607c6587d8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sat, 22 May 2010 17:43:56 -0700 Subject: [PATCH 02/17] Hotpatch for Facebook mirror problems: drop messages when hitting rate limit (err 341) instead of retrying forever. On unknown errors, now throwing an exception so it'll hit the message retry limits. --- plugins/Facebook/facebookutil.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index ab2d427264..045891649c 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -158,9 +158,22 @@ function facebookBroadcastNotice($notice) remove_facebook_app($flink); - } else { + } else if ($code == 341) { + // 341 Feed action request limit reached - Unable to update Facebook status + // Reposting immediately probably won't work, so drop the message for now. :( + + common_log(LOG_ERR, "Facebook rate limit hit: dropping notice $notice->id"); + return true; + } else { // Try sending again later. + // + // @fixme at the moment, returning false here could lead to an infinite loop + // if the error condition isn't actually transitory. + // + // Temporarily throwing an exception to kill the process so it'll hit our + // retry limits. + throw new Exception("Facebook error $code on notice $notice->id"); return false; } From f7add6f25f37780fde2269b254c237caea9ef98d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 24 May 2010 07:47:15 -0700 Subject: [PATCH 03/17] Handle funky notice deletion cases more gracefully: if we already have a deleted_notice entry, don't freak out when we try to save it again on the second try. --- classes/Notice.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index e173a24690..d85c8cd33a 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -97,15 +97,20 @@ class Notice extends Memcached_DataObject // For auditing purposes, save a record that the notice // was deleted. - $deleted = new Deleted_notice(); + // @fixme we have some cases where things get re-run and so the + // insert fails. + $deleted = Deleted_notice::staticGet('id', $this->id); + if (!$deleted) { + $deleted = new Deleted_notice(); - $deleted->id = $this->id; - $deleted->profile_id = $this->profile_id; - $deleted->uri = $this->uri; - $deleted->created = $this->created; - $deleted->deleted = common_sql_now(); + $deleted->id = $this->id; + $deleted->profile_id = $this->profile_id; + $deleted->uri = $this->uri; + $deleted->created = $this->created; + $deleted->deleted = common_sql_now(); - $deleted->insert(); + $deleted->insert(); + } // Clear related records From 8d8751472766d1d6b0f89616152503ad35f65ab0 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 21 May 2010 05:03:23 +0000 Subject: [PATCH 04/17] Upgrade to latest old REST API library (0.1.0) --- plugins/Facebook/facebook/facebook.php | 74 ++++++++++--------- .../facebook/facebookapi_php5_restlib.php | 56 +------------- 2 files changed, 42 insertions(+), 88 deletions(-) diff --git a/plugins/Facebook/facebook/facebook.php b/plugins/Facebook/facebook/facebook.php index 440706cbc3..76696c1d55 100644 --- a/plugins/Facebook/facebook/facebook.php +++ b/plugins/Facebook/facebook/facebook.php @@ -45,7 +45,9 @@ class Facebook { public $user; public $profile_user; public $canvas_user; + public $ext_perms = array(); protected $base_domain; + /* * Create a Facebook client like this: * @@ -104,17 +106,17 @@ class Facebook { * * For nitty-gritty details of when each of these is used, check out * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature - * - * @param bool resolve_auth_token convert an auth token into a session */ - public function validate_fb_params($resolve_auth_token=true) { + public function validate_fb_params() { $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig'); // note that with preload FQL, it's possible to receive POST params in // addition to GET, so use a different prefix to differentiate them if (!$this->fb_params) { $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig'); - $fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig'); + $fb_post_params = $this->get_valid_fb_params($_POST, + 48 * 3600, // 48 hours + 'fb_post_sig'); $this->fb_params = array_merge($fb_params, $fb_post_params); } @@ -128,6 +130,9 @@ class Facebook { $this->fb_params['canvas_user'] : null; $this->base_domain = isset($this->fb_params['base_domain']) ? $this->fb_params['base_domain'] : null; + $this->ext_perms = isset($this->fb_params['ext_perms']) ? + explode(',', $this->fb_params['ext_perms']) + : array(); if (isset($this->fb_params['session_key'])) { $session_key = $this->fb_params['session_key']; @@ -141,13 +146,11 @@ class Facebook { $this->set_user($user, $session_key, $expires); - } - // if no Facebook parameters were found in the GET or POST variables, - // then fall back to cookies, which may have cached user information - // Cookies are also used to receive session data via the Javascript API - else if ($cookies = - $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) { - + } else if ($cookies = + $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) { + // if no Facebook parameters were found in the GET or POST variables, + // then fall back to cookies, which may have cached user information + // Cookies are also used to receive session data via the Javascript API $base_domain_cookie = 'base_domain_' . $this->api_key; if (isset($_COOKIE[$base_domain_cookie])) { $this->base_domain = $_COOKIE[$base_domain_cookie]; @@ -160,25 +163,6 @@ class Facebook { $cookies['session_key'], $expires); } - // finally, if we received no parameters, but the 'auth_token' GET var - // is present, then we are in the middle of auth handshake, - // so go ahead and create the session - else if ($resolve_auth_token && isset($_GET['auth_token']) && - $session = $this->do_get_session($_GET['auth_token'])) { - if ($this->generate_session_secret && - !empty($session['secret'])) { - $session_secret = $session['secret']; - } - - if (isset($session['base_domain'])) { - $this->base_domain = $session['base_domain']; - } - - $this->set_user($session['uid'], - $session['session_key'], - $session['expires'], - isset($session_secret) ? $session_secret : null); - } return !empty($this->fb_params); } @@ -309,11 +293,28 @@ class Facebook { // require_add and require_install have been removed. // see http://developer.facebook.com/news.php?blog=1&story=116 for more details - public function require_login() { - if ($user = $this->get_loggedin_user()) { + public function require_login($required_permissions = '') { + $user = $this->get_loggedin_user(); + $has_permissions = true; + + if ($required_permissions) { + $this->require_frame(); + $permissions = array_map('trim', explode(',', $required_permissions)); + foreach ($permissions as $permission) { + if (!in_array($permission, $this->ext_perms)) { + $has_permissions = false; + break; + } + } + } + + if ($user && $has_permissions) { return $user; } - $this->redirect($this->get_login_url(self::current_url(), $this->in_frame())); + + $this->redirect( + $this->get_login_url(self::current_url(), $this->in_frame(), + $required_permissions)); } public function require_frame() { @@ -342,10 +343,11 @@ class Facebook { return $page . '?' . http_build_query($params); } - public function get_login_url($next, $canvas) { + public function get_login_url($next, $canvas, $req_perms = '') { $page = self::get_facebook_url().'/login.php'; - $params = array('api_key' => $this->api_key, - 'v' => '1.0'); + $params = array('api_key' => $this->api_key, + 'v' => '1.0', + 'req_perms' => $req_perms); if ($next) { $params['next'] = $next; diff --git a/plugins/Facebook/facebook/facebookapi_php5_restlib.php b/plugins/Facebook/facebook/facebookapi_php5_restlib.php index fa1088cd00..e249a326b2 100755 --- a/plugins/Facebook/facebook/facebookapi_php5_restlib.php +++ b/plugins/Facebook/facebook/facebookapi_php5_restlib.php @@ -569,7 +569,7 @@ function toggleDisplay(id, type) { return $this->call_method('facebook.events.invite', array('eid' => $eid, 'uids' => $uids, - 'personal_message', $personal_message)); + 'personal_message' => $personal_message)); } /** @@ -1350,53 +1350,6 @@ function toggleDisplay(id, type) { ); } - /** - * Dashboard API - */ - - /** - * Set the news for the specified user. - * - * @param int $uid The user for whom you are setting news for - * @param string $news Text of news to display - * - * @return bool Success - */ - public function dashboard_setNews($uid, $news) { - return $this->call_method('facebook.dashboard.setNews', - array('uid' => $uid, - 'news' => $news) - ); - } - - /** - * Get the current news of the specified user. - * - * @param int $uid The user to get the news of - * - * @return string The text of the current news for the user - */ - public function dashboard_getNews($uid) { - return json_decode( - $this->call_method('facebook.dashboard.getNews', - array('uid' => $uid) - ), true); - } - - /** - * Set the news for the specified user. - * - * @param int $uid The user you are clearing the news of - * - * @return bool Success - */ - public function dashboard_clearNews($uid) { - return $this->call_method('facebook.dashboard.clearNews', - array('uid' => $uid) - ); - } - - /** * Creates a note with the specified title and content. @@ -2005,7 +1958,7 @@ function toggleDisplay(id, type) { * @return array A list of strings describing any compile errors for the * submitted FBML */ - function profile_setFBML($markup, + public function profile_setFBML($markup, $uid=null, $profile='', $profile_action='', @@ -3267,9 +3220,8 @@ function toggleDisplay(id, type) { } else { $get['v'] = '1.0'; } - if (isset($this->use_ssl_resources) && - $this->use_ssl_resources) { - $post['return_ssl_resources'] = true; + if (isset($this->use_ssl_resources)) { + $post['return_ssl_resources'] = (bool) $this->use_ssl_resources; } return array($get, $post); } From 777ca74500025c616ae689f9a92b3233cf8466f7 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 24 May 2010 21:25:21 +0000 Subject: [PATCH 05/17] Upgrade Facebook posting: - Use FQL to check for publish stream permission instead of old REST API - Better error handling, especially for error code 100 - More logging / better log messages --- plugins/Facebook/facebookutil.php | 320 +++++++++++++++++++++++------- 1 file changed, 247 insertions(+), 73 deletions(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index 045891649c..e52a3deaef 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -81,114 +81,288 @@ function isFacebookBound($notice, $flink) { function facebookBroadcastNotice($notice) { $facebook = getFacebook(); - $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); + $flink = Foreign_link::getByUserID( + $notice->profile_id, + FACEBOOK_SERVICE + ); if (isFacebookBound($notice, $flink)) { // Okay, we're good to go, update the FB status - $status = null; $fbuid = $flink->foreign_id; $user = $flink->getUser(); - $attachments = $notice->attachments(); try { - // Get the status 'verb' (prefix) the user has set + // Check permissions - // XXX: Does this call count against our per user FB request limit? - // If so we should consider storing verb elsewhere or not storing + common_debug( + 'FacebookPlugin - checking for publish_stream permission for user ' + . "$user->nickname ($user->id), Facebook UID: $fbuid" + ); - $prefix = trim($facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, - $fbuid)); + // NOTE: $facebook->api_client->users_hasAppPermission('publish_stream', $fbuid) + // has been returning bogus results, so we're using FQL to check for + // publish_stream permission now - $status = "$prefix $notice->content"; + $fql = "SELECT publish_stream FROM permissions WHERE uid = $fbuid"; + $result = $facebook->api_client->fql_query($fql); - common_debug("FacebookPlugin - checking for publish_stream permission for user $user->id"); + $canPublish = 0; - $can_publish = $facebook->api_client->users_hasAppPermission('publish_stream', - $fbuid); + if (!empty($result)) { + $canPublish = $result[0]['publish_stream']; + } - common_debug("FacebookPlugin - checking for status_update permission for user $user->id"); + if ($canPublish == 1) { + common_debug( + "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid " + . 'has publish_stream permission.' + ); + } else { + common_debug( + "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid " + . 'does NOT have publish_stream permission. Facebook ' + . 'returned: ' . var_export($result, true) + ); + } - $can_update = $facebook->api_client->users_hasAppPermission('status_update', - $fbuid); - if (!empty($attachments) && $can_publish == 1) { - $fbattachment = format_attachments($attachments); - $facebook->api_client->stream_publish($status, $fbattachment, - null, null, $fbuid); - common_log(LOG_INFO, - "FacebookPlugin - Posted notice $notice->id w/attachment " . - "to Facebook user's stream (fbuid = $fbuid)."); - } elseif ($can_update == 1 || $can_publish == 1) { - $facebook->api_client->users_setStatus($status, $fbuid, false, true); - common_log(LOG_INFO, - "FacebookPlugin - Posted notice $notice->id to Facebook " . - "as a status update (fbuid = $fbuid)."); + common_debug( + 'FacebookPlugin - checking for status_update permission for user ' + . "$user->nickname ($user->id), Facebook UID: $fbuid. " + ); + + $canUpdate = $facebook->api_client->users_hasAppPermission( + 'status_update', + $fbuid + ); + + if ($canUpdate == 1) { + common_debug( + "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid " + . 'has status_update permission.' + ); + } else { + common_debug( + "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid " + .'does NOT have status_update permission. Facebook ' + . 'returned: ' . var_export($can_publish, true) + ); + } + + // Post to Facebook + + if ($notice->hasAttachments() && $canPublish == 1) { + publishStream($notice, $user, $fbuid); + } elseif ($canUpdate == 1 || $canPublish == 1) { + statusUpdate($notice, $user, $fbuid); } else { $msg = "FacebookPlugin - Not sending notice $notice->id to Facebook " . - "because user $user->nickname hasn't given the " . + "because user $user->nickname has not given the " . 'Facebook app \'status_update\' or \'publish_stream\' permission.'; common_log(LOG_WARNING, $msg); } // Finally, attempt to update the user's profile box - if ($can_publish == 1 || $can_update == 1) { - updateProfileBox($facebook, $flink, $notice); + if ($canPublish == 1 || $canUpdate == 1) { + updateProfileBox($facebook, $flink, $notice, $user); } } catch (FacebookRestClientException $e) { - - $code = $e->getCode(); - - $msg = "FacebookPlugin - Facebook returned error code $code: " . - $e->getMessage() . ' - ' . - "Unable to update Facebook status (notice $notice->id) " . - "for $user->nickname (user id: $user->id)!"; - - common_log(LOG_WARNING, $msg); - - if ($code == 100 || $code == 200 || $code == 250) { - - // 100 The account is 'inactive' (probably - this is not well documented) - // 200 The application does not have permission to operate on the passed in uid parameter. - // 250 Updating status requires the extended permission status_update or publish_stream. - // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML - - remove_facebook_app($flink); - - } else if ($code == 341) { - // 341 Feed action request limit reached - Unable to update Facebook status - // Reposting immediately probably won't work, so drop the message for now. :( - - common_log(LOG_ERR, "Facebook rate limit hit: dropping notice $notice->id"); - return true; - } else { - - // Try sending again later. - // - // @fixme at the moment, returning false here could lead to an infinite loop - // if the error condition isn't actually transitory. - // - // Temporarily throwing an exception to kill the process so it'll hit our - // retry limits. - throw new Exception("Facebook error $code on notice $notice->id"); - - return false; - } - + return handleFacebookError($e, $notice, $flink); } } return true; - } -function updateProfileBox($facebook, $flink, $notice) { - $fbaction = new FacebookAction($output = 'php://output', - $indent = null, $facebook, $flink); +function handleFacebookError($e, $notice, $flink) +{ + $fbuid = $flink->foreign_id; + $user = $flink->getUser(); + $code = $e->getCode(); + $errmsg = $e->getMessage(); + + // XXX: Check for any others? + switch($code) { + case 100: // Invalid parameter + $msg = "FacebookPlugin - Facebook claims notice %d was posted with an invalid parameter (error code 100):" + . "\"%s\" (Notice details: nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). " + . "Removing notice from the Facebook queue for safety."; + common_log( + LOG_ERROR, sprintf( + $msg, + $notice->id, + $errmsg, + $user->nickname, + $user->id, + $fbuid, + $notice->content + ) + ); + return true; + break; + case 200: // Permissions error + case 250: // Updating status requires the extended permission status_update + remove_facebook_app($flink); + return true; // dequeue + break; + case 341: // Feed action request limit reached + $msg = "FacebookPlugin - User %s (User ID=%d, Facebook ID=%d) has exceeded " + . "his/her limit for posting notices to Facebook today. Dequeuing " + . "notice %d."; + common_log( + LOG_INFO, sprintf( + $msg, + $user->nickname, + $user->id, + $fbuid, + $notice->id + ) + ); + // @fixme: We want to rety at a later time when the throttling has expired + // instead of just giving up. + return true; + break; + default: + $msg = "FacebookPlugin - Facebook returned an error we don't know how to deal with while trying to " + . "post notice %d. Error code: %d, error message: \"%s\". (Notice details: " + . "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Re-queueing " + . "notice, and will try to send again later."; + common_log( + LOG_ERROR, sprintf( + $msg, + $notice->id, + $code, + $errmsg, + $user->nickname, + $user->id, + $fbuid, + $notice->content + ) + ); + // Re-queue and try again later + return false; + break; + } +} + +function statusUpdate($notice, $user, $fbuid) +{ + common_debug( + "FacebookPlugin - Attempting to post notice $notice->id " + . "as a status update for $user->nickname ($user->id), " + . "Facebook UID: $fbuid" + ); + + $text = formatNotice($notice, $user, $fbuid); + + $facebook = getFacebook(); + $result = $facebook->api_client->users_setStatus( + $text, + $fbuid, + false, + true + ); + + common_debug('Facebook returned: ' . var_export($result, true)); + + common_log( + LOG_INFO, + "FacebookPlugin - Posted notice $notice->id as a status " + . "update for $user->nickname ($user->id), " + . "Facebook UID: $fbuid" + ); +} + +function publishStream($notice, $user, $fbuid) +{ + common_debug( + "FacebookPlugin - Attempting to post notice $notice->id " + . "as stream item with attachment for $user->nickname ($user->id), " + . "Facebook UID: $fbuid" + ); + + $text = formatNotice($notice, $user, $fbuid); + $fbattachment = format_attachments($notice->attachments()); + + $facebook = getFacebook(); + $facebook->api_client->stream_publish( + $text, + $fbattachment, + null, + null, + $fbuid + ); + + common_debug('Facebook returned: ' . var_export($result, true)); + + common_log( + LOG_INFO, + "FacebookPlugin - Posted notice $notice->id as a stream " + . "item with attachment for $user->nickname ($user->id), " + . "Facebook UID: $fbuid" + ); +} + + +function formatNotice($notice, $user, $fbuid) +{ + // Get the status 'verb' the user has set, if any + + common_debug( + "FacebookPlugin - Looking to see if $user->nickname ($user->id), " + . "Facebook UID: $fbuid has set a verb for Facebook posting..." + ); + + $facebook = getFacebook(); + $verb = trim( + $facebook->api_client->data_getUserPreference( + FACEBOOK_NOTICE_PREFIX, + $fbuid + ) + ); + + common_debug("Facebook returned " . var_export($verb, true)); + + $text = null; + + if (!empty($verb)) { + common_debug("FacebookPlugin - found a verb: $verb"); + $text = trim($verb) . ' ' . $notice->content; + } else { + common_debug("FacebookPlugin - no verb found."); + $text = $notice->content; + } + + return $text; +} + +function updateProfileBox($facebook, $flink, $notice, $user) { + + $facebook = getFacebook(); + $fbaction = new FacebookAction( + $output = 'php://output', + $indent = null, + $facebook, + $flink + ); + + common_debug( + 'FacebookPlugin - Attempting to update profile box with ' + . "content from notice $notice->id for $user->nickname ($user->id)" + . "Facebook UID: $fbuid" + ); + $fbaction->updateProfileBox($notice); + + common_debug( + 'FacebookPlugin - finished updating profile box for ' + . "$user->nickname ($user->id) Facebook UID: $fbuid" + ); + } function format_attachments($attachments) From 1f3a16bbfb41b366bea05f5ba05bb41f44108ab8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 24 May 2010 22:41:34 +0000 Subject: [PATCH 06/17] Clear up warnings I introduced by refactoring Facebook posting --- plugins/Facebook/facebookutil.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index e52a3deaef..d573c34ac1 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -147,7 +147,7 @@ function facebookBroadcastNotice($notice) common_debug( "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid " .'does NOT have status_update permission. Facebook ' - . 'returned: ' . var_export($can_publish, true) + . 'returned: ' . var_export($canPublish, true) ); } @@ -297,8 +297,6 @@ function publishStream($notice, $user, $fbuid) $fbuid ); - common_debug('Facebook returned: ' . var_export($result, true)); - common_log( LOG_INFO, "FacebookPlugin - Posted notice $notice->id as a stream " @@ -307,7 +305,6 @@ function publishStream($notice, $user, $fbuid) ); } - function formatNotice($notice, $user, $fbuid) { // Get the status 'verb' the user has set, if any @@ -350,6 +347,8 @@ function updateProfileBox($facebook, $flink, $notice, $user) { $flink ); + $fbuid = $flink->foreign_id; + common_debug( 'FacebookPlugin - Attempting to update profile box with ' . "content from notice $notice->id for $user->nickname ($user->id)" From 9cde924bb3f928d9df0519a9a6b995633eb45789 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 24 May 2010 23:27:53 +0000 Subject: [PATCH 07/17] Accidentally used the wrong log level (LOG ERROR instead of LOG_ERR) --- plugins/Facebook/facebookutil.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index d573c34ac1..0f24f5441e 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -192,7 +192,7 @@ function handleFacebookError($e, $notice, $flink) . "\"%s\" (Notice details: nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). " . "Removing notice from the Facebook queue for safety."; common_log( - LOG_ERROR, sprintf( + LOG_ERR, sprintf( $msg, $notice->id, $errmsg, @@ -232,7 +232,7 @@ function handleFacebookError($e, $notice, $flink) . "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Re-queueing " . "notice, and will try to send again later."; common_log( - LOG_ERROR, sprintf( + LOG_ERR, sprintf( $msg, $notice->id, $code, @@ -351,7 +351,7 @@ function updateProfileBox($facebook, $flink, $notice, $user) { common_debug( 'FacebookPlugin - Attempting to update profile box with ' - . "content from notice $notice->id for $user->nickname ($user->id)" + . "content from notice $notice->id for $user->nickname ($user->id), " . "Facebook UID: $fbuid" ); From 09dab2ce5ae819c73d7984822d418c43f1fba223 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 25 May 2010 15:40:38 +0000 Subject: [PATCH 08/17] Dequeue notice when we hit any Facebook error. --- plugins/Facebook/facebookutil.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index 0f24f5441e..c7b0f02c31 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -229,8 +229,8 @@ function handleFacebookError($e, $notice, $flink) default: $msg = "FacebookPlugin - Facebook returned an error we don't know how to deal with while trying to " . "post notice %d. Error code: %d, error message: \"%s\". (Notice details: " - . "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Re-queueing " - . "notice, and will try to send again later."; + . "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Removing notice " + . "from the Facebook queue for safety."; common_log( LOG_ERR, sprintf( $msg, @@ -243,8 +243,7 @@ function handleFacebookError($e, $notice, $flink) $notice->content ) ); - // Re-queue and try again later - return false; + return true; // dequeue break; } } From f98609204fb9b5966b9e4c9e4bf8bf605656c31c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 25 May 2010 11:36:42 -0700 Subject: [PATCH 09/17] Backing out locale switch change to see if this affects our mystery memory leak. Revert "Locale switch cleanup: use common_switch_locale() which is safer for updating gettext state. Also moved a few calls to reduce chance of hitting an exception before switching back." This reverts commit 74a89b1fc37067d91d31bd66922053361eb4e616. --- lib/mail.php | 20 ++++++++++---------- lib/util.php | 17 ----------------- plugins/Facebook/facebookutil.php | 6 +++--- plugins/TwitterBridge/twitter.php | 6 +++--- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/lib/mail.php b/lib/mail.php index ab5742e33d..f45b2d333f 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -224,6 +224,9 @@ function mail_subscribe_notify_profile($listenee, $other) if ($other->hasRight(Right::EMAILONSUBSCRIBE) && $listenee->email && $listenee->emailnotifysub) { + // use the recipient's localization + common_init_locale($listenee->language); + $profile = $listenee->getProfile(); $name = $profile->getBestName(); @@ -233,9 +236,6 @@ function mail_subscribe_notify_profile($listenee, $other) $recipients = $listenee->email; - // use the recipient's localization - common_switch_locale($listenee->language); - $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; @@ -277,7 +277,7 @@ function mail_subscribe_notify_profile($listenee, $other) common_local_url('emailsettings')); // reset localization - common_switch_locale(); + common_init_locale(); mail_send($recipients, $headers, $body); } } @@ -479,7 +479,7 @@ function mail_confirm_sms($code, $nickname, $address) function mail_notify_nudge($from, $to) { - common_switch_locale($to->language); + common_init_locale($to->language); // TRANS: Subject for 'nudge' notification email $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname); @@ -497,7 +497,7 @@ function mail_notify_nudge($from, $to) $from->nickname, common_local_url('all', array('nickname' => $to->nickname)), common_config('site', 'name')); - common_switch_locale(); + common_init_locale(); $headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname); @@ -531,7 +531,7 @@ function mail_notify_message($message, $from=null, $to=null) return true; } - common_switch_locale($to->language); + common_init_locale($to->language); // TRANS: Subject for direct-message notification email $subject = sprintf(_('New private message from %s'), $from->nickname); @@ -555,7 +555,7 @@ function mail_notify_message($message, $from=null, $to=null) $headers = _mail_prepare_headers('message', $to->nickname, $from->nickname); - common_switch_locale(); + common_init_locale(); return mail_to_user($to, $subject, $body, $headers); } @@ -583,7 +583,7 @@ function mail_notify_fave($other, $user, $notice) $bestname = $profile->getBestName(); - common_switch_locale($other->language); + common_init_locale($other->language); // TRANS: Subject for favorite notification email $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname); @@ -611,7 +611,7 @@ function mail_notify_fave($other, $user, $notice) $headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname); - common_switch_locale(); + common_init_locale(); mail_to_user($other, $subject, $body, $headers); } diff --git a/lib/util.php b/lib/util.php index 59d5132ec6..eed61d0291 100644 --- a/lib/util.php +++ b/lib/util.php @@ -34,14 +34,6 @@ function common_user_error($msg, $code=400) $err->showPage(); } -/** - * This should only be used at setup; processes switching languages - * to send text to other users should use common_switch_locale(). - * - * @param string $language Locale language code (optional; empty uses - * current user's preference or site default) - * @return mixed success - */ function common_init_locale($language=null) { if(!$language) { @@ -58,15 +50,6 @@ function common_init_locale($language=null) return $ok; } -/** - * Initialize locale and charset settings and gettext with our message catalog, - * using the current user's language preference or the site default. - * - * This should generally only be run at framework initialization; code switching - * languages at runtime should call common_switch_language(). - * - * @access private - */ function common_init_language() { mb_internal_encoding('UTF-8'); diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index c7b0f02c31..9c35276b76 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -461,12 +461,12 @@ function remove_facebook_app($flink) function mail_facebook_app_removed($user) { + common_init_locale($user->language); + $profile = $user->getProfile(); $site_name = common_config('site', 'name'); - common_switch_locale($user->language); - $subject = sprintf( _m('Your %1$s Facebook application access has been disabled.', $site_name)); @@ -480,7 +480,7 @@ function mail_facebook_app_removed($user) "re-installing the %2\$s Facebook application.\n\nRegards,\n\n%2\$s"), $user->nickname, $site_name); - common_switch_locale(); + common_init_locale(); return mail_to_user($user, $subject, $body); } diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 896eee2dac..21adc7a908 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -335,9 +335,9 @@ function remove_twitter_link($flink) function mail_twitter_bridge_removed($user) { - $profile = $user->getProfile(); + common_init_locale($user->language); - common_switch_locale($user->language); + $profile = $user->getProfile(); $subject = sprintf(_m('Your Twitter bridge has been disabled.')); @@ -354,7 +354,7 @@ function mail_twitter_bridge_removed($user) common_local_url('twittersettings'), common_config('site', 'name')); - common_switch_locale(); + common_init_locale(); return mail_to_user($user, $subject, $body); } From 3d4ce6f10b94d487e8eff89f689fba22327634f0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 25 May 2010 12:31:16 -0700 Subject: [PATCH 10/17] Revert "Backing out locale switch change to see if this affects our mystery memory leak." This reverts commit f98609204fb9b5966b9e4c9e4bf8bf605656c31c. --- lib/mail.php | 20 ++++++++++---------- lib/util.php | 17 +++++++++++++++++ plugins/Facebook/facebookutil.php | 6 +++--- plugins/TwitterBridge/twitter.php | 6 +++--- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/mail.php b/lib/mail.php index f45b2d333f..ab5742e33d 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -224,9 +224,6 @@ function mail_subscribe_notify_profile($listenee, $other) if ($other->hasRight(Right::EMAILONSUBSCRIBE) && $listenee->email && $listenee->emailnotifysub) { - // use the recipient's localization - common_init_locale($listenee->language); - $profile = $listenee->getProfile(); $name = $profile->getBestName(); @@ -236,6 +233,9 @@ function mail_subscribe_notify_profile($listenee, $other) $recipients = $listenee->email; + // use the recipient's localization + common_switch_locale($listenee->language); + $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; @@ -277,7 +277,7 @@ function mail_subscribe_notify_profile($listenee, $other) common_local_url('emailsettings')); // reset localization - common_init_locale(); + common_switch_locale(); mail_send($recipients, $headers, $body); } } @@ -479,7 +479,7 @@ function mail_confirm_sms($code, $nickname, $address) function mail_notify_nudge($from, $to) { - common_init_locale($to->language); + common_switch_locale($to->language); // TRANS: Subject for 'nudge' notification email $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname); @@ -497,7 +497,7 @@ function mail_notify_nudge($from, $to) $from->nickname, common_local_url('all', array('nickname' => $to->nickname)), common_config('site', 'name')); - common_init_locale(); + common_switch_locale(); $headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname); @@ -531,7 +531,7 @@ function mail_notify_message($message, $from=null, $to=null) return true; } - common_init_locale($to->language); + common_switch_locale($to->language); // TRANS: Subject for direct-message notification email $subject = sprintf(_('New private message from %s'), $from->nickname); @@ -555,7 +555,7 @@ function mail_notify_message($message, $from=null, $to=null) $headers = _mail_prepare_headers('message', $to->nickname, $from->nickname); - common_init_locale(); + common_switch_locale(); return mail_to_user($to, $subject, $body, $headers); } @@ -583,7 +583,7 @@ function mail_notify_fave($other, $user, $notice) $bestname = $profile->getBestName(); - common_init_locale($other->language); + common_switch_locale($other->language); // TRANS: Subject for favorite notification email $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname); @@ -611,7 +611,7 @@ function mail_notify_fave($other, $user, $notice) $headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname); - common_init_locale(); + common_switch_locale(); mail_to_user($other, $subject, $body, $headers); } diff --git a/lib/util.php b/lib/util.php index eed61d0291..59d5132ec6 100644 --- a/lib/util.php +++ b/lib/util.php @@ -34,6 +34,14 @@ function common_user_error($msg, $code=400) $err->showPage(); } +/** + * This should only be used at setup; processes switching languages + * to send text to other users should use common_switch_locale(). + * + * @param string $language Locale language code (optional; empty uses + * current user's preference or site default) + * @return mixed success + */ function common_init_locale($language=null) { if(!$language) { @@ -50,6 +58,15 @@ function common_init_locale($language=null) return $ok; } +/** + * Initialize locale and charset settings and gettext with our message catalog, + * using the current user's language preference or the site default. + * + * This should generally only be run at framework initialization; code switching + * languages at runtime should call common_switch_language(). + * + * @access private + */ function common_init_language() { mb_internal_encoding('UTF-8'); diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index 9c35276b76..c7b0f02c31 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -461,12 +461,12 @@ function remove_facebook_app($flink) function mail_facebook_app_removed($user) { - common_init_locale($user->language); - $profile = $user->getProfile(); $site_name = common_config('site', 'name'); + common_switch_locale($user->language); + $subject = sprintf( _m('Your %1$s Facebook application access has been disabled.', $site_name)); @@ -480,7 +480,7 @@ function mail_facebook_app_removed($user) "re-installing the %2\$s Facebook application.\n\nRegards,\n\n%2\$s"), $user->nickname, $site_name); - common_init_locale(); + common_switch_locale(); return mail_to_user($user, $subject, $body); } diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 21adc7a908..896eee2dac 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -335,10 +335,10 @@ function remove_twitter_link($flink) function mail_twitter_bridge_removed($user) { - common_init_locale($user->language); - $profile = $user->getProfile(); + common_switch_locale($user->language); + $subject = sprintf(_m('Your Twitter bridge has been disabled.')); $site_name = common_config('site', 'name'); @@ -354,7 +354,7 @@ function mail_twitter_bridge_removed($user) common_local_url('twittersettings'), common_config('site', 'name')); - common_init_locale(); + common_switch_locale(); return mail_to_user($user, $subject, $body); } From 95159112b2331ee832a4cf1e711cb8f1f0193c44 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 25 May 2010 13:09:21 -0700 Subject: [PATCH 11/17] Hotpatch for infinite redirection-following loop seen processing URLs to http://clojure.org/ -- if we end up with an unstable redirect target (final item in a redirect chain ends up redirecting us somewhere else when we visit it again), just save the last version we saw instead of trying to start over. Pretty much everything in File and File_redirection initial processing needs to be rewritten to be non-awful; this code is very hard to follow and very easy to make huge bugs. A fair amount of the complication is probably obsoleted by the redirection following being built into HTTPClient now. --- classes/File.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/classes/File.php b/classes/File.php index 33273bbdcc..8297e50910 100644 --- a/classes/File.php +++ b/classes/File.php @@ -116,7 +116,11 @@ class File extends Memcached_DataObject return false; } - function processNew($given_url, $notice_id=null) { + /** + * @fixme refactor this mess, it's gotten pretty scary. + * @param bool $followRedirects + */ + function processNew($given_url, $notice_id=null, $followRedirects=true) { if (empty($given_url)) return -1; // error, no url to process $given_url = File_redirection::_canonUrl($given_url); if (empty($given_url)) return -1; // error, no url to process @@ -124,6 +128,10 @@ class File extends Memcached_DataObject if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { + // @fixme for new URLs this also looks up non-redirect data + // such as target content type, size, etc, which we need + // for File::saveNew(); so we call it even if not following + // new redirects. $redir_data = File_redirection::where($given_url); if (is_array($redir_data)) { $redir_url = $redir_data['url']; @@ -134,11 +142,19 @@ class File extends Memcached_DataObject throw new ServerException("Can't process url '$given_url'"); } // TODO: max field length - if ($redir_url === $given_url || strlen($redir_url) > 255) { + if ($redir_url === $given_url || strlen($redir_url) > 255 || !$followRedirects) { $x = File::saveNew($redir_data, $given_url); $file_id = $x->id; } else { - $x = File::processNew($redir_url, $notice_id); + // This seems kind of messed up... for now skipping this part + // if we're already under a redirect, so we don't go into + // horrible infinite loops if we've been given an unstable + // redirect (where the final destination of the first request + // doesn't match what we get when we ask for it again). + // + // Seen in the wild with clojure.org, which redirects through + // wikispaces for auth and appends session data in the URL params. + $x = File::processNew($redir_url, $notice_id, /*followRedirects*/false); $file_id = $x->id; File_redirection::saveNew($redir_data, $file_id, $given_url); } From d9a89d174ad1cb28669a8f3c76be23f27c182d58 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 25 May 2010 21:08:25 +0000 Subject: [PATCH 12/17] Small update to the README: Facebook has changed the name of one of its application settings fields. --- plugins/Facebook/README | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Facebook/README b/plugins/Facebook/README index 14c1d32419..532f1d82e4 100644 --- a/plugins/Facebook/README +++ b/plugins/Facebook/README @@ -38,11 +38,11 @@ editor or write them down. In Facebook's application editor, specify the following URLs for your app: -- Canvas Callback URL : http://example.net/mublog/facebook/app/ -- Post-Remove Callback URL: http://example.net/mublog/facebook/app/remove -- Post-Add Redirect URL : http://apps.facebook.com/yourapp/ -- Canvas Page URL : http://apps.facebook.com/yourapp/ -- Connect URL : http://example.net/mublog/ +- Canvas Callback URL : http://example.net/mublog/facebook/app/ +- Post-Remove Callback URL : http://example.net/mublog/facebook/app/remove +- Post-Authorize Redirect URL : http://apps.facebook.com/yourapp/ +- Canvas Page URL : http://apps.facebook.com/yourapp/ +- Connect URL : http://example.net/mublog/ *** ATTENTION *** These URLs have changed slightly since StatusNet version 0.8.1, From 9193c110f14e09523791683e7799a45163b881c2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 20 May 2010 12:21:29 -0700 Subject: [PATCH 13/17] WikiHowProfile plugin; pulls avatar from WikiHow profile pages when registering or adding account with OpenID. (Full name, location, homepage, and bio are also on the profile page but not marked up in a way they can be easily retrieved yet.) OpenID plugin: Added events at OpenID account creation and update time to allow additional customizations for particular sites. --- plugins/OpenID/finishaddopenid.php | 11 +- plugins/OpenID/finishopenidlogin.php | 10 +- plugins/OpenID/openid.php | 11 +- plugins/WikiHowProfile/README | 6 + .../WikiHowProfile/WikiHowProfilePlugin.php | 196 ++++++++++++++++++ 5 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 plugins/WikiHowProfile/README create mode 100644 plugins/WikiHowProfile/WikiHowProfilePlugin.php diff --git a/plugins/OpenID/finishaddopenid.php b/plugins/OpenID/finishaddopenid.php index 991e6584ee..18e150a83c 100644 --- a/plugins/OpenID/finishaddopenid.php +++ b/plugins/OpenID/finishaddopenid.php @@ -126,12 +126,15 @@ class FinishaddopenidAction extends Action $this->message(_m('Error connecting user.')); return; } - if ($sreg) { - if (!oid_update_user($cur, $sreg)) { - $this->message(_m('Error updating profile')); - return; + if (Event::handle('StartOpenIDUpdateUser', array($cur, $canonical, &$sreg))) { + if ($sreg) { + if (!oid_update_user($cur, $sreg)) { + $this->message(_m('Error updating profile')); + return; + } } } + Event::handle('EndOpenIDUpdateUser', array($cur, $canonical, $sreg)); // success! diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php index 32b092a0bd..60d46d4ce9 100644 --- a/plugins/OpenID/finishopenidlogin.php +++ b/plugins/OpenID/finishopenidlogin.php @@ -280,6 +280,8 @@ class FinishopenidloginAction extends Action return; } + Event::handle('StartOpenIDCreateNewUser', array($canonical, &$sreg)); + $location = ''; if (!empty($sreg['country'])) { if ($sreg['postcode']) { @@ -319,6 +321,8 @@ class FinishopenidloginAction extends Action $result = oid_link_user($user->id, $canonical, $display); + Event::handle('EndOpenIDCreateNewUser', array($user, $canonical, $sreg)); + oid_set_last($display); common_set_user($user); common_real_login(true); @@ -358,7 +362,11 @@ class FinishopenidloginAction extends Action return; } - oid_update_user($user, $sreg); + if (Event::handle('StartOpenIDUpdateUser', array($user, $canonical, &$sreg))) { + oid_update_user($user, $sreg); + } + Event::handle('EndOpenIDUpdateUser', array($user, $canonical, $sreg)); + oid_set_last($display); common_set_user($user); common_real_login(true); diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index 4ec336e1c3..cdeedbf4d0 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -212,11 +212,14 @@ function _oid_print_instructions() 'OpenID provider.')); } -# update a user from sreg parameters - -function oid_update_user(&$user, &$sreg) +/** + * Update a user from sreg parameters + * @param User $user + * @param array $sreg fields from OpenID sreg response + * @access private + */ +function oid_update_user($user, $sreg) { - $profile = $user->getProfile(); $orig_profile = clone($profile); diff --git a/plugins/WikiHowProfile/README b/plugins/WikiHowProfile/README new file mode 100644 index 0000000000..ee6096c9fb --- /dev/null +++ b/plugins/WikiHowProfile/README @@ -0,0 +1,6 @@ +This is an additional plugin which piggybacks on OpenID authentication to pull +profile information from WikiHow user pages when creating or updating accounts. + +WikiHow runs a customized MediaWiki setup, with locally-built extensions to add +profile features such as an avatar. As this additional info isn't yet exposed +through OpenID, we need to pull it separately. diff --git a/plugins/WikiHowProfile/WikiHowProfilePlugin.php b/plugins/WikiHowProfile/WikiHowProfilePlugin.php new file mode 100644 index 0000000000..b72bd55d6d --- /dev/null +++ b/plugins/WikiHowProfile/WikiHowProfilePlugin.php @@ -0,0 +1,196 @@ +. + * + * @category Plugins + * @package StatusNet + * @author Brion Vibber + * @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')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Sample plugin main class + * + * Each plugin requires a main class to interact with the StatusNet system. + * + * @category Plugins + * @package WikiHowProfilePlugin + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class WikiHowProfilePlugin extends Plugin +{ + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'WikiHow avatar fetcher', + 'version' => STATUSNET_VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:Sample', + 'rawdescription' => + _m('Fetches avatar and other profile info for WikiHow users when setting up an account via OpenID.')); + return true; + } + + /** + * Hook for OpenID user creation; we'll pull the avatar. + * + * @param User $user + * @param string $canonical OpenID provider URL + * @param array $sreg query data from provider + */ + function onEndOpenIDCreateNewUser($user, $canonical, $sreg) + { + $this->updateProfile($user, $canonical); + return true; + } + + /** + * Hook for OpenID profile updating; we'll pull the avatar. + * + * @param User $user + * @param string $canonical OpenID provider URL (wiki profile page) + * @param array $sreg query data from provider + */ + function onEndOpenIDUpdateUser($user, $canonical, $sreg) + { + $this->updateProfile($user, $canonical); + return true; + } + + /** + * @param User $user + * @param string $canonical OpenID provider URL (wiki profile page) + */ + private function updateProfile($user, $canonical) + { + $prefix = 'http://www.wikihow.com/User:'; + + if (substr($canonical, 0, strlen($prefix)) == $prefix) { + // Yes, it's a WikiHow user! + $profile = $this->fetchProfile($canonical); + + if (!empty($profile['avatar'])) { + $this->saveAvatar($user, $profile['avatar']); + } + } + } + + /** + * Given a user's WikiHow profile URL, find their avatar. + * + * @param string $profileUrl user page on the wiki + * + * @return array of data; possible members: + * 'avatar' => full URL to avatar image + * + * @throws Exception on various low-level failures + * + * @todo pull location, web site, and about sections -- they aren't currently marked up cleanly. + */ + private function fetchProfile($profileUrl) + { + $client = HTTPClient::start(); + $response = $client->get($profileUrl); + if (!$response->isOk()) { + throw new Exception("WikiHow profile page fetch failed."); + // HTTP error response already logged. + return false; + } + + // Suppress warnings during HTML parsing; non-well-formed bits will + // spew horrible warning everywhere even though it works fine. + $old = error_reporting(); + error_reporting($old & ~E_WARNING); + + $dom = new DOMDocument(); + $ok = $dom->loadHTML($response->getBody()); + + error_reporting($old); + + if (!$ok) { + throw new Exception("HTML parse failure during check for WikiHow avatar."); + return false; + } + + $data = array(); + + $avatar = $dom->getElementById('avatarULimg'); + if ($avatar) { + $src = $avatar->getAttribute('src'); + + $base = new Net_URL2($profileUrl); + $absolute = $base->resolve($src); + $avatarUrl = strval($absolute); + + common_log(LOG_DEBUG, "WikiHow avatar found for $profileUrl - $avatarUrl"); + $data['avatar'] = $avatarUrl; + } + + return $data; + } + + /** + * Actually save the avatar we found locally. + * + * @param User $user + * @param string $url to avatar URL + * @todo merge wrapper funcs for this into common place for 1.0 core + */ + private function saveAvatar($user, $url) + { + if (!common_valid_http_url($url)) { + throw new ServerException(sprintf(_m("Invalid avatar URL %s"), $url)); + } + + // @fixme this should be better encapsulated + // ripped from OStatus via oauthstore.php (for old OMB client) + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + if (!copy($url, $temp_filename)) { + throw new ServerException(sprintf(_m("Unable to fetch avatar from %s"), $url)); + } + + $profile = $user->getProfile(); + $id = $profile->id; + // @fixme should we be using different ids? + + $imagefile = new ImageFile($id, $temp_filename); + $filename = Avatar::filename($id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + $profile->setOriginal($filename); + } + +} + From 80d1e86a7c54521f364600c85c9f29ff29aaa611 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 26 May 2010 00:39:44 +0000 Subject: [PATCH 14/17] Add repeat info to statusnet:notice_info Atom element --- classes/Notice.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index d85c8cd33a..3d7d21533b 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1240,7 +1240,7 @@ class Notice extends Memcached_DataObject $noticeInfoAttr = array( 'local_id' => $this->id, // local notice ID (useful to clients for ordering) - 'source' => $this->source // the client name (source attribution) + 'source' => $this->source, // the client name (source attribution) ); $ns = $this->getSource(); @@ -1251,7 +1251,11 @@ class Notice extends Memcached_DataObject } if (!empty($cur)) { - $noticeInfoAttr['favorited'] = ($cur->hasFave($this)) ? 'true' : 'false'; + $noticeInfoAttr['favorite'] = ($cur->hasFave($this)) ? "true" : "false"; + } + + if (!empty($this->repeat_of)) { + $noticeInfoAttr['repeat_of'] = $this->repeat_of; } $xs->element('statusnet:notice_info', $noticeInfoAttr, null); From 3e9b35677746ba0a9877fd1a20ef4e3ae52bc7b5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 26 May 2010 20:31:36 +0000 Subject: [PATCH 15/17] Remove settting/getting a 'verb' for Facebook stream entries / status updates. Facebook has disabled the ability to store user preferences via their old REST API, causing our application to break. Also, verbs in status updates seem to be deprecated, and stream posts don't seem to have a verb. --- plugins/Facebook/facebooksettings.php | 21 --------------- plugins/Facebook/facebookutil.php | 39 ++------------------------- 2 files changed, 2 insertions(+), 58 deletions(-) diff --git a/plugins/Facebook/facebooksettings.php b/plugins/Facebook/facebooksettings.php index 766d0e1996..f94a346b57 100644 --- a/plugins/Facebook/facebooksettings.php +++ b/plugins/Facebook/facebooksettings.php @@ -54,22 +54,11 @@ class FacebooksettingsAction extends FacebookAction $noticesync = $this->boolean('noticesync'); $replysync = $this->boolean('replysync'); - $prefix = $this->trimmed('prefix'); $original = clone($this->flink); $this->flink->set_flags($noticesync, false, $replysync, false); $result = $this->flink->update($original); - if ($prefix == '' || $prefix == '0') { - // Facebook bug: saving empty strings to prefs now fails - // http://bugs.developers.facebook.com/show_bug.cgi?id=7110 - $trimmed = $prefix . ' '; - } else { - $trimmed = substr($prefix, 0, 128); - } - $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, - $trimmed); - if ($result === false) { $this->showForm(_m('There was a problem saving your sync preferences!')); } else { @@ -110,16 +99,6 @@ class FacebooksettingsAction extends FacebookAction $this->elementStart('li'); - $prefix = trim($this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX)); - - $this->input('prefix', _m('Prefix'), - ($prefix) ? $prefix : null, - _m('A string to prefix notices with.')); - - $this->elementEnd('li'); - - $this->elementStart('li'); - $this->submit('save', _m('Save')); $this->elementEnd('li'); diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index c7b0f02c31..1290fed557 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -256,11 +256,9 @@ function statusUpdate($notice, $user, $fbuid) . "Facebook UID: $fbuid" ); - $text = formatNotice($notice, $user, $fbuid); - $facebook = getFacebook(); $result = $facebook->api_client->users_setStatus( - $text, + $notice->content, $fbuid, false, true @@ -284,12 +282,11 @@ function publishStream($notice, $user, $fbuid) . "Facebook UID: $fbuid" ); - $text = formatNotice($notice, $user, $fbuid); $fbattachment = format_attachments($notice->attachments()); $facebook = getFacebook(); $facebook->api_client->stream_publish( - $text, + $notice->content, $fbattachment, null, null, @@ -304,38 +301,6 @@ function publishStream($notice, $user, $fbuid) ); } -function formatNotice($notice, $user, $fbuid) -{ - // Get the status 'verb' the user has set, if any - - common_debug( - "FacebookPlugin - Looking to see if $user->nickname ($user->id), " - . "Facebook UID: $fbuid has set a verb for Facebook posting..." - ); - - $facebook = getFacebook(); - $verb = trim( - $facebook->api_client->data_getUserPreference( - FACEBOOK_NOTICE_PREFIX, - $fbuid - ) - ); - - common_debug("Facebook returned " . var_export($verb, true)); - - $text = null; - - if (!empty($verb)) { - common_debug("FacebookPlugin - found a verb: $verb"); - $text = trim($verb) . ' ' . $notice->content; - } else { - common_debug("FacebookPlugin - no verb found."); - $text = $notice->content; - } - - return $text; -} - function updateProfileBox($facebook, $flink, $notice, $user) { $facebook = getFacebook(); From c5b61078e1548fba2820620e2e8f5fcbbda611a8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 27 May 2010 13:49:23 -0700 Subject: [PATCH 16/17] Pass auth user into Atom feed generators (needed for outputting favorited status in statusnet:notice_info tag) --- actions/apitimelinefavorites.php | 2 +- actions/apitimelinefriends.php | 2 +- actions/apitimelinegroup.php | 2 +- actions/apitimelinehome.php | 2 +- actions/apitimelinementions.php | 2 +- actions/apitimelinepublic.php | 2 +- actions/apitimelineretweetsofme.php | 2 +- actions/apitimelinetag.php | 2 +- actions/apitimelineuser.php | 2 +- lib/atomgroupnoticefeed.php | 5 +++-- lib/atomnoticefeed.php | 17 +++++++++++++++-- lib/atomusernoticefeed.php | 5 +++-- 12 files changed, 30 insertions(+), 15 deletions(-) diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index 79632447ef..a889b49182 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -150,7 +150,7 @@ class ApiTimelineFavoritesAction extends ApiBareAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index ac350ab1b7..9c6ffcf9c5 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -152,7 +152,7 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index 56d1de094c..76fa74767e 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -105,7 +105,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction function showTimeline() { // We'll pull common formatting out of this for other formats - $atom = new AtomGroupNoticeFeed($this->group); + $atom = new AtomGroupNoticeFeed($this->group, $this->auth_user); $self = $this->getSelfUri(); diff --git a/actions/apitimelinehome.php b/actions/apitimelinehome.php index 1618c9923c..2a6b7bf5c1 100644 --- a/actions/apitimelinehome.php +++ b/actions/apitimelinehome.php @@ -151,7 +151,7 @@ class ApiTimelineHomeAction extends ApiBareAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php index c3aec7c5af..dc39122e57 100644 --- a/actions/apitimelinementions.php +++ b/actions/apitimelinementions.php @@ -151,7 +151,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 9034614253..49062e603e 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -130,7 +130,7 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php index c77912fd0f..ea922fc427 100644 --- a/actions/apitimelineretweetsofme.php +++ b/actions/apitimelineretweetsofme.php @@ -117,7 +117,7 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index fed1437ea8..c21b227020 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -138,7 +138,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed(); + $atom = new AtomNoticeFeed($this->auth_user); $atom->setId($id); $atom->setTitle($title); diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 11431a82ca..9ee6abaf54 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -115,7 +115,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction // We'll use the shared params from the Atom stub // for other feed types. - $atom = new AtomUserNoticeFeed($this->user); + $atom = new AtomUserNoticeFeed($this->user, $this->auth_user); $link = common_local_url( 'showstream', diff --git a/lib/atomgroupnoticefeed.php b/lib/atomgroupnoticefeed.php index 08c1c707c5..7934a4f9e5 100644 --- a/lib/atomgroupnoticefeed.php +++ b/lib/atomgroupnoticefeed.php @@ -50,12 +50,13 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed * Constructor * * @param Group $group the group for the feed + * @param User $cur the current authenticated user, if any * @param boolean $indent flag to turn indenting on or off * * @return void */ - function __construct($group, $indent = true) { - parent::__construct($indent); + function __construct($group, $cur = null, $indent = true) { + parent::__construct($cur, $indent); $this->group = $group; $title = sprintf(_("%s timeline"), $group->nickname); diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index 35a45118ce..ef44de4b6c 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -44,9 +44,22 @@ if (!defined('STATUSNET')) */ class AtomNoticeFeed extends Atom10Feed { - function __construct($indent = true) { + var $cur; + + /** + * Constructor - adds a bunch of XML namespaces we need in our + * notice-specific Atom feeds, and allows setting the current + * authenticated user (useful for API methods). + * + * @param User $cur the current authenticated user (optional) + * @param boolean $indent Whether to indent XML output + * + */ + function __construct($cur = null, $indent = true) { parent::__construct($indent); + $this->cur = $cur; + // Feeds containing notice info use these namespaces $this->addNamespace( @@ -115,7 +128,7 @@ class AtomNoticeFeed extends Atom10Feed $source = $this->showSource(); $author = $this->showAuthor(); - $cur = common_current_user(); + $cur = empty($this->cur) ? common_current_user() : $this->cur; $this->addEntryRaw($notice->asAtomEntry(false, $source, $author, $cur)); } diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php index 428cc2de2f..b569d93790 100644 --- a/lib/atomusernoticefeed.php +++ b/lib/atomusernoticefeed.php @@ -50,13 +50,14 @@ class AtomUserNoticeFeed extends AtomNoticeFeed * Constructor * * @param User $user the user for the feed + * @param User $cur the current authenticated user, if any * @param boolean $indent flag to turn indenting on or off * * @return void */ - function __construct($user, $indent = true) { - parent::__construct($indent); + function __construct($user, $cur = null, $indent = true) { + parent::__construct($cur, $indent); $this->user = $user; if (!empty($user)) { $profile = $user->getProfile(); From 697a9948df3c9d4275b3525227007bfd5a1c5709 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 27 May 2010 14:18:08 -0700 Subject: [PATCH 17/17] Ticket #2329: fix for use of _m() translation functions from outside of plugin directories --- lib/language.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/language.php b/lib/language.php index 64b59e7396..3846b8f358 100644 --- a/lib/language.php +++ b/lib/language.php @@ -205,12 +205,20 @@ function _mdomain($backtrace) if (DIRECTORY_SEPARATOR !== '/') { $path = strtr($path, DIRECTORY_SEPARATOR, '/'); } - $cut = strpos($path, '/plugins/') + 9; - $cut2 = strpos($path, '/', $cut); - if ($cut && $cut2) { - $cached[$path] = substr($path, $cut, $cut2 - $cut); - } else { + $plug = strpos($path, '/plugins/'); + if ($plug === false) { + // We're not in a plugin; return null for the default domain. return null; + } else { + $cut = $plug + 9; + $cut2 = strpos($path, '/', $cut); + if ($cut2) { + $cached[$path] = substr($path, $cut, $cut2 - $cut); + } else { + // We might be running directly from the plugins dir? + // If so, there's no place to store locale info. + return null; + } } } return $cached[$path];