]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'master' of gitorious.org:statusnet/mainline into testing
authorBrion Vibber <brion@pobox.com>
Thu, 27 May 2010 21:54:43 +0000 (14:54 -0700)
committerBrion Vibber <brion@pobox.com>
Thu, 27 May 2010 21:54:43 +0000 (14:54 -0700)
26 files changed:
actions/apitimelinefavorites.php
actions/apitimelinefriends.php
actions/apitimelinegroup.php
actions/apitimelinehome.php
actions/apitimelinementions.php
actions/apitimelinepublic.php
actions/apitimelineretweetsofme.php
actions/apitimelinetag.php
actions/apitimelineuser.php
classes/File.php
classes/Notice.php
lib/atomgroupnoticefeed.php
lib/atomnoticefeed.php
lib/atomusernoticefeed.php
lib/language.php
lib/stompqueuemanager.php
plugins/Facebook/README
plugins/Facebook/facebook/facebook.php
plugins/Facebook/facebook/facebookapi_php5_restlib.php
plugins/Facebook/facebooksettings.php
plugins/Facebook/facebookutil.php
plugins/OpenID/finishaddopenid.php
plugins/OpenID/finishopenidlogin.php
plugins/OpenID/openid.php
plugins/WikiHowProfile/README [new file with mode: 0644]
plugins/WikiHowProfile/WikiHowProfilePlugin.php [new file with mode: 0644]

index 79632447ef0782b1e9887a703e505efd0d60f054..a889b4918288dc35a9ff0722d1493231454e95af 100644 (file)
@@ -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);
index ac350ab1b7a25acf8c5ee77dba5cd6269089670c..9c6ffcf9c53536c03ab6626e9b013398e50c904b 100644 (file)
@@ -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);
index 56d1de094c5da37a12445f107151e66ced41af08..76fa74767e72bca6b75060465ab54395a86c1fab 100644 (file)
@@ -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();
 
index 1618c9923c8be7eb79b4c8bd2221afb982bca4b8..2a6b7bf5c17177bff76d4ee920f115d773011f2e 100644 (file)
@@ -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);
index c3aec7c5afacb8dca1f6b971cd2d65d36cff9f1e..dc39122e570938c8fc34ad6a0849f2f589083f93 100644 (file)
@@ -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);
index 9034614253d438b5566a1993c6b1da0182094b18..49062e603e3d81142c799d0c4ea6aea71bac3d5a 100644 (file)
@@ -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);
index c77912fd0f24e209bed482232be7110eb7dd45be..ea922fc427121a039cf06d54b9a6716f35145618 100644 (file)
@@ -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);
index fed1437ea805805a06ba4c3ba608aebbc952076d..c21b2270202719268d646ecfa1601b81d6a5fe78 100644 (file)
@@ -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);
index 11431a82ca9231658ccf0ea8d82ec693905354e3..9ee6abaf54e7798f84c5b0ec495ab4a368d2bd1b 100644 (file)
@@ -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',
index 33273bbdccb577047e545f77504da008cd31fd35..8297e5091046ec4debf714718ad9e850bcf7229d 100644 (file)
@@ -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);
                 }
index e173a2469095d7fd4410ecaee7333fe78322b904..3d7d21533b55a8b537cb9f96d165502bd89a04d5 100644 (file)
@@ -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
 
@@ -1235,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();
@@ -1246,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);
index 08c1c707c578091a0d5eb7d67b0795d776b9f013..7934a4f9e5483ffcff4f17b249184f0860d13d92 100644 (file)
@@ -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);
index 35a45118ce6525ed2f6ce52ba846e307e9d2a9af..ef44de4b6ce615bdafe4fc7e34f315ed06bceb01 100644 (file)
@@ -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));
     }
index 428cc2de2f01192f46a212e97e398f97ba7afd5b..b569d937907d3f49f5be98665add1dbc811fdc28 100644 (file)
@@ -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();
index 64b59e73966e61b9894e85efbd57eadd4e6a2f9b..3846b8f358bdb9581d3e54286c93a1fd14ddbe3f 100644 (file)
@@ -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];
index 5d5c7ccfbd9cfd81b6450bd2cfd082ab8e10c803..de4ba7f01fdce59b8ebfa3b94bbb68f4390db7f9 100644 (file)
@@ -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);
     }
 
     /**
index 14c1d324197b988577caa12cb5c4469f64185d30..532f1d82e40f31dc05e38c14623fdb587266fcfd 100644 (file)
@@ -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,
index 440706cbc3fdb5543001003e88e2bc9721414192..76696c1d557abf8fe58b9ad40538900b394a9cfc 100644 (file)
@@ -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;
index fa1088cd00bffedbcd3d7af4593b5d4e998b7c1f..e249a326b219f067d6bf42756e9bdcd2a9ac4453 100755 (executable)
@@ -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);
   }
index 766d0e19963b734a9c736fcbe49a753eec83c2c2..f94a346b570e2010c7e657ef40da400021d8fe81 100644 (file)
@@ -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');
index ab2d427264254d4f15b8194f07af1044d8d9533f..1290fed55778513396c07035d5c1d2cc72ec48c3 100644 (file)
@@ -81,101 +81,251 @@ 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'];
+            }
+
+            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)
+                );
+            }
 
-            common_debug("FacebookPlugin - checking for status_update permission for user $user->id");
+            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($canPublish, 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).");
+            // 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) {
+            return handleFacebookError($e, $notice, $flink);
+        }
+    }
 
-            $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)!";
+    return true;
+}
 
-            common_log(LOG_WARNING, $msg);
+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_ERR, 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\"). Removing notice "
+           . "from the Facebook queue for safety.";
+        common_log(
+            LOG_ERR, sprintf(
+                $msg,
+                $notice->id,
+                $code,
+                $errmsg,
+                $user->nickname,
+                $user->id,
+                $fbuid,
+                $notice->content
+            )
+        );
+        return true; // dequeue
+        break;
+    }
+}
 
-            if ($code == 100 || $code == 200 || $code == 250) {
+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"
+    );
 
-                // 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
+    $facebook = getFacebook();
+    $result = $facebook->api_client->users_setStatus(
+         $notice->content,
+         $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"
+    );
+}
 
-                remove_facebook_app($flink);
+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"
+    );
 
-        } else {
+    $fbattachment = format_attachments($notice->attachments());
 
-                // Try sending again later.
+    $facebook = getFacebook();
+    $facebook->api_client->stream_publish(
+        $notice->content,
+        $fbattachment,
+        null,
+        null,
+        $fbuid
+    );
+
+    common_log(
+        LOG_INFO,
+        "FacebookPlugin - Posted notice $notice->id as a stream "
+        . "item with attachment for $user->nickname ($user->id), "
+        . "Facebook UID: $fbuid"
+    );
+}
 
-                return false;
-            }
+function updateProfileBox($facebook, $flink, $notice, $user) {
 
-        }
-    }
+    $facebook = getFacebook();
+    $fbaction = new FacebookAction(
+        $output = 'php://output',
+        $indent = null,
+        $facebook,
+        $flink
+    );
 
-    return true;
+    $fbuid = $flink->foreign_id;
 
-}
+    common_debug(
+          'FacebookPlugin - Attempting to update profile box with '
+          . "content from notice $notice->id for $user->nickname ($user->id), "
+          . "Facebook UID: $fbuid"
+    );
 
-function updateProfileBox($facebook, $flink, $notice) {
-    $fbaction = new FacebookAction($output = 'php://output',
-                                   $indent = null, $facebook, $flink);
     $fbaction->updateProfileBox($notice);
+
+    common_debug(
+        'FacebookPlugin - finished updating profile box for '
+        . "$user->nickname ($user->id) Facebook UID: $fbuid"
+    );
+
 }
 
 function format_attachments($attachments)
index df1763a52cc3843aabbbe891d5a6ee28109a7ae7..064e976426c58b58d9a6452cd5cb81db2e59bb01 100644 (file)
@@ -132,12 +132,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!
 
index 57723ff97f33114c48786ca8a9adf3239770127f..415fd8e66568ba698ff6379dd8db0152005c58cb 100644 (file)
@@ -286,6 +286,8 @@ class FinishopenidloginAction extends Action
             return;
         }
 
+        Event::handle('StartOpenIDCreateNewUser', array($canonical, &$sreg));
+
         $location = '';
         if (!empty($sreg['country'])) {
             if ($sreg['postcode']) {
@@ -325,6 +327,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);
@@ -364,7 +368,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);
index 5ee9343d2818715c9e4f802510d8f98f695be52c..574ecca72b9823047b5fce57c5744dd4edd42453 100644 (file)
@@ -221,11 +221,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 (file)
index 0000000..ee6096c
--- /dev/null
@@ -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 (file)
index 0000000..b72bd55
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Plugin to pull WikiHow-style user avatars at OpenID setup time.
+ * These are not currently exposed via OpenID.
+ *
+ * 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  Plugins
+ * @package   StatusNet
+ * @author    Brion Vibber <brion@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')) {
+    // 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 <brion@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 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);
+    }
+
+}
+