]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.7.x' into 0.8.x
authorSarven Capadisli <csarven@controlyourself.ca>
Wed, 8 Apr 2009 22:55:13 +0000 (22:55 +0000)
committerSarven Capadisli <csarven@controlyourself.ca>
Wed, 8 Apr 2009 22:55:13 +0000 (22:55 +0000)
1  2 
README
lib/action.php
lib/twitter.php

diff --combined README
index 2053bb8d25fec093f96ad19536ff081e7ce24665,f18d9c7c16a2b706c86092076218714666ed396f..dcd5f6a953efc748769822d1b15d0ee64e4ab907
--- 1/README
--- 2/README
+++ b/README
@@@ -2,8 -2,8 +2,8 @@@
  README
  ------
  
- Laconica 0.7.2.1 ("Talk about the Passion")
11 March 2009
+ Laconica 0.7.3 ("You Are The Everything")
7 April 2009
  
  This is the README file for Laconica, the Open Source microblogging
  platform. It includes installation instructions, descriptions of
@@@ -71,93 -71,29 +71,29 @@@ for additional terms
  New this version
  ================
  
- This is a minor bug-fix and feature release since version 0.7.1,
- released Feb 9 2009. Notable changes this version:
- - First version of a web-based installer
- - Use Net_URL_Mapper instead of mod_rewrite to map "fancy URLs",
-   for a much simpler installation and use of PATH_INFO on sites
-   that don't have mod_rewrite.
- - A plugin framework for system events, to make it easier to build
-   server-side plugins.
- - A plugin for Google Analytics
- - A plugin to use blogspam.net to check notices for spam
- - A plugin to send linkbacks for notices about blog posts
- - Configurable check for duplicate notices in a specific time
-   period
- - Better Atom feeds
- - First implementation of Twitter Search API
- - Add streamlined mobile device-friendly styles when enabled in config.
- - A queue server for sending notices to Twitter
- - A queue server for sending notices to Facebook
- - A queue server for sending notices to a ping server
- - Fixed a bug in nonces for OAuth in OpenMicroBlogging
- - Fixed bugs in transfer of avatars in OpenMicroBlogging
- - @-links go to permalinks for local users
- - Better handling of DB errors (instead of dreaded DB_DataObject blank
-   screen)
- - Initial version of an RPM spec file
- - More consistent display of notices in notice search
- - A stylesheet for printed output
- - "Social graph" methods for Twitter API
- - Documentation for the JavaScript badge
- - Debugged a ton of problems that happened with E_NOTICE on
- - Better caching in RSS feeds
- - Optionally send email when an @-message is received
- - Automatically add tags for every group message
- - Add framebusting JavaScript to help avoid clickjacking attacks.
- - Optionally ignore some notice sources for public page.
- - Add default SMS carriers and notice sources to distribution file.
- - Change titles to use mixed case instead of all uppercase.
- - Use exceptions for error handling.
- Changes in version 0.7.1:
- - Vast improvement in auto-linking to URLs.
- - Link to group search from user's group page
- - Improved interface in Facebook application
- - Fix bad redirects in delete notice
- - Updated PostgreSQL database creation script
- - Show filesize in avatar/logo upload
- - Vastly improved avatar/logo upload
- - Allow re-authentication with OpenID
- - Correctly link hashtabs inside parens and brackets
- - Group and avatar image transparency works
- - Better handling of commands through the Web and Ajax channels
- - Fix links for profile page feeds
- - Fixed destroy method in API
- - Fix endpoint of Connect menu when XMPP disabled
- - Show number of group members
- - Enable configuration files in /etc/laconica/
- Changes in version 0.7.0:
- - Support for groups. Users can join groups and send themed notices
-   to those groups. All other members of the group receive the notices.
- - Laconica-specific extensions to the Twitter API.
- - A Facebook application.
- - A massive UI redesign. The HTML generated by Laconica has changed
-   significantly, to make theming easier and to give a more open look
-   by default. Also, sidebar.
- - Massive code hygiene changes to move towards compliance with the PEAR
-   coding standards and to support the new UI redesign.
- - Began the breakup of util.php -- moved about 30% of code to a views
-   hierarchy.
- - UI elements for statistical information (like top posters or most
-   popular groups) added in a sidebar.
- - include Javascript badge by Kent Brewster.
- - Updated online documentation.
- - Cropping of user avatars using Jcrop.
- - fix for Twitter bridge to not send "Expect:" headers.
- - add 'dm' as a synonym for 'd' in commands.
- - Upgrade upstream version of jQuery to 1.3.
- - Upgrade upstream version of PHP-OpenID to 2.1.2.
- - Move OpenMicroBlogging specification to its own repository.
- - Make tag-based RSS streams work.
- - Additional locales: Bulgarian, Catalan, Greek, Hebrew, simplified
-   Chinese, Telugu, Taiwanese Chinese, Vietnamese,
- - PostgreSQL updates.
- - Nasty bug in Twitter bridge that wouldn't verify with Twitter
+ This is a minor bug-fix and feature release since version 0.7.2.1,
+ released Mar 11 2009. Notable changes this version:
+ - A plugin to allow a templating language for customization
+ - A plugin for Piwik Analytics engine
+ - A bookmarklet for posting a notice about a Web page you're reading
+ - A welcome notice ('welcomebot') and default subscription for new users
+ - Support for SSL for some or all pages on the site
+ - Better handling of empty notice lists on many pages
+ - Major improvements to the Twitter friend-sync offline processing
+ - subscribers, subscriptions, groups are listed on the Personal page.
+ - "Invite" link restored to main menu
+ - Better memory handling in FOAF output
+ - Fix for SUP support (FriendFeed)
+ - Correct and intelligent redirect HTTP status codes
+ - Fix DB collations for search and sort
+ - Better H1s and Titles using user full names
+ - Fixes to make the linkback plugin operational
+ - Better indication that a notice is being published by Ajax (spinner)
+ - Better and unified Atom output
+ - Hiding "register" and "join now" messages when site is closed
+ - ping, twitter and facebook queuehandlers working better
+ - Updated RPM spec
  
  Prerequisites
  =============
@@@ -240,10 -176,6 +176,10 @@@ and the URLs are listed here for your c
    version may render your Laconica site unable to send or receive XMPP
    messages.
  - Facebook library. Used for the Facebook application.
 +- PEAR Services_oEmbed. Used for some multimedia integration.
 +- PEAR HTTP_Request is an oEmbed dependency.
 +- PEAR Validat is an oEmbed dependency.e
 +- PEAR Net_URL is an oEmbed dependency.2
  
  A design goal of Laconica is that the basic Web functionality should
  work on even the most restrictive commercial hosting services.
@@@ -261,9 -193,9 +197,9 @@@ especially if you've previously install
  1. Unpack the tarball you downloaded on your Web server. Usually a
     command like this will work:
  
-          tar zxf laconica-0.7.2.1.tar.gz
+          tar zxf laconica-0.7.3.tar.gz
  
-    ...which will make a laconica-0.7.2.1 subdirectory in your current
+    ...which will make a laconica-0.7.3 subdirectory in your current
     directory. (If you don't have shell access on your Web server, you
     may have to unpack the tarball on your local computer and FTP the
     files to the server.)
  2. Move the tarball to a directory of your choosing in your Web root
     directory. Usually something like this will work:
  
-          mv laconica-0.7.2.1 /var/www/mublog
+          mv laconica-0.7.3 /var/www/mublog
  
     This will make your Laconica instance available in the mublog path of
     your server, like "http://example.net/mublog". "microblog" or
@@@ -761,7 -693,7 +697,7 @@@ Upgradin
  If you've been using Laconica 0.6, 0.5 or lower, or if you've been
  tracking the "git" version of the software, you will probably want
  to upgrade and keep your existing data. There is no automated upgrade
- procedure in Laconica 0.7.2.1. Try these step-by-step instructions; read
+ procedure in Laconica 0.7.3. Try these step-by-step instructions; read
  to the end first before trying them.
  
  0. Download Laconica and set up all the prerequisites as if you were
@@@ -1209,7 -1141,7 +1145,7 @@@ repository (see below), and you get a c
  T_STRING") in the browser, check to see that you don't have any
  conflicts in your code.
  
- If you upgraded to Laconica 0.7.2.1 without reading the "Notice inboxes"
+ If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes"
  section above, and all your users' 'Personal' tabs are empty, read the
  "Notice inboxes" section above.
  
@@@ -1298,6 -1230,8 +1234,8 @@@ if anyone's been overlooked in error
  * Leslie Michael Orchard
  * Eric Helgeson
  * Ken Sedgwick
+ * Brian Hendrickson
+ * Tobias Diekershoff
  
  Thanks also to the developers of our upstream library code and to the
  thousands of people who have tried out Identi.ca, installed Laconi.ca,
diff --combined lib/action.php
index 75531d34b51a308bec75e393445ddcd9fbac452f,cc98d4445a4b6c1d95b602b50677c14c28d53cd8..f2027e0f17c68daef94a50f04ec074907ed4cf35
@@@ -97,9 -97,18 +97,18 @@@ class Action extends HTMLOutputter // l
              $this->startHTML();
              Event::handle('EndShowHTML', array($this));
          }
+         if (Event::handle('StartShowHead', array($this))) {
          $this->showHead();
+             Event::handle('EndShowHead', array($this));
+         }
+         if (Event::handle('StartShowBody', array($this))) {
          $this->showBody();
+             Event::handle('EndShowBody', array($this));
+         }
+         if (Event::handle('StartEndHTML', array($this))) {
          $this->endHTML();
+             Event::handle('EndEndHTML', array($this));
+         }
      }
  
      /**
              if ($user) {
                  $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
                                  _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
-             }
-             $this->menuItem(common_local_url('peoplesearch'),
-                             _('Search'), _('Search for people or text'), false, 'nav_search');
-             if ($user) {
                  $this->menuItem(common_local_url('profilesettings'),
                                  _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
                  if (common_config('xmpp', 'enabled')) {
                      $this->menuItem(common_local_url('imsettings'),
                                      _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
                      $this->menuItem(common_local_url('smssettings'),
                                      _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
                  }
+                 $this->menuItem(common_local_url('invite'),
+                                  _('Invite'),
+                                  sprintf(_('Invite friends and colleagues to join you on %s'),
+                                  common_config('site', 'name')),
+                                  false, 'nav_invitecontact');
                  $this->menuItem(common_local_url('logout'),
                                  _('Logout'), _('Logout from the site'), false, 'nav_logout');
-             } else {
-                 $this->menuItem(common_local_url('login'),
-                                 _('Login'), _('Login to the site'), false, 'nav_login');
+             }
+             else {
                  if (!common_config('site', 'closed')) {
                      $this->menuItem(common_local_url('register'),
                                      _('Register'), _('Create an account'), false, 'nav_register');
                  }
                  $this->menuItem(common_local_url('openidlogin'),
                                  _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
+                 $this->menuItem(common_local_url('login'),
+                                 _('Login'), _('Login to the site'), false, 'nav_login');
              }
              $this->menuItem(common_local_url('doc', array('title' => 'help')),
                              _('Help'), _('Help me!'), false, 'nav_help');
+             $this->menuItem(common_local_url('peoplesearch'),
+                             _('Search'), _('Search for people or text'), false, 'nav_search');
              Event::handle('EndPrimaryNav', array($this));
          }
          $this->elementEnd('ul');
      {
          $this->elementStart('div', array('id' => 'aside_primary',
                                           'class' => 'aside'));
+         if (Event::handle('StartShowExportData', array($this))) {
          $this->showExportData();
+             Event::handle('EndShowExportData', array($this));
+         }
          if (Event::handle('StartShowSections', array($this))) {
              $this->showSections();
              Event::handle('EndShowSections', array($this));
          }
          if ($lm) {
              header('Last-Modified: ' . date(DATE_RFC1123, $lm));
 -            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
 -                $ims = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
 +            if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
 +                $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
 +                $ims = strtotime($if_modified_since);
                  if ($lm <= $ims) {
 -                    $if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
 +                    $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
 +                      $_SERVER['HTTP_IF_NONE_MATCH'] : null;
                      if (!$if_none_match ||
                          !$etag ||
                          $this->_hasEtag($etag, $if_none_match)) {
       *
       * @return string current URL
       */
      function selfUrl()
      {
          $action = $this->trimmed('action');
          $args   = $this->args;
          unset($args['action']);
+         if (array_key_exists('submit', $args)) {
+             unset($args['submit']);
+         }
          foreach (array_keys($_COOKIE) as $cookie) {
              unset($args[$cookie]);
          }
diff --combined lib/twitter.php
index db2092210bd6520e8a88255616b7cece305671e0,ccc6c93cae00730100cdd984b918ba571891be10..c1d0dc254d8c99dc7ff8bc6a730644ee44e53097
@@@ -19,7 -19,7 +19,7 @@@
  
  if (!defined('LACONICA')) { exit(1); }
  
- define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1
+ define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
  
  function get_twitter_data($uri, $screen_name, $password)
  {
      if ($errmsg) {
          common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
              __FILE__);
+         if (defined('SCRIPT_DEBUG')) {
+             print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
+         }
      }
  
      curl_close($ch);
      return $data;
  }
  
- function twitter_user_info($screen_name, $password)
+ function twitter_json_data($uri, $screen_name, $password)
  {
+     $json_data = get_twitter_data($uri, $screen_name, $password);
  
-     $uri = "http://twitter.com/users/show/$screen_name.json";
-     $data = get_twitter_data($uri, $screen_name, $password);
-     if (!$data) {
+     if (!$json_data) {
          return false;
      }
  
-     $twit_user = json_decode($data);
+     $data = json_decode($json_data);
  
-     if (!$twit_user) {
+     if (!$data) {
          return false;
      }
  
-     return $twit_user;
+     return $data;
  }
  
- function update_twitter_user($fuser, $twitter_id, $screen_name)
+ function twitter_user_info($screen_name, $password)
  {
+     $uri = "http://twitter.com/users/show/$screen_name.json";
+     return twitter_json_data($uri, $screen_name, $password);
+ }
  
-     $original = clone($fuser);
-     $fuser->nickname = $screen_name;
-     $fuser->uri = 'http://twitter.com/' . $screen_name;
-     $result = $fuser->updateKeys($original);
+ function twitter_friends_ids($screen_name, $password)
+ {
+     $uri = "http://twitter.com/friends/ids/$screen_name.json";
+     return twitter_json_data($uri, $screen_name, $password);
+ }
+ function update_twitter_user($twitter_id, $screen_name)
+ {
+     $uri = 'http://twitter.com/' . $screen_name;
+     $fuser = new Foreign_user();
+     $fuser->query('BEGIN');
+     // Dropping down to SQL because regular db_object udpate stuff doesn't seem
+     // to work so good with tables that have multiple column primary keys
+     // Any time we update the uri for a forein user we have to make sure there
+     // are no dupe entries first -- unique constraint on the uri column
+     $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
+     $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
+     $result = $fuser->query($qry);
+     if ($result) {
+         common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
+         if (defined('SCRIPT_DEBUG')) {
+             print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
+         }
+     }
+     // Update the user
+     $qry = 'UPDATE foreign_user SET nickname = ';
+     $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
+     $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
+     $result = $fuser->query($qry);
  
      if (!$result) {
+         common_log(LOG_WARNING,
+             "Couldn't update foreign_user data for Twitter user: $screen_name");
          common_log_db_error($fuser, 'UPDATE', __FILE__);
+         if (defined('SCRIPT_DEBUG')) {
+             print "UPDATE failed: for Twitter user:  $twitter_id - $screen_name. - ";
+             print common_log_objstring($fuser) . "\n";
+             $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+             print "DB_DataObject Error: " . $error->getMessage() . "\n";
+         }
          return false;
      }
  
+     $fuser->query('COMMIT');
+     $fuser->free();
+     unset($fuser);
      return true;
  }
  
  function add_twitter_user($twitter_id, $screen_name)
  {
  
+     $new_uri = 'http://twitter.com/' . $screen_name;
+     // Clear out any bad old foreign_users with the new user's legit URL
+     // This can happen when users move around or fakester accounts get
+     // repoed, and things like that.
+     $luser = new Foreign_user();
+     $luser->uri = $new_uri;
+     $luser->service = TWITTER_SERVICE;
+     $result = $luser->delete();
+     if ($result) {
+         common_log(LOG_WARNING,
+             "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
+         if (defined('SCRIPT_DEBUG')) {
+             print "Removed invalid Twitter user squatting on uri: $new_uri\n";
+         }
+     }
+     $luser->free();
+     unset($luser);
      // Otherwise, create a new Twitter user
-     $fuser = DB_DataObject::factory('foreign_user');
+     $fuser = new Foreign_user();
  
      $fuser->nickname = $screen_name;
      $fuser->uri = 'http://twitter.com/' . $screen_name;
      $fuser->id = $twitter_id;
-     $fuser->service = TWITTER_SERVICE; // Twitter
+     $fuser->service = TWITTER_SERVICE;
      $fuser->created = common_sql_now();
      $result = $fuser->insert();
  
      if (!$result) {
-         common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+         common_log(LOG_WARNING,
+             "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
          common_log_db_error($fuser, 'INSERT', __FILE__);
-         return false;
+         if (defined('SCRIPT_DEBUG')) {
+             print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
+             print common_log_objstring($fuser) . "\n";
+             $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+             print "DB_DataObject Error: " . $error->getMessage() . "\n";
+         }
+     } else {
+         common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+         if (defined('SCRIPT_DEBUG')) {
+             print "Added new Twitter user: $screen_name ($twitter_id).\n";
+         }
      }
  
-     common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
-     return true;
+     return $result;
  }
  
  // Creates or Updates a Twitter user
@@@ -117,53 -199,87 +199,87 @@@ function save_twitter_user($twitter_id
  
      // Check to see whether the Twitter user is already in the system,
      // and update its screen name and uri if so.
-     $fuser = Foreign_user::getForeignUser($twitter_id, 1);
+     $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
  
      if ($fuser) {
  
+         $result = true;
          // Only update if Twitter screen name has changed
          if ($fuser->nickname != $screen_name) {
+             $result = update_twitter_user($twitter_id, $screen_name);
  
              common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
                  "$fuser->id to $screen_name, was $fuser->nickname");
  
-             return update_twitter_user($fuser, $twitter_id, $screen_name);
+             if (defined('SCRIPT_DEBUG')) {
+                 print 'Updated nickname (and URI) for Twitter user ' .
+                     "$fuser->id to $screen_name, was $fuser->nickname\n";
+             }
          }
  
+         return $result;
      } else {
          return add_twitter_user($twitter_id, $screen_name);
      }
  
+     $fuser->free();
+     unset($fuser);
      return true;
  }
  
  function retreive_twitter_friends($twitter_id, $screen_name, $password)
  {
+     $friends = array();
  
      $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
-     $twitter_user = twitter_user_info($screen_name, $password);
+     $friends_ids = twitter_friends_ids($screen_name, $password);
+     if (!$friends_ids) {
+         return $friends;
+     }
+     if (defined('SCRIPT_DEBUG')) {
+         print "Twitter 'social graph' ids method says $screen_name has " .
+             count($friends_ids) . " friends.\n";
+     }
  
      // Calculate how many pages to get...
-     $pages = ceil($twitter_user->friends_count / 100);
+     $pages = ceil(count($friends_ids) / 100);
  
      if ($pages == 0) {
-         common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
+         common_log(LOG_WARNING,
+             "Twitter bridge - $screen_name seems to have no friends.");
+         if (defined('SCRIPT_DEBUG')) {
+             print "$screen_name seems to have no friends.\n";
+         }
      }
  
-     $friends = array();
      for ($i = 1; $i <= $pages; $i++) {
  
          $data = get_twitter_data($uri . $i, $screen_name, $password);
  
          if (!$data) {
-             return null;
+             common_log(LOG_WARNING,
+                 "Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
+             if (defined('SCRIPT_DEBUG')) {
+                 print "Couldn't retrieve page $i of $screen_name's friends.\n";
+             }
+             continue;
          }
  
          $more_friends = json_decode($data);
  
          if (!$more_friends) {
-             return null;
+             common_log(LOG_WARNING,
+                 "Twitter bridge - No data for page $i of $screen_name's friends.");
+             if (defined('SCRIPT_DEBUG')) {
+                 print "No data for page $i of $screen_name's friends.\n";
+             }
+             continue;
          }
  
           $friends = array_merge($friends, $more_friends);
@@@ -177,19 -293,27 +293,27 @@@ function save_twitter_friends($user, $t
  
      $friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
  
-     if (is_null($friends)) {
-         common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
+     if (empty($friends)) {
+         common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
+         if (defined('SCRIPT_DEBUG')) {
+             print "Couldn't get friends data from Twitter for $screen_name.\n";
+         }
          return false;
      }
  
      foreach ($friends as $friend) {
  
          $friend_name = $friend->screen_name;
-         $friend_id = $friend->id;
+         $friend_id = (int) $friend->id;
  
          // Update or create the Foreign_user record
          if (!save_twitter_user($friend_id, $friend_name)) {
-             return false;
+             common_log(LOG_WARNING,
+                 "Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
+             if (defined('SCRIPT_DEBUG')) {
+                 print "Couldn't save $screen_name's friend, $friend_name.\n";
+             }
+             continue;
          }
  
          // Check to see if there's a related local user
  
              // Get associated user and subscribe her
              $friend_user = User::staticGet('id', $flink->user_id);
-             subs_subscribe_to($user, $friend_user);
-             common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
+             if (!empty($friend_user)) {
+                 $result = subs_subscribe_to($user, $friend_user);
+                 if ($result === true) {
+                     common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
+                     if (defined('SCRIPT_DEBUG')) {
+                         print("Subscribed $friend_user->nickname to $user->nickname.\n");
+                     }
+                 } else {
+                     if (defined('SCRIPT_DEBUG')) {
+                         print "$result ($friend_user->nickname to $user->nickname)\n";
+                     }
+                 }
+             }
          }
      }
  
  function is_twitter_bound($notice, $flink) {
  
      // Check to see if notice should go to Twitter
 -    if ($flink->noticesync & FOREIGN_NOTICE_SEND) {
 +    if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
  
          // If it's not a Twitter-style reply, or if the user WANTS to send replies.
          if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
@@@ -295,4 -431,3 +431,3 @@@ function broadcast_twitter($notice
  
      return $success;
  }