]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'sgmurphy-clone/0.7.x' into 0.7.x
authorEvan Prodromou <evan@controlyourself.ca>
Mon, 9 Feb 2009 17:03:45 +0000 (12:03 -0500)
committerEvan Prodromou <evan@controlyourself.ca>
Mon, 9 Feb 2009 17:03:45 +0000 (12:03 -0500)
22 files changed:
EVENTS.txt [new file with mode: 0644]
actions/editgroup.php
actions/facebookinvite.php
actions/finishopenidlogin.php
actions/newgroup.php
actions/peopletag.php
actions/profilesettings.php
actions/register.php
actions/twitapiaccount.php
actions/updateprofile.php
actions/userauthorization.php
config.php.sample
index.php
lib/action.php
lib/clientexception.php [new file with mode: 0644]
lib/common.php
lib/event.php [new file with mode: 0644]
lib/plugin.php [new file with mode: 0644]
lib/serverexception.php [new file with mode: 0644]
lib/util.php
plugins/GoogleAnalyticsPlugin.php [new file with mode: 0644]
scripts/update_facebook.php

diff --git a/EVENTS.txt b/EVENTS.txt
new file mode 100644 (file)
index 0000000..4b8260b
--- /dev/null
@@ -0,0 +1,36 @@
+InitializePlugin: a chance to initialize a plugin in a complete
+                 environment
+
+CleanupPlugin: a chance to cleanup a plugin at the end of a program
+
+StartPrimaryNav: Showing the primary nav menu
+- $action: the current action
+
+EndPrimaryNav: At the end of the primary nav menu
+- $action: the current action
+
+StartSecondaryNav: Showing the secondary nav menu
+- $action: the current action
+
+EndSecondaryNav: At the end of the secondary nav menu
+- $action: the current action
+
+StartShowScripts: Showing JavaScript links
+- $action: the current action
+
+EndShowScripts: End showing JavaScript links; good place to add custom
+               links like Google Analytics
+- $action: the current action
+
+StartShowJQueryScripts: Showing JQuery script links (use this to link to e.g. Google mirrors)
+- $action: the current action
+
+EndShowJQueryScripts: End showing JQuery script links
+- $action: the current action
+
+StartShowLaconicaScripts: Showing Laconica script links (use this to link to a CDN or something)
+- $action: the current action
+
+EndShowLaconicaScripts: End showing Laconica script links
+- $action: the current action
+
index 98ebcb87acf541b90b7c44006a191af3a509afc4..e7e79040a4811622473f1296e52da72b90420bcc 100644 (file)
@@ -191,13 +191,13 @@ class EditgroupAction extends Action
                                         array('http', 'https')))) {
             $this->showForm(_('Homepage is not a valid URL.'));
             return;
-        } else if (!is_null($fullname) && strlen($fullname) > 255) {
+        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($description) && strlen($description) > 140) {
+        } else if (!is_null($description) && mb_strlen($description) > 140) {
             $this->showForm(_('description is too long (max 140 chars).'));
             return;
-        } else if (!is_null($location) && strlen($location) > 255) {
+        } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
             return;
         }
index 3c872f94bf2901fd5e13d3a17fa2d8fc6d82e11f..1302064ad7fc6c8b33f540c3c8eb4df06beaccc3 100644 (file)
@@ -71,13 +71,13 @@ class FacebookinviteAction extends FacebookAction
             common_config('site', 'name')));
         $this->element('p', null, _('Invitations have been sent to the following users:'));
 
-        $friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to acces the list?
+        $friend_ids = $_POST['ids']; // XXX: Hmm... is this the best way to access the list?
 
         $this->elementStart('ul', array('id' => 'facebook-friends'));
 
         foreach ($friend_ids as $friend) {
             $this->elementStart('li');
-            $this->element('fb:profile-pic', array('uid' => $friend));
+            $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
             $this->element('fb:name', array('uid' => $friend,
                                             'capitalize' => 'true'));
             $this->elementEnd('li');
@@ -92,6 +92,12 @@ class FacebookinviteAction extends FacebookAction
 
         // Get a list of users who are already using the app for exclusion
         $exclude_ids = $this->facebook->api_client->friends_getAppUsers();
+        $exclude_ids_csv = null;
+        
+        // fbml needs these as a csv string, not an array
+        if ($exclude_ids) {
+            $exclude_ids_csv = implode(',', $exclude_ids);
+        }
 
         $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) .
             htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
@@ -103,10 +109,17 @@ class FacebookinviteAction extends FacebookAction
                                                       'content' => $content));
         $this->hidden('invite', 'true');
         $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name'));
-        $this->element('fb:multi-friend-selector', array('showborder' => 'false',
-                                                               'actiontext' => $actiontext,
-                                                               'exclude_ids' => implode(',', $exclude_ids),
-                                                               'bypass' => 'cancel'));
+        
+        $multi_params = array('showborder' => 'false');    
+        $multi_params['actiontext'] = $actiontext;
+        
+        if ($exclude_ids_csv) {
+            $multi_params['exclude_ids'] = $exclude_ids_csv;
+        }
+
+        $multi_params['bypass'] = 'cancel';
+                
+        $this->element('fb:multi-friend-selector', $multi_params);
 
         $this->elementEnd('fb:request-form');
 
index bc91511207653fd2e0ad1fa910afeba632ece3ae..1e7b73a7f3a43b756ec787bb66e430b1f916b8a1 100644 (file)
@@ -242,7 +242,7 @@ class FinishopenidloginAction extends Action
             }
         }
 
-        if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+        if ($sreg['fullname'] && mb_strlen($sreg['fullname']) <= 255) {
             $fullname = $sreg['fullname'];
         }
 
index 42fd380dfe51ce2a2670474f8c5d9953739aa832..cbd8dfeec53ff46fc10ceddccbee5a1cc0825418 100644 (file)
@@ -142,13 +142,13 @@ class NewgroupAction extends Action
                                         array('http', 'https')))) {
             $this->showForm(_('Homepage is not a valid URL.'));
             return;
-        } else if (!is_null($fullname) && strlen($fullname) > 255) {
+        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($description) && strlen($description) > 140) {
+        } else if (!is_null($description) && mb_strlen($description) > 140) {
             $this->showForm(_('description is too long (max 140 chars).'));
             return;
-        } else if (!is_null($location) && strlen($location) > 255) {
+        } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
             return;
         }
index 3578c53fdf0d8d7f473b79ab382ba5253768d90e..6b1e34f1ab47db2b1c2aa4d2f273171233ed23a2 100644 (file)
@@ -1,9 +1,12 @@
 <?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+/**
+ * Laconica, the distributed open-source microblogging tool
  *
- * This program is free software: you can redistribute it and/or modify
+ * Action for showing profiles self-tagged with a given tag
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * 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  Action
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @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); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once INSTALLDIR.'/lib/profilelist.php';
 
+/**
+ * This class outputs a paginated list of profiles self-tagged with a given tag
+ *
+ * @category Output
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Zach Copley <zach@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/
+ *
+ * @see      Action
+ */
+
 class PeopletagAction extends Action
 {
-    
-    var $tag = null;
+
+    var $tag  = null;
     var $page = null;
-        
-    function handle($args)
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
     {
-        parent::handle($args);    
-        
-        parent::prepare($args);
+        parent::prepare($argarray);
 
         $this->tag = $this->trimmed('tag');
 
         if (!common_valid_profile_tag($this->tag)) {
-            $this->clientError(sprintf(_('Not a valid people tag: %s'), $this->tag));
+            $this->clientError(sprintf(_('Not a valid people tag: %s'),
+                $this->tag));
             return;
         }
 
-        $this->page = $this->trimmed('page');
+        $this->page = ($this->arg('page')) ? $this->arg('page') : 1;
 
-        if (!$this->page) {
-            $this->page = 1;
-        }
-        
+        common_set_returnto($this->selfUrl());
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return boolean is read only action?
+     */
+    function handle($argarray)
+    {
+        parent::handle($argarray);
         $this->showPage();
     }
-    
+
+    /**
+     * Whips up a query to get a list of profiles based on the provided
+     * people tag and page, initalizes a ProfileList widget, and displays
+     * it to the user.
+     *
+     * @return nothing
+     */
     function showContent()
     {
-        
+
         $profile = new Profile();
 
-        $offset = ($page-1)*PROFILES_PER_PAGE;
-        $limit = PROFILES_PER_PAGE + 1;
-        
-        if (common_config('db','type') == 'pgsql') {
+        $offset = ($this->page - 1) * PROFILES_PER_PAGE;
+        $limit  = PROFILES_PER_PAGE + 1;
+
+        if (common_config('db', 'type') == 'pgsql') {
             $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
         } else {
             $lim = ' LIMIT ' . $offset . ', ' . $limit;
         }
 
-        # XXX: memcached this
-        
+        // XXX: memcached this
+
         $qry =  'SELECT profile.* ' .
                 'FROM profile JOIN profile_tag ' .
                 'ON profile.id = profile_tag.tagger ' .
                 'WHERE profile_tag.tagger = profile_tag.tagged ' .
                 'AND tag = "%s" ' .
-                'ORDER BY profile_tag.modified DESC';
-        
+                'ORDER BY profile_tag.modified DESC%s';
+
         $profile->query(sprintf($qry, $this->tag, $lim));
 
-        $pl = new ProfileList($profile, null, $this);
+        $pl  = new ProfileList($profile, null, $this);
         $cnt = $pl->show();
-                
+
         $this->pagination($this->page > 1,
                           $cnt > PROFILES_PER_PAGE,
                           $this->page,
-                          $this->trimmed('action'),
+                          'peopletag',
                           array('tag' => $this->tag));
     }
-    
-    function title() 
+
+    /**
+     * Returns the page title
+     *
+     * @return string page title
+     */
+    function title()
     {
-        return sprintf( _('Users self-tagged with %s - page %d'), $this->tag, $this->page);
+        return sprintf(_('Users self-tagged with %s - page %d'),
+            $this->tag, $this->page);
     }
-    
+
 }
index 82e6c3c82f78cb27b18d13a567166b70f4ca39bc..60f7c0796e2c64246f9064c25db02d70e4361016 100644 (file)
@@ -198,13 +198,13 @@ class ProfilesettingsAction extends AccountSettingsAction
                    !Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) {
             $this->showForm(_('Homepage is not a valid URL.'));
             return;
-        } else if (!is_null($fullname) && strlen($fullname) > 255) {
+        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($bio) && strlen($bio) > 140) {
+        } else if (!is_null($bio) && mb_strlen($bio) > 140) {
             $this->showForm(_('Bio is too long (max 140 chars).'));
             return;
-        } else if (!is_null($location) && strlen($location) > 255) {
+        } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
             return;
         }  else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) {
index 01d94f488421760861c9f2712565b5e9b7f21ef7..5d7a8ce690d5fa88655f2a6c0f5fa2596c6c478e 100644 (file)
@@ -167,13 +167,13 @@ class RegisterAction extends Action
                                         array('http', 'https')))) {
             $this->showForm(_('Homepage is not a valid URL.'));
             return;
-        } else if (!is_null($fullname) && strlen($fullname) > 255) {
+        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($bio) && strlen($bio) > 140) {
+        } else if (!is_null($bio) && mb_strlen($bio) > 140) {
             $this->showForm(_('Bio is too long (max 140 chars).'));
             return;
-        } else if (!is_null($location) && strlen($location) > 255) {
+        } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
             return;
         } else if (strlen($password) < 6) {
index dc8e2e798b5d268f9f76529acbe7a5debb4ac1fd..b7c09cc9dc7a3d8c3c7fbd9455e2f411613b673b 100644 (file)
@@ -56,7 +56,7 @@ class TwitapiaccountAction extends TwitterapiAction
 
         $location = trim($this->arg('location'));
 
-        if (!is_null($location) && strlen($location) > 255) {
+        if (!is_null($location) && mb_strlen($location) > 255) {
 
             // 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']);
index c79112dace79a514a520814317bd0179f35c2779..898c535432655860c06284695fb4e4c8c200a358 100644 (file)
@@ -93,22 +93,22 @@ class UpdateprofileAction extends Action
         }
         # optional stuff
         $fullname = $req->get_parameter('omb_listenee_fullname');
-        if ($fullname && strlen($fullname) > 255) {
+        if ($fullname && mb_strlen($fullname) > 255) {
             $this->clientError(_("Full name is too long (max 255 chars)."));
             return false;
         }
         $homepage = $req->get_parameter('omb_listenee_homepage');
-        if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
+        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
             $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage));
             return false;
         }
         $bio = $req->get_parameter('omb_listenee_bio');
-        if ($bio && strlen($bio) > 140) {
+        if ($bio && mb_strlen($bio) > 140) {
             $this->clientError(_("Bio is too long (max 140 chars)."));
             return false;
         }
         $location = $req->get_parameter('omb_listenee_location');
-        if ($location && strlen($location) > 255) {
+        if ($location && mb_strlen($location) > 255) {
             $this->clientError(_("Location is too long (max 255 chars)."));
             return false;
         }
index 58fc96c0eb2e078b454001c58046838ec27423be..7455a41a6f1e26c08c02a4e315fdff56dd75e8de 100644 (file)
@@ -469,19 +469,19 @@ class UserauthorizationAction extends Action
         }
         # optional stuff
         $fullname = $req->get_parameter('omb_listenee_fullname');
-        if ($fullname && strlen($fullname) > 255) {
+        if ($fullname && mb_strlen($fullname) > 255) {
             throw new OAuthException("Full name '$fullname' too long.");
         }
         $homepage = $req->get_parameter('omb_listenee_homepage');
-        if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
+        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
             throw new OAuthException("Invalid homepage '$homepage'");
         }
         $bio = $req->get_parameter('omb_listenee_bio');
-        if ($bio && strlen($bio) > 140) {
+        if ($bio && mb_strlen($bio) > 140) {
             throw new OAuthException("Bio too long '$bio'");
         }
         $location = $req->get_parameter('omb_listenee_location');
-        if ($location && strlen($location) > 255) {
+        if ($location && mb_strlen($location) > 255) {
             throw new OAuthException("Location too long '$location'");
         }
         $avatar = $req->get_parameter('omb_listenee_avatar');
index db1a216635a71efe8e93d5551fbc52467fb0d34e..a2c5801f45c41995eafe64f1f8a2f40900dd087e 100644 (file)
@@ -142,3 +142,7 @@ $config['sphinx']['port'] = 3312;
 # config section for the built-in Facebook application
 #$config['facebook']['apikey'] = 'APIKEY';
 #$config['facebook']['secret'] = 'SECRET';
+
+# Add Google Analytics
+# require_once('plugins/GoogleAnalyticsPlugin.php');
+# $ga = new GoogleAnalyticsPlugin('your secret code');
index 387b642e2c6a2e8a81772bbbf8b29f36b1c1ffcc..e62d9469ab553924bc9d02aa24c5492bbf8b1a46 100644 (file)
--- a/index.php
+++ b/index.php
@@ -22,6 +22,8 @@ define('LACONICA', true);
 
 require_once INSTALLDIR . '/lib/common.php';
 
+// XXX: we need a little more structure in this script
+
 // get and cache current user
 
 $user = common_current_user();
@@ -45,16 +47,16 @@ if (!$user && common_config('site', 'private') &&
     common_redirect(common_local_url('login'));
 }
 
-$actionfile = INSTALLDIR."/actions/$action.php";
-
-if (file_exists($actionfile)) {
-
-    include_once $actionfile;
-
-    $action_class = ucfirst($action).'Action';
+$action_class = ucfirst($action).'Action';
 
+if (!class_exists($action_class)) {
+    $cac = new ClientErrorAction(_('Unknown action'), 404);
+    $cac->showPage();
+} else {
     $action_obj = new $action_class();
 
+    // XXX: find somewhere for this little block to live
+
     if ($config['db']['mirror'] && $action_obj->isReadOnly()) {
         if (is_array($config['db']['mirror'])) {
             // "load balancing", ha ha
@@ -66,9 +68,24 @@ if (file_exists($actionfile)) {
         }
         $config['db']['database'] = $mirror;
     }
-    if (call_user_func(array($action_obj, 'prepare'), $_REQUEST)) {
-        call_user_func(array($action_obj, 'handle'), $_REQUEST);
+
+    try {
+        if ($action_obj->prepare($_REQUEST)) {
+            $action_obj->handle($_REQUEST);
+        }
+    } catch (ClientException $cex) {
+        $cac = new ClientErrorAction($cex->getMessage(), $cex->getCode());
+        $cac->showPage();
+    } catch (ServerException $sex) { // snort snort guffaw
+        $sac = new ServerErrorAction($sex->getMessage(), $sex->getCode());
+        $sac->showPage();
+    } catch (Exception $ex) {
+        $sac = new ServerErrorAction($ex->getMessage());
+        $sac->showPage();
     }
-} else {
-    common_user_error(_('Unknown action'));
-}
\ No newline at end of file
+}
+
+// XXX: cleanup exit() calls or add an exit handler so
+// this always gets called
+
+Event::handle('CleanupPlugin');
index c4172ada1135293fd2ce5a5d7c3487cfb7184c8a..0628dc70db340f1c6457a340e96a71d905661a43 100644 (file)
@@ -179,18 +179,27 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showScripts()
     {
-        $this->element('script', array('type' => 'text/javascript',
-                                       'src' => common_path('js/jquery.min.js')),
-                       ' ');
-        $this->element('script', array('type' => 'text/javascript',
-                                       'src' => common_path('js/jquery.form.js')),
-                       ' ');
-        $this->element('script', array('type' => 'text/javascript',
-                                       'src' => common_path('js/xbImportNode.js')),
-                       ' ');
-        $this->element('script', array('type' => 'text/javascript',
-                                       'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
-                       ' ');
+        if (Event::handle('StartShowScripts', array($this))) {
+            if (Event::handle('StartShowJQueryScripts', array($this))) {
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/jquery.min.js')),
+                               ' ');
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/jquery.form.js')),
+                               ' ');
+                Event::handle('EndShowJQueryScripts', array($this));
+            }
+            if (Event::handle('StartShowLaconicaScripts', array($this))) {
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/xbImportNode.js')),
+                               ' ');
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
+                               ' ');
+                Event::handle('EndShowLaconicaScripts', array($this));
+            }
+            Event::handle('EndShowScripts', array($this));
+        }
     }
 
     /**
@@ -312,42 +321,46 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showPrimaryNav()
     {
+        $user = common_current_user();
+
         $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
         $this->element('dt', null, _('Primary site navigation'));
         $this->elementStart('dd');
-        $user = common_current_user();
         $this->elementStart('ul', array('class' => 'nav'));
-        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');
-            } else {
-                $this->menuItem(common_local_url('smssettings'),
-                            _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
+        if (Event::handle('StartPrimaryNav', 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('logout'),
-                            _('Logout'), _('Logout from the site'), false, 'nav_logout');
-        } else {
-            $this->menuItem(common_local_url('login'),
-                            _('Login'), _('Login to the site'), false, 'nav_login');
-            if (!common_config('site', 'closed')) {
-                $this->menuItem(common_local_url('register'),
-                                _('Register'), _('Create an account'), false, 'nav_register');
+            $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');
+                } else {
+                    $this->menuItem(common_local_url('smssettings'),
+                                    _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
+                }
+                $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');
+                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('openidlogin'),
-                            _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
+            $this->menuItem(common_local_url('doc', array('title' => 'help')),
+                            _('Help'), _('Help me!'), false, 'nav_help');
+            Event::handle('EndPrimaryNav', array($this));
         }
-        $this->menuItem(common_local_url('doc', array('title' => 'help')),
-                        _('Help'), _('Help me!'), false, 'nav_help');
         $this->elementEnd('ul');
         $this->elementEnd('dd');
         $this->elementEnd('dl');
@@ -570,18 +583,21 @@ class Action extends HTMLOutputter // lawsuit
         $this->element('dt', null, _('Secondary site navigation'));
         $this->elementStart('dd', null);
         $this->elementStart('ul', array('class' => 'nav'));
-        $this->menuItem(common_local_url('doc', array('title' => 'help')),
-                        _('Help'));
-        $this->menuItem(common_local_url('doc', array('title' => 'about')),
-                        _('About'));
-        $this->menuItem(common_local_url('doc', array('title' => 'faq')),
-                        _('FAQ'));
-        $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
-                        _('Privacy'));
-        $this->menuItem(common_local_url('doc', array('title' => 'source')),
-                        _('Source'));
-        $this->menuItem(common_local_url('doc', array('title' => 'contact')),
-                        _('Contact'));
+        if (Event::handle('StartSecondaryNav', array($this))) {
+            $this->menuItem(common_local_url('doc', array('title' => 'help')),
+                            _('Help'));
+            $this->menuItem(common_local_url('doc', array('title' => 'about')),
+                            _('About'));
+            $this->menuItem(common_local_url('doc', array('title' => 'faq')),
+                            _('FAQ'));
+            $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
+                            _('Privacy'));
+            $this->menuItem(common_local_url('doc', array('title' => 'source')),
+                            _('Source'));
+            $this->menuItem(common_local_url('doc', array('title' => 'contact')),
+                            _('Contact'));
+            Event::handle('EndSecondaryNav', array($this));
+        }
         $this->elementEnd('ul');
         $this->elementEnd('dd');
         $this->elementEnd('dl');
@@ -789,11 +805,12 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
+
     function serverError($msg, $code=500)
     {
         $action = $this->trimmed('action');
         common_debug("Server error '$code' on '$action': $msg", __FILE__);
-        common_server_error($msg, $code);
+        throw new ServerException($msg, $code);
     }
 
     /**
@@ -804,11 +821,12 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
+
     function clientError($msg, $code=400)
     {
         $action = $this->trimmed('action');
         common_debug("User error '$code' on '$action': $msg", __FILE__);
-        common_user_error($msg, $code);
+        throw new ClientException($msg, $code);
     }
 
     /**
diff --git a/lib/clientexception.php b/lib/clientexception.php
new file mode 100644 (file)
index 0000000..3020d7f
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * class for a client exception (user error)
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Exception
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @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);
+}
+
+/**
+ * Class for client exceptions
+ *
+ * Subclass of PHP Exception for user errors.
+ *
+ * @category Exception
+ * @package  Laconica
+ * @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/
+ */
+
+class ClientException extends Exception
+{
+    public function __construct($message = null, $code = 400) {
+        parent::__construct($message, $code);
+    }
+
+    // custom string representation of object
+    public function __toString() {
+        return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
+    }
+}
index 5b4e3c40c87d12173ed1058eccaf0c55c56b1765..7bfd14c4291517b9f0d31663dc0e18381ebfc5f8 100644 (file)
@@ -49,6 +49,12 @@ require_once('DB/DataObject/Cast.php'); # for dates
 
 require_once(INSTALLDIR.'/lib/language.php');
 
+// This gets included before the config file, so that admin code and plugins
+// can use it
+
+require_once(INSTALLDIR.'/lib/event.php');
+require_once(INSTALLDIR.'/lib/plugin.php');
+
 // try to figure out where we are
 
 $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
@@ -177,6 +183,8 @@ foreach ($_config_files as $_config_file) {
     }
 }
 
+// XXX: how many of these could be auto-loaded on use?
+
 require_once('Validate.php');
 require_once('markdown.php');
 
@@ -188,6 +196,9 @@ require_once(INSTALLDIR.'/lib/subs.php');
 require_once(INSTALLDIR.'/lib/Shorturl_api.php');
 require_once(INSTALLDIR.'/lib/twitter.php');
 
+require_once(INSTALLDIR.'/lib/clientexception.php');
+require_once(INSTALLDIR.'/lib/serverexception.php');
+
 // XXX: other formats here
 
 define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
@@ -200,5 +211,12 @@ function __autoload($class)
         require_once(INSTALLDIR.'/classes/' . $class . '.php');
     } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) {
         require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php');
+    } else if (mb_substr($class, -6) == 'Action' &&
+               file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php')) {
+        require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php');
     }
 }
+
+// Give plugins a chance to initialize in a fully-prepared environment
+
+Event::handle('InitializePlugin');
diff --git a/lib/event.php b/lib/event.php
new file mode 100644 (file)
index 0000000..d815ae5
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * utilities for defining and running event handlers
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Event
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @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);
+}
+
+/**
+ * Class for events
+ *
+ * This "class" two static functions for managing events in the Laconica code.
+ *
+ * @category Event
+ * @package  Laconica
+ * @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/
+ *
+ * @todo     Define a system for using Event instances
+ */
+
+class Event {
+
+    /* Global array of hooks, mapping eventname => array of callables */
+
+    protected static $_handlers = array();
+
+    /**
+     * Add an event handler
+     *
+     * To run some code at a particular point in Laconica processing.
+     * Named events include receiving an XMPP message, adding a new notice,
+     * or showing part of an HTML page.
+     *
+     * The arguments to the handler vary by the event. Handlers can return
+     * two possible values: false means that the event has been replaced by
+     * the handler completely, and no default processing should be done.
+     * Non-false means successful handling, and that the default processing
+     * should succeed. (Note that this only makes sense for some events.)
+     *
+     * Handlers can also abort processing by throwing an exception; these will
+     * be caught by the closest code and displayed as errors.
+     *
+     * @param string   $name    Name of the event
+     * @param callable $handler Code to run
+     *
+     * @return void
+     */
+
+    public static function addHandler($name, $handler) {
+        if (array_key_exists($name, Event::$_handlers)) {
+            Event::$_handlers[$name][] = $handler;
+        } else {
+            Event::$_handlers[$name] = array($handler);
+        }
+    }
+
+    /**
+     * Handle an event
+     *
+     * Events are any point in the code that we want to expose for admins
+     * or third-party developers to use.
+     *
+     * We pass in an array of arguments (including references, for stuff
+     * that can be changed), and each assigned handler gets run with those
+     * arguments. Exceptions can be thrown to indicate an error.
+     *
+     * @param string $name Name of the event that's happening
+     * @param array  $args Arguments for handlers
+     *
+     * @return boolean flag saying whether to continue processing, based
+     *                 on results of handlers.
+     */
+
+    public static function handle($name, $args=array()) {
+        $result = null;
+        if (array_key_exists($name, Event::$_handlers)) {
+            foreach (Event::$_handlers[$name] as $handler) {
+                $result = call_user_func_array($handler, $args);
+                if ($result === false) {
+                    break;
+                }
+            }
+        }
+        return ($result !== false);
+    }
+}
diff --git a/lib/plugin.php b/lib/plugin.php
new file mode 100644 (file)
index 0000000..7b2436e
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Utility class for plugins
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @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);
+}
+
+/**
+ * Base class for plugins
+ *
+ * A base class for Laconica plugins. Mostly a light wrapper around
+ * the Event framework.
+ *
+ * Subclasses of Plugin will automatically handle an event if they define
+ * a method called "onEventName". (Well, OK -- only if they call parent::__construct()
+ * in their constructors.)
+ *
+ * They will also automatically handle the InitializePlugin and CleanupPlugin with the
+ * initialize() and cleanup() methods, respectively.
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @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/
+ *
+ * @see      Event
+ */
+
+class Plugin
+{
+    function __construct()
+    {
+        Event::addHandler('InitializePlugin', array($this, 'initialize'));
+        Event::addHandler('CleanupPlugin', array($this, 'cleanup'));
+
+        foreach (get_class_methods($this) as $method) {
+            if (mb_substr($method, 0, 2) == 'on') {
+                Event::addHandler(mb_substr($method, 2), array($this, $method));
+            }
+        }
+    }
+
+    function initialize()
+    {
+        return true;
+    }
+
+    function cleanup()
+    {
+        return true;
+    }
+}
diff --git a/lib/serverexception.php b/lib/serverexception.php
new file mode 100644 (file)
index 0000000..b8ed684
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * class for a server exception (user error)
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Exception
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @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);
+}
+
+/**
+ * Class for server exceptions
+ *
+ * Subclass of PHP Exception for server errors. The user typically can't fix these.
+ *
+ * @category Exception
+ * @package  Laconica
+ * @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/
+ */
+
+class ServerException extends Exception
+{
+    public function __construct($message = null, $code = 400) {
+        parent::__construct($message, $code);
+    }
+
+    public function __toString() {
+        return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
+    }
+}
index 7ce4e229eb76f93a8a49568b2283f9fd95e33d4c..c5a092f6300c4f7c8bdae1bd27b4e8d0cac9f95f 100644 (file)
@@ -478,7 +478,7 @@ function common_linkify($url) {
     }
     else $title = '';
 
-    return "<a href=\"$url\" $title class=\"extlink\">$display</a>";
+    return "<a href=\"$url\" $title rel=\"external\">$display</a>";
 }
 
 function common_longurl($short_url)
diff --git a/plugins/GoogleAnalyticsPlugin.php b/plugins/GoogleAnalyticsPlugin.php
new file mode 100644 (file)
index 0000000..87a70e3
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to use Google Analytics
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @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);
+}
+
+/**
+ * Plugin to use Google Analytics
+ *
+ * This plugin will spoot out the correct JavaScript spell to invoke Google Analytics on a page.
+ *
+ * Note that Google Analytics is not compatible with the Franklin Street Statement; consider using
+ * Pikiw (http://www.pikiw.org/) instead!
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @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/
+ *
+ * @see      Event
+ */
+
+class GoogleAnalyticsPlugin extends Plugin
+{
+    var $code = null;
+
+    function __construct($code=null)
+    {
+        $this->code = $code;
+        parent::__construct();
+    }
+
+    function onEndShowScripts($action)
+    {
+        $js1 = 'var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");'.
+          'document.write(unescape("%3Cscript src=\'" + gaJsHost + "google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));';
+        $js2 = sprintf('try{'.
+                       'var pageTracker = _gat._getTracker("%s");'.
+                       'pageTracker._trackPageview();'.
+                       '} catch(err) {}',
+                       $this->code);
+        $action->elementStart('script', array('type' => 'text/javascript'));
+        $action->raw($js1);
+        $action->elementEnd('script');
+        $action->elementStart('script', array('type' => 'text/javascript'));
+        $action->raw($js2);
+        $action->elementEnd('script');
+    }
+}
index 141bcfe0caf2e2392ed77567f37ef11edf45277c..60e10417faf4984557b950d3cc8497c17ee6d7d7 100755 (executable)
@@ -34,22 +34,19 @@ require_once INSTALLDIR . '/lib/facebookutil.php';
 $last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated';
 
 // Lock file name
-$tmp_file = INSTALLDIR . '/scripts/update_facebook.lock';
+$lock_file = INSTALLDIR . '/scripts/update_facebook.lock';
 
 // Make sure only one copy of the script is running at a time
-if (!($tmp_file = @fopen($tmp_file, "w")))
-{
-       die("Can't open lock file. Script already running?");
+$lock_file = @fopen($lock_file, "w+");
+if (!flock( $lock_file, LOCK_EX | LOCK_NB, &$wouldblock) || $wouldblock) {
+    die("Can't open lock file. Script already running?\n");
 }
 
 $facebook = getFacebook();
-
 $current_time = time();
-
 $since = getLastUpdated();
-
+updateLastUpdated($current_time);
 $notice = getFacebookNotices($since);
-
 $cnt = 0;
 
 while($notice->fetch()) {
@@ -73,26 +70,30 @@ while($notice->fetch()) {
              
                 // Avoid a Loop
                 if ($notice->source != 'Facebook') {
-                    updateStatus($fbuid, $content);
-                    updateProfileBox($facebook, $flink, $notice);
-                    $cnt++;
+                    
+                    try {
+                        $facebook->api_client->users_setStatus($content, 
+                            $fbuid, false, true);
+                        updateProfileBox($facebook, $flink, $notice);
+                        $cnt++;
+                    } catch(FacebookRestClientException $e) {
+                        print "Couldn't sent notice $notice->id!\n";
+                        print $e->getMessage();
+
+                        // Remove flink?
+                    }
                 }
-            }
+        }
     }
 }
 
 if ($cnt > 0) {
     print date('r', $current_time) . 
-       ": Found $cnt new notices to send to Facebook since last run at " . 
-        date('Y-m-d H:i:s', $since) . "\n";
-
+    ": Found $cnt new notices for Facebook since last run at " . 
+     date('r', $since) . "\n";
 }
 
-#Save the last updated time. It needs to do this even if there were no
-#changes made, otherwise it will never create it and thus never send
-#any updates at all.
-updateLastUpdated($current_time);
-
+fclose($lock_file);
 exit(0);
 
 
@@ -111,37 +112,30 @@ function userCanUpdate($fbuid) {
     return $result;
 }
 
-
-function updateStatus($fbuid, $content) {
-    global $facebook;
-
-    try {
-        $result = $facebook->api_client->users_setStatus($content, $fbuid, false, true);
-    } catch(FacebookRestClientException $e){
-       print_r($e);
-    }
-}
-
 function getLastUpdated(){
-       global $last_updated_file, $current_time;
-
-       $file = fopen($last_updated_file, 'r');
-
-       if ($file) {
-           $last = fgets($file);
-       } else {
-           print "Unable to read $last_updated_file. Using current time.\n";
-           return $current_time;
-       }
-
-       fclose($file);
-
-       return $last;
+    global $last_updated_file, $current_time;
+    $last = $current_time;
+
+    if (file_exists($last_updated_file) && 
+        ($file = fopen($last_updated_file, 'r'))) {
+            $last = fgets($file);
+    } else {
+        print "$last_updated_file doesn't exit. Trying to create it...\n";
+        $file = fopen($last_updated_file, 'w+') or 
+            die("Can't open $last_updated_file for writing!\n");
+        print 'Success. Using current time (' . date('r', $last) . 
+            ") to look for new notices.\n";
+    }
+    
+    fclose($file);
+    return $last;
 }
 
 function updateLastUpdated($time){
-       global $last_updated_file;
-       $file = fopen($last_updated_file, 'w') or die("Can't open $last_updated_file for writing!");
-       fwrite($file, $time);
-       fclose($file);
+    global $last_updated_file;
+    $file = fopen($last_updated_file, 'w') or
+        die("Can't open $last_updated_file for writing!");
+    fwrite($file, $time);
+    fclose($file);
 }
+