]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'master' of /var/www/mublog
authorEvan Prodromou <git@evanprodromou.name>
Fri, 23 Jan 2009 07:58:31 +0000 (08:58 +0100)
committerEvan Prodromou <git@evanprodromou.name>
Fri, 23 Jan 2009 07:58:31 +0000 (08:58 +0100)
Conflicts:

actions/api.php
actions/deletenotice.php
actions/recoverpassword.php
actions/remotesubscribe.php
actions/tag.php
actions/tagrss.php
actions/twitapiaccount.php
actions/twitapiusers.php
classes/Notice.php
classes/User.php
lib/common.php
lib/language.php
lib/subs.php
lib/twitterapi.php
lib/util.php
scripts/inbox_users.php
scripts/update_translations.php

Merged development trunk into laconica head. woohoo!

1  2 
actions/api.php
actions/twitapiaccount.php
actions/twitapiusers.php
classes/Notice.php
classes/User.php
lib/language.php
lib/subs.php
lib/twitterapi.php
scripts/sitemap.php

diff --cc actions/api.php
index ccebcd89e5cdca32fda64973964bfe73f2166090,47c1196052e25bd5a3ccf3dfe0a84b1ddcd55765..dfe2c8857b67ff4d8a94d9de1a299856d525c32d
  
  if (!defined('LACONICA')) { exit(1); }
  
- class ApiAction extends Action {
-       var $user;
-       var $content_type;
-       var $api_arg;
-       var $api_method;
-       var $api_action;
-       function handle($args) {
-               parent::handle($args);
-               $this->api_action = $this->arg('apiaction');
-               $method = $this->arg('method');
-               $argument = $this->arg('argument');
-               if (isset($argument)) {
-                       $cmdext = explode('.', $argument);
-                       $this->api_arg =  $cmdext[0];
-                       $this->api_method = $method;
-                       $this->content_type = strtolower($cmdext[1]);
-               } else {
-                       # Requested format / content-type will be an extension on the method
-                       $cmdext = explode('.', $method);
-                       $this->api_method = $cmdext[0];
-                       $this->content_type = strtolower($cmdext[1]);
-               }
-               if($this->requires_auth()) {
-                       if (!isset($_SERVER['PHP_AUTH_USER'])) {
-                               # This header makes basic auth go
-                               header('WWW-Authenticate: Basic realm="Laconica API"');
-                               # If the user hits cancel -- bam!
-                               $this->show_basic_auth_error();
-                       } else {
-                               $nickname = $_SERVER['PHP_AUTH_USER'];
-                               $password = $_SERVER['PHP_AUTH_PW'];
-                               $user = common_check_user($nickname, $password);
+ class ApiAction extends Action
+ {
+     var $user;
+     var $content_type;
+     var $api_arg;
+     var $api_method;
+     var $api_action;
+     function handle($args)
+     {
+         parent::handle($args);
+         $this->api_action = $this->arg('apiaction');
+         $method = $this->arg('method');
+         $argument = $this->arg('argument');
+         if (isset($argument)) {
+             $cmdext = explode('.', $argument);
+             $this->api_arg =  $cmdext[0];
+             $this->api_method = $method;
+             $this->content_type = strtolower($cmdext[1]);
+         } else {
+             # Requested format / content-type will be an extension on the method
+             $cmdext = explode('.', $method);
+             $this->api_method = $cmdext[0];
+             $this->content_type = strtolower($cmdext[1]);
+         }
  
-                               if ($user) {
-                                       $this->user = $user;
-                                       $this->process_command();
-                               } else {
-                                       # basic authentication failed
-                                       $this->show_basic_auth_error();
-                               }
-                       }
-               } else {
+         if ($this->requires_auth()) {
+             if (!isset($_SERVER['PHP_AUTH_USER'])) {
+                 # This header makes basic auth go
+                 header('WWW-Authenticate: Basic realm="Laconica API"');
+                 # If the user hits cancel -- bam!
+                 $this->show_basic_auth_error();
+             } else {
+                 $nickname = $_SERVER['PHP_AUTH_USER'];
+                 $password = $_SERVER['PHP_AUTH_PW'];
+                 $user = common_check_user($nickname, $password);
+                 if ($user) {
+                     $this->user = $user;
+                     $this->process_command();
+                 } else {
+                     # basic authentication failed
+                     $this->show_basic_auth_error();
+                 }
+             }
+         } else {
  
 -            # Look for the user in the session
 -            if (common_logged_in()) {
 -                 $this->user = common_current_user();
 -            }
 +                      # Caller might give us a username even if not required
 +                      if (isset($_SERVER['PHP_AUTH_USER'])) {
 +                              $user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
 +                              if ($user) {
 +                                      $this->user = $user;
 +                              }
 +                              # Twitter doesn't throw an error if the user isn't found
 +                      }
  
-                       $this->process_command();
-               }
-       }
+             $this->process_command();
+         }
+     }
  
-       function process_command() {
-               $action = "twitapi$this->api_action";
-               $actionfile = INSTALLDIR."/actions/$action.php";
+     function process_command()
+     {
+         $action = "twitapi$this->api_action";
+         $actionfile = INSTALLDIR."/actions/$action.php";
  
-               if (file_exists($actionfile)) {
-                       require_once($actionfile);
-                       $action_class = ucfirst($action)."Action";
-                       $action_obj = new $action_class();
+         if (file_exists($actionfile)) {
+             require_once($actionfile);
+             $action_class = ucfirst($action)."Action";
+             $action_obj = new $action_class();
  
              if (!$action_obj->prepare($this->args)) {
                  return;
index c1960561e21d7a0db77edb9b93f7c97d57c12e39,e51a29a2d01af8580fad261e77c012a7c9419d7d..dc8e2e798b5d268f9f76529acbe7a5debb4ac1fd
@@@ -21,79 -21,82 +21,85 @@@ if (!defined('LACONICA')) { exit(1); 
  
  require_once(INSTALLDIR.'/lib/twitterapi.php');
  
- class TwitapiaccountAction extends TwitterapiAction {
+ class TwitapiaccountAction extends TwitterapiAction
+ {
  
-       function verify_credentials($args, $apidata) {
 -    function verify_credentials($args, $apidata)
++      function verify_credentials($args, $apidata)
+     {
 -        parent::handle($args);
  
 -        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
 -            $this->clientError(_('API method not found!'), $code = 404);
 -            return;
 -        }
 +              if ($apidata['content-type'] == 'xml') {
 +                      header('Content-Type: application/xml; charset=utf-8');
 +                      print '<authorized>true</authorized>';
 +              } elseif ($apidata['content-type'] == 'json') {
 +                      header('Content-Type: application/json; charset=utf-8');
 +                      print '{"authorized":true}';
 +              } else {
 +                      common_user_error(_('API method not found!'), $code=404);
 +              }
  
 -        $this->show_extended_profile($apidata['user'], $apidata);
 -    }
 +      }
  
-       function end_session($args, $apidata) {
-               parent::handle($args);
-               common_server_error(_('API method under construction.'), $code=501);
-       }
+     function end_session($args, $apidata)
+     {
+         parent::handle($args);
+         $this->serverError(_('API method under construction.'), $code=501);
+     }
  
-       function update_location($args, $apidata) {
-               parent::handle($args);
+     function update_location($args, $apidata)
+     {
+         parent::handle($args);
  
-               if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-                       $this->client_error(_('This method requires a POST.'), 400, $apidata['content-type']);
-                       return;
-               }
+         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+             $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
+             return;
+         }
  
-               $location = trim($this->arg('location'));
+         $location = trim($this->arg('location'));
  
-               if (!is_null($location) && strlen($location) > 255) {
+         if (!is_null($location) && strlen($location) > 255) {
  
-                       // XXX: But Twitter just truncates and runs with it. -- Zach
-                       $this->client_error(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']);
-                       return;
-               }
+             // XXX: But Twitter just truncates and runs with it. -- Zach
+             $this->clientError(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']);
+             return;
+         }
  
-               $user = $apidata['user'];
-               $profile = $user->getProfile();
+         $user = $apidata['user'];
+         $profile = $user->getProfile();
  
-               if (!$profile) {
-                       common_server_error(_('User has no profile.'));
-                       return;
-               }
+         if (!$profile) {
+             $this->serverError(_('User has no profile.'));
+             return;
+         }
  
-               $orig_profile = clone($profile);
-               $profile->location = $location;
+         $orig_profile = clone($profile);
+         $profile->location = $location;
  
-               $result = $profile->update($orig_profile);
+         $result = $profile->update($orig_profile);
  
-               if (!$result) {
-                       common_log_db_error($profile, 'UPDATE', __FILE__);
-                       common_server_error(_('Couldn\'t save profile.'));
-                       return;
-               }
+         if (!$result) {
+             common_log_db_error($profile, 'UPDATE', __FILE__);
+             $this->serverError(_('Couldn\'t save profile.'));
+             return;
+         }
  
-               common_broadcast_profile($profile);
-               $type = $apidata['content-type'];
+         common_broadcast_profile($profile);
+         $type = $apidata['content-type'];
  
-               $this->init_document($type);
-               $this->show_profile($profile, $type);
-               $this->end_document($type);
-       }
+         $this->init_document($type);
+         $this->show_profile($profile, $type);
+         $this->end_document($type);
+     }
  
  
-       function update_delivery_device($args, $apidata) {
-               parent::handle($args);
-               common_server_error(_('API method under construction.'), $code=501);
-       }
+     function update_delivery_device($args, $apidata)
+     {
+         parent::handle($args);
+         $this->serverError(_('API method under construction.'), $code=501);
+     }
  
-       function rate_limit_status($args, $apidata) {
-               parent::handle($args);
-               common_server_error(_('API method under construction.'), $code=501);
-       }
- }
+     function rate_limit_status($args, $apidata)
+     {
+         parent::handle($args);
+         $this->serverError(_('API method under construction.'), $code=501);
    }
 -}
++}
index 337ec91d1968d1af26bf93a2fe113dd9d95f36e2,ed2417561157241e8af434dc9b427f68ee68c7f5..8f16e56131e87f3ec2f2173fa4bd275d8dda0980
@@@ -21,98 -21,34 +21,100 @@@ if (!defined('LACONICA')) { exit(1); 
  
  require_once(INSTALLDIR.'/lib/twitterapi.php');
  
- class TwitapiusersAction extends TwitterapiAction {
+ class TwitapiusersAction extends TwitterapiAction
+ {
  
-       function show($args, $apidata) {
-               parent::handle($args);
+     function show($args, $apidata)
+     {
+         parent::handle($args);
  
-               if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-                       common_user_error(_('API method not found!'), $code = 404);
-                       return;
-               }
+         if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+             $this->clientError(_('API method not found!'), $code = 404);
+             return;
+         }
  
-               $this->auth_user = $apidata['user'];
 -        $user = null;
 -        $email = $this->arg('email');
++ $this->auth_user = $apidata['user'];
 +              $user = null;
 +              $email = $this->arg('email');
  
 -        if ($email) {
 -            $user = User::staticGet('email', $email);
 -        } elseif (isset($apidata['api_arg'])) {
 -            $user = $this->get_user($apidata['api_arg']);
 -        }
 +              if ($email) {
 +                      $user = User::staticGet('email', $email);
 +              } elseif (isset($apidata['api_arg'])) {
 +                      $user = $this->get_user($apidata['api_arg']);
 +              }
  
 -        if (!$user) {
 -            // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
 -            $this->clientError(_('Not found.'), 404, $apidata['content-type']);
 -            return;
 -        }
 +              if (!$user) {
 +                      // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
 +                      $this->client_error(_('Not found.'), 404, $apidata['content-type']);
 +                      return;
 +              }
 +
 +              $profile = $user->getProfile();
 +
 +              if (!$profile) {
 +                      common_server_error(_('User has no profile.'));
 +                      return;
 +              }
 +
 +              $twitter_user = $this->twitter_user_array($profile, true);
 +
 +              // Add in extended user fields offered up by this method
 +              $twitter_user['created_at'] = $this->date_twitter($profile->created);
 +
 +              $subbed = DB_DataObject::factory('subscription');
 +              $subbed->subscriber = $profile->id;
 +              $subbed_count = (int) $subbed->count() - 1;
 +
 +              $notices = DB_DataObject::factory('notice');
 +              $notices->profile_id = $profile->id;
 +              $notice_count = (int) $notices->count();
 +
 +              $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
 +              $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
 +
 +              // Other fields Twitter sends...
 +              $twitter_user['profile_background_color'] = '';
 +              $twitter_user['profile_text_color'] = '';
 +              $twitter_user['profile_link_color'] = '';
 +              $twitter_user['profile_sidebar_fill_color'] = '';
 +
 +              $faves = DB_DataObject::factory('fave');
 +              $faves->user_id = $user->id;
 +              $faves_count = (int) $faves->count();
 +              $twitter_user['favourites_count'] = $faves_count;
 +
 +              $timezone = 'UTC';
 +
 +              if ($user->timezone) {
 +                      $timezone = $user->timezone;
 +              }
 +
 +              $t = new DateTime;
 +              $t->setTimezone(new DateTimeZone($timezone));
 +              $twitter_user['utc_offset'] = $t->format('Z');
 +              $twitter_user['time_zone'] = $timezone;
 +
 +              if (isset($this->auth_user)) {
 +
 +                      if ($this->auth_user->isSubscribed($profile)) {
 +                              $twitter_user['following'] = 'true';
 +                      } else {
 +                              $twitter_user['following'] = 'false';
 +                      }
 +
 +                      // Not implemented yet
 +                      $twitter_user['notifications'] = 'false';
 +              }
  
 -        $this->show_extended_profile($user, $apidata);
 -    }
 +              if ($apidata['content-type'] == 'xml') {
 +                      $this->init_document('xml');
 +                      $this->show_twitter_xml_user($twitter_user);
 +                      $this->end_document('xml');
 +              } elseif ($apidata['content-type'] == 'json') {
 +                      $this->init_document('json');
 +                      $this->show_json_objects($twitter_user);
 +                      $this->end_document('json');
 +              }
  
 +      }
  }
index 2816966321b2b7251a746dd1224ddb2c7bb98081,de7540705a6f90a533540ca032fc6abeb1d4ade7..4a06c925857637474fe048fceef2a30fd2dc1e8d
@@@ -102,76 -106,70 +106,74 @@@ class Notice extends Memcached_DataObje
  
          if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
              common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
-                       return _('Too many notices too fast; take a breather and post again in a few minutes.');
+             return _('Too many notices too fast; take a breather and post again in a few minutes.');
          }
  
-               $banned = common_config('profile', 'banned');
+         $banned = common_config('profile', 'banned');
  
-               if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
-                       common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
+         if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
+             common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
              return _('You are banned from posting notices on this site.');
-               }
+         }
  
-               $notice = new Notice();
-               $notice->profile_id = $profile_id;
+         $notice = new Notice();
+         $notice->profile_id = $profile_id;
  
-               $blacklist = common_config('public', 'blacklist');
+         $blacklist = common_config('public', 'blacklist');
  
-               # Blacklisted are non-false, but not 1, either
+         # Blacklisted are non-false, but not 1, either
  
-               if ($blacklist && in_array($profile_id, $blacklist)) {
-                       $notice->is_local = -1;
-               } else {
-                       $notice->is_local = $is_local;
-               }
+         if ($blacklist && in_array($profile_id, $blacklist)) {
+             $notice->is_local = -1;
+         } else {
+             $notice->is_local = $is_local;
+         }
  
-               
-               $notice->reply_to = $reply_to;
-               $notice->created = common_sql_now();
-               $notice->content = common_shorten_links($content);
-               $notice->rendered = common_render_content($notice->content, $notice);
-               $notice->source = $source;
-               $notice->uri = $uri;
 +              $notice->query('BEGIN');
-               $id = $notice->insert();
 +
+         $notice->reply_to = $reply_to;
+         $notice->created = common_sql_now();
+         $notice->content = common_shorten_links($content);
+         $notice->rendered = common_render_content($notice->content, $notice);
+         $notice->source = $source;
+         $notice->uri = $uri;
  
-               if (!$id) {
-                       common_log_db_error($notice, 'INSERT', __FILE__);
-                       return _('Problem saving notice.');
-               }
+         $id = $notice->insert();
  
-               # Update the URI after the notice is in the database
-               if (!$uri) {
-                       $orig = clone($notice);
-                       $notice->uri = common_notice_uri($notice);
+         if (!$id) {
+             common_log_db_error($notice, 'INSERT', __FILE__);
+             return _('Problem saving notice.');
+         }
  
-                       if (!$notice->update($orig)) {
-                               common_log_db_error($notice, 'UPDATE', __FILE__);
-                               return _('Problem saving notice.');
-                       }
-               }
+         # Update the URI after the notice is in the database
+         if (!$uri) {
+             $orig = clone($notice);
+             $notice->uri = common_notice_uri($notice);
  
-               # XXX: do we need to change this for remote users?
+             if (!$notice->update($orig)) {
+                 common_log_db_error($notice, 'UPDATE', __FILE__);
+                 return _('Problem saving notice.');
+             }
+         }
  
-               common_save_replies($notice);
-               $notice->saveTags();
+         # XXX: do we need to change this for remote users?
  
-               // Add to notice inboxes
-               
-               $notice->addToInboxes();
+         $notice->saveReplies();
+         $notice->saveTags();
+         $notice->saveGroups();
  
-               
-               # Clear the cache for subscribed users, so they'll update at next request
-               # XXX: someone clever could prepend instead of clearing the cache
++        $notice->addToInboxes();
 +              $notice->query('COMMIT');
-               if (common_config('memcached', 'enabled')) {
-                       $notice->blowCaches();
-               }
 +
+         # Clear the cache for subscribed users, so they'll update at next request
+         # XXX: someone clever could prepend instead of clearing the cache
+         if (common_config('memcached', 'enabled')) {
+             $notice->blowCaches();
+         }
  
-               return $notice;
-       }
 -        $notice->addToInboxes();
+         return $notice;
+     }
  
      static function checkEditThrottle($profile_id) {
          $profile = Profile::staticGet($profile_id);
index 5dab5c7017c7398b81f10950f2e7fb33c3acea3f,5f4fb9b6fb057e4eddeabfef4e6209e3d524a89c..b1bae88351ad82a3f5b78b0de587aa9e3c3eeb78
@@@ -353,65 -368,72 +368,72 @@@ class User extends Memcached_DataObjec
          } else {
              return $profile->getNotices($offset, $limit, $since_id, $before_id);
          }
-       }
-       function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE) {
-               $qry =
-                 'SELECT notice.* ' .
-                 'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
-                 'WHERE fave.user_id = %d ';
-               return Notice::getStream(sprintf($qry, $this->id),
-                                                                'user:faves:'.$this->id,
-                                                                $offset, $limit);
-       }
-         function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) {
-               $enabled = common_config('inboxes', 'enabled');
-               # Complicated code, depending on whether we support inboxes yet
-               # XXX: make this go away when inboxes become mandatory
-               if ($enabled === false ||
-                       ($enabled == 'transitional' && $this->inboxed == 0)) {
-                       $qry =
-                         'SELECT notice.* ' .
-                         'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
-                         'WHERE subscription.subscriber = %d ';
-                       $order = NULL;
-               } else if ($enabled === true ||
-                          ($enabled == 'transitional' && $this->inboxed == 1)) {
-                       $qry =
-                         'SELECT notice.* ' .
-                         'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
-                         'WHERE notice_inbox.user_id = %d ';
-                       $order = null;
-               }
-               return Notice::getStream(sprintf($qry, $this->id),
-                                                                'user:notices_with_friends:' . $this->id,
-                                                                $offset, $limit, $since_id, $before_id,
-                                                                $order, $since);
-       }
-         function blowFavesCache() {
-               $cache = common_memcache();
-               if ($cache) {
-                       # Faves don't happen chronologically, so we need to blow
-                       # ;last cache, too
-                       $cache->delete(common_cache_key('user:faves:'.$this->id));
-                       $cache->delete(common_cache_key('user:faves:'.$this->id).';last');
-               }
-       }
-         function getSelfTags() {
-               return Profile_tag::getTags($this->id, $this->id);
-       }
-         function setSelfTags($newtags) {
-               return Profile_tag::setTags($this->id, $this->id, $newtags);
-       }
-     function block($other) {
+     }
+       function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE)
+       {
+         $qry =
+           'SELECT notice.* ' .
+           'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
+           'WHERE fave.user_id = %d ';
+         return Notice::getStream(sprintf($qry, $this->id),
+                                  'user:faves:'.$this->id,
+                                  $offset, $limit);
+     }
+         function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+         {
+         $enabled = common_config('inboxes', 'enabled');
+         # Complicated code, depending on whether we support inboxes yet
+         # XXX: make this go away when inboxes become mandatory
+         if ($enabled === false ||
+             ($enabled == 'transitional' && $this->inboxed == 0)) {
+             $qry =
+               'SELECT notice.* ' .
+               'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
+               'WHERE subscription.subscriber = %d ';
+             $order = null;
+         } else if ($enabled === true ||
+                ($enabled == 'transitional' && $this->inboxed == 1)) {
+             $qry =
+               'SELECT notice.* ' .
+               'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
+               'WHERE notice_inbox.user_id = %d ';
+             # NOTE: we override ORDER
 -            $order = 'ORDER BY notice_inbox.created DESC, notice_inbox.notice_id DESC ';
++            $order = null;
+         }
+         return Notice::getStream(sprintf($qry, $this->id),
+                                  'user:notices_with_friends:' . $this->id,
+                                  $offset, $limit, $since_id, $before_id,
+                                  $order, $since);
+     }
+         function blowFavesCache()
+         {
+         $cache = common_memcache();
+         if ($cache) {
+             # Faves don't happen chronologically, so we need to blow
+             # ;last cache, too
+             $cache->delete(common_cache_key('user:faves:'.$this->id));
+             $cache->delete(common_cache_key('user:faves:'.$this->id).';last');
+         }
+     }
+         function getSelfTags()
+         {
+         return Profile_tag::getTags($this->id, $this->id);
+     }
+         function setSelfTags($newtags)
+         {
+         return Profile_tag::setTags($this->id, $this->id, $newtags);
+     }
+     function block($other)
+     {
  
          # Add a new block record
  
index f474c4999ece8bf7f71a15e50a0044e079ed404b,1d00cc31e18f88d33c2743f3d35183d08fac6722..a73b73f28091351ed84235c1a38086c8c84361cd
   *
   * 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 I18n
+  * @package  Laconica
+  * @author   Matthew Gregg <matthew.gregg@gmail.com>
+  * @author   Ciaran Gultnieks <ciaran@ciarang.com>
+  * @author   Evan Prodromou <evan@controlyourself.ca>
+  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+  * @link     http://laconi.ca/
+  */
+ if (!defined('LACONICA')) {
+     exit(1);
+ }
+ /**
+  * Content negotiation for language codes
+  *
+  * @param string $httplang HTTP Accept-Language header
+  *
+  * @return string language code for best language match
   */
  
- if (!defined('LACONICA')) { exit(1); }
+ function client_prefered_language($httplang)
+ {
+     $client_langs = array();
  
- function client_prefered_language($httplang) {
-         $client_langs = array();
-         $all_languages = common_config('site','languages');
+     $all_languages = common_config('site', 'languages');
  
-         preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',strtolower($httplang),$httplang);
-         for ($i = 0; $i < count($httplang); $i++) {
-              if(!empty($httplang[2][$i])) {
-                     #if no q default to 1.0
-                     $client_langs[$httplang[2][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
-                 }
-                 if(!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
-                     #if a catchall default 0.01 lower
-                     $client_langs[$httplang[3][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i]-0.01 : 0.99);
-                 }
-             }
-             #sort in decending q
-             arsort($client_langs);
+     preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',
+                    strtolower($httplang), $httplang);
  
-             foreach ($client_langs as $lang => $q) {
-                 if (isset($all_languages[$lang])) {
-                     return($all_languages[$lang]['lang']);
-                 }
-             }
-             return FALSE;
- }
+     for ($i = 0; $i < count($httplang); $i++) {
+         if (!empty($httplang[2][$i])) {
+             // if no q default to 1.0
+             $client_langs[$httplang[2][$i]] =
+               ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
+         }
+         if (!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
+             // if a catchall default 0.01 lower
+             $client_langs[$httplang[3][$i]] =
+               ($httplang[6][$i]? (float) $httplang[6][$i]-0.01 : 0.99);
+         }
+     }
+     // sort in decending q
+     arsort($client_langs);
  
- function get_nice_language_list() {
-         $nice_lang = array();
-         $all_languages = common_config('site','languages');
-         foreach ($all_languages as $lang) {
-                 $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
+     foreach ($client_langs as $lang => $q) {
+         if (isset($all_languages[$lang])) {
+             return($all_languages[$lang]['lang']);
          }
-         return $nice_lang;
+     }
+     return false;
  }
  
- // Get a list of all languages that are enabled in the default config. This
- // should ONLY be called when setting up the default config in common.php.
- // Any other attempt to get a list of lanugages should instead call
- // common_config('site','languages')
+ /**
+  * returns a simple code -> name mapping for languages
+  *
+  * @return array map of available languages by code to language name.
+  */
+ function get_nice_language_list()
+ {
+     $nice_lang = array();
+     $all_languages = common_config('site', 'languages');
+     foreach ($all_languages as $lang) {
+         $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
+     }
+     return $nice_lang;
+ }
+ /**
+  * Get a list of all languages that are enabled in the default config
+  *
+  * This should ONLY be called when setting up the default config in common.php.
+  * Any other attempt to get a list of lanugages should instead call
+  * common_config('site','languages')
+  *
+  * @return array mapping of language codes to language info
+  */
 -
 -function get_all_languages()
 -{
 -    return
 -      array('en-us' => array('q' => 1, 'lang' => 'en_US',
 -                             'name' => 'English (US)', 'direction' => 'ltr'),
 -            'en-nz' => array('q' => 1, 'lang' => 'en_NZ',
 -                             'name' => 'English (NZ)', 'direction' => 'ltr'),
 -            'en-gb' => array('q' => 1, 'lang' => 'en_GB',
 -                             'name' => 'English (British)', 'direction' => 'ltr'),
 -            'en' => array('q' => 1, 'lang' => 'en',
 -                          'name' => 'English', 'direction' => 'ltr'),
 -            'da' => array('q' => 0.1, 'lang' => 'da_DK',
 -                          'name' => 'Danish', 'direction' => 'ltr'),
 -            'nl' => array('q' => 1, 'lang' => 'nl_NL',
 -                          'name' => 'Dutch', 'direction' => 'ltr'),
 -            'eo' => array('q' => 0.1, 'lang' => 'eo',
 -                          'name' => 'Esperanto', 'direction' => 'ltr'),
 -            'fr-fr' => array('q' => 0.9, 'lang' => 'fr_FR',
 -                             'name' => 'French', 'direction' => 'ltr'),
 -            'de' => array('q' => 1, 'lang' => 'de_DE',
 -                          'name' => 'German', 'direction' => 'ltr'),
 -            'it' => array('q' => 1, 'lang' => 'it_IT',
 -                          'name' => 'Italian', 'direction' => 'ltr'),
 -            'ko' => array('q' => 0.1, 'lang' => 'ko',
 -                          'name' => 'Korean', 'direction' => 'ltr'),
 -            'nb' => array('q' => 1, 'lang' => 'nb_NO',
 -                          'name' => 'Norwegian (bokmal)', 'direction' => 'ltr'),
 -            'pt' => array('q' => 0.2, 'lang' => 'pt',
 -                          'name' => 'Portuguese', 'direction' => 'ltr'),
 -            'pt-br' => array('q' => 1, 'lang' => 'pt_BR',
 -                             'name' => 'Portuguese Brazil', 'direction' => 'ltr'),
 -            'es' => array('q' => 1, 'lang' => 'es',
 -                          'name' => 'Spanish', 'direction' => 'ltr'),
 -            'tr' => array('q' => 1, 'lang' => 'tr_TR',
 -                          'name' => 'Turkish', 'direction' => 'ltr'),
 -            'uk' => array('q' => 1, 'lang' => 'uk_UA',
 -                          'name' => 'Ukrainian', 'direction' => 'ltr'),
 -            'pl' => array('q' => 1, 'lang' => 'pl_PL',
 -                          'name' => 'Polish', 'direction' => 'ltr'),
 -            'mk' => array('q' => 1, 'lang' => 'mk_MK',
 -                          'name' => 'Macedonian', 'direction' => 'ltr'),
 -            'jp' => array('q' => 0.1, 'lang' => 'ja_JP',
 -                          'name' => 'Japanese', 'direction' => 'ltr'),
 -            'cs' => array('q' => 1, 'lang' => 'cs_CZ',
 -                          'name' => 'Czech', 'direction' => 'ltr'),
 -            'ca' => array('q' => 1, 'lang' => 'ca_ES',
 -                          'name' => 'Catalan', 'direction' => 'ltr'),
 -            );
 +function get_all_languages() {
 +      return array(
 +              'bg' => array('q' => 0.8, 'lang' => 'bg_BG', 'name' => 'Bulgarian', 'direction' => 'ltr'),
 +              'ca'    => array('q' => 0.5, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'),
 +              'cs'    => array('q' => 0.5, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'),
 +              'de'    => array('q' => 0.5, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'),
 +              'el'    => array('q' => 0.1, 'lang' => 'el',    'name' => 'Greek', 'direction' => 'ltr'),
 +              'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
 +              'en-gb' => array('q' => 0.3, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
 +              'en'    => array('q' => 1, 'lang' => 'en',    'name' => 'English', 'direction' => 'ltr'),
 +              'es'    => array('q' => 0.5, 'lang' => 'es',    'name' => 'Spanish', 'direction' => 'ltr'),
 +              'fr-fr' => array('q' => 0.2, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),
 +              'he'    => array('q' => 0.5, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'ltr'),
 +              'it'    => array('q' => 0.9, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'rtl'),
 +              'jp'    => array('q' => 0.5, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'),
 +#             'ko'    => array('q' => 0, 'lang' => 'ko',    'name' => 'Korean', 'direction' => 'ltr'),
 +              'mk'    => array('q' => 0.5, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'),
 +              'nb'    => array('q' => 0.1, 'lang' => 'nb_NO', 'name' => 'Norwegian (bokmal)', 'direction' => 'ltr'),
 +              'nl'    => array('q' => 0.5, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'),
 +              'pl'    => array('q' => 0.5, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'),
 +#             'pt'    => array('q' => 0, 'lang' => 'pt',    'name' => 'Portuguese', 'direction' => 'ltr'),
 +              'pt-br' => array('q' => 0.7, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'),
 +              'ru'    => array('q' => 0.1, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'),
 +              'sv'    => array('q' => 0.9, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'),
 +              'te'    => array('q' => 0.3, 'lang' => 'te_IN', 'name' => 'Telugu', 'direction' => 'ltr'),
 +              'tr'    => array('q' => 0.5, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'),
 +              'uk'    => array('q' => 0.7, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'),
 +              'vi'    => array('q' => 0.7, 'lang' => 'vi_VN', 'name' => 'Vietnamese', 'direction' => 'ltr'),
 +              'zh-cn'    => array('q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'),
 +              'zh-hant'    => array('q' => 0.2, 'lang' => 'zh_hant', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'),
 +      );
  }
--
diff --cc lib/subs.php
index 483b2f78e8d2345e6b5656f68395caebc57d170e,6fa1dcad3bbba5e07ce01e0a4595c5d11ea4a20e..0e7b9ded522c07e55811fc313ad084e046221e5c
@@@ -58,37 -60,41 +60,40 @@@ function subs_subscribe_to($user, $othe
  
      subs_notify($other, $user);
  
-     $cache = common_memcache();
 -    if (common_config('memcached', 'enabled')) {
 -        $cache = new Memcache();
 -        if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
 -            $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
 -        }
 -    }
++        $cache = common_memcache();
 +
 +    if ($cache) {
 +        $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
 +      }
 +
-       if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
-               if (!$other->subscribeTo($user)) {
-                       return _('Could not subscribe other to you.');
-               }
+     if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
+         if (!$other->subscribeTo($user)) {
+             return _('Could not subscribe other to you.');
+         }
 -        if (common_config('memcached', 'enabled')) {
 -            $cache = new Memcache();
 -            if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
 -                $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id));
 -            }
 -        }
 +        $cache = common_memcache();
 +
 +        if ($cache) {
 +            $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id));
 +              }
  
-               subs_notify($user, $other);
-       }
+         subs_notify($user, $other);
+     }
  
-       return true;
+     return true;
  }
  
- function subs_notify($listenee, $listener) {
-       # XXX: add other notifications (Jabber, SMS) here
-       # XXX: queue this and handle it offline
-       # XXX: Whatever happens, do it in Twitter-like API, too
-       subs_notify_email($listenee, $listener);
+ function subs_notify($listenee, $listener)
+ {
+     # XXX: add other notifications (Jabber, SMS) here
+     # XXX: queue this and handle it offline
+     # XXX: Whatever happens, do it in Twitter-like API, too
+     subs_notify_email($listenee, $listener);
  }
  
- function subs_notify_email($listenee, $listener) {
-       mail_subscribe_notify($listenee, $listener);
+ function subs_notify_email($listenee, $listener)
+ {
+     mail_subscribe_notify($listenee, $listener);
  }
  
  /* Unsubscribe $user from nickname $other_nickname
@@@ -109,29 -116,31 +115,30 @@@ function subs_unsubscribe_user($user, $
  /* Unsubscribe user $user from profile $other
   * NB: other can be a remote user. */
  
- function subs_unsubscribe_to($user, $other) {
+ function subs_unsubscribe_to($user, $other)
+ {
  
-       if (!$user->isSubscribed($other))
-               return _('Not subscribed!');
+     if (!$user->isSubscribed($other))
+         return _('Not subscribed!.');
  
-       $sub = DB_DataObject::factory('subscription');
+     $sub = DB_DataObject::factory('subscription');
  
-       $sub->subscriber = $user->id;
-       $sub->subscribed = $other->id;
+     $sub->subscriber = $user->id;
+     $sub->subscribed = $other->id;
  
-       $sub->find(true);
+     $sub->find(true);
  
-       // note we checked for existence above
+     // note we checked for existence above
  
-       if (!$sub->delete())
-               return _('Couldn\'t delete subscription.');
+     if (!$sub->delete())
+         return _('Couldn\'t delete subscription.');
  
 -    if (common_config('memcached', 'enabled')) {
 -        $cache = new Memcache();
 -        if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
 -            $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
 -        }
 -    }
 +    $cache = common_memcache();
 +
 +    if ($cache) {
 +        $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
 +      }
  
-       return true;
+     return true;
  }
  
index 2083e8961760ad418e0fe56bf5a065af6b78ee6b,50bcb06fe9388cdb2fe11eb8789a68849f80c794..3d2c74febcf02cb00991f593a3f7d9d0415b9ce7
@@@ -92,492 -97,597 +97,597 @@@ class TwitterapiAction extends Actio
  
          # We trim() to avoid extraneous whitespace in the output
  
-               $entry['content'] = common_xml_safe_str(trim($notice->rendered));
-               $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
-               $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
-               $entry['published'] = common_date_iso8601($notice->created);
-               $entry['id'] = "tag:$server,2008:$entry[link]";
-               $entry['updated'] = $entry['published'];
-               # RSS Item specific
-               $entry['description'] = $entry['content'];
-               $entry['pubDate'] = common_date_rfc2822($notice->created);
-               $entry['guid'] = $entry['link'];
-               return $entry;
-       }
-       function twitter_rss_dmsg_array($message) {
-               $server = common_config('site', 'server');
-               $entry = array();
-               $entry['title'] = sprintf('Message from %s to %s',
-                       $message->getFrom()->nickname, $message->getTo()->nickname);
-               $entry['content'] = common_xml_safe_str(trim($message->content));
-               $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
-               $entry['published'] = common_date_iso8601($message->created);
-               $entry['id'] = "tag:$server,2008:$entry[link]";
-               $entry['updated'] = $entry['published'];
-               # RSS Item specific
-               $entry['description'] = $entry['content'];
-               $entry['pubDate'] = common_date_rfc2822($message->created);
-               $entry['guid'] = $entry['link'];
-               return $entry;
-       }
-       function twitter_dmsg_array($message) {
-               $twitter_dm = array();
-               $from_profile = $message->getFrom();
-               $to_profile = $message->getTo();
-               $twitter_dm['id'] = $message->id;
-               $twitter_dm['sender_id'] = $message->from_profile;
-               $twitter_dm['text'] = trim($message->content);
-               $twitter_dm['recipient_id'] = $message->to_profile;
-               $twitter_dm['created_at'] = $this->date_twitter($message->created);
-               $twitter_dm['sender_screen_name'] = $from_profile->nickname;
-               $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
-               $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
-               $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
-               return $twitter_dm;
-       }
-       function show_twitter_xml_status($twitter_status) {
-               common_element_start('status');
-               foreach($twitter_status as $element => $value) {
-                       switch ($element) {
-                       case 'user':
-                               $this->show_twitter_xml_user($twitter_status['user']);
-                               break;
-                       case 'text':
-                               common_element($element, NULL, common_xml_safe_str($value));
-                               break;
-                       default:
-                               common_element($element, NULL, $value);
-                       }
-               }
-               common_element_end('status');
-       }
-       function show_twitter_xml_user($twitter_user, $role='user') {
-               common_element_start($role);
-               foreach($twitter_user as $element => $value) {
-                       if ($element == 'status') {
-                               $this->show_twitter_xml_status($twitter_user['status']);
-                       } else {
-                               common_element($element, NULL, $value);
-                       }
-               }
-               common_element_end($role);
-       }
-       function show_twitter_rss_item($entry) {
-               common_element_start('item');
-               common_element('title', NULL, $entry['title']);
-               common_element('description', NULL, $entry['description']);
-               common_element('pubDate', NULL, $entry['pubDate']);
-               common_element('guid', NULL, $entry['guid']);
-               common_element('link', NULL, $entry['link']);
-               common_element_end('item');
-       }
-       function show_twitter_atom_entry($entry) {
-           common_element_start('entry');
-               common_element('title', NULL, $entry['title']);
-               common_element('content', array('type' => 'html'), $entry['content']);
-               common_element('id', NULL, $entry['id']);
-               common_element('published', NULL, $entry['published']);
-               common_element('updated', NULL, $entry['updated']);
-               common_element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), NULL);
-               common_element_end('entry');
-       }
-       function show_json_objects($objects) {
-               print(json_encode($objects));
-       }
-       function show_single_xml_status($notice) {
-               $this->init_document('xml');
-               $twitter_status = $this->twitter_status_array($notice);
-               $this->show_twitter_xml_status($twitter_status);
-               $this->end_document('xml');
-       }
-       function show_single_json_status($notice) {
-               $this->init_document('json');
-               $status = $this->twitter_status_array($notice);
-               $this->show_json_objects($status);
-               $this->end_document('json');
-       }
-       function show_single_xml_dmsg($message) {
-               $this->init_document('xml');
-               $dmsg = $this->twitter_dmsg_array($message);
-               $this->show_twitter_xml_dmsg($dmsg);
-               $this->end_document('xml');
-       }
-       function show_single_json_dmsg($message) {
-               $this->init_document('json');
-               $dmsg = $this->twitter_dmsg_array($message);
-               $this->show_json_objects($dmsg);
-               $this->end_document('json');
-       }
-       function show_twitter_xml_dmsg($twitter_dm) {
-               common_element_start('direct_message');
-               foreach($twitter_dm as $element => $value) {
-                       switch ($element) {
-                       case 'sender':
-                       case 'recipient':
-                               $this->show_twitter_xml_user($value, $element);
-                               break;
-                       case 'text':
-                               common_element($element, NULL, common_xml_safe_str($value));
-                               break;
-                       default:
-                               common_element($element, NULL, $value);
-                       }
-               }
-               common_element_end('direct_message');
-       }
-       function show_xml_timeline($notice) {
-               $this->init_document('xml');
-               common_element_start('statuses', array('type' => 'array'));
-               if (is_array($notice)) {
-                       foreach ($notice as $n) {
-                               $twitter_status = $this->twitter_status_array($n);
-                               $this->show_twitter_xml_status($twitter_status);
-                       }
-               } else {
-                       while ($notice->fetch()) {
-                               $twitter_status = $this->twitter_status_array($notice);
-                               $this->show_twitter_xml_status($twitter_status);
-                       }
-               }
-               common_element_end('statuses');
-               $this->end_document('xml');
-       }
-       function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=NULL) {
-               $this->init_document('rss');
-               common_element_start('channel');
-               common_element('title', NULL, $title);
-               common_element('link', NULL, $link);
-               if (!is_null($suplink)) {
-                       # For FriendFeed's SUP protocol
-                       common_element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
-                                                                                'rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                                                                'href' => $suplink,
-                                                                                'type' => 'application/json'));
-               }
-               common_element('description', NULL, $subtitle);
-               common_element('language', NULL, 'en-us');
-               common_element('ttl', NULL, '40');
-               if (is_array($notice)) {
-                       foreach ($notice as $n) {
-                               $entry = $this->twitter_rss_entry_array($n);
-                               $this->show_twitter_rss_item($entry);
-                       }
-               } else {
-                       while ($notice->fetch()) {
-                               $entry = $this->twitter_rss_entry_array($notice);
-                               $this->show_twitter_rss_item($entry);
-                       }
-               }
-               common_element_end('channel');
-               $this->end_twitter_rss();
-       }
-       function show_atom_timeline($notice, $title, $id, $link, $subtitle=NULL, $suplink=NULL) {
-               $this->init_document('atom');
-               common_element('title', NULL, $title);
-               common_element('id', NULL, $id);
-               common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
-               if (!is_null($suplink)) {
-                       # For FriendFeed's SUP protocol
-                       common_element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                                                                'href' => $suplink,
-                                                                                'type' => 'application/json'));
-               }
-               common_element('subtitle', NULL, $subtitle);
-               if (is_array($notice)) {
-                       foreach ($notice as $n) {
-                               $entry = $this->twitter_rss_entry_array($n);
-                               $this->show_twitter_atom_entry($entry);
-                       }
-               } else {
-                       while ($notice->fetch()) {
-                               $entry = $this->twitter_rss_entry_array($notice);
-                               $this->show_twitter_atom_entry($entry);
-                       }
-               }
-               $this->end_document('atom');
-       }
-       function show_json_timeline($notice) {
-               $this->init_document('json');
-               $statuses = array();
-               if (is_array($notice)) {
-                       foreach ($notice as $n) {
-                               $twitter_status = $this->twitter_status_array($n);
-                               array_push($statuses, $twitter_status);
-                       }
-               } else {
-                       while ($notice->fetch()) {
-                               $twitter_status = $this->twitter_status_array($notice);
-                               array_push($statuses, $twitter_status);
-                       }
-               }
-               $this->show_json_objects($statuses);
-               $this->end_document('json');
-       }
-       // Anyone know what date format this is?
-       // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
-       function date_twitter($dt) {
-               $t = strtotime($dt);
-               return date("D M d G:i:s O Y", $t);
-       }
-       function replier_by_reply($reply_id) {
-               $notice = Notice::staticGet($reply_id);
-               if ($notice) {
-                       $profile = $notice->getProfile();
-                       if ($profile) {
-                               return intval($profile->id);
-                       } else {
-                               common_debug('Can\'t find a profile for notice: ' . $notice->id, __FILE__);
-                       }
-               } else {
-                       common_debug("Can't get notice: $reply_id", __FILE__);
-               }
-               return NULL;
-       }
-       // XXX: Candidate for a general utility method somewhere?
-       function count_subscriptions($profile) {
-               $count = 0;
-               $sub = new Subscription();
-               $sub->subscribed = $profile->id;
-               $count = $sub->find();
-               if ($count > 0) {
-                       return $count - 1;
-               } else {
-                       return 0;
-               }
-       }
-       function init_document($type='xml') {
-               switch ($type) {
-                case 'xml':
-                       header('Content-Type: application/xml; charset=utf-8');
-                       common_start_xml();
-                       break;
-                case 'json':
-                       header('Content-Type: application/json; charset=utf-8');
-                       // Check for JSONP callback
-                       $callback = $this->arg('callback');
-                       if ($callback) {
-                               print $callback . '(';
-                       }
-                       break;
-                case 'rss':
-                       header("Content-Type: application/rss+xml; charset=utf-8");
-                       $this->init_twitter_rss();
-                       break;
-                case 'atom':
-                       header('Content-Type: application/atom+xml; charset=utf-8');
-                       $this->init_twitter_atom();
-                       break;
-                default:
-                       $this->client_error(_('Not a supported data format.'));
-                       break;
-               }
-               return;
-       }
-       function end_document($type='xml') {
-               switch ($type) {
-                case 'xml':
-                       common_end_xml();
-                       break;
-                case 'json':
-                       // Check for JSONP callback
-                       $callback = $this->arg('callback');
-                       if ($callback) {
-                               print ')';
-                       }
-                       break;
-                case 'rss':
-                       $this->end_twitter_rss();
-                       break;
-                case 'atom':
-                       $this->end_twitter_rss();
-                       break;
-                default:
-                       $this->client_error(_('Not a supported data format.'));
-                       break;
-               }
-               return;
-       }
-       function client_error($msg, $code = 400, $content_type = 'json') {
-               static $status = array(400 => 'Bad Request',
-                                                          401 => 'Unauthorized',
-                                                          402 => 'Payment Required',
-                                                          403 => 'Forbidden',
-                                                          404 => 'Not Found',
-                                                          405 => 'Method Not Allowed',
-                                                          406 => 'Not Acceptable',
-                                                          407 => 'Proxy Authentication Required',
-                                                          408 => 'Request Timeout',
-                                                          409 => 'Conflict',
-                                                          410 => 'Gone',
-                                                          411 => 'Length Required',
-                                                          412 => 'Precondition Failed',
-                                                          413 => 'Request Entity Too Large',
-                                                          414 => 'Request-URI Too Long',
-                                                          415 => 'Unsupported Media Type',
-                                                          416 => 'Requested Range Not Satisfiable',
-                                                          417 => 'Expectation Failed');
-               $action = $this->trimmed('action');
-               common_debug("User error '$code' on '$action': $msg", __FILE__);
-               if (!array_key_exists($code, $status)) {
-                       $code = 400;
-               }
-               $status_string = $status[$code];
-               header('HTTP/1.1 '.$code.' '.$status_string);
-               if ($content_type == 'xml') {
-                       $this->init_document('xml');
-                       common_element_start('hash');
-                       common_element('error', NULL, $msg);
-                       common_element('request', NULL, $_SERVER['REQUEST_URI']);
-                       common_element_end('hash');
-                       $this->end_document('xml');
-               } else {
-                       $this->init_document('json');
-                       $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-                       print(json_encode($error_array));
-                       $this->end_document('json');
-               }
-       }
-       function init_twitter_rss() {
-               common_start_xml();
-               common_element_start('rss', array('version' => '2.0'));
-       }
-       function end_twitter_rss() {
-               common_element_end('rss');
-               common_end_xml();
-       }
-       function init_twitter_atom() {
-               common_start_xml();
-               common_element_start('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
-       }
-       function end_twitter_atom() {
-               common_end_xml();
-               common_element_end('feed');
-       }
-       function show_profile($profile, $content_type='xml', $notice=NULL) {
-               $profile_array = $this->twitter_user_array($profile, true);
-               switch ($content_type) {
-                case 'xml':
-                       $this->show_twitter_xml_user($profile_array);
-                       break;
-                case 'json':
-                       $this->show_json_objects($profile_array);
-                       break;
-                default:
-                       $this->client_error(_('Not a supported data format.'));
-                       return;
-               }
-               return;
-       }
-       function get_user($id, $apidata=NULL) {
-               if (!$id) {
-                       return $apidata['user'];
-               } else if (is_numeric($id)) {
-                       return User::staticGet($id);
-               } else {
-                       $nickname = common_canonical_nickname($id);
-                       return User::staticGet('nickname', $nickname);
-               }
-       }
-       function get_profile($id) {
-               if (is_numeric($id)) {
-                       return Profile::staticGet($id);
-               } else {
-                       $user = User::staticGet('nickname', $id);
-                       if ($user) {
-                               return $user->getProfile();
-                       } else {
-                               return NULL;
-                       }
-               }
-       }
-       function source_link($source) {
-               $source_name = _($source);
-               switch ($source) {
-                case 'web':
-                case 'xmpp':
-                case 'mail':
-                case 'omb':
-                case 'api':
-                       break;
-                default:
-                       $ns = Notice_source::staticGet($source);
-                       if ($ns) {
-                               $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
-                       }
-                       break;
-               }
-               return $source_name;
-       }
- }
+         $entry['content'] = common_xml_safe_str(trim($notice->rendered));
+         $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+         $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
+         $entry['published'] = common_date_iso8601($notice->created);
+         $entry['id'] = "tag:$server,2008:$entry[link]";
+         $entry['updated'] = $entry['published'];
+         # RSS Item specific
+         $entry['description'] = $entry['content'];
+         $entry['pubDate'] = common_date_rfc2822($notice->created);
+         $entry['guid'] = $entry['link'];
+         return $entry;
+     }
+     function twitter_rss_dmsg_array($message)
+     {
+         $server = common_config('site', 'server');
+         $entry = array();
+         $entry['title'] = sprintf('Message from %s to %s',
+             $message->getFrom()->nickname, $message->getTo()->nickname);
+         $entry['content'] = common_xml_safe_str(trim($message->content));
+         $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
+         $entry['published'] = common_date_iso8601($message->created);
+         $entry['id'] = "tag:$server,2008:$entry[link]";
+         $entry['updated'] = $entry['published'];
+         # RSS Item specific
+         $entry['description'] = $entry['content'];
+         $entry['pubDate'] = common_date_rfc2822($message->created);
+         $entry['guid'] = $entry['link'];
+         return $entry;
+     }
+     function twitter_dmsg_array($message)
+     {
+         $twitter_dm = array();
+         $from_profile = $message->getFrom();
+         $to_profile = $message->getTo();
+         $twitter_dm['id'] = $message->id;
+         $twitter_dm['sender_id'] = $message->from_profile;
+         $twitter_dm['text'] = trim($message->content);
+         $twitter_dm['recipient_id'] = $message->to_profile;
+         $twitter_dm['created_at'] = $this->date_twitter($message->created);
+         $twitter_dm['sender_screen_name'] = $from_profile->nickname;
+         $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
+         $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
+         $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
+         return $twitter_dm;
+     }
+     function show_twitter_xml_status($twitter_status)
+     {
+         common_element_start('status');
+         foreach($twitter_status as $element => $value) {
+             switch ($element) {
+             case 'user':
+                 $this->show_twitter_xml_user($twitter_status['user']);
+                 break;
+             case 'text':
+                 common_element($element, null, common_xml_safe_str($value));
+                 break;
+             default:
+                 common_element($element, null, $value);
+             }
+         }
+         common_element_end('status');
+     }
+     function show_twitter_xml_user($twitter_user, $role='user')
+     {
+         common_element_start($role);
+         foreach($twitter_user as $element => $value) {
+             if ($element == 'status') {
+                 $this->show_twitter_xml_status($twitter_user['status']);
+             } else {
+                 common_element($element, null, $value);
+             }
+         }
+         common_element_end($role);
+     }
+     function show_twitter_rss_item($entry)
+     {
+         common_element_start('item');
+         common_element('title', null, $entry['title']);
+         common_element('description', null, $entry['description']);
+         common_element('pubDate', null, $entry['pubDate']);
+         common_element('guid', null, $entry['guid']);
+         common_element('link', null, $entry['link']);
+         common_element_end('item');
+     }
+     function show_twitter_atom_entry($entry)
+     {
+         common_element_start('entry');
+         common_element('title', null, $entry['title']);
+         common_element('content', array('type' => 'html'), $entry['content']);
+         common_element('id', null, $entry['id']);
+         common_element('published', null, $entry['published']);
+         common_element('updated', null, $entry['updated']);
+         common_element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), null);
+         common_element_end('entry');
+     }
+     function show_json_objects($objects)
+     {
+         print(json_encode($objects));
+     }
+     function show_single_xml_status($notice)
+     {
+         $this->init_document('xml');
+         $twitter_status = $this->twitter_status_array($notice);
+         $this->show_twitter_xml_status($twitter_status);
+         $this->end_document('xml');
+     }
+     function show_single_json_status($notice)
+     {
+         $this->init_document('json');
+         $status = $this->twitter_status_array($notice);
+         $this->show_json_objects($status);
+         $this->end_document('json');
+     }
+     function show_single_xml_dmsg($message)
+     {
+         $this->init_document('xml');
+         $dmsg = $this->twitter_dmsg_array($message);
+         $this->show_twitter_xml_dmsg($dmsg);
+         $this->end_document('xml');
+     }
+     function show_single_json_dmsg($message)
+     {
+         $this->init_document('json');
+         $dmsg = $this->twitter_dmsg_array($message);
+         $this->show_json_objects($dmsg);
+         $this->end_document('json');
+     }
+     function show_twitter_xml_dmsg($twitter_dm)
+     {
+         common_element_start('direct_message');
+         foreach($twitter_dm as $element => $value) {
+             switch ($element) {
+             case 'sender':
+             case 'recipient':
+                 $this->show_twitter_xml_user($value, $element);
+                 break;
+             case 'text':
+                 common_element($element, null, common_xml_safe_str($value));
+                 break;
+             default:
+                 common_element($element, null, $value);
+             }
+         }
+         common_element_end('direct_message');
+     }
+     function show_xml_timeline($notice)
+     {
+         $this->init_document('xml');
+         common_element_start('statuses', array('type' => 'array'));
+         if (is_array($notice)) {
+             foreach ($notice as $n) {
+                 $twitter_status = $this->twitter_status_array($n);
+                 $this->show_twitter_xml_status($twitter_status);
+             }
+         } else {
+             while ($notice->fetch()) {
+                 $twitter_status = $this->twitter_status_array($notice);
+                 $this->show_twitter_xml_status($twitter_status);
+             }
+         }
+         common_element_end('statuses');
+         $this->end_document('xml');
+     }
+     function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
+     {
+         $this->init_document('rss');
+         common_element_start('channel');
+         common_element('title', null, $title);
+         common_element('link', null, $link);
+         if (!is_null($suplink)) {
+             # For FriendFeed's SUP protocol
+             common_element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
+                                          'rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                          'href' => $suplink,
+                                          'type' => 'application/json'));
+         }
+         common_element('description', null, $subtitle);
+         common_element('language', null, 'en-us');
+         common_element('ttl', null, '40');
+         if (is_array($notice)) {
+             foreach ($notice as $n) {
+                 $entry = $this->twitter_rss_entry_array($n);
+                 $this->show_twitter_rss_item($entry);
+             }
+         } else {
+             while ($notice->fetch()) {
+                 $entry = $this->twitter_rss_entry_array($notice);
+                 $this->show_twitter_rss_item($entry);
+             }
+         }
+         common_element_end('channel');
+         $this->end_twitter_rss();
+     }
+     function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null)
+     {
+         $this->init_document('atom');
+         common_element('title', null, $title);
+         common_element('id', null, $id);
+         common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+         if (!is_null($suplink)) {
+             # For FriendFeed's SUP protocol
+             common_element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                          'href' => $suplink,
+                                          'type' => 'application/json'));
+         }
+         common_element('subtitle', null, $subtitle);
+         if (is_array($notice)) {
+             foreach ($notice as $n) {
+                 $entry = $this->twitter_rss_entry_array($n);
+                 $this->show_twitter_atom_entry($entry);
+             }
+         } else {
+             while ($notice->fetch()) {
+                 $entry = $this->twitter_rss_entry_array($notice);
+                 $this->show_twitter_atom_entry($entry);
+             }
+         }
+         $this->end_document('atom');
+     }
+     function show_json_timeline($notice)
+     {
+         $this->init_document('json');
+         $statuses = array();
+         if (is_array($notice)) {
+             foreach ($notice as $n) {
+                 $twitter_status = $this->twitter_status_array($n);
+                 array_push($statuses, $twitter_status);
+             }
+         } else {
+             while ($notice->fetch()) {
+                 $twitter_status = $this->twitter_status_array($notice);
+                 array_push($statuses, $twitter_status);
+             }
+         }
+         $this->show_json_objects($statuses);
+         $this->end_document('json');
+     }
+     // Anyone know what date format this is?
+     // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
+     function date_twitter($dt)
+     {
+         $t = strtotime($dt);
+         return date("D M d G:i:s O Y", $t);
+     }
+     function replier_by_reply($reply_id)
+     {
+         $notice = Notice::staticGet($reply_id);
+         if ($notice) {
+             $profile = $notice->getProfile();
+             if ($profile) {
+                 return intval($profile->id);
+             } else {
+                 common_debug('Can\'t find a profile for notice: ' . $notice->id, __FILE__);
+             }
+         } else {
+             common_debug("Can't get notice: $reply_id", __FILE__);
+         }
+         return null;
+     }
+     // XXX: Candidate for a general utility method somewhere?
+     function count_subscriptions($profile)
+     {
+         $count = 0;
+         $sub = new Subscription();
+         $sub->subscribed = $profile->id;
+         $count = $sub->find();
+         if ($count > 0) {
+             return $count - 1;
+         } else {
+             return 0;
+         }
+     }
+     function init_document($type='xml')
+     {
+         switch ($type) {
+          case 'xml':
+             header('Content-Type: application/xml; charset=utf-8');
+             common_start_xml();
+             break;
+          case 'json':
+             header('Content-Type: application/json; charset=utf-8');
+             // Check for JSONP callback
+             $callback = $this->arg('callback');
+             if ($callback) {
+                 print $callback . '(';
+             }
+             break;
+          case 'rss':
+             header("Content-Type: application/rss+xml; charset=utf-8");
+             $this->init_twitter_rss();
+             break;
+          case 'atom':
+             header('Content-Type: application/atom+xml; charset=utf-8');
+             $this->init_twitter_atom();
+             break;
+          default:
+             $this->client_error(_('Not a supported data format.'));
+             break;
+         }
+         return;
+     }
+     function end_document($type='xml')
+     {
+         switch ($type) {
+          case 'xml':
+             common_end_xml();
+             break;
+          case 'json':
+             // Check for JSONP callback
+             $callback = $this->arg('callback');
+             if ($callback) {
+                 print ')';
+             }
+             break;
+          case 'rss':
+             $this->end_twitter_rss();
+             break;
+          case 'atom':
+             $this->end_twitter_rss();
+             break;
+          default:
+             $this->client_error(_('Not a supported data format.'));
+             break;
+         }
+         return;
+     }
+     function client_error($msg, $code = 400, $content_type = 'json')
+     {
+         static $status = array(400 => 'Bad Request',
+                                401 => 'Unauthorized',
+                                402 => 'Payment Required',
+                                403 => 'Forbidden',
+                                404 => 'Not Found',
+                                405 => 'Method Not Allowed',
+                                406 => 'Not Acceptable',
+                                407 => 'Proxy Authentication Required',
+                                408 => 'Request Timeout',
+                                409 => 'Conflict',
+                                410 => 'Gone',
+                                411 => 'Length Required',
+                                412 => 'Precondition Failed',
+                                413 => 'Request Entity Too Large',
+                                414 => 'Request-URI Too Long',
+                                415 => 'Unsupported Media Type',
+                                416 => 'Requested Range Not Satisfiable',
+                                417 => 'Expectation Failed');
+         $action = $this->trimmed('action');
+         common_debug("User error '$code' on '$action': $msg", __FILE__);
+         if (!array_key_exists($code, $status)) {
+             $code = 400;
+         }
+         $status_string = $status[$code];
+         header('HTTP/1.1 '.$code.' '.$status_string);
+         if ($content_type == 'xml') {
+             $this->init_document('xml');
+             common_element_start('hash');
+             common_element('error', null, $msg);
+             common_element('request', null, $_SERVER['REQUEST_URI']);
+             common_element_end('hash');
+             $this->end_document('xml');
+         } else {
+             $this->init_document('json');
+             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+             print(json_encode($error_array));
+             $this->end_document('json');
+         }
+     }
+     function init_twitter_rss()
+     {
+         common_start_xml();
+         common_element_start('rss', array('version' => '2.0'));
+     }
+     function end_twitter_rss()
+     {
+         common_element_end('rss');
+         common_end_xml();
+     }
+     function init_twitter_atom()
+     {
+         common_start_xml();
+         common_element_start('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
+     }
+     function end_twitter_atom()
+     {
+         common_end_xml();
+         common_element_end('feed');
+     }
+     function show_profile($profile, $content_type='xml', $notice=null)
+     {
+         $profile_array = $this->twitter_user_array($profile, true);
+         switch ($content_type) {
+          case 'xml':
+             $this->show_twitter_xml_user($profile_array);
+             break;
+          case 'json':
+             $this->show_json_objects($profile_array);
+             break;
+          default:
+             $this->client_error(_('Not a supported data format.'));
+             return;
+         }
+         return;
+     }
+     function get_user($id, $apidata=null)
+     {
+         if (!$id) {
+             return $apidata['user'];
+         } else if (is_numeric($id)) {
+             return User::staticGet($id);
+         } else {
+             $nickname = common_canonical_nickname($id);
+             return User::staticGet('nickname', $nickname);
+         }
+     }
+     function get_profile($id)
+     {
+         if (is_numeric($id)) {
+             return Profile::staticGet($id);
+         } else {
+             $user = User::staticGet('nickname', $id);
+             if ($user) {
+                 return $user->getProfile();
+             } else {
+                 return null;
+             }
+         }
+     }
+     function source_link($source)
+     {
+         $source_name = _($source);
+         switch ($source) {
+          case 'web':
+          case 'xmpp':
+          case 'mail':
+          case 'omb':
+          case 'api':
+             break;
+          default:
+             $ns = Notice_source::staticGet($source);
+             if ($ns) {
+                 $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+             }
+             break;
+         }
+         return $source_name;
+     }
+     function show_extended_profile($user, $apidata)
+     {
+         $this->auth_user = $apidata['user'];
+         $profile = $user->getProfile();
+         if (!$profile) {
+             common_server_error(_('User has no profile.'));
+             return;
+         }
+         $twitter_user = $this->twitter_user_array($profile, true);
+         // Add in extended user fields offered up by this method
+         $twitter_user['created_at'] = $this->date_twitter($profile->created);
+         $subbed = DB_DataObject::factory('subscription');
+         $subbed->subscriber = $profile->id;
+         $subbed_count = (int) $subbed->count() - 1;
+         $notices = DB_DataObject::factory('notice');
+         $notices->profile_id = $profile->id;
+         $notice_count = (int) $notices->count();
+         $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
+         $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
+         // Other fields Twitter sends...
+         $twitter_user['profile_background_color'] = '';
+         $twitter_user['profile_text_color'] = '';
+         $twitter_user['profile_link_color'] = '';
+         $twitter_user['profile_sidebar_fill_color'] = '';
+         $faves = DB_DataObject::factory('fave');
+         $faves->user_id = $user->id;
+         $faves_count = (int) $faves->count();
+         $twitter_user['favourites_count'] = $faves_count;
+         $timezone = 'UTC';
+         if ($user->timezone) {
+             $timezone = $user->timezone;
+         }
+         $t = new DateTime;
+         $t->setTimezone(new DateTimeZone($timezone));
+         $twitter_user['utc_offset'] = $t->format('Z');
+         $twitter_user['time_zone'] = $timezone;
+         $following = 'false';
+         if (isset($this->auth_user)) {
+             if ($this->auth_user->isSubscribed($profile)) {
+                 $following = 'true';
+             }
+             // Not implemented yet
+             $twitter_user['notifications'] = 'false';
+         }
+         $twitter_user['following'] = $following;
+         if ($apidata['content-type'] == 'xml') {
+             $this->init_document('xml');
+             $this->show_twitter_xml_user($twitter_user);
+             $this->end_document('xml');
+         } elseif ($apidata['content-type'] == 'json') {
+             $this->init_document('json');
+             $this->show_json_objects($twitter_user);
+             $this->end_document('json');
+         }
+     }
 -}
++}
index 6b845beae1d50b91db49c06c42d1cd782bb0cc05,51a9bbd7573629d74803b446611783880b6f1ca0..51a9bbd7573629d74803b446611783880b6f1ca0
mode 100755,100644..100755