]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.9.x' into refactor-api
authorZach Copley <zach@status.net>
Fri, 25 Sep 2009 01:18:26 +0000 (18:18 -0700)
committerZach Copley <zach@status.net>
Fri, 25 Sep 2009 01:18:26 +0000 (18:18 -0700)
* 0.9.x: (88 commits)
  Left a couple debugging statements in (removed)
  Output If-Modified-Since header for all RSS 1.0 feeds (again)
  Revert "move scripts to just before </body>, add event for scripts that need to be in <head>"
  Implemented join and leave groups api methods
  implemented etag and last modified
  Fixed broken Piwik plugin - was not using the supplied site code
  move scripts to just before </body>, add event for scripts that need to be in <head>
  some UI fixes
  Using timeline string instead of title for WindowName because IE doesn't
  Added JavaScript to initialize the poped Window
  Some layout and rendering adjustment for Realtime plugin
  Created addPop() for Realtime plugin and added param to include iconurl
  move some stuff around for realtime
  hack around address hack in util.js
  Add some more realtime feeds
  Do realtime popup with PHP instead of Javascript
  JavaScript fixes for IE
  Revert "Added realtime streams for all and showstream timelines"
  Revert "Fixed indenting"
  Revert "Made it slighly more compact with less jQuery selection"
  ...

actions/apifriendstimeline.php [new file with mode: 0644]
lib/router.php
lib/twitterapi.php

diff --git a/actions/apifriendstimeline.php b/actions/apifriendstimeline.php
new file mode 100644 (file)
index 0000000..8c8d934
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the friends timeline
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/twitterapi.php';
+
+class ApifriendstimelineAction extends TwitterapiAction
+{
+
+    var $user    = null;
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+       $this->page     = (int)$this->arg('page', 1);
+        $this->count    = (int)$this->arg('count', 20);
+        $this->max_id   = (int)$this->arg('max_id', 0);
+        $this->since_id = (int)$this->arg('since_id', 0);
+        $this->since    = $this->arg('since');
+
+       if ($this->requiresAuth()) {
+            if ($this->checkBasicAuthUser() == false) {
+               return;
+           }
+       }
+
+       $this->user = $this->getTargetUser($this->arg('id'));
+
+       if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->arg('format'));
+            return;
+        }
+
+       $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    function handle($args) {
+       parent::handle($args);
+       $this->showTimeline();
+    }
+
+    function showTimeline()
+    {
+        $profile    = $this->user->getProfile();
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s and friends"), $user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:FriendsTimeline:" . $user->id;
+        $link       = common_local_url('all',
+                                      array('nickname' => $user->nickname));
+        $subtitle   = sprintf(_('Updates from %1$s and friends on %2$s!'),
+                             $user->nickname, $sitename);
+
+        switch($this->arg('format')) {
+        case 'xml':
+            $this->show_xml_timeline($this->notices);
+            break;
+        case 'rss':
+            $this->show_rss_timeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+
+            $target_id = $this->arg('id');
+
+            if (isset($target_id)) {
+                $selfuri = common_root_url() .
+                 'api/statuses/friends_timeline/' .
+                 $target_id . '.atom';
+            } else {
+                $selfuri = common_root_url() .
+                 'api/statuses/friends_timeline.atom';
+            }
+            $this->show_atom_timeline($this->notices, $title, $id, $link,
+                                     $subtitle, null, $selfuri);
+            break;
+        case 'json':
+            $this->show_json_timeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+           break;
+        }
+    }
+
+    function getNotices()
+    {
+       $notices = array();
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
+            $notice = $this->user->noticeInbox(($this->page-1) * $this->count,
+                                              $this->count, $this->since_id,
+                                              $this->max_id, $this->since);
+        } else {
+            $notice = $this->user->noticesWithFriends(($this->page-1) * $this->count,
+                                                     $this->count, $this->since_id,
+                                                     $this->max_id, $this->since);
+        }
+
+       while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+       return $notices;
+    }
+
+    function requiresAuth()
+    {
+       // If the site is "private", all API methods except statusnet/config
+        // need authentication
+
+        if (common_config('site', 'private')) {
+           return true;
+        }
+
+       // bare auth: only needs auth if without an argument or query param specifying user id
+
+       $id           = $this->arg('id');
+       $user_id      = $this->arg('user_id');
+       $screen_name  = $this->arg('screen_name');
+
+       if (empty($id) && empty($user_id) && empty($screen_name)) {
+           return true;
+       }
+
+       return false;
+    }
+
+    /**
+     * Is this page read-only?
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     */
+
+    function lastModified()
+    {
+        if (empty($this->notices)) {
+            return null;
+        }
+
+        if (count($this->notices) == 0) {
+            return null;
+        }
+
+        return strtotime($this->notices[0]->created);
+    }
+
+}
\ No newline at end of file
index c18f273ed06ae6b948953d75ab0a392caad8d2f1..33b0984738e72c9305071503e3daea0fd787b1a6 100644 (file)
@@ -266,15 +266,27 @@ class Router
 
         // statuses API
 
+        $m->connect('api/statuses/friends_timeline.:format',
+                    array('action' => 'apifriendstimeline',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/friends_timeline/:id.:format',
+                    array('action' => 'apifriendstimeline',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/home_timeline',
+                    array('action' => 'apifriendstimeline'));
+
         $m->connect('api/statuses/:method',
                     array('action' => 'api',
                           'apiaction' => 'statuses'),
-                    array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
+                    array('method' => '(public_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
 
         $m->connect('api/statuses/:method/:argument',
                     array('action' => 'api',
                           'apiaction' => 'statuses'),
-                    array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
+                    array('method' => '(user_timeline|replies|mentions|show|destroy|friends|followers)'));
 
         // users
 
index d199e4dee27d657e05bcadf20c3b07200706068c..959b0981a5820dc36a1931b28613a6edc612380a 100644 (file)
@@ -502,7 +502,7 @@ class TwitterapiAction extends Action
             $enclosure = $entry['enclosures'][0];
             $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
         }
-        
+
         if(array_key_exists('tags', $entry)){
             foreach($entry['tags'] as $tag){
                 $this->element('category', null,$tag);
@@ -934,7 +934,7 @@ class TwitterapiAction extends Action
         return;
     }
 
-    function clientError($msg, $code = 400, $content_type = 'json')
+    function clientError($msg, $code = 400, $format = 'xml')
     {
 
         static $status = array(400 => 'Bad Request',
@@ -967,20 +967,23 @@ class TwitterapiAction extends Action
         $status_string = $status[$code];
         header('HTTP/1.1 '.$code.' '.$status_string);
 
-        if ($content_type == 'xml') {
+        if ($format == 'xml') {
             $this->init_document('xml');
             $this->elementStart('hash');
             $this->element('error', null, $msg);
             $this->element('request', null, $_SERVER['REQUEST_URI']);
             $this->elementEnd('hash');
             $this->end_document('xml');
-        } else {
+        } elseif ($format == 'json'){
             $this->init_document('json');
             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
             print(json_encode($error_array));
             $this->end_document('json');
-        }
+        } else {
 
+            // If user didn't request a useful format, throw a regular client error
+            throw new ClientException($msg, $code);
+        }
     }
 
     function init_twitter_rss()
@@ -1063,6 +1066,38 @@ class TwitterapiAction extends Action
         }
     }
 
+    function getTargetUser($id)
+    {
+        if (empty($id)) {
+
+            // Twitter supports these other ways of passing the user ID
+            if (is_numeric($this->arg('id'))) {
+                return User::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User::staticGet('nickname', $nickname);
+            } else if ($this->arg('user_id')) {
+                // This is to ensure that a non-numeric user_id still
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('user_id'))) {
+                    return User::staticGet('id', $this->arg('user_id'));
+                }
+            } else if ($this->arg('screen_name')) {
+                $nickname = common_canonical_nickname($this->arg('screen_name'));
+                return User::staticGet('nickname', $nickname);
+            } else {
+                // Fall back to trying the currently authenticated user
+                return $this->auth_user;
+            }
+
+        } else if (is_numeric($id)) {
+            return User::staticGet($id);
+        } else {
+            $nickname = common_canonical_nickname($id);
+            return User::staticGet('nickname', $nickname);
+        }
+    }
+
     function get_group($id, $apidata=null)
     {
         if (empty($id)) {
@@ -1170,4 +1205,85 @@ class TwitterapiAction extends Action
         }
     }
 
+    function checkBasicAuthUser()
+    {
+        $this->basicAuthProcessHeader();
+
+        if (!isset($this->auth_user)) {
+            header('WWW-Authenticate: Basic realm="StatusNet API"');
+
+            // show error if the user clicks 'cancel'
+
+            $this->showBasicAuthError();
+            return false;
+
+        } else {
+            $nickname = $this->auth_user;
+            $password = $this->auth_pw;
+            $this->auth_user = common_check_user($nickname, $password);
+
+            if (empty($this->auth_user)) {
+
+                // basic authentication failed
+
+                list($proxy, $ip) = common_client_ip();
+                common_log(LOG_WARNING,
+                    "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
+                $this->showBasicAuthError();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function basicAuthProcessHeader()
+    {
+        if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
+            $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
+        }
+
+        if (isset($_SERVER['PHP_AUTH_USER'])) {
+            $this->auth_user = $_SERVER['PHP_AUTH_USER'];
+            $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
+        } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) {
+            // decode the HTTP_AUTHORIZATION header on php-cgi server self
+            // on fcgid server the header name is AUTHORIZATION
+
+            $auth_hash = base64_decode(substr($authorization_header, 6));
+            list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
+
+            // set all to null on a empty basic auth request
+            if ($this->auth_user == "") {
+                $this->auth_user = null;
+                $this->auth_pw = null;
+            }
+        } else {
+            $this->auth_user = null;
+            $this->auth_pw = null;
+        }
+    }
+
+    function showBasicAuthError()
+    {
+        header('HTTP/1.1 401 Unauthorized');
+        $msg = 'Could not authenticate you.';
+
+        if ($this->arg('format') == 'xml') {
+            header('Content-Type: application/xml; charset=utf-8');
+            $this->startXML();
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endXML();
+        } elseif ($this->arg('format') == 'json') {
+            header('Content-Type: application/json; charset=utf-8');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+        } else {
+            header('Content-type: text/plain');
+            print "$msg\n";
+        }
+    }
+
 }