]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.7.x' into 0.8.x
authorEvan Prodromou <evan@controlyourself.ca>
Thu, 26 Feb 2009 21:22:51 +0000 (13:22 -0800)
committerEvan Prodromou <evan@controlyourself.ca>
Thu, 26 Feb 2009 21:22:51 +0000 (13:22 -0800)
127 files changed:
.gitignore
EVENTS.txt
README
actions/all.php
actions/allrss.php
actions/api.php
actions/avatarsettings.php
actions/doc.php
actions/emailsettings.php
actions/finishopenidlogin.php
actions/finishremotesubscribe.php
actions/grouprss.php
actions/login.php
actions/newmessage.php
actions/newnotice.php
actions/noticesearch.php
actions/peoplesearch.php
actions/public.php
actions/replies.php
actions/showfavorites.php
actions/showgroup.php
actions/showstream.php
actions/tag.php
actions/tagother.php
actions/twitapiaccount.php
actions/twitapistatuses.php
actions/twittersettings.php
actions/userauthorization.php
actions/userrss.php
bin/flowplayer-3.0.5.swf [new file with mode: 0644]
bin/flowplayer.audio-3.0.3.swf [new file with mode: 0644]
bin/flowplayer.controls-3.0.3.swf [new file with mode: 0644]
classes/Channel.php [deleted file]
classes/Command.php [deleted file]
classes/CommandInterpreter.php [deleted file]
classes/Notice.php
classes/Notice_tag.php
classes/User.php
classes/laconica.ini
config.php.sample
db/carrier.sql [deleted file]
db/laconica.sql
db/sms_carrier.sql [new file with mode: 0644]
doc-src/about [new file with mode: 0644]
doc-src/badge [new file with mode: 0644]
doc-src/contact [new file with mode: 0644]
doc-src/faq [new file with mode: 0644]
doc-src/groups [new file with mode: 0644]
doc-src/help [new file with mode: 0644]
doc-src/im [new file with mode: 0644]
doc-src/openid [new file with mode: 0644]
doc-src/openmublog [new file with mode: 0644]
doc-src/privacy [new file with mode: 0644]
doc-src/sms [new file with mode: 0644]
doc-src/source [new file with mode: 0644]
doc-src/tags [new file with mode: 0644]
doc/about [deleted file]
doc/contact [deleted file]
doc/faq [deleted file]
doc/groups [deleted file]
doc/help [deleted file]
doc/im [deleted file]
doc/openid [deleted file]
doc/openmublog [deleted file]
doc/privacy [deleted file]
doc/sms [deleted file]
doc/source [deleted file]
doc/tags [deleted file]
extlib/Net/URL.php [new file with mode: 0644]
extlib/Net/URL/Mapper.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Exception.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Part.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Part/Dynamic.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Part/Fixed.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Part/Wildcard.php [new file with mode: 0644]
extlib/Net/URL/Mapper/Path.php [new file with mode: 0644]
htaccess.sample
index.php
js/flowplayer-3.0.5.min.js [new file with mode: 0644]
js/identica-badge.js
js/jquery.js
js/jquery.min.js
js/jquery.simplemodal-1.2.2.pack.js [new file with mode: 0644]
js/video.js [new file with mode: 0644]
lib/action.php
lib/channel.php [new file with mode: 0644]
lib/command.php [new file with mode: 0644]
lib/commandinterpreter.php [new file with mode: 0644]
lib/common.php
lib/dberroraction.php [new file with mode: 0644]
lib/facebookutil.php
lib/featureduserssection.php
lib/feed.php [new file with mode: 0644]
lib/feedlist.php
lib/grouplist.php
lib/htmloutputter.php
lib/imagefile.php
lib/jabber.php
lib/mail.php
lib/noticesection.php
lib/omb.php
lib/openid.php
lib/peoplesearchresults.php [new file with mode: 0644]
lib/popularnoticesection.php
lib/profilelist.php
lib/router.php [new file with mode: 0644]
lib/rssaction.php
lib/searchaction.php
lib/section.php
lib/twitter.php
lib/util.php
lib/xmlstringer.php [new file with mode: 0644]
plugins/BlogspamNetPlugin.php [new file with mode: 0644]
plugins/GoogleAnalyticsPlugin.php
scripts/facebookqueuehandler.php [new file with mode: 0755]
scripts/sitemap.php
scripts/startdaemons.sh
scripts/stopdaemons.sh
scripts/twitterqueuehandler.php [new file with mode: 0755]
scripts/update_facebook.php [deleted file]
scripts/xmppdaemon.php
theme/base/css/display.css
theme/base/css/mobile.css [new file with mode: 0644]
theme/base/css/modal.css [new file with mode: 0644]
theme/base/css/modal_ie.css [new file with mode: 0644]
theme/base/images/x.png [new file with mode: 0644]
theme/readme.txt

index f5a3e0212f8d7b11cf2a542d176a99da57b04819..83a53dfa3fef76899d8cf1316800396452f44961 100644 (file)
@@ -1,11 +1,15 @@
 avatar/*
 files/*
 _darcs/*
+logs/*
 config.php
 .htaccess
+httpd.conf
 *.tmproj
 dataobject.ini
 *~
 *.bak
 *.orig
 *.rej
+.#*
+*.swp
index 4b8260b3ced271a937e3b7bd62a4afb8d6d71311..37e2203d50eb5ac8c9112626bacf0ba1cc35d411 100644 (file)
@@ -15,6 +15,24 @@ StartSecondaryNav: Showing the secondary nav menu
 EndSecondaryNav: At the end of the secondary nav menu
 - $action: the current action
 
+StartShowStyles: Showing Style links; good place to add UA style resets
+- $action: the current action
+
+EndShowStyles: End showing Style links; good place to add custom styles
+- $action: the current action
+
+StartShowLaconicaStyles: Showing Laconica Style links
+- $action: the current action
+
+EndShowLaconicaStyles: End showing Laconica Style links;  good place to add handheld or JavaScript dependant styles
+- $action: the current action
+
+StartShowUAStyles: Showing custom UA Style links
+- $action: the current action
+
+EndShowUAStyles: End showing custom UA Style links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles
+- $action: the current action
+
 StartShowScripts: Showing JavaScript links
 - $action: the current action
 
@@ -34,3 +52,39 @@ StartShowLaconicaScripts: Showing Laconica script links (use this to link to a C
 EndShowLaconicaScripts: End showing Laconica script links
 - $action: the current action
 
+StartShowSections: Start the list of sections in the sidebar
+- $action: the current action
+
+EndShowSections: End the list of sections in the sidebar
+- $action: the current action
+
+StartShowHeader: Showing before the header container
+- $action: the current action
+
+EndShowHeader: Showing after the header container
+- $action: the current action
+
+StartShowFooter: Showing before the footer container
+- $action: the current action
+
+EndShowFooter: Showing after the footer container
+- $action: the current action
+
+StartShowContentBlock: Showing before the content container
+- $action: the current action
+
+EndShowContentBlock: Showing after the content container
+- $action: the current action
+
+StartNoticeSave: before inserting a notice (good place for content filters)
+- $notice: notice being saved (no ID or URI)
+
+EndNoticeSave: after inserting a notice and related code
+- $notice: notice that was saved (with ID and URI)
+
+StartShowLocalNavBlock: Showing the local nav menu
+- $action: the current action
+
+EndShowLocalNavBlock: At the end of the local nav menu
+- $action: the current action
+
diff --git a/README b/README
index 2c9ae84d93134dd5ba3155cb14dc1d353b84f788..9534c74c195e0e0cf49238f482b3c79f5c322b2f 100644 (file)
--- a/README
+++ b/README
@@ -511,7 +511,7 @@ server is probably a good idea for high-volume sites.
    needs as a parameter the install path; if you run it from the
    Laconica dir, "." should suffice.
 
-This will run six (for now) queue handlers:
+This will run eight (for now) queue handlers:
 
 * xmppdaemon.php - listens for new XMPP messages from users and stores
   them as notices in the database.
@@ -525,6 +525,10 @@ This will run six (for now) queue handlers:
   of registered users.
 * xmppconfirmhandler.php - sends confirmation messages to registered
   users.
+* twitterqueuehandler.php - sends queued notices to Twitter for user
+  who have opted to set up Twitter bridging.
+* facebookqueuehandler.php - sends queued notices to Facebook for users
+  of the built-in Facebook application.
 
 Note that these queue daemons are pretty raw, and need your care. In
 particular, they leak memory, and you may want to restart them on a
@@ -557,6 +561,53 @@ Sample cron job:
 # Update Twitter friends subscriptions every half hour
 0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
 
+Built-in Facebook Application 
+-----------------------------
+
+Laconica's Facebook application allows your users to automatically
+update their Facebook statuses with their latest notices, invite
+their friends to use the app (and thus your site), view their notice
+timelines, and post notices -- all from within Facebook. The application
+is built into Laconica and runs on your host.  For automatic Facebook
+status updating to work you will need to enable queuing and run the
+facebookqueuehandler.php daemon (see the "Queues and daemons" section
+above).
+
+Quick setup instructions*:
+
+Install the Facebook Developer application on Facebook: 
+
+    http://www.facebook.com/developers/
+
+Use it to create a new application and generate an API key and secret.
+Uncomment the Facebook app section of your config.php and copy in the
+key and secret, e.g.:
+
+    # Config section for the built-in Facebook application
+    $config['facebook']['apikey'] = 'APIKEY';
+    $config['facebook']['secret'] = 'SECRET';
+
+In Facebook's application editor, specify the following URLs for your app:
+
+- Callback URL: http://example.net/mublog/facebook/
+- Post-Remove URL: http://example.net/mublog/facebook/remove
+- Post-Add Redirect URL: http://apps.facebook.com/yourapp/
+- Canvas URL: http://apps.facebook.com/yourapp/
+
+(Replace 'example.net' with your host's URL, 'mublog' with the path
+to your Laconica installation, and 'yourapp' with the name of the
+Facebook application you created.)
+
+Additionally, Choose "Web" for Application type in the Advanced tab.
+In the "Canvas setting" section, choose the "FBML" for Render Method,
+"Smart Size" for IFrame size, and "Full width (760px)" for Canvas Width.
+Everything else can be left with default values.
+
+*For more detailed instructions please see the installation guide on the
+Laconica wiki:
+
+    http://laconi.ca/trac/wiki/FacebookApplication
+
 Sitemaps
 --------
 
index d75d1b94612b8f6adc42cd5f22c61490fed98a6f..08dcccbddb711c12ff7578e165d99c72b1068545 100644 (file)
@@ -42,9 +42,9 @@ class AllAction extends Action
         if (!$this->page) {
             $this->page = 1;
         }
-        
+
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -69,13 +69,22 @@ class AllAction extends Action
         }
     }
 
-    function showFeeds()
+    function getFeeds()
     {
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => common_local_url('allrss', array('nickname' =>
-                                                                                $this->user->nickname)),
-                                     'type' => 'application/rss+xml',
-                                     'title' => sprintf(_('Feed for friends of %s'), $this->user->nickname)));
+        return array(new Feed(Feed::RSS1,
+                              common_local_url('allrss', array('nickname' =>
+                                                               $this->user->nickname)),
+                              sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->user->nickname)),
+                     new Feed(Feed::RSS2,
+                              common_local_url('api', array('apiaction' => 'statuses',
+                                                            'method' => 'friends',
+                                                            'argument' => $this->user->nickname.'.rss')),
+                              sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->user->nickname)),
+                     new Feed(Feed::ATOM,
+                              common_local_url('api', array('apiaction' => 'statuses',
+                                                            'method' => 'friends',
+                                                            'argument' => $this->user->nickname.'.atom')),
+                              sprintf(_('Feed for friends of %s (Atom)'), $this->user->nickname)));
     }
 
     function showLocalNav()
@@ -84,15 +93,6 @@ class AllAction extends Action
         $nav->show();
     }
 
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-        $fl->show(array(0=>array('href'=>common_local_url('allrss', array('nickname' => $this->user->nickname)),
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'allrss')));
-    }
-
     function showContent()
     {
         $notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
@@ -110,7 +110,7 @@ class AllAction extends Action
         $user =& common_current_user();
         if ($user && ($user->id == $this->user->id)) {
             $this->element('h1', NULL, _("You and friends"));
-        } else { 
+        } else {
             $this->element('h1', NULL, sprintf(_('%s and friends'), $this->user->nickname));
         }
     }
index 05787f3f73cb8cdc5398cb4dc8c7140facc371cc..0114c43962ad312075337ef347010fada0c25a1b 100644 (file)
@@ -53,7 +53,9 @@ class AllrssAction extends Rss10Action
 
     /**
      * Initialization.
-     * 
+     *
+     * @param array $args Web and URL arguments
+     *
      * @return boolean false if user doesn't exist
      */
     function prepare($args)
@@ -81,7 +83,7 @@ class AllrssAction extends Rss10Action
     {
         $user   = $this->user;
         $notice = $user->noticesWithFriends(0, $limit);
-                                            
+
         while ($notice->fetch()) {
             $notices[] = clone($notice);
         }
@@ -104,7 +106,8 @@ class AllrssAction extends Rss10Action
                    'link' => common_local_url('all',
                                              array('nickname' =>
                                                    $user->nickname)),
-                   'description' => sprintf(_('Feed for friends of %s'), $user->nickname));
+                   'description' => sprintf(_('Feed for friends of %s'),
+                                            $user->nickname));
         return $c;
     }
 
@@ -123,10 +126,5 @@ class AllrssAction extends Rss10Action
         $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
         return $avatar ? $avatar->url : null;
     }
-
-    function isReadOnly()
-    {
-        return true;
-    }
 }
 
index 21fe4eea32004868e9a795422fcddb77ceaeaf73..a27d244929f032468cc5ae0e4373a936a8d47c37 100644 (file)
@@ -131,14 +131,14 @@ class ApiAction extends Action
                                  'statuses/followers',
                                  'favorites/favorites');
 
-        # If the site is "private", all API methods need authentication
-
+        $fullname = "$this->api_action/$this->api_method";
+        
+        // If the site is "private", all API methods except laconica/config 
+        // need authentication
         if (common_config('site', 'private')) {
-            return true;
+            return $fullname != 'laconica/config' || false;
         }
 
-        $fullname = "$this->api_action/$this->api_method";
-
         if (in_array($fullname, $bareauth)) {
             # bareauth: only needs auth if without an argument
             if ($this->api_arg) {
index 7dd53f6eb5af40f4e959d4a79a28f6ef5f988808..f38a44a24a3e782dc20b588005a6d39ac09c47b4 100644 (file)
@@ -145,6 +145,7 @@ class AvatarsettingsAction extends AccountSettingsAction
                                         'height' => AVATAR_PROFILE_SIZE,
                                         'alt' => $user->nickname));
             $this->elementEnd('div');
+            $this->submit('delete', _('Delete'));
             $this->elementEnd('li');
         }
 
@@ -256,6 +257,8 @@ class AvatarsettingsAction extends AccountSettingsAction
             $this->uploadAvatar();
         } else if ($this->arg('crop')) {
             $this->cropAvatar();
+        } else if ($this->arg('delete')) {
+            $this->deleteAvatar();
         } else {
             $this->showForm(_('Unexpected form submission.'));
         }
@@ -344,6 +347,29 @@ class AvatarsettingsAction extends AccountSettingsAction
             $this->showForm(_('Failed updating avatar.'));
         }
     }
+    
+    /**
+     * Get rid of the current avatar.
+     *
+     * @return void
+     */
+    
+    function deleteAvatar()
+    {
+        $user = common_current_user();
+        $profile = $user->getProfile();
+        
+        $avatar = $profile->getOriginalAvatar();
+        $avatar->delete();
+        $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+        $avatar->delete();
+        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+        $avatar->delete();
+        $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
+        $avatar->delete();
+
+        $this->showForm(_('Avatar deleted.'), true);
+    }
 
     /**
      * Add the jCrop stylesheet
index 6957659add3c1bcaa8455b2ffac167ceab952057..ebffb7c1542fc64843d635b7a19c310c689f20f5 100644 (file)
@@ -50,7 +50,7 @@ class DocAction extends Action
 
     /**
      * Class handler.
-     * 
+     *
      * @param array $args array of arguments
      *
      * @return nothing
@@ -59,7 +59,7 @@ class DocAction extends Action
     {
         parent::handle($args);
         $this->title    = $this->trimmed('title');
-        $this->filename = INSTALLDIR.'/doc/'.$this->title;
+        $this->filename = INSTALLDIR.'/doc-src/'.$this->title;
         if (!file_exists($this->filename)) {
             $this->clientError(_('No such document.'));
             return;
@@ -71,14 +71,14 @@ class DocAction extends Action
     function showPageTitle() {
         $this->element('h1', array('class' => 'entry-title'), $this->title());
     }
-    
+
     // overrided to add hentry, and content-inner classes
     function showContentBlock()
      {
          $this->elementStart('div', array('id' => 'content', 'class' => 'hentry'));
          $this->showPageTitle();
          $this->showPageNoticeBlock();
-         $this->elementStart('div', array('id' => 'content_inner', 
+         $this->elementStart('div', array('id' => 'content_inner',
              'class' => 'entry-content'));
          // show the actual content (forms, lists, whatever)
          $this->showContent();
@@ -88,7 +88,7 @@ class DocAction extends Action
 
     /**
      * Display content.
-     * 
+     *
      * @return nothing
      */
     function showContent()
@@ -100,7 +100,7 @@ class DocAction extends Action
 
     /**
      * Page title.
-     * 
+     *
      * @return page title
      */
     function title()
index b84acb214150902cc15e68b6bfe1794f64fa8ab1..634388fdddbdf41acae10d679719d45148c5d869 100644 (file)
@@ -164,6 +164,11 @@ class EmailsettingsAction extends AccountSettingsAction
                         $user->emailnotifymsg);
         $this->elementEnd('li');
         $this->elementStart('li');
+        $this->checkbox('emailnotifyattn',
+                        _('Send me email when someone sends me an "@-reply".'),
+                        $user->emailnotifyattn);
+        $this->elementEnd('li');
+        $this->elementStart('li');
         $this->checkbox('emailnotifynudge',
                         _('Allow friends to nudge me and send me an email.'),
                         $user->emailnotifynudge);
@@ -255,6 +260,7 @@ class EmailsettingsAction extends AccountSettingsAction
         $emailnotifyfav   = $this->boolean('emailnotifyfav');
         $emailnotifymsg   = $this->boolean('emailnotifymsg');
         $emailnotifynudge = $this->boolean('emailnotifynudge');
+        $emailnotifyattn  = $this->boolean('emailnotifyattn');
         $emailmicroid     = $this->boolean('emailmicroid');
         $emailpost        = $this->boolean('emailpost');
 
@@ -270,6 +276,7 @@ class EmailsettingsAction extends AccountSettingsAction
         $user->emailnotifyfav   = $emailnotifyfav;
         $user->emailnotifymsg   = $emailnotifymsg;
         $user->emailnotifynudge = $emailnotifynudge;
+        $user->emailnotifyattn  = $emailnotifyattn;
         $user->emailmicroid     = $emailmicroid;
         $user->emailpost        = $emailpost;
 
index 1e7b73a7f3a43b756ec787bb66e430b1f916b8a1..6d92cb9aaecb787edcf2f26ac7a8f8d4a13e2005 100644 (file)
@@ -83,7 +83,7 @@ class FinishopenidloginAction extends Action
 
     function showContent()
     {
-        if ($this->message_text) {
+        if (!empty($this->message_text)) {
             $this->element('p', null, $this->message);
             return;
         }
@@ -232,7 +232,8 @@ class FinishopenidloginAction extends Action
             return;
         }
 
-        if ($sreg['country']) {
+        $location = '';
+        if (!empty($sreg['country'])) {
             if ($sreg['postcode']) {
                 # XXX: use postcode to get city and region
                 # XXX: also, store postcode somewhere -- it's valuable!
@@ -242,12 +243,16 @@ class FinishopenidloginAction extends Action
             }
         }
 
-        if ($sreg['fullname'] && mb_strlen($sreg['fullname']) <= 255) {
+        if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
             $fullname = $sreg['fullname'];
+        } else {
+            $fullname = '';
         }
 
-        if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+        if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
             $email = $sreg['email'];
+        } else {
+            $email = '';
         }
 
         # XXX: add language
@@ -328,7 +333,7 @@ class FinishopenidloginAction extends Action
 
         # Try the passed-in nickname
 
-        if ($sreg['nickname']) {
+        if (!empty($sreg['nickname'])) {
             $nickname = $this->nicknamize($sreg['nickname']);
             if ($this->isNewNickname($nickname)) {
                 return $nickname;
@@ -337,7 +342,7 @@ class FinishopenidloginAction extends Action
 
         # Try the full name
 
-        if ($sreg['fullname']) {
+        if (!empty($sreg['fullname'])) {
             $fullname = $this->nicknamize($sreg['fullname']);
             if ($this->isNewNickname($fullname)) {
                 return $fullname;
index f9094a50cafa91fd2b240d3df930229d9f7471e8..76db887deb4dca85f0347e5c46a18a4ee5021e82 100644 (file)
@@ -237,7 +237,13 @@ class FinishremotesubscribeAction extends Action
     {
         $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
         copy($url, $temp_filename);
-        return $profile->setOriginal($temp_filename);
+        $imagefile = new ImageFile($profile->id, $temp_filename);
+        $filename = Avatar::filename($profile->id,
+                                     image_type_to_extension($imagefile->type),
+                                     null,
+                                     common_timestamp());
+        rename($temp_filename, Avatar::path($filename));
+        return $profile->setOriginal($filename);
     }
 
     function access_token($omb)
index 1a7b858b1ee0da247f620252597d2abb1a67ae81..de76a59600dbb67d409c44bf434784b3d62e04f0 100644 (file)
@@ -111,13 +111,13 @@ class groupRssAction extends Rss10Action
     {
 
         $group = $this->group;
-        
+
         if (is_null($group)) {
             return null;
         }
-        
+
         $notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
-        
+
         while ($notice->fetch()) {
             $notices[] = clone($notice);
         }
@@ -141,13 +141,4 @@ class groupRssAction extends Rss10Action
     {
         return $this->group->homepage_logo;
     }
-
-    # override parent to add X-SUP-ID URL
-    
-    function initRss($limit=0)
-    {
-        $url = common_local_url('sup', null, $this->group->id);
-        header('X-SUP-ID: '.$url);
-        parent::initRss($limit);
-    }
 }
index 71e4679292c79102dd97f80f6683a655dfef5535..b049791fb1858dac3d79625317efa0ae77bb5bd9 100644 (file)
@@ -108,13 +108,15 @@ class LoginAction extends Action
         $nickname = common_canonical_nickname($this->trimmed('nickname'));
         $password = $this->arg('password');
 
-        if (!common_check_user($nickname, $password)) {
+        $user = common_check_user($nickname, $password);
+
+        if (!$user) {
             $this->showForm(_('Incorrect username or password.'));
             return;
         }
 
         // success!
-        if (!common_set_user($nickname)) {
+        if (!common_set_user($user)) {
             $this->serverError(_('Error setting user.'));
             return;
         }
index f83015a372ce764502cf457e120b9d75c0229c90..82276ff341c6e6d8ea54d033fe2588aba48e21c3 100644 (file)
@@ -201,7 +201,7 @@ class NewmessageAction extends Action
 
     function showNoticeForm()
     {
-        $message_form = new MessageForm($this, $this->to, $this->content);
+        $message_form = new MessageForm($this, $this->other, $this->content);
         $message_form->show();
     }
 }
index 5e7691f33d1cae68442b8b18b4c643eb3deca24b..9face9644356a0a08375f3dfdeee1dda5d7a7059 100644 (file)
@@ -98,7 +98,12 @@ class NewnoticeAction extends Action
                 return;
             }
 
-            $this->saveNewNotice();
+            try {
+                $this->saveNewNotice();
+            } catch (Exception $e) {
+                $this->showForm($e->getMessage());
+                return;
+            }
         } else {
             $this->showForm();
         }
@@ -123,15 +128,13 @@ class NewnoticeAction extends Action
         $content = $this->trimmed('status_textarea');
 
         if (!$content) {
-            $this->showForm(_('No content!'));
-            return;
+            $this->clientError(_('No content!'));
         } else {
             $content_shortened = common_shorten_links($content);
 
             if (mb_strlen($content_shortened) > 140) {
-                $this->showForm(_('That\'s too long. '.
-                                  'Max notice size is 140 chars.'));
-                return;
+                $this->clientError(_('That\'s too long. '.
+                                     'Max notice size is 140 chars.'));
             }
         }
 
@@ -154,7 +157,7 @@ class NewnoticeAction extends Action
                                   ($replyto == 'false') ? null : $replyto);
 
         if (is_string($notice)) {
-            $this->showForm($notice);
+            $this->clientError($notice);
             return;
         }
 
index a5f01350c4c732bfd1fab1e87ecb8b57873c9ee5..dc58d7528ae315c61c4f58a26bc841d9dc3a4772 100644 (file)
@@ -57,11 +57,11 @@ class NoticesearchAction extends SearchAction
 
         return true;
     }
-    
+
     /**
      * Get instructions
-     * 
-     * @return string instruction text 
+     *
+     * @return string instruction text
      */
     function getInstructions()
     {
@@ -70,7 +70,7 @@ class NoticesearchAction extends SearchAction
 
     /**
      * Get title
-     * 
+     *
      * @return string title
      */
     function title()
@@ -78,6 +78,20 @@ class NoticesearchAction extends SearchAction
         return _('Text search');
     }
 
+    function getFeeds()
+    {
+        $q = $this->trimmed('q');
+
+        if (!$q) {
+            return null;
+        }
+
+        return array(new Feed(Feed::RSS1, common_local_url('noticesearchrss',
+                                                           array('q' => $q)),
+                              sprintf(_('Search results for "%s" on %s'),
+                                      $q, common_config('site', 'name'))));
+    }
+
     /**
      * Show results
      *
@@ -119,26 +133,6 @@ class NoticesearchAction extends SearchAction
                           $page, 'noticesearch', array('q' => $q));
     }
 
-    /**
-     * Show header
-     *
-     * @param array $arr array containing the query
-     *
-     * @return void
-     */
-
-    function extraHead()
-    {
-        $q = $this->trimmed('q');
-        if ($q) {
-            $this->element('link', array('rel' => 'alternate',
-                                         'href' => common_local_url('noticesearchrss',
-                                                                    array('q' => $q)),
-                                         'type' => 'application/rss+xml',
-                                         'title' => _('Search Stream Feed')));
-        }
-    }
-
     /**
      * Show notice
      *
index 615201c461f49c8c521e1530417b9bb4fc894422..14177fcf0d48cb996fb77d11f69e65104b9675c4 100644 (file)
@@ -86,33 +86,9 @@ class PeoplesearchAction extends SearchAction
         }
 
         $profile->free();
-        
+
         $this->pagination($page > 1, $cnt > PROFILES_PER_PAGE,
                           $page, 'peoplesearch', array('q' => $q));
     }
 }
 
-class PeopleSearchResults extends ProfileList
-{
-    var $terms = null;
-    var $pattern = null;
-    
-    function __construct($profile, $terms, $action)
-    {
-        parent::__construct($profile, $terms, $action);
-        $this->terms = array_map('preg_quote', 
-                                 array_map('htmlspecialchars', $terms));
-        $this->pattern = '/('.implode('|',$terms).')/i';
-    }
-    
-    function highlight($text)
-    {
-        return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
-    }
-
-    function isReadOnly()
-    {
-        return true;
-    }
-}
-
index cc6537f74f8afd4731f2bd15162fbaf176bd8bc7..a20ae40321ca17b36ae28313f4a87eac90d69f92 100644 (file)
@@ -73,9 +73,9 @@ class PublicAction extends Action
     {
         parent::prepare($args);
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-        
+
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -119,12 +119,20 @@ class PublicAction extends Action
      * @return void
      */
 
-    function showFeeds()
+    function getFeeds()
     {
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => common_local_url('publicrss'),
-                                     'type' => 'application/rss+xml',
-                                     'title' => _('Public Stream Feed')));
+        return array(new Feed(Feed::RSS1, common_local_url('publicrss'),
+                              _('Public Stream Feed (RSS 1.0)')),
+                     new Feed(Feed::RSS2,
+                              common_local_url('api',
+                                               array('apiaction' => 'statuses',
+                                                     'method' => 'public_timeline.rss')),
+                              _('Public Stream Feed (RSS 2.0)')),
+                     new Feed(Feed::ATOM,
+                              common_local_url('api',
+                                               array('apiaction' => 'statuses',
+                                                     'method' => 'public_timeline.atom')),
+                              _('Public Stream Feed (Atom)')));
     }
 
     /**
@@ -185,27 +193,6 @@ class PublicAction extends Action
                           $this->page, 'public');
     }
 
-    /**
-     * Makes a list of exported feeds for this page
-     *
-     * @return void
-     *
-     * @todo I18N
-     */
-
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-        $fl->show(array(0 => array('href' => common_local_url('publicrss'),
-                                   'type' => 'rss',
-                                   'version' => 'RSS 1.0',
-                                   'item' => 'publicrss'),
-                        1 => array('href' => common_local_url('publicatom'),
-                                   'type' => 'atom',
-                                   'version' => 'Atom 1.0',
-                                   'item' => 'publicatom')));
-    }
-
     function showSections()
     {
         // $top = new TopPostersSection($this);
index 7eff74a6691e34505ceda2aa6dccbf9bc979bf33..4ab9b14ed26be98187ee0949c0a2a4b3fac352c6 100644 (file)
@@ -84,7 +84,7 @@ class RepliesAction extends Action
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
 
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -129,16 +129,13 @@ class RepliesAction extends Action
      * @return void
      */
 
-    function showFeeds()
+    function getFeeds()
     {
         $rssurl   = common_local_url('repliesrss',
                                      array('nickname' => $this->user->nickname));
         $rsstitle = sprintf(_('Feed for replies to %s'), $this->user->nickname);
 
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => $rssurl,
-                                     'type' => 'application/rss+xml',
-                                     'title' => $rsstitle));
+        return array(new Feed(Feed::RSS1, $rssurl, $rsstitle));
     }
 
     /**
@@ -153,25 +150,6 @@ class RepliesAction extends Action
         $nav->show();
     }
 
-    /**
-     * Show the replies feed links
-     *
-     * @return void
-     */
-
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-
-        $rssurl = common_local_url('repliesrss',
-                                   array('nickname' => $this->user->nickname));
-
-        $fl->show(array(0=>array('href'=> $rssurl,
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'repliesrss')));
-    }
-
     /**
      * Show the content
      *
index 31479e1a7891d0933e63cb8bc5735ccc99d59345..d1c9283f0f47bff1a6811039a891760898de3f87 100644 (file)
@@ -113,7 +113,7 @@ class ShowfavoritesAction extends Action
         }
 
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -136,10 +136,10 @@ class ShowfavoritesAction extends Action
     /**
      * Feeds for the <head> section
      *
-     * @return void
+     * @return array Feed objects to show
      */
 
-    function showFeeds()
+    function getFeeds()
     {
         $feedurl   = common_local_url('favoritesrss',
                                       array('nickname' =>
@@ -147,10 +147,7 @@ class ShowfavoritesAction extends Action
         $feedtitle = sprintf(_('Feed for favorites of %s'),
                              $this->user->nickname);
 
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => $feedurl,
-                                     'type' => 'application/rss+xml',
-                                     'title' => $feedtitle));
+        return array(new Feed(Feed::RSS1, $feedurl, $feedtitle));
     }
 
     /**
@@ -165,28 +162,6 @@ class ShowfavoritesAction extends Action
         $nav->show();
     }
 
-    /**
-     * Show the replies feed links
-     *
-     * @return void
-     */
-
-    function showExportData()
-    {
-        $feedurl = common_local_url('favoritesrss',
-                                    array('nickname' =>
-                                          $this->user->nickname));
-
-        $fl = new FeedList($this);
-
-        // XXX: I18N
-
-        $fl->show(array(0=>array('href'=> $feedurl,
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'Favorites')));
-    }
-
     /**
      * Show the content
      *
index 7bc68fbc64a151ac377c414a4ace0e0d52997d26..c20941a35ee07776acb475fd69152e4b139b79b4 100644 (file)
@@ -244,7 +244,7 @@ class ShowgroupAction extends Action
         if ($this->group->location) {
             $this->elementStart('dl', 'entity_location');
             $this->element('dt', null, _('Location'));
-            $this->element('dd', 'location', $this->group->location);
+            $this->element('dd', 'label', $this->group->location);
             $this->elementEnd('dl');
         }
 
@@ -292,37 +292,18 @@ class ShowgroupAction extends Action
     }
 
     /**
-     * Show a list of links to feeds this page produces
+     * Get a list of the feeds for this page
      *
      * @return void
      */
 
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-        $fl->show(array(0=>array('href'=>common_local_url('grouprss',
-                                                          array('nickname' => $this->group->nickname)),
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'notices')));
-    }
-
-    /**
-     * Show a list of links to feeds this page produces
-     *
-     * @return void
-     */
-
-    function showFeeds()
+    function getFeeds()
     {
         $url =
           common_local_url('grouprss',
                            array('nickname' => $this->group->nickname));
 
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => $url,
-                                     'type' => 'application/rss+xml',
-                                     'title' => sprintf(_('Notice feed for %s group'),
+        return array(new Feed(Feed::RSS1, $url, sprintf(_('Notice feed for %s group'),
                                                         $this->group->nickname)));
     }
 
index 28bb8453f8c1111dd04a44b9fcff947273fd146b..65482167e10d5eda4ce8b40803bff07f845d9156 100644 (file)
@@ -111,7 +111,7 @@ class ShowstreamAction extends Action
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
 
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -155,58 +155,39 @@ class ShowstreamAction extends Action
         return;
     }
 
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-        $fl->show(array(0=>array('href'=>common_local_url('userrss',
-                                                          array('nickname' => $this->user->nickname)),
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'notices'),
-                        1=>array('href'=>common_local_url('usertimeline',
-                                                          array('nickname' => $this->user->nickname)),
-                                 'type' => 'atom',
-                                 'version' => 'Atom 1.0',
-                                 'item' => 'usertimeline'),
-                        2=>array('href'=>common_local_url('foaf',
-                                                          array('nickname' => $this->user->nickname)),
-                                 'type' => 'rdf',
-                                 'version' => 'FOAF',
-                                 'item' => 'foaf')));
-    }
-
-    function showFeeds()
+    function getFeeds()
     {
-        $this->element('link', array('rel' => 'alternate',
-                        'type' => 'application/rss+xml',
-                        'href' => common_local_url('userrss',
-                         array('nickname' => $this->user->nickname)),
-                               'title' => sprintf(_('Notice feed for %s (RSS)'),
-                                 $this->user->nickname)));
-
-         $this->element('link',
-             array('rel' => 'alternate',
-                   'href' => common_local_url('api',
-                     array('apiaction' => 'statuses',
-                           'method' => 'user_timeline.atom',
-                           'argument' => $this->user->nickname)),
-                           'type' => 'application/atom+xml',
-                           'title' => sprintf(_('Notice feed for %s (Atom)'),
-                             $this->user->nickname)));
+        return array(new Feed(Feed::RSS1,
+                              common_local_url('userrss',
+                                               array('nickname' => $this->user->nickname)),
+                              sprintf(_('Notice feed for %s (RSS 1.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::RSS2,
+                              common_local_url('api',
+                                               array('apiaction' => 'statuses',
+                                                     'method' => 'user_timeline',
+                                                     'argument' => $this->user->nickname.'.rss')),
+                              sprintf(_('Notice feed for %s (RSS 2.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::ATOM,
+                              common_local_url('api',
+                                               array('apiaction' => 'statuses',
+                                                     'method' => 'user_timeline',
+                                                     'argument' => $this->user->nickname.'.atom')),
+                              sprintf(_('Notice feed for %s (Atom)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::FOAF,
+                              common_local_url('foaf', array('nickname' =>
+                                                             $this->user->nickname)),
+                              sprintf(_('FOAF for %s'), $this->user->nickname)));
     }
 
     function extraHead()
     {
-        // FOAF
-        $this->element('link', array('rel' => 'meta',
-                                     'href' => common_local_url('foaf', array('nickname' =>
-                                                                              $this->user->nickname)),
-                                     'type' => 'application/rdf+xml',
-                                     'title' => 'FOAF'));
         // for remote subscriptions etc.
         $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
                                      'content' => common_local_url('xrds', array('nickname' =>
-                                                                               $this->user->nickname))));
+                                                                                 $this->user->nickname))));
 
         if ($this->profile->bio) {
             $this->element('meta', array('name' => 'description',
@@ -248,6 +229,15 @@ class ShowstreamAction extends Action
                                     'height' => AVATAR_PROFILE_SIZE,
                                     'alt' => $this->profile->nickname));
         $this->elementEnd('dd');
+
+        $user = User::staticGet('id', $this->profile->id);
+        $cur = common_current_user();
+        if ($cur && $cur->id == $user->id) {
+            $this->elementStart('dd');
+            $this->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar'));
+            $this->elementEnd('dd');
+        }
+
         $this->elementEnd('dl');
 
         $this->elementStart('dl', 'entity_nickname');
@@ -256,7 +246,7 @@ class ShowstreamAction extends Action
         $hasFN = ($this->profile->fullname) ? 'nickname url uid' : 'fn nickname url uid';
         $this->element('a', array('href' => $this->profile->profileurl,
                                   'rel' => 'me', 'class' => $hasFN),
-                            $this->profile->nickname);
+                       $this->profile->nickname);
         $this->elementEnd('dd');
         $this->elementEnd('dl');
 
@@ -272,7 +262,7 @@ class ShowstreamAction extends Action
         if ($this->profile->location) {
             $this->elementStart('dl', 'entity_location');
             $this->element('dt', null, _('Location'));
-            $this->element('dd', 'location', $this->profile->location);
+            $this->element('dd', 'label', $this->profile->location);
             $this->elementEnd('dl');
         }
 
@@ -302,11 +292,11 @@ class ShowstreamAction extends Action
             $this->elementStart('ul', 'tags xoxo');
             foreach ($tags as $tag) {
                 $this->elementStart('li');
-                $this->element('span', 'mark_hash', '#');
-                $this->element('a', array('rel' => 'tag',
-                                          'href' => common_local_url('peopletag',
-                                                                     array('tag' => $tag))),
-                               $tag);
+                // Avoid space by using raw output.
+                $pt = '<span class="mark_hash">#</span><a rel="tag" href="' .
+                      common_local_url('peopletag', array('tag' => $tag)) .
+                      '">' . $tag . '</a>';
+                $this->raw($pt);
                 $this->elementEnd('li');
             }
             $this->elementEnd('ul');
@@ -324,7 +314,7 @@ class ShowstreamAction extends Action
             $this->elementStart('li', 'entity_edit');
             $this->element('a', array('href' => common_local_url('profilesettings'),
                                       'title' => _('Edit profile settings')),
-                                      _('Edit'));
+                           _('Edit'));
             $this->elementEnd('li');
         }
 
@@ -346,9 +336,8 @@ class ShowstreamAction extends Action
             $this->elementEnd('li');
         }
 
-        $user = User::staticGet('id', $this->profile->id);
         if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) {
-           $this->elementStart('li', 'entity_send-a-message');
+            $this->elementStart('li', 'entity_send-a-message');
             $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)),
                                       'title' => _('Send a direct message to this user')),
                            _('Message'));
@@ -490,7 +479,7 @@ class ShowstreamAction extends Action
         $this->elementStart('dl', 'entity_member-since');
         $this->element('dt', null, _('Member since'));
         $this->element('dd', null, date('j M Y',
-                                                 strtotime($this->profile->created)));
+                                        strtotime($this->profile->created)));
         $this->elementEnd('dl');
 
         $this->elementStart('dl', 'entity_subscriptions');
index 4401f892a94603df41e2b06748bba5cca35bcb05..231f2c299280580842099f251541fbc9d2619204 100644 (file)
@@ -37,9 +37,9 @@ class TagAction extends Action
         }
 
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-        
+
         common_set_returnto($this->selfUrl());
-        
+
         return true;
     }
 
@@ -61,12 +61,11 @@ class TagAction extends Action
         $this->showPage();
     }
 
-    function showFeeds()
+    function getFeeds()
     {
-        $this->element('link', array('rel' => 'alternate',
-                                     'href' => common_local_url('tagrss', array('tag' => $this->tag)),
-                                     'type' => 'application/rss+xml',
-                                     'title' => sprintf(_('Feed for tag %s'), $this->tag)));
+        return array(new Feed(Feed::RSS1,
+                              common_local_url('tagrss', array('tag' => $this->tag)),
+                              sprintf(_('Feed for tag %s'), $this->tag)));
     }
 
     function showPageNotice()
@@ -74,15 +73,6 @@ class TagAction extends Action
         return sprintf(_('Messages tagged "%s", most recent first'), $this->tag);
     }
 
-    function showExportData()
-    {
-        $fl = new FeedList($this);
-        $fl->show(array(0=>array('href'=>common_local_url('tagrss', array('tag' => $this->tag)),
-                                 'type' => 'rss',
-                                 'version' => 'RSS 1.0',
-                                 'item' => 'tagrss')));
-    }
-
     function showContent()
     {
         $notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
index 3e8a12fd693d3d78a3717530dee6e31ec35795b9..0d18945a09e88b2e8d2cc9b2704584cc93184be1 100644 (file)
@@ -110,7 +110,7 @@ class TagotherAction extends Action
         if ($this->profile->location) {
             $this->elementStart('dl', 'entity_location');
             $this->element('dt', null, _('Location'));
-            $this->element('dd', 'location', $this->profile->location);
+            $this->element('dd', 'label', $this->profile->location);
             $this->elementEnd('dl');
         }
         if ($this->profile->homepage) {
@@ -135,7 +135,8 @@ class TagotherAction extends Action
                                            'id' => 'form_tag_user',
                                            'class' => 'form_settings',
                                            'name' => 'tagother',
-                                           'action' => $this->selfUrl()));
+                                           'action' => common_local_url('tagother', array('id' => $this->profile->id))));
+
         $this->elementStart('fieldset');
         $this->element('legend', null, _('Tag user'));
         $this->hidden('token', common_session_token());
index b7c09cc9dc7a3d8c3c7fbd9455e2f411613b673b..c19cd370d927f2b2bb6e8f5bf044f328f5e12926 100644 (file)
@@ -24,20 +24,19 @@ require_once(INSTALLDIR.'/lib/twitterapi.php');
 class TwitapiaccountAction extends TwitterapiAction
 {
 
-       function verify_credentials($args, $apidata)
+    function verify_credentials($args, $apidata)
     {
-
-               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);
-               }
-
-       }
+        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 {
+            header('Content-Type: text/html; charset=utf-8');
+            print 'Authorized';
+        }
+    }
 
     function end_session($args, $apidata)
     {
index 18e24c0f582c1a1781dc09f058510e3c2bd32276..216835026de7806b1a5c9d65211c9102cfa88433 100644 (file)
@@ -204,7 +204,7 @@ class TwitapistatusesAction extends TwitterapiAction
         # FriendFeed's SUP protocol
         # Also added RSS and Atom feeds
 
-        $suplink = common_local_url('sup', null, $user->id);
+        $suplink = common_local_url('sup', null, null, $user->id);
         header('X-SUP-ID: '.$suplink);
 
         # XXX: since
@@ -470,19 +470,28 @@ class TwitapistatusesAction extends TwitterapiAction
         return $this->subscriptions($apidata, 'subscribed', 'subscriber');
     }
 
-    function followers($args, $apidata)
+    function friendsIDs($args, $apidata)
     {
         parent::handle($args);
+        return $this->subscriptions($apidata, 'subscribed', 'subscriber', true);
+    }
 
+    function followers($args, $apidata)
+    {
+        parent::handle($args);
         return $this->subscriptions($apidata, 'subscriber', 'subscribed');
     }
 
-    function subscriptions($apidata, $other_attr, $user_attr)
+    function followersIDs($args, $apidata)
     {
+        parent::handle($args);
+        return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
+    }
 
-        # XXX: lite
+    function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false)
+    {
 
-        $this->auth_user = $apidate['user'];
+        $this->auth_user = $apidata['user'];
         $user = $this->get_user($apidata['api_arg'], $apidata);
 
         if (!$user) {
@@ -514,7 +523,10 @@ class TwitapistatusesAction extends TwitterapiAction
         }
 
         $sub->orderBy('created DESC');
-        $sub->limit(($page-1)*100, 100);
+
+        if (!$onlyIDs) {
+            $sub->limit(($page-1)*100, 100);
+        }
 
         $others = array();
 
@@ -529,7 +541,13 @@ class TwitapistatusesAction extends TwitterapiAction
         $type = $apidata['content-type'];
 
         $this->init_document($type);
-        $this->show_profiles($others, $type);
+
+        if ($onlyIDs) {
+            $this->showIDs($others, $type);
+        } else {
+            $this->show_profiles($others, $type);
+        }
+
         $this->end_document($type);
     }
 
@@ -555,6 +573,28 @@ class TwitapistatusesAction extends TwitterapiAction
         }
     }
 
+    function showIDs($profiles, $type)
+    {
+        switch ($type) {
+         case 'xml':
+            $this->elementStart('ids');
+            foreach ($profiles as $profile) {
+                $this->element('id', null, $profile->id);
+            }
+            $this->elementEnd('ids');
+            break;
+         case 'json':
+            $ids = array();
+            foreach ($profiles as $profile) {
+                $ids[] = (int)$profile->id;
+            }
+            print json_encode($ids);
+            break;
+         default:
+            $this->clientError(_('unsupported file type'));
+        }
+    }
+
     function featured($args, $apidata)
     {
         parent::handle($args);
index 2d41469bba01b9c2456af282819b42d78052e96b..a79859bbf0a9205c40c25022913477b0a4d5193c 100644 (file)
@@ -32,6 +32,7 @@ if (!defined('LACONICA')) {
 }
 
 require_once INSTALLDIR.'/lib/connectsettingsaction.php';
+require_once INSTALLDIR.'/lib/twitter.php';
 
 define('SUBSCRIPTIONS', 80);
 
@@ -90,7 +91,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         $fuser = null;
 
-        $flink = Foreign_link::getByUserID($user->id, 1); // 1 == Twitter
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
 
         if ($flink) {
             $fuser = $flink->getForeignUser();
@@ -358,7 +359,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         $flink->user_id     = $user->id;
         $flink->foreign_id  = $twit_user->id;
-        $flink->service     = 1; // Twitter
+        $flink->service     = TWITTER_SERVICE;
         $flink->credentials = $password;
         $flink->created     = common_sql_now();
 
index 7455a41a6f1e26c08c02a4e315fdff56dd75e8de..ed17ceec977723ecd20899d380a645f183eeea20 100644 (file)
@@ -330,7 +330,13 @@ class UserauthorizationAction extends Action
     {
         $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
         copy($url, $temp_filename);
-        return $profile->setOriginal($temp_filename);
+        $imagefile = new ImageFile($profile->id, $temp_filename);
+        $filename = Avatar::filename($profile->id,
+                                     image_type_to_extension($imagefile->type),
+                                     null,
+                                     common_timestamp());
+        rename($temp_filename, Avatar::path($filename));
+        return $profile->setOriginal($filename);
     }
 
     function showAcceptMessage($tok)
index 04855cccaded30a05ac67a0789647aae8b7e882f..a3e5a3aab710a05699ef64eeffb5c6084397d35a 100644 (file)
@@ -46,13 +46,13 @@ class UserrssAction extends Rss10Action
     {
 
         $user = $this->user;
-        
+
         if (is_null($user)) {
             return null;
         }
-        
+
         $notice = $user->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
-        
+
         while ($notice->fetch()) {
             $notices[] = clone($notice);
         }
@@ -87,10 +87,10 @@ class UserrssAction extends Rss10Action
     }
 
     # override parent to add X-SUP-ID URL
-    
+
     function initRss($limit=0)
     {
-        $url = common_local_url('sup', null, $this->user->id);
+        $url = common_local_url('sup', null, null, $this->user->id);
         header('X-SUP-ID: '.$url);
         parent::initRss($limit);
     }
@@ -100,4 +100,3 @@ class UserrssAction extends Rss10Action
         return true;
     }
 }
-
diff --git a/bin/flowplayer-3.0.5.swf b/bin/flowplayer-3.0.5.swf
new file mode 100644 (file)
index 0000000..05b64a0
Binary files /dev/null and b/bin/flowplayer-3.0.5.swf differ
diff --git a/bin/flowplayer.audio-3.0.3.swf b/bin/flowplayer.audio-3.0.3.swf
new file mode 100644 (file)
index 0000000..ef85f1b
Binary files /dev/null and b/bin/flowplayer.audio-3.0.3.swf differ
diff --git a/bin/flowplayer.controls-3.0.3.swf b/bin/flowplayer.controls-3.0.3.swf
new file mode 100644 (file)
index 0000000..09a27e8
Binary files /dev/null and b/bin/flowplayer.controls-3.0.3.swf differ
diff --git a/classes/Channel.php b/classes/Channel.php
deleted file mode 100644 (file)
index fdeff21..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * 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/>.
- */
-
-if (!defined('LACONICA')) { exit(1); }
-
-class Channel
-{
-
-    function on($user)
-    {
-        return false;
-    }
-
-    function off($user)
-    {
-        return false;
-    }
-
-    function output($user, $text)
-    {
-        return false;
-    }
-
-    function error($user, $text)
-    {
-        return false;
-    }
-
-    function source()
-    {
-        return null;
-    }
-}
-
-class XMPPChannel extends Channel
-{
-
-    var $conn = null;
-
-    function source()
-    {
-        return 'xmpp';
-    }
-
-    function __construct($conn)
-    {
-        $this->conn = $conn;
-    }
-
-    function on($user)
-    {
-        return $this->set_notify($user, 1);
-    }
-
-    function off($user)
-    {
-        return $this->set_notify($user, 0);
-    }
-
-    function output($user, $text)
-    {
-        $text = '['.common_config('site', 'name') . '] ' . $text;
-        jabber_send_message($user->jabber, $text);
-    }
-
-    function error($user, $text)
-    {
-        $text = '['.common_config('site', 'name') . '] ' . $text;
-        jabber_send_message($user->jabber, $text);
-    }
-
-    function set_notify(&$user, $notify)
-    {
-        $orig = clone($user);
-        $user->jabbernotify = $notify;
-        $result = $user->update($orig);
-        if (!$result) {
-            $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
-            common_log(LOG_ERR,
-                       'Could not set notify flag to ' . $notify .
-                       ' for user ' . common_log_objstring($user) .
-                       ': ' . $last_error->message);
-            return false;
-        } else {
-            common_log(LOG_INFO,
-                       'User ' . $user->nickname . ' set notify flag to ' . $notify);
-            return true;
-        }
-    }
-}
-
-class WebChannel extends Channel
-{
-    var $out = null;
-
-    function __construct($out=null)
-    {
-        $this->out = $out;
-    }
-
-    function source()
-    {
-        return 'web';
-    }
-
-    function on($user)
-    {
-        return false;
-    }
-
-    function off($user)
-    {
-        return false;
-    }
-
-    function output($user, $text)
-    {
-        # XXX: buffer all output and send it at the end
-        # XXX: even better, redirect to appropriate page
-        #      depending on what command was run
-        $this->out->startHTML();
-        $this->out->elementStart('head');
-        $this->out->element('title', null, _('Command results'));
-        $this->out->elementEnd('head');
-        $this->out->elementStart('body');
-        $this->out->element('p', array('id' => 'command_result'), $text);
-        $this->out->elementEnd('body');
-        $this->out->endHTML();
-    }
-
-    function error($user, $text)
-    {
-        common_user_error($text);
-    }
-}
-
-class AjaxWebChannel extends WebChannel
-{
-    function output($user, $text)
-    {
-        $this->out->startHTML('text/xml;charset=utf-8');
-        $this->out->elementStart('head');
-        $this->out->element('title', null, _('Command results'));
-        $this->out->elementEnd('head');
-        $this->out->elementStart('body');
-        $this->out->element('p', array('id' => 'command_result'), $text);
-        $this->out->elementEnd('body');
-        $this->out->endHTML();
-    }
-
-    function error($user, $text)
-    {
-        $this->out->startHTML('text/xml;charset=utf-8');
-        $this->out->elementStart('head');
-        $this->out->element('title', null, _('Ajax Error'));
-        $this->out->elementEnd('head');
-        $this->out->elementStart('body');
-        $this->out->element('p', array('id' => 'error'), $text);
-        $this->out->elementEnd('body');
-        $this->out->endHTML();
-    }
-}
-
-class MailChannel extends Channel
-{
-
-    var $addr = null;
-
-    function source()
-    {
-        return 'mail';
-    }
-
-    function __construct($addr=null)
-    {
-        $this->addr = $addr;
-    }
-
-    function on($user)
-    {
-        return $this->set_notify($user, 1);
-    }
-
-    function off($user)
-    {
-        return $this->set_notify($user, 0);
-    }
-
-    function output($user, $text)
-    {
-
-        $headers['From'] = $user->incomingemail;
-        $headers['To'] = $this->addr;
-
-        $headers['Subject'] = _('Command complete');
-
-        return mail_send(array($this->addr), $headers, $text);
-    }
-
-    function error($user, $text)
-    {
-
-        $headers['From'] = $user->incomingemail;
-        $headers['To'] = $this->addr;
-
-        $headers['Subject'] = _('Command failed');
-
-        return mail_send(array($this->addr), $headers, $text);
-    }
-
-    function set_notify($user, $value)
-    {
-        $orig = clone($user);
-        $user->smsnotify = $value;
-        $result = $user->update($orig);
-        if (!$result) {
-            common_log_db_error($user, 'UPDATE', __FILE__);
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/classes/Command.php b/classes/Command.php
deleted file mode 100644 (file)
index eacbdac..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * 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/>.
- */
-
-if (!defined('LACONICA')) { exit(1); }
-
-require_once(INSTALLDIR.'/classes/Channel.php');
-
-class Command
-{
-    
-    var $user = null;
-    
-    function __construct($user=null)
-    {
-        $this->user = $user;
-    }
-    
-    function execute($channel)
-    {
-        return false;
-    }
-}
-
-class UnimplementedCommand extends Command
-{
-    function execute($channel)
-    {
-        $channel->error($this->user, _("Sorry, this command is not yet implemented."));
-    }
-}
-
-class TrackingCommand extends UnimplementedCommand
-{
-}
-
-class TrackOffCommand extends UnimplementedCommand
-{
-}
-
-class TrackCommand extends UnimplementedCommand
-{
-    var $word = null;
-    function __construct($user, $word)
-    {
-        parent::__construct($user);
-        $this->word = $word;
-    }
-}
-
-class UntrackCommand extends UnimplementedCommand
-{
-    var $word = null;
-    function __construct($user, $word)
-    {
-        parent::__construct($user);
-        $this->word = $word;
-    }
-}
-
-class NudgeCommand extends UnimplementedCommand
-{
-    var $other = null;
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-}
-
-class InviteCommand extends UnimplementedCommand
-{
-    var $other = null;
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-}
-
-class StatsCommand extends Command
-{
-    function execute($channel)
-    {
-
-        $subs = new Subscription();
-        $subs->subscriber = $this->user->id;
-        $subs_count = (int) $subs->count() - 1;
-
-        $subbed = new Subscription();
-        $subbed->subscribed = $this->user->id;
-        $subbed_count = (int) $subbed->count() - 1;
-
-        $notices = new Notice();
-        $notices->profile_id = $this->user->id;
-        $notice_count = (int) $notices->count();
-        
-        $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
-                                   "Subscribers: %2\$s\n".
-                                   "Notices: %3\$s"),
-                                 $subs_count,
-                                 $subbed_count,
-                                 $notice_count));
-    }
-}
-
-class FavCommand extends Command
-{
-    
-    var $other = null;
-    
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    
-    function execute($channel)
-    {
-        
-        $recipient = 
-          common_relative_profile($this->user, common_canonical_nickname($this->other));
-        
-        if (!$recipient) {
-            $channel->error($this->user, _('No such user.'));
-            return;
-        }
-        $notice = $recipient->getCurrentNotice();
-        if (!$notice) {
-            $channel->error($this->user, _('User has no last notice'));
-            return;
-        }
-        
-        $fave = Fave::addNew($this->user, $notice);
-
-        if (!$fave) {
-            $channel->error($this->user, _('Could not create favorite.'));
-            return;
-        }
-
-        $other = User::staticGet('id', $recipient->id);
-        
-        if ($other && $other->id != $user->id) {
-            if ($other->email && $other->emailnotifyfav) {
-                mail_notify_fave($other, $this->user, $notice);
-            }
-        }
-        
-        $this->user->blowFavesCache();
-        
-        $channel->output($this->user, _('Notice marked as fave.'));
-    }
-}
-
-class WhoisCommand extends Command
-{
-    var $other = null;
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    
-    function execute($channel)
-    {
-        $recipient = 
-          common_relative_profile($this->user, common_canonical_nickname($this->other));
-        
-        if (!$recipient) {
-            $channel->error($this->user, _('No such user.'));
-            return;
-        }
-        
-        $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
-                         $recipient->profileurl);
-        if ($recipient->fullname) {
-            $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
-        }
-        if ($recipient->location) {
-            $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
-        }
-        if ($recipient->homepage) {
-            $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
-        }
-        if ($recipient->bio) {
-            $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
-        }
-        $channel->output($this->user, $whois);
-    }
-}
-
-class MessageCommand extends Command
-{
-    var $other = null;
-    var $text = null;
-    function __construct($user, $other, $text)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-        $this->text = $text;
-    }
-    
-    function execute($channel)
-    {
-        $other = User::staticGet('nickname', common_canonical_nickname($this->other));
-        $len = mb_strlen($this->text);
-        if ($len == 0) {
-            $channel->error($this->user, _('No content!'));
-            return;
-        } else if ($len > 140) {
-            $content = common_shorten_links($content);
-            if (mb_strlen($content) > 140) {
-                $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len));
-                return;
-            }
-        }
-        
-        if (!$other) {
-            $channel->error($this->user, _('No such user.'));
-            return;
-        } else if (!$this->user->mutuallySubscribed($other)) {
-            $channel->error($this->user, _('You can\'t send a message to this user.'));
-            return;
-        } else if ($this->user->id == $other->id) {
-            $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.'));
-            return;
-        }
-        $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
-        if ($message) {
-            $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
-        } else {
-            $channel->error($this->user, _('Error sending direct message.'));
-        }
-    }
-}
-
-class GetCommand extends Command
-{
-    
-    var $other = null;
-    
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    
-    function execute($channel)
-    {
-        $target_nickname = common_canonical_nickname($this->other);
-        
-        $target =
-          common_relative_profile($this->user, $target_nickname);
-
-        if (!$target) {
-            $channel->error($this->user, _('No such user.'));
-            return;
-        }
-        $notice = $target->getCurrentNotice();
-        if (!$notice) {
-            $channel->error($this->user, _('User has no last notice'));
-            return;
-        }
-        $notice_content = $notice->content;
-        
-        $channel->output($this->user, $target_nickname . ": " . $notice_content);
-    }
-}
-
-class SubCommand extends Command
-{
-    
-    var $other = null;
-    
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    
-    function execute($channel)
-    {
-        
-        if (!$this->other) {
-            $channel->error($this->user, _('Specify the name of the user to subscribe to'));
-            return;
-        }
-        
-        $result = subs_subscribe_user($this->user, $this->other);
-        
-        if ($result == 'true') {
-            $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
-        } else {
-            $channel->error($this->user, $result);
-        }
-    }
-}
-
-class UnsubCommand extends Command
-{
-
-    var $other = null;
-    
-    function __construct($user, $other)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-
-    function execute($channel)
-    {
-        if(!$this->other) {
-            $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
-            return;
-        }
-        
-        $result=subs_unsubscribe_user($this->user, $this->other);
-        
-        if ($result) {
-            $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
-        } else {
-            $channel->error($this->user, $result);
-        }
-    }
-}
-
-class OffCommand extends Command
-{
-    var $other = null;
-    function __construct($user, $other=null)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    function execute($channel)
-    {
-        if ($other) {
-            $channel->error($this->user, _("Command not yet implemented."));
-        } else {
-            if ($channel->off($this->user)) {
-                $channel->output($this->user, _('Notification off.'));
-            } else {
-                $channel->error($this->user, _('Can\'t turn off notification.'));
-            }
-        }
-    }
-}
-
-class OnCommand extends Command
-{
-    var $other = null;
-    function __construct($user, $other=null)
-    {
-        parent::__construct($user);
-        $this->other = $other;
-    }
-    
-    function execute($channel)
-    {
-        if ($other) {
-            $channel->error($this->user, _("Command not yet implemented."));
-        } else {
-            if ($channel->on($this->user)) {
-                $channel->output($this->user, _('Notification on.'));
-            } else {
-                $channel->error($this->user, _('Can\'t turn on notification.'));
-            }
-        }
-    }
-}
-
-class HelpCommand extends Command
-{
-    function execute($channel)
-    {
-        $channel->output($this->user,
-                         _("Commands:\n".
-                           "on - turn on notifications\n".
-                           "off - turn off notifications\n".
-                           "help - show this help\n".
-                           "follow <nickname> - subscribe to user\n".
-                           "leave <nickname> - unsubscribe from user\n".
-                           "d <nickname> <text> - direct message to user\n".
-                           "get <nickname> - get last notice from user\n".
-                           "whois <nickname> - get profile info on user\n".
-                           "fav <nickname> - add user's last notice as a 'fave'\n".
-                           "stats - get your stats\n".
-                           "stop - same as 'off'\n".
-                           "quit - same as 'off'\n".
-                           "sub <nickname> - same as 'follow'\n".
-                           "unsub <nickname> - same as 'leave'\n".
-                           "last <nickname> - same as 'get'\n".
-                           "on <nickname> - not yet implemented.\n".
-                           "off <nickname> - not yet implemented.\n".                           
-                           "nudge <nickname> - not yet implemented.\n".
-                           "invite <phone number> - not yet implemented.\n".
-                           "track <word> - not yet implemented.\n".
-                           "untrack <word> - not yet implemented.\n".
-                           "track off - not yet implemented.\n".
-                           "untrack all - not yet implemented.\n".
-                           "tracks - not yet implemented.\n".
-                           "tracking - not yet implemented.\n"));
-    }
-}
diff --git a/classes/CommandInterpreter.php b/classes/CommandInterpreter.php
deleted file mode 100644 (file)
index 0679f54..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * 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/>.
- */
-
-if (!defined('LACONICA')) { exit(1); }
-
-require_once(INSTALLDIR.'/classes/Command.php');
-
-class CommandInterpreter
-{
-
-    function handle_command($user, $text)
-    {
-        # XXX: localise
-
-        $text = preg_replace('/\s+/', ' ', trim($text));
-        list($cmd, $arg) = explode(' ', $text, 2);
-
-        # We try to support all the same commands as Twitter, see
-        # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
-        # There are a few compatibility commands from earlier versions of
-        # Laconica
-
-        switch(strtolower($cmd)) {
-         case 'help':
-            if ($arg) {
-                return null;
-            }
-            return new HelpCommand($user);
-         case 'on':
-            if ($arg) {
-                list($other, $extra) = explode(' ', $arg, 2);
-                if ($extra) {
-                    return null;
-                } else {
-                    return new OnCommand($user, $other);
-                }
-            } else {
-                return new OnCommand($user);
-            }
-         case 'off':
-            if ($arg) {
-                list($other, $extra) = explode(' ', $arg, 2);
-                if ($extra) {
-                    return null;
-                } else {
-                    return new OffCommand($user, $other);
-                }
-            } else {
-                return new OffCommand($user);
-            }
-         case 'stop':
-         case 'quit':
-            if ($arg) {
-                return null;
-            } else {
-                return new OffCommand($user);
-            }
-         case 'follow':
-         case 'sub':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new SubCommand($user, $other);
-            }
-         case 'leave':
-         case 'unsub':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new UnsubCommand($user, $other);
-            }
-         case 'get':
-         case 'last':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new GetCommand($user, $other);
-            }
-         case 'd':
-         case 'dm':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if (!$extra) {
-                return null;
-            } else {
-                return new MessageCommand($user, $other, $extra);
-            }
-         case 'whois':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new WhoisCommand($user, $other);
-            }
-         case 'fav':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new FavCommand($user, $other);
-            }
-         case 'nudge':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new NudgeCommand($user, $other);
-            }
-         case 'stats':
-            if ($arg) {
-                return null;
-            }
-            return new StatsCommand($user);
-         case 'invite':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else {
-                return new InviteCommand($user, $other);
-            }
-         case 'track':
-            if (!$arg) {
-                return null;
-            }
-            list($word, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else if ($word == 'off') {
-                return new TrackOffCommand($user);
-            } else {
-                return new TrackCommand($user, $word);
-            }
-         case 'untrack':
-            if (!$arg) {
-                return null;
-            }
-            list($word, $extra) = explode(' ', $arg, 2);
-            if ($extra) {
-                return null;
-            } else if ($word == 'all') {
-                return new TrackOffCommand($user);
-            } else {
-                return new UntrackCommand($user, $word);
-            }
-         case 'tracks':
-         case 'tracking':
-            if ($arg) {
-                return null;
-            }
-            return new TrackingCommand($user);
-         default:
-            return false;
-        }
-    }
-}
-
index 32998836862043f1ca74a9602f478a9328440774..8300667fa469c5d8c3f5d1d73c0c415e9cd38f0a 100644 (file)
@@ -34,22 +34,23 @@ class Notice extends Memcached_DataObject
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
 
-    public $__table = 'notice';                             // table name
-    public $id;                                 // int(4)    primary_key not_null
-    public $profile_id;                         // int(4)     not_null
+    public $__table = 'notice';                          // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $profile_id;                      // int(4)   not_null
     public $uri;                             // varchar(255)  unique_key
     public $content;                         // varchar(140)
-    public $rendered;                         // text()
+    public $rendered;                        // text()
     public $url;                             // varchar(255)
-    public $created;                         // datetime()     not_null
-    public $modified;                         // timestamp()      not_null default_CURRENT_TIMESTAMP
-    public $reply_to;                         // int(4)
-    public $is_local;                         // tinyint(1)
-    public $source;                             // varchar(32)
+    public $created;                         // datetime()   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+    public $reply_to;                        // int(4)
+    public $is_local;                        // tinyint(1)
+    public $source;                          // varchar(32)
 
     /* Static get */
-    function staticGet($k,$v=null)
-    { return Memcached_DataObject::staticGet('Notice',$k,$v); }
+    function staticGet($k,$v=NULL) {
+        return Memcached_DataObject::staticGet('Notice',$k,$v);
+    }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
@@ -94,23 +95,28 @@ class Notice extends Memcached_DataObject
         /* Add them to the database */
         foreach(array_unique($match[1]) as $hashtag) {
             /* elide characters we don't want in the tag */
-            $hashtag = common_canonical_tag($hashtag);
-
-            $tag = DB_DataObject::factory('Notice_tag');
-            $tag->notice_id = $this->id;
-            $tag->tag = $hashtag;
-            $tag->created = $this->created;
-            $id = $tag->insert();
-            if (!$id) {
-                $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
-                common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message);
-                common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message));
-                return;
-            }
+            $this->saveTag($hashtag);
         }
         return true;
     }
 
+    function saveTag($hashtag)
+    {
+        $hashtag = common_canonical_tag($hashtag);
+
+        $tag = new Notice_tag();
+        $tag->notice_id = $this->id;
+        $tag->tag = $hashtag;
+        $tag->created = $this->created;
+        $id = $tag->insert();
+
+        if (!$id) {
+            throw new ServerException(sprintf(_('DB error inserting hashtag: %s'),
+                                              $last_error->message));
+            return;
+        }
+    }
+
     static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) {
 
         $profile = Profile::staticGet($profile_id);
@@ -136,10 +142,12 @@ class Notice extends Memcached_DataObject
         $notice->profile_id = $profile_id;
 
         $blacklist = common_config('public', 'blacklist');
+        $autosource = common_config('public', 'autosource');
 
         # Blacklisted are non-false, but not 1, either
 
-        if ($blacklist && in_array($profile_id, $blacklist)) {
+        if (($blacklist && in_array($profile_id, $blacklist)) ||
+            ($source && $autosource && in_array($source, $autosource))) {
             $notice->is_local = -1;
         } else {
             $notice->is_local = $is_local;
@@ -154,32 +162,37 @@ class Notice extends Memcached_DataObject
         $notice->source = $source;
         $notice->uri = $uri;
 
-        $id = $notice->insert();
-
-        if (!$id) {
-            common_log_db_error($notice, 'INSERT', __FILE__);
-            return _('Problem saving notice.');
-        }
+        if (Event::handle('StartNoticeSave', array(&$notice))) {
 
-        # Update the URI after the notice is in the database
-        if (!$uri) {
-            $orig = clone($notice);
-            $notice->uri = common_notice_uri($notice);
+            $id = $notice->insert();
 
-            if (!$notice->update($orig)) {
-                common_log_db_error($notice, 'UPDATE', __FILE__);
+            if (!$id) {
+                common_log_db_error($notice, 'INSERT', __FILE__);
                 return _('Problem saving notice.');
             }
-        }
 
-        # XXX: do we need to change this for remote users?
+            # Update the URI after the notice is in the database
+            if (!$uri) {
+                $orig = clone($notice);
+                $notice->uri = common_notice_uri($notice);
+
+                if (!$notice->update($orig)) {
+                    common_log_db_error($notice, 'UPDATE', __FILE__);
+                    return _('Problem saving notice.');
+                }
+            }
+
+            # XXX: do we need to change this for remote users?
+
+            $notice->saveReplies();
+            $notice->saveTags();
+            $notice->saveGroups();
 
-        $notice->saveReplies();
-        $notice->saveTags();
-        $notice->saveGroups();
+            $notice->addToInboxes();
+            $notice->query('COMMIT');
 
-        $notice->addToInboxes();
-               $notice->query('COMMIT');
+            Event::handle('EndNoticeSave', array($notice));
+        }
 
         # Clear the cache for subscribed users, so they'll update at next request
         # XXX: someone clever could prepend instead of clearing the cache
@@ -614,6 +627,15 @@ class Notice extends Memcached_DataObject
                 continue;
             }
 
+            // we automatically add a tag for every group name, too
+
+            $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname),
+                                           'notice_id' => $this->id));
+
+            if (is_null($tag)) {
+                $this->saveTag($nickname);
+            }
+
             if ($profile->isMember($group)) {
 
                 $gi = new Group_inbox();
@@ -725,10 +747,19 @@ class Notice extends Memcached_DataObject
                         if (!$id) {
                             common_log_db_error($reply, 'INSERT', __FILE__);
                             return;
+                        } else {
+                            $replied[$recipient->id] = 1;
                         }
                     }
                 }
             }
         }
+
+        foreach (array_keys($replied) as $recipient) {
+            $user = User::staticGet('id', $recipient);
+            if ($user) {
+                mail_notify_attn($user, $this);
+            }
+        }
     }
 }
index 94f9296d602c92cc1c121cd498ad988ce4b6024d..0365973f565b0b5879d8e628a82a51b343e174a8 100644 (file)
@@ -19,7 +19,7 @@
 
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Notice_tag extends Memcached_DataObject 
+class Notice_tag extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -35,9 +35,9 @@ class Notice_tag extends Memcached_DataObject
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
-    
+
     static function getStream($tag, $offset=0, $limit=20) {
-        $qry = 
+        $qry =
           'SELECT notice.* ' .
           'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' .
           'WHERE notice_tag.tag = "%s" ';
@@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
                                  'notice_tag:notice_stream:' . common_keyize($tag),
                                  $offset, $limit);
     }
-    
+
     function blowCache()
     {
         $cache = common_memcache();
@@ -54,4 +54,9 @@ class Notice_tag extends Memcached_DataObject
             $cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag));
         }
     }
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
+    }
 }
index a6a1b11b9f502d7a6a27d155ce840eb92154a0ae..40cf18df67406da2e16eb22089a21e328383e1c8 100644 (file)
@@ -40,6 +40,7 @@ class User extends Memcached_DataObject
     public $emailnotifyfav;                  // tinyint(1)   default_1
     public $emailnotifynudge;                // tinyint(1)   default_1
     public $emailnotifymsg;                  // tinyint(1)   default_1
+    public $emailnotifyattn;                 // tinyint(1)   default_1
     public $emailmicroid;                    // tinyint(1)   default_1
     public $language;                        // varchar(50)
     public $timezone;                        // varchar(50)
@@ -62,8 +63,10 @@ class User extends Memcached_DataObject
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
     /* Static get */
-    function staticGet($k,$v=null)
-    { return Memcached_DataObject::staticGet('User',$k,$v); }
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('User',$k,$v);
+    }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
@@ -180,16 +183,16 @@ class User extends Memcached_DataObject
         $profile->nickname = $nickname;
         $profile->profileurl = common_profile_url($nickname);
 
-        if ($fullname) {
+        if (!empty($fullname)) {
             $profile->fullname = $fullname;
         }
-        if ($homepage) {
+        if (!empty($homepage)) {
             $profile->homepage = $homepage;
         }
-        if ($bio) {
+        if (!empty($bio)) {
             $profile->bio = $bio;
         }
-        if ($location) {
+        if (!empty($location)) {
             $profile->location = $location;
         }
 
@@ -197,7 +200,7 @@ class User extends Memcached_DataObject
 
         $id = $profile->insert();
 
-        if (!$id) {
+        if (empty($id)) {
             common_log_db_error($profile, 'INSERT', __FILE__);
             return false;
         }
@@ -207,13 +210,13 @@ class User extends Memcached_DataObject
         $user->id = $id;
         $user->nickname = $nickname;
 
-        if ($password) { # may not have a password for OpenID users
+        if (!empty($password)) { # may not have a password for OpenID users
             $user->password = common_munge_password($password, $id);
         }
 
         # Users who respond to invite email have proven their ownership of that address
 
-        if ($code) {
+        if (!empty($code)) {
             $invite = Invitation::staticGet($code);
             if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) {
                 $user->email = $invite->address;
@@ -250,7 +253,7 @@ class User extends Memcached_DataObject
             return false;
         }
 
-        if ($email && !$user->email) {
+        if (!empty($email) && !$user->email) {
 
             $confirm = new Confirm_address();
             $confirm->code = common_confirmation_code(128);
@@ -265,7 +268,7 @@ class User extends Memcached_DataObject
             }
         }
 
-        if ($code && $user->email) {
+        if (!empty($code) && $user->email) {
             $user->emailChanged();
         }
 
index 255122a97f74e0f988e56d1dedc20f20fa92a397..5fd2cd1f86661e182414198509d29032d8693d06 100755 (executable)
@@ -292,7 +292,8 @@ created = 142
 modified = 384
 
 [sms_carrier__keys]
-id = N
+id = K
+name = U
 
 [subscription]
 subscriber = 129
@@ -331,6 +332,7 @@ emailnotifysub = 17
 emailnotifyfav = 17
 emailnotifynudge = 17
 emailnotifymsg = 17
+emailnotifyattn = 17
 emailmicroid = 17
 language = 2
 timezone = 2
index a2c5801f45c41995eafe64f1f8a2f40900dd087e..6e55eaffc8bdf9190fafd0e735008ed787360af1 100644 (file)
@@ -18,6 +18,8 @@ $config['site']['server'] = 'localhost';
 $config['site']['path'] = 'laconica';
 #$config['site']['fancy'] = false;
 #$config['site']['theme'] = 'default';
+#To enable the built-in mobile style sheet, defaults to false.
+#$config['site']['mobile'] = true;
 #For contact email, defaults to $_SERVER["SERVER_ADMIN"]
 #$config['site']['email'] = 'admin@example.net';
 #Brought by...
@@ -107,6 +109,14 @@ $config['sphinx']['port'] = 3312;
 #$config['public']['blacklist'][] = 123;
 #$config['public']['blacklist'][] = 2307;
 
+#Mark certain notice sources as automatic and thus not
+#appropriate for public feed
+#$config['public]['autosource'][] = 'twitterfeed';
+#$config['public]['autosource'][] = 'rssdent';
+#$config['public]['autosource'][] = 'Ping.Fm';
+#$config['public]['autosource'][] = 'HelloTxt';
+#$config['public]['autosource'][] = 'Updating.Me';
+
 #Do notice broadcasts offline
 #If you use this, you must run the six offline daemons in the
 #background. See the README for details.
@@ -139,7 +149,7 @@ $config['sphinx']['port'] = 3312;
 #$config['profile']['banned'][] = 'hacker';
 #$config['profile']['banned'][] = 12345;
 
-# config section for the built-in Facebook application
+# Config section for the built-in Facebook application
 #$config['facebook']['apikey'] = 'APIKEY';
 #$config['facebook']['secret'] = 'SECRET';
 
diff --git a/db/carrier.sql b/db/carrier.sql
deleted file mode 100644 (file)
index 932f7c8..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-insert into sms_carrier
-    (name, email_pattern, created)
-values
-    ('3 River Wireless', '%s@sms.3rivers.net', now()),
-    ('7-11 Speakout', '%s@cingularme.com', now()),
-    ('Airtel (Karnataka, India)', '%s@airtelkk.com', now()),
-    ('Alaska Communications Systems', '%s@msg.acsalaska.com', now()),
-    ('Alltel Wireless', '%s@message.alltel.com', now()),
-    ('AT&T Wireless', '%s@txt.att.net', now()),
-    ('Bell Mobility (Canada)', '%s@txt.bell.ca', now()),
-    ('Boost Mobile', '%s@myboostmobile.com', now()),
-    ('Cellular One (Dobson)', '%s@mobile.celloneusa.com', now()),
-    ('Cincinnati Bell Wireless', '%s@gocbw.com', now()),
-    ('Cingular (Postpaid)', '%s@cingularme.com', now()),
-    ('Centennial Wireless', '%s@cwemail.com', now()),
-    ('Cingular (GoPhone prepaid)', '%s@cingularme.com', now()),
-    ('Claro (Nicaragua)', '%s@ideasclaro-ca.com', now()),
-    ('Comcel', '%s@comcel.com.co', now()),
-    ('Cricket', '%s@sms.mycricket.com', now()),
-    ('CTI', '%s@sms.ctimovil.com.ar', now()),
-    ('Emtel (Mauritius)', '%s@emtelworld.net', now()),
-    ('Fido (Canada)', '%s@fido.ca', now()),
-    ('General Communications Inc.', '%s@msg.gci.net', now()),
-    ('Globalstar', '%s@msg.globalstarusa.com', now()),
-    ('Helio', '%s@myhelio.com', now()),
-    ('Illinois Valley Cellular', '%s@ivctext.com', now()),
-    ('i wireless', '%s.iws@iwspcs.net', now()),
-    ('Meteor (Ireland)', '%s@sms.mymeteor.ie', now()),
-    ('Mero Mobile (Nepal)', '%s@sms.spicenepal.com', now()),
-    ('MetroPCS', '%s@mymetropcs.com', now()),
-    ('Movicom', '%s@movimensaje.com.ar', now()),
-    ('Mobitel (Sri Lanka)', '%s@sms.mobitel.lk', now()),
-    ('Movistar (Colombia)', '%s@movistar.com.co', now()),
-    ('MTN (South Africa)', '%s@sms.co.za', now()),
-    ('MTS (Canada)', '%s@text.mtsmobility.com', now()),
-    ('Nextel (Argentina)', '%s@nextel.net.ar', now()),
-    ('Orange (Poland)', '%s@orange.pl', now()),
-    ('Orange (UK)', '%s@orange.net', now()),
-    ('Personal (Argentina)', '%s@personal-net.com.ar', now()),
-    ('Plus GSM (Poland)', '%s@text.plusgsm.pl', now()),
-    ('President''s Choice (Canada)', '%s@txt.bell.ca', now()),
-    ('Qwest', '%s@qwestmp.com', now()),
-    ('Rogers (Canada)', '%s@pcs.rogers.com', now()),
-    ('Sasktel (Canada)', '%s@sms.sasktel.com', now()),
-    ('Setar Mobile email (Aruba)', '%s@mas.aw', now()),
-    ('Solo Mobile', '%s@txt.bell.ca', now()),
-    ('Sprint (PCS)', '%s@messaging.sprintpcs.com', now()),
-    ('Sprint (Nextel)', '%s@page.nextel.com', now()),
-    ('Suncom', '%s@tms.suncom.com', now()),
-    ('T-Mobile', '%s@tmomail.net', now()),
-    ('T-Mobile (Austria)', '%s@sms.t-mobile.at', now()),
-    ('Telus Mobility (Canada)', '%s@msg.telus.com', now()),
-    ('Thumb Cellular', '%s@sms.thumbcellular.com', now()),
-    ('Tigo (Formerly Ola)', '%s@sms.tigo.com.co', now()),
-    ('Unicel', '%s@utext.com', now()),
-    ('US Cellular', '%s@email.uscc.net', now()),
-    ('Verizon', '%s@vtext.com', now()),
-    ('Virgin Mobile (Canada)', '%s@vmobile.ca', now()),
-    ('Virgin Mobile (USA)', '%s@vmobl.com', now()),
-    ('Vodafone NZ (txt ''R'' to 901 to enable first)', '%s@sms.vodafone.net.nz', now()),
-    ('YCC', '%s@sms.ycc.ru', now());
index 254cf5fabf8b92815b8fee7db93694423009732a..6cacdba44c19861a5eb15b975316c92ffc874a90 100644 (file)
@@ -31,7 +31,7 @@ create table avatar (
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 create table sms_carrier (
-    id integer auto_increment primary key comment 'primary key for SMS carrier',
+    id integer primary key comment 'primary key for SMS carrier',
     name varchar(64) unique key comment 'name of the carrier',
     email_pattern varchar(255) not null comment 'sprintf pattern for making an email address from a phone number',
     created datetime not null comment 'date this record was created',
@@ -50,6 +50,7 @@ create table user (
     emailnotifyfav tinyint default 1 comment 'Notify by email of favorites',
     emailnotifynudge tinyint default 1 comment 'Notify by email of nudges',
     emailnotifymsg tinyint default 1 comment 'Notify by email of direct messages',
+    emailnotifyattn tinyint default 1 comment 'Notify by email of @-replies',
     emailmicroid tinyint default 1 comment 'whether to publish email microid',
     language varchar(50) comment 'preferred language',
     timezone varchar(50) comment 'timezone',
@@ -260,7 +261,8 @@ create table notice_tag (
     created datetime not null comment 'date this record was created',
 
     constraint primary key (tag, notice_id),
-    index notice_tag_created_idx (created)
+    index notice_tag_created_idx (created),
+    index notice_tag_notice_id_idx (notice_id)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 /* Synching with foreign services */
@@ -358,7 +360,8 @@ create table profile_tag (
 
    constraint primary key (tagger, tagged, tag),
    index profile_tag_modified_idx (modified),
-   index profile_tag_tagger_tag_idx (tagger, tag)
+   index profile_tag_tagger_tag_idx (tagger, tag),
+   index profile_tag_tagged_idx (tagged)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 create table profile_block (
@@ -402,7 +405,9 @@ create table group_member (
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
 
-    constraint primary key (group_id, profile_id)
+    constraint primary key (group_id, profile_id),
+    index group_member_profile_id_idx (profile_id),
+    index group_member_created_idx (created)
 
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql
new file mode 100644 (file)
index 0000000..6879f20
--- /dev/null
@@ -0,0 +1,63 @@
+INSERT INTO sms_carrier
+    (id, name, email_pattern, created)
+VALUES
+    (100056, '3 River Wireless', '%s@sms.3rivers.net', now()),
+    (100057, '7-11 Speakout', '%s@cingularme.com', now()),
+    (100058, 'Airtel (Karnataka, India)', '%s@airtelkk.com', now()),
+    (100059, 'Alaska Communications Systems', '%s@msg.acsalaska.com', now()),
+    (100060, 'Alltel Wireless', '%s@message.alltel.com', now()),
+    (100061, 'AT&T Wireless', '%s@txt.att.net', now()),
+    (100062, 'Bell Mobility (Canada)', '%s@txt.bell.ca', now()),
+    (100063, 'Boost Mobile', '%s@myboostmobile.com', now()),
+    (100064, 'Cellular One (Dobson)', '%s@mobile.celloneusa.com', now()),
+    (100065, 'Cingular (Postpaid)', '%s@cingularme.com', now()),
+    (100066, 'Centennial Wireless', '%s@cwemail.com', now()),
+    (100067, 'Cingular (GoPhone prepaid)', '%s@cingularme.com', now()),
+    (100068, 'Claro (Nicaragua)', '%s@ideasclaro-ca.com', now()),
+    (100069, 'Comcel', '%s@comcel.com.co', now()),
+    (100070, 'Cricket', '%s@sms.mycricket.com', now()),
+    (100071, 'CTI', '%s@sms.ctimovil.com.ar', now()),
+    (100072, 'Emtel (Mauritius)', '%s@emtelworld.net', now()),
+    (100073, 'Fido (Canada)', '%s@fido.ca', now()),
+    (100074, 'General Communications Inc.', '%s@msg.gci.net', now()),
+    (100075, 'Globalstar', '%s@msg.globalstarusa.com', now()),
+    (100076, 'Helio', '%s@myhelio.com', now()),
+    (100077, 'Illinois Valley Cellular', '%s@ivctext.com', now()),
+    (100078, 'i wireless', '%s.iws@iwspcs.net', now()),
+    (100079, 'Meteor (Ireland)', '%s@sms.mymeteor.ie', now()),
+    (100080, 'Mero Mobile (Nepal)', '%s@sms.spicenepal.com', now()),
+    (100081, 'MetroPCS', '%s@mymetropcs.com', now()),
+    (100082, 'Movicom', '%s@movimensaje.com.ar', now()),
+    (100083, 'Mobitel (Sri Lanka)', '%s@sms.mobitel.lk', now()),
+    (100084, 'Movistar (Colombia)', '%s@movistar.com.co', now()),
+    (100085, 'MTN (South Africa)', '%s@sms.co.za', now()),
+    (100086, 'MTS (Canada)', '%s@text.mtsmobility.com', now()),
+    (100087, 'Nextel (Argentina)', '%s@nextel.net.ar', now()),
+    (100088, 'Orange (Poland)', '%s@orange.pl', now()),
+    (100089, 'Personal (Argentina)', '%s@personal-net.com.ar', now()),
+    (100090, 'Plus GSM (Poland)', '%s@text.plusgsm.pl', now()),
+    (100091, 'President\'s Choice (Canada)', '%s@txt.bell.ca', now()),
+    (100092, 'Qwest', '%s@qwestmp.com', now()),
+    (100093, 'Rogers (Canada)', '%s@pcs.rogers.com', now()),
+    (100094, 'Sasktel (Canada)', '%s@sms.sasktel.com', now()),
+    (100095, 'Setar Mobile email (Aruba)', '%s@mas.aw', now()),
+    (100096, 'Solo Mobile', '%s@txt.bell.ca', now()),
+    (100097, 'Sprint (PCS)', '%s@messaging.sprintpcs.com', now()),
+    (100098, 'Sprint (Nextel)', '%s@page.nextel.com', now()),
+    (100099, 'Suncom', '%s@tms.suncom.com', now()),
+    (100100, 'T-Mobile', '%s@tmomail.net', now()),
+    (100101, 'T-Mobile (Austria)', '%s@sms.t-mobile.at', now()),
+    (100102, 'Telus Mobility (Canada)', '%s@msg.telus.com', now()),
+    (100103, 'Thumb Cellular', '%s@sms.thumbcellular.com', now()),
+    (100104, 'Tigo (Formerly Ola)', '%s@sms.tigo.com.co', now()),
+    (100105, 'Unicel', '%s@utext.com', now()),
+    (100106, 'US Cellular', '%s@email.uscc.net', now()),
+    (100107, 'Verizon', '%s@vtext.com', now()),
+    (100108, 'Virgin Mobile (Canada)', '%s@vmobile.ca', now()),
+    (100109, 'Virgin Mobile (USA)', '%s@vmobl.com', now()),
+    (100110, 'YCC', '%s@sms.ycc.ru', now()),
+    (100111, 'Orange (UK)', '%s@orange.net', now()),
+    (100112, 'Cincinnati Bell Wireless', '%s@gocbw.com', now()),
+    (100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()),
+    (100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()),
+    (100115, 'E-Plus', '%s@smsmail.eplus.de', now());
diff --git a/doc-src/about b/doc-src/about
new file mode 100644 (file)
index 0000000..3036a51
--- /dev/null
@@ -0,0 +1,10 @@
+%%site.name%% is a
+[micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service
+based on the Free Software [Laconica](http://laconi.ca/) tool.
+
+If you [register](%%action.register%%) for an account,
+you can post small (140 chars or less) text notices
+about yourself, where you are, what you're doing, or practically
+anything you want. You can also subscribe to the notices of your
+friends, or other people you're interested in, and follow them on the
+Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed.
diff --git a/doc-src/badge b/doc-src/badge
new file mode 100644 (file)
index 0000000..1c368eb
--- /dev/null
@@ -0,0 +1,65 @@
+Install the %%site.name%% badge on you blog or web site to show the latest updates 
+from you and your friends!
+
+<MTMarkdownOptions output='raw'>
+<script type="text/javascript" src="http://identi.ca/js/identica-badge.js">
+{
+   "user":"kentbrew",
+   "server":"identi.ca",
+   "headerText":" and friends"
+}
+</script>
+</MTMarkdownOptions>
+
+Things to try
+--------------
+
+* Click an avatar and the badge will refresh with that user's timeline
+* Click a nickname to open a user's profile in your browser
+* Click a notice's timestamp to view the notice in your browser
+* @-replies and #tags are live links
+
+## Installation instructions 
+
+Copy and paste the following JavaScript into an HTML page where
+you want the badge to show up.  Substitute your own ID in the user
+parameter.
+
+<pre>
+       &lt;script type=&quot;text/javascript&quot; src=&quot;http://identi.ca/js/identica-badge.js&quot;&gt;
+       {
+          &quot;user&quot;:&quot;kentbrew&quot;,
+          &quot;server&quot;:&quot;identi.ca&quot;,
+          &quot;headerText&quot;:&quot; and friends&quot;
+       }
+       &lt;/script&gt;
+
+</pre>
+
+
+
+Valid parameters for the badge:
+-------------------------------
+
+* user : defaults to 7000 (@kentbrew)
+* headerText  : defaults to empty
+* height : defaults to 350px
+* width : defaults to 300px
+* background : defaults to #193441. If you set evenBackground, oddBackground,
+  and headerBackground, you won't see it at all.
+* border : defaults to 1px solid black
+* userColor : defaults to whatever link color is set to on your page
+* headerBackground : defaults to transparent 
+* headerColor :  defaults to white
+* evenBackground : defaults to #fff
+* oddBackground : defaults to #eee
+* thumbnailBorder : 1px solid black
+* thumbnailSize : defaults to 24px
+* padding : defaults to 3px
+* server : defaults to identi.ca
+
+Licence
+-------
+
+Identi.ca badge by [Kent Brewster](http://kentbrewster.com/identica-badge/). 
+Licenced under [CC-BY-SA-3](http://kentbrewster.com/rights-and-permissions/).
diff --git a/doc-src/contact b/doc-src/contact
new file mode 100644 (file)
index 0000000..a8efc45
--- /dev/null
@@ -0,0 +1,24 @@
+There are a number of options for getting in contact with responsible
+people for %%site.name%%.
+
+Post a notice
+-------------
+
+If you have a question about how to do something, just post a notice
+with your question. People here like to answer messages. Watch the
+[public timeline](%%action.public%%) for answers; they'll usually start
+with "@" plus your user name.
+
+Bugs
+----
+
+If you think you've found a bug in the [Laconica](http://laconi.ca/) software,
+or if there's a new feature you'd like to see, add it into the [Laconica bug database](http://laconi.ca/PITS/HomePage). Don't forget to check the list of
+existing bugs to make sure it hasn't already been reported!
+
+Email
+-----
+
+You can reach the responsible party for this server at [%%site.email%%](mailto:%%site.email%%).
+
+
diff --git a/doc-src/faq b/doc-src/faq
new file mode 100644 (file)
index 0000000..31582b9
--- /dev/null
@@ -0,0 +1,42 @@
+These are some *Frequently Asked Questions* about this service, with
+some answers.
+
+What is %%site.name%%?
+----------------------
+
+%%site.name%% is a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service.
+You can use it to write short notices about yourself, where you are,
+and what you're doing, and those notices will be sent to all your friends
+and fans.
+
+How is %%site.name%% different from Twitter, Jaiku, Pownce, Plurk, others?
+--------------------------------------------------------------------------
+
+%%site.name%% is an [Open Network Service](http://opendefinition.org/ossd). Our main
+goal is to provide a fair and transparent service that preserves users' autonomy. In
+particular, all the software used for %%site.name%% is [Free Software](http://en.wikipedia.org/wiki/Free_Software), and all the data is available
+under the [%%license.title%%](%%license.url%%) license, making it Open Data.
+
+The software also implements the [OpenMicroBlogging](http://openmicroblogging.org/) protocol, meaning that you can have friends on other microblogging services
+that can receive your notices.
+
+The goal here is *autonomy* -- you deserve the right to manage your own on-line
+presence. If you don't like how %%site.name%% works, you can take your data and the source code and set up your own server (or move your account to another one).
+
+Where is feature X?
+-------------------
+
+The software we run, [Laconica](http://laconi.ca/), is still in its early stages,
+and many features people expect from microblogging sites are not yet implemented. Some important ones that are expected "soon":
+
+* More [AJAX](http://en.wikipedia.org/wiki/AJAX)-y interface
+* Maps
+* Cross-post to Pownce, Jaiku, etc.
+* Pull messages from Twitter, Pownce, Jaiku, etc.
+* [Facebook](http://www.facebook.com/) integration
+* Image, video, audio notices
+
+There is [a list of bugs and features](http://laconi.ca/trac/) that you may find
+interesting. New ideas or complaints are very welcome.
+
+
diff --git a/doc-src/groups b/doc-src/groups
new file mode 100644 (file)
index 0000000..645390e
--- /dev/null
@@ -0,0 +1,42 @@
+Users on %%site.name%% can create *groups* that other users can join.
+Groups can be a great way to share information and entertainment with
+a group of people who have a common interest or background.
+
+You can find out about groups on the server on the
+[Groups](%%action.groups%%) page. You can join a group by clicking on
+the "Join" button either in the group list or on the group's home page.
+
+Starting a new group
+--------------------
+
+If you want, you can start a new group for friends and people with
+common interests. Note that all groups are free for anyone to join.
+
+To start a new group, use the [new group](%%action.newgroup%%) tool
+and fill out the form. Describe your group as best you can if you want
+people to be able to find it.
+
+When choosing the nickname for your group, try to keep it short. The
+nickname is included in every message to and from the group, so the
+less chars the better. Try using acronyms for organizations, or
+airport codes for places (like 'pdx' instead of 'portland').
+
+Sending messages to a group
+---------------------------
+
+You can send a message to a group using the syntax "!groupname"
+anywhere in the message. If you have more than one group named, the
+notice will go to each group. Only members can send notices to a
+group, and groups do not respond to direct messages (DMs).
+
+Receiving messages
+------------------
+
+New group messages will appear in your inbox, and will also come to
+your phone or IM client if you've set them up to receive notices.
+
+Remote groups
+-------------
+
+While it's technically possible, this version of Laconica does not
+support remote group membership.
diff --git a/doc-src/help b/doc-src/help
new file mode 100644 (file)
index 0000000..a8cfccd
--- /dev/null
@@ -0,0 +1,32 @@
+%%site.name%% is a **microblogging service**. Users post short (140
+character) notices which are broadcast to their friends and fans using
+the Web, RSS, or instant messages.
+
+If you'd like to try it out, first [register](%%action.register%%) a new account.
+Then, on the [public timeline](%%action.public%%), enter your message into
+the textbox at the top of the page, and click "Send". It will go out on the
+public timeline and to anyone who is subscribed to your notices (probably nobody,
+at first).
+
+To subscribe to other people's notifications, go to their profile page
+and click the "subscribe" button. They'll get a notice that you're now
+subscribed to their notifications, and, who knows?, they might subscribe
+back.
+
+More help
+---------
+
+Here are some documents that you might find helpful in understanding
+%%site.name%% and how to use it.
+
+* [About](%%doc.about%%) - an overview of the service
+* [FAQ](%%doc.faq%%) - frequently-asked questions about %%site.name%%
+* [Contact](%%doc.contact%%) - who to contact with questions about the service
+* [IM](%%doc.im%%) - using the instant-message (IM) features of %%site.name%%
+* [SMS](%%doc.sms%%) - tying your cellphone to %%site.name%%
+* [tags](%%doc.tags%%) - different ways to use tagging
+* [Groups](%%doc.groups%%) - joining together in groups
+* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service
+* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users
+* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy
+* [Source](%%doc.source%%) - How to get the Laconica source code
diff --git a/doc-src/im b/doc-src/im
new file mode 100644 (file)
index 0000000..da07f9f
--- /dev/null
@@ -0,0 +1,35 @@
+You can post messages to %%site.name%% using a [Jabber](http://jabber.org/) client
+on your computer, mobile phone, or other platform. ([GTalk](http://talk.google.com/),
+Google's Jabber program, will also work.) This can be a convenient way to keep
+up with your friends on %%site.name%%.
+
+If you don't already have a Jabber account, you can use GTalk or one of the other
+[public Jabber services](http://www.jabber.org/im-services). You'll probably also
+need an IM client like [Pidgin](http://www.pidgin.im/).
+
+Managing your IM settings
+-------------------------
+
+Use the [IM settings](%%action.imsettings%%) page to set your IM preferences. You can add or change your Jabber address and set the flags for Jabber update.
+
+When you add or change your address, you'll receive a message from **%%xmpp.user%%@%%xmpp.server%%** asking you to confirm the change. (You may need to
+add %%xmpp.user%%@%%xmpp.server%% to your buddy list *before* changing your IM
+settings; this is definitely true for GTalk.)
+
+Sending updates
+---------------
+
+You send updates by sending messages to %%xmpp.user%%@%%xmpp.server%%. Messages
+should be less than 140 characters; longer messages will be truncated.
+
+Commands
+--------
+
+You can do some minor management of your account through Jabber. These are the
+currently-implemented commands:
+
+* **on**: Turn on notifications. You'll receive copies of messages by people
+  you subscribe to.
+* **off**: Turn off notifications. You'll no longer receive Jabber
+  notifications.
+
diff --git a/doc-src/openid b/doc-src/openid
new file mode 100644 (file)
index 0000000..c741e36
--- /dev/null
@@ -0,0 +1,11 @@
+%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
+
+If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
+To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
+
+There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
+
+* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
+* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
+* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
+* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/doc-src/openmublog b/doc-src/openmublog
new file mode 100644 (file)
index 0000000..6e3abee
--- /dev/null
@@ -0,0 +1,25 @@
+[OpenMicroBlogging](http://openmicroblogging.org/) is a protocol that
+lets users of one [microblogging](http://en.wikipedia.org/wiki/microblogging) service
+subscribe to notices by users of another service. The protocol, based on
+[OAuth](http://oauth.net/), is open and free, and doesn't depend on any
+central authority to maintain the federated microblogs.
+
+The [Laconica](http://laconi.ca/) software that runs %%site.name%% supports
+OpenMicroBlogging 0.1. Anyone can make a new installation of Laconica on their
+own servers, and users of that new installation can subscribe to notices from
+%%site.name%%.
+
+Remote subscription
+-------------------
+
+If you have an account on a remote site that supports OpenMicroBlogging, and you
+want to subscribe to the notices of a user on this site, click on the "Subscribe"
+link under their avatar on their profile page. This should take you to the
+[remote subscription](%%action.remotesubscribe%%) page. Make sure that you've got the
+right nickname registered, and enter your profile URL on the other microblogging
+service.
+
+You'll be taken to your microblogging service, where you'll be asked to confirm the
+subscription. When you confirm, your service will receive new notifications from
+the user on %%site.name%%, and your service will forward them to you (using IM, SMS,
+the Web, or whatever else).
diff --git a/doc-src/privacy b/doc-src/privacy
new file mode 100644 (file)
index 0000000..90c7b3c
--- /dev/null
@@ -0,0 +1,45 @@
+This document outlines this service's respect for your personal
+privacy as a user of the service.
+
+- Almost all the text and files that users upload to this site is
+  available under the site license (see the license block at the bottom
+  of this page). Users agree to the license when they register to use
+  the site for the first time. Typically that means that the data can
+  be copied far and wide, for commercial and non-commercial purposes,
+  and in modified or unmodified form. If you're not OK with that,
+  don't use the service.
+- The following data items are considered *private data* that won't be
+  shared with other users, business partners, or the public at large:
+  * your password
+  * your email address
+  * your IM address (AIM, Jabber, or other instant messaging address)
+  * your phone number
+  * your "private messages"
+  * your login credentials (username and password) for other services (Twitter, Facebook, etc.)
+- Some private data may be published in aggregate, e.g. "30% of our
+  users are registered with Hotmail addresses."
+- Your notices (including files) can be downloaded and re-used by
+  other services, either one-by-one or in bulk as
+  [RSS](http://en.wikipedia.org/wiki/RSS) files.
+- Your profile information (including subscriptions and avatars) can be
+  downloaded and re-used by other services, either scraped from the HTML
+  interface or in bulk as [FOAF](http://en.wikipedia.org/wiki/FOAF) files.
+- Your notices will be forwarded to users who subscribe to them,
+  including users on another microblogging service.
+- Your profile information will be sent to microblogging services for
+  users who subscribe to you or to whom you subscribe.
+- Based on your email preferences, you may receive automated email
+  messages for important system events, such as when others subscribe
+  to your notices.
+- Based on your email preferences, you may receive an email
+  newsletter. You can opt out of the newsletter if you don't want to
+  receive it.
+- In urgent situations, administrators may send you email directly to
+  your registered email address, even if you've requested no notices
+  or newsletter. *Administrators will use digitally-signed email.*
+- This service will comply with court orders to turn over your private
+  information.
+  
+  
+  
+
diff --git a/doc-src/sms b/doc-src/sms
new file mode 100644 (file)
index 0000000..1beb497
--- /dev/null
@@ -0,0 +1,68 @@
+You can post messages to %%site.name%% using a many kinds of cell
+phones that support SMS messaging. This site does not support SMS
+directly; rather, it uses your carrier's email gateway to send and
+receive messages.
+
+Managing your SMS settings
+--------------------------
+
+Use the [SMS settings](%%action.smssettings%%) page to set your SMS
+preferences. You can add or change your SMS number and set the
+flags for SMS updates.
+
+When you add or change your phone number, you'll receive a message on your
+phone with a verification code. Enter it into the SMS settings page to
+confirm that the owner of the phone authorizes sending it messages.
+
+Note that only the carriers listed in the drop down list on the form
+are supported by %%site.name%%. They're the only ones we know how to
+make email addresses for.
+
+Receiving messages
+------------------
+
+Once you've verified your phone number, you can enable sending
+messages to your phone. If you have a lot of friends and a typical
+phone, it can be hard to keep up.
+
+Sending messages
+----------------
+
+To send a message, you must send an email to the incoming email
+address visible on your SMS settings page. The method for sending
+email from your phone varies from carrier to carrier and from handset
+to handet; if in doubt, ask your carrier.
+
+Keep your incoming email address a secret -- it's the only way we know
+you're really you!
+
+Commands
+--------
+
+You can use the following commands with %%site.name%%.
+
+* on - turn on notifications
+* off - turn off notifications
+* help - show this help
+* follow <nickname> - subscribe to user
+* leave <nickname> - unsubscribe from user
+* d <nickname> <text> - direct message to user
+* get <nickname> - get last notice from user
+* whois <nickname> - get profile info on user
+* fav <nickname> - add user's last notice as a 'fave'
+* stats - get your stats
+* stop - same as 'off'
+* quit - same as 'off'
+* sub <nickname> - same as 'follow'
+* unsub <nickname> - same as 'leave'
+* last <nickname> - same as 'get'
+* on <nickname> - not yet implemented.
+* off <nickname> - not yet implemented.
+* nudge <nickname> - not yet implemented.
+* invite <phone number> - not yet implemented.
+* track <word> - not yet implemented.
+* untrack <word> - not yet implemented.
+* track off - not yet implemented.
+* untrack all - not yet implemented.
+* tracks - not yet implemented.
+* tracking - not yet implemented.
diff --git a/doc-src/source b/doc-src/source
new file mode 100644 (file)
index 0000000..83debbe
--- /dev/null
@@ -0,0 +1,12 @@
+This service uses a Free microblogging tool called **Laconica**.
+Laconica is available under the [GNU Affero General Public License
+Version 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html), a
+Free Software license for network services.
+
+You can get a copy of the software from the
+[Laconica](http://laconi.ca/) main site. The version of the software
+that runs on *this* site is unmodified from that version. The site
+also depends on certain libraries and other software; you can get
+those at the Laconica site, too.
+
+
diff --git a/doc-src/tags b/doc-src/tags
new file mode 100644 (file)
index 0000000..2ed352e
--- /dev/null
@@ -0,0 +1,40 @@
+%%site.name%% supports
+[tags](http://en.wikipedia.org/wiki/Tag_(metadata)) to help you
+organize your activities here. You can use tags for people and for
+notices.
+
+Tagging a notice
+----------------
+
+You can tag a notice using a *hashtag*; a # character followed by
+letters and numbers as well as '.', '-', and '_'. Note that accented
+latin characters are not supported, and non-roman scripts are right out.
+
+The HTML for the notice will link to a stream of all the other notices
+with that tag. This can be a great way to keep track of a conversation.
+
+The most popular current tags on the site can be found in the [public
+tag cloud](%%action.publictagcloud%%). Their size shows their
+popularity and recency.
+
+Tagging yourself
+----------------
+
+You can also add tags for yourself on your [profile
+settings](%%action.profilesettings%%) page. Use single words to
+describe yourself, your experiences and your interest. The tags will
+become links on your profile page to a list of all the users on the
+site who use that same tag. It can be a nice way to find people who
+are related to you geographically or who have a common interest.
+
+Tagging your subscriptions
+--------------------------
+
+You can also tag your subscriptions, on the subscriptions page. This
+makes it easy to organize your subscriptions into groups and sort
+through them separately.
+
+You can also send a notice "to the attention of" everyone you've
+marked with a particular tag (note: *not* people who've marked
+themselves with that tag). "@#family hello" will send a notice to
+everyone you've marked with the tag 'family'.
\ No newline at end of file
diff --git a/doc/about b/doc/about
deleted file mode 100644 (file)
index 3036a51..0000000
--- a/doc/about
+++ /dev/null
@@ -1,10 +0,0 @@
-%%site.name%% is a
-[micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service
-based on the Free Software [Laconica](http://laconi.ca/) tool.
-
-If you [register](%%action.register%%) for an account,
-you can post small (140 chars or less) text notices
-about yourself, where you are, what you're doing, or practically
-anything you want. You can also subscribe to the notices of your
-friends, or other people you're interested in, and follow them on the
-Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed.
diff --git a/doc/contact b/doc/contact
deleted file mode 100644 (file)
index a8efc45..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-There are a number of options for getting in contact with responsible
-people for %%site.name%%.
-
-Post a notice
--------------
-
-If you have a question about how to do something, just post a notice
-with your question. People here like to answer messages. Watch the
-[public timeline](%%action.public%%) for answers; they'll usually start
-with "@" plus your user name.
-
-Bugs
-----
-
-If you think you've found a bug in the [Laconica](http://laconi.ca/) software,
-or if there's a new feature you'd like to see, add it into the [Laconica bug database](http://laconi.ca/PITS/HomePage). Don't forget to check the list of
-existing bugs to make sure it hasn't already been reported!
-
-Email
------
-
-You can reach the responsible party for this server at [%%site.email%%](mailto:%%site.email%%).
-
-
diff --git a/doc/faq b/doc/faq
deleted file mode 100644 (file)
index 31582b9..0000000
--- a/doc/faq
+++ /dev/null
@@ -1,42 +0,0 @@
-These are some *Frequently Asked Questions* about this service, with
-some answers.
-
-What is %%site.name%%?
-----------------------
-
-%%site.name%% is a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service.
-You can use it to write short notices about yourself, where you are,
-and what you're doing, and those notices will be sent to all your friends
-and fans.
-
-How is %%site.name%% different from Twitter, Jaiku, Pownce, Plurk, others?
---------------------------------------------------------------------------
-
-%%site.name%% is an [Open Network Service](http://opendefinition.org/ossd). Our main
-goal is to provide a fair and transparent service that preserves users' autonomy. In
-particular, all the software used for %%site.name%% is [Free Software](http://en.wikipedia.org/wiki/Free_Software), and all the data is available
-under the [%%license.title%%](%%license.url%%) license, making it Open Data.
-
-The software also implements the [OpenMicroBlogging](http://openmicroblogging.org/) protocol, meaning that you can have friends on other microblogging services
-that can receive your notices.
-
-The goal here is *autonomy* -- you deserve the right to manage your own on-line
-presence. If you don't like how %%site.name%% works, you can take your data and the source code and set up your own server (or move your account to another one).
-
-Where is feature X?
--------------------
-
-The software we run, [Laconica](http://laconi.ca/), is still in its early stages,
-and many features people expect from microblogging sites are not yet implemented. Some important ones that are expected "soon":
-
-* More [AJAX](http://en.wikipedia.org/wiki/AJAX)-y interface
-* Maps
-* Cross-post to Pownce, Jaiku, etc.
-* Pull messages from Twitter, Pownce, Jaiku, etc.
-* [Facebook](http://www.facebook.com/) integration
-* Image, video, audio notices
-
-There is [a list of bugs and features](http://laconi.ca/trac/) that you may find
-interesting. New ideas or complaints are very welcome.
-
-
diff --git a/doc/groups b/doc/groups
deleted file mode 100644 (file)
index 645390e..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-Users on %%site.name%% can create *groups* that other users can join.
-Groups can be a great way to share information and entertainment with
-a group of people who have a common interest or background.
-
-You can find out about groups on the server on the
-[Groups](%%action.groups%%) page. You can join a group by clicking on
-the "Join" button either in the group list or on the group's home page.
-
-Starting a new group
---------------------
-
-If you want, you can start a new group for friends and people with
-common interests. Note that all groups are free for anyone to join.
-
-To start a new group, use the [new group](%%action.newgroup%%) tool
-and fill out the form. Describe your group as best you can if you want
-people to be able to find it.
-
-When choosing the nickname for your group, try to keep it short. The
-nickname is included in every message to and from the group, so the
-less chars the better. Try using acronyms for organizations, or
-airport codes for places (like 'pdx' instead of 'portland').
-
-Sending messages to a group
----------------------------
-
-You can send a message to a group using the syntax "!groupname"
-anywhere in the message. If you have more than one group named, the
-notice will go to each group. Only members can send notices to a
-group, and groups do not respond to direct messages (DMs).
-
-Receiving messages
-------------------
-
-New group messages will appear in your inbox, and will also come to
-your phone or IM client if you've set them up to receive notices.
-
-Remote groups
--------------
-
-While it's technically possible, this version of Laconica does not
-support remote group membership.
diff --git a/doc/help b/doc/help
deleted file mode 100644 (file)
index a8cfccd..0000000
--- a/doc/help
+++ /dev/null
@@ -1,32 +0,0 @@
-%%site.name%% is a **microblogging service**. Users post short (140
-character) notices which are broadcast to their friends and fans using
-the Web, RSS, or instant messages.
-
-If you'd like to try it out, first [register](%%action.register%%) a new account.
-Then, on the [public timeline](%%action.public%%), enter your message into
-the textbox at the top of the page, and click "Send". It will go out on the
-public timeline and to anyone who is subscribed to your notices (probably nobody,
-at first).
-
-To subscribe to other people's notifications, go to their profile page
-and click the "subscribe" button. They'll get a notice that you're now
-subscribed to their notifications, and, who knows?, they might subscribe
-back.
-
-More help
----------
-
-Here are some documents that you might find helpful in understanding
-%%site.name%% and how to use it.
-
-* [About](%%doc.about%%) - an overview of the service
-* [FAQ](%%doc.faq%%) - frequently-asked questions about %%site.name%%
-* [Contact](%%doc.contact%%) - who to contact with questions about the service
-* [IM](%%doc.im%%) - using the instant-message (IM) features of %%site.name%%
-* [SMS](%%doc.sms%%) - tying your cellphone to %%site.name%%
-* [tags](%%doc.tags%%) - different ways to use tagging
-* [Groups](%%doc.groups%%) - joining together in groups
-* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service
-* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users
-* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy
-* [Source](%%doc.source%%) - How to get the Laconica source code
diff --git a/doc/im b/doc/im
deleted file mode 100644 (file)
index da07f9f..0000000
--- a/doc/im
+++ /dev/null
@@ -1,35 +0,0 @@
-You can post messages to %%site.name%% using a [Jabber](http://jabber.org/) client
-on your computer, mobile phone, or other platform. ([GTalk](http://talk.google.com/),
-Google's Jabber program, will also work.) This can be a convenient way to keep
-up with your friends on %%site.name%%.
-
-If you don't already have a Jabber account, you can use GTalk or one of the other
-[public Jabber services](http://www.jabber.org/im-services). You'll probably also
-need an IM client like [Pidgin](http://www.pidgin.im/).
-
-Managing your IM settings
--------------------------
-
-Use the [IM settings](%%action.imsettings%%) page to set your IM preferences. You can add or change your Jabber address and set the flags for Jabber update.
-
-When you add or change your address, you'll receive a message from **%%xmpp.user%%@%%xmpp.server%%** asking you to confirm the change. (You may need to
-add %%xmpp.user%%@%%xmpp.server%% to your buddy list *before* changing your IM
-settings; this is definitely true for GTalk.)
-
-Sending updates
----------------
-
-You send updates by sending messages to %%xmpp.user%%@%%xmpp.server%%. Messages
-should be less than 140 characters; longer messages will be truncated.
-
-Commands
---------
-
-You can do some minor management of your account through Jabber. These are the
-currently-implemented commands:
-
-* **on**: Turn on notifications. You'll receive copies of messages by people
-  you subscribe to.
-* **off**: Turn off notifications. You'll no longer receive Jabber
-  notifications.
-
diff --git a/doc/openid b/doc/openid
deleted file mode 100644 (file)
index c741e36..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
-
-If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
-To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
-
-There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
-
-* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
-* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
-* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
-* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/doc/openmublog b/doc/openmublog
deleted file mode 100644 (file)
index 6e3abee..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-[OpenMicroBlogging](http://openmicroblogging.org/) is a protocol that
-lets users of one [microblogging](http://en.wikipedia.org/wiki/microblogging) service
-subscribe to notices by users of another service. The protocol, based on
-[OAuth](http://oauth.net/), is open and free, and doesn't depend on any
-central authority to maintain the federated microblogs.
-
-The [Laconica](http://laconi.ca/) software that runs %%site.name%% supports
-OpenMicroBlogging 0.1. Anyone can make a new installation of Laconica on their
-own servers, and users of that new installation can subscribe to notices from
-%%site.name%%.
-
-Remote subscription
--------------------
-
-If you have an account on a remote site that supports OpenMicroBlogging, and you
-want to subscribe to the notices of a user on this site, click on the "Subscribe"
-link under their avatar on their profile page. This should take you to the
-[remote subscription](%%action.remotesubscribe%%) page. Make sure that you've got the
-right nickname registered, and enter your profile URL on the other microblogging
-service.
-
-You'll be taken to your microblogging service, where you'll be asked to confirm the
-subscription. When you confirm, your service will receive new notifications from
-the user on %%site.name%%, and your service will forward them to you (using IM, SMS,
-the Web, or whatever else).
diff --git a/doc/privacy b/doc/privacy
deleted file mode 100644 (file)
index 90c7b3c..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-This document outlines this service's respect for your personal
-privacy as a user of the service.
-
-- Almost all the text and files that users upload to this site is
-  available under the site license (see the license block at the bottom
-  of this page). Users agree to the license when they register to use
-  the site for the first time. Typically that means that the data can
-  be copied far and wide, for commercial and non-commercial purposes,
-  and in modified or unmodified form. If you're not OK with that,
-  don't use the service.
-- The following data items are considered *private data* that won't be
-  shared with other users, business partners, or the public at large:
-  * your password
-  * your email address
-  * your IM address (AIM, Jabber, or other instant messaging address)
-  * your phone number
-  * your "private messages"
-  * your login credentials (username and password) for other services (Twitter, Facebook, etc.)
-- Some private data may be published in aggregate, e.g. "30% of our
-  users are registered with Hotmail addresses."
-- Your notices (including files) can be downloaded and re-used by
-  other services, either one-by-one or in bulk as
-  [RSS](http://en.wikipedia.org/wiki/RSS) files.
-- Your profile information (including subscriptions and avatars) can be
-  downloaded and re-used by other services, either scraped from the HTML
-  interface or in bulk as [FOAF](http://en.wikipedia.org/wiki/FOAF) files.
-- Your notices will be forwarded to users who subscribe to them,
-  including users on another microblogging service.
-- Your profile information will be sent to microblogging services for
-  users who subscribe to you or to whom you subscribe.
-- Based on your email preferences, you may receive automated email
-  messages for important system events, such as when others subscribe
-  to your notices.
-- Based on your email preferences, you may receive an email
-  newsletter. You can opt out of the newsletter if you don't want to
-  receive it.
-- In urgent situations, administrators may send you email directly to
-  your registered email address, even if you've requested no notices
-  or newsletter. *Administrators will use digitally-signed email.*
-- This service will comply with court orders to turn over your private
-  information.
-  
-  
-  
-
diff --git a/doc/sms b/doc/sms
deleted file mode 100644 (file)
index 1beb497..0000000
--- a/doc/sms
+++ /dev/null
@@ -1,68 +0,0 @@
-You can post messages to %%site.name%% using a many kinds of cell
-phones that support SMS messaging. This site does not support SMS
-directly; rather, it uses your carrier's email gateway to send and
-receive messages.
-
-Managing your SMS settings
---------------------------
-
-Use the [SMS settings](%%action.smssettings%%) page to set your SMS
-preferences. You can add or change your SMS number and set the
-flags for SMS updates.
-
-When you add or change your phone number, you'll receive a message on your
-phone with a verification code. Enter it into the SMS settings page to
-confirm that the owner of the phone authorizes sending it messages.
-
-Note that only the carriers listed in the drop down list on the form
-are supported by %%site.name%%. They're the only ones we know how to
-make email addresses for.
-
-Receiving messages
-------------------
-
-Once you've verified your phone number, you can enable sending
-messages to your phone. If you have a lot of friends and a typical
-phone, it can be hard to keep up.
-
-Sending messages
-----------------
-
-To send a message, you must send an email to the incoming email
-address visible on your SMS settings page. The method for sending
-email from your phone varies from carrier to carrier and from handset
-to handet; if in doubt, ask your carrier.
-
-Keep your incoming email address a secret -- it's the only way we know
-you're really you!
-
-Commands
---------
-
-You can use the following commands with %%site.name%%.
-
-* on - turn on notifications
-* off - turn off notifications
-* help - show this help
-* follow <nickname> - subscribe to user
-* leave <nickname> - unsubscribe from user
-* d <nickname> <text> - direct message to user
-* get <nickname> - get last notice from user
-* whois <nickname> - get profile info on user
-* fav <nickname> - add user's last notice as a 'fave'
-* stats - get your stats
-* stop - same as 'off'
-* quit - same as 'off'
-* sub <nickname> - same as 'follow'
-* unsub <nickname> - same as 'leave'
-* last <nickname> - same as 'get'
-* on <nickname> - not yet implemented.
-* off <nickname> - not yet implemented.
-* nudge <nickname> - not yet implemented.
-* invite <phone number> - not yet implemented.
-* track <word> - not yet implemented.
-* untrack <word> - not yet implemented.
-* track off - not yet implemented.
-* untrack all - not yet implemented.
-* tracks - not yet implemented.
-* tracking - not yet implemented.
diff --git a/doc/source b/doc/source
deleted file mode 100644 (file)
index 83debbe..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-This service uses a Free microblogging tool called **Laconica**.
-Laconica is available under the [GNU Affero General Public License
-Version 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html), a
-Free Software license for network services.
-
-You can get a copy of the software from the
-[Laconica](http://laconi.ca/) main site. The version of the software
-that runs on *this* site is unmodified from that version. The site
-also depends on certain libraries and other software; you can get
-those at the Laconica site, too.
-
-
diff --git a/doc/tags b/doc/tags
deleted file mode 100644 (file)
index 2ed352e..0000000
--- a/doc/tags
+++ /dev/null
@@ -1,40 +0,0 @@
-%%site.name%% supports
-[tags](http://en.wikipedia.org/wiki/Tag_(metadata)) to help you
-organize your activities here. You can use tags for people and for
-notices.
-
-Tagging a notice
-----------------
-
-You can tag a notice using a *hashtag*; a # character followed by
-letters and numbers as well as '.', '-', and '_'. Note that accented
-latin characters are not supported, and non-roman scripts are right out.
-
-The HTML for the notice will link to a stream of all the other notices
-with that tag. This can be a great way to keep track of a conversation.
-
-The most popular current tags on the site can be found in the [public
-tag cloud](%%action.publictagcloud%%). Their size shows their
-popularity and recency.
-
-Tagging yourself
-----------------
-
-You can also add tags for yourself on your [profile
-settings](%%action.profilesettings%%) page. Use single words to
-describe yourself, your experiences and your interest. The tags will
-become links on your profile page to a list of all the users on the
-site who use that same tag. It can be a nice way to find people who
-are related to you geographically or who have a common interest.
-
-Tagging your subscriptions
---------------------------
-
-You can also tag your subscriptions, on the subscriptions page. This
-makes it easy to organize your subscriptions into groups and sort
-through them separately.
-
-You can also send a notice "to the attention of" everyone you've
-marked with a particular tag (note: *not* people who've marked
-themselves with that tag). "@#family hello" will send a notice to
-everyone you've marked with the tag 'family'.
\ No newline at end of file
diff --git a/extlib/Net/URL.php b/extlib/Net/URL.php
new file mode 100644 (file)
index 0000000..3dcfef6
--- /dev/null
@@ -0,0 +1,485 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2002-2004, Richard Heyes                                |
+// | All rights reserved.                                                  |
+// |                                                                       |
+// | Redistribution and use in source and binary forms, with or without    |
+// | modification, are permitted provided that the following conditions    |
+// | are met:                                                              |
+// |                                                                       |
+// | o Redistributions of source code must retain the above copyright      |
+// |   notice, this list of conditions and the following disclaimer.       |
+// | o Redistributions in binary form must reproduce the above copyright   |
+// |   notice, this list of conditions and the following disclaimer in the |
+// |   documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote      |
+// |   products derived from this software without specific prior written  |
+// |   permission.                                                         |
+// |                                                                       |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
+// |                                                                       |
+// +-----------------------------------------------------------------------+
+// | Author: Richard Heyes <richard at php net>                            |
+// +-----------------------------------------------------------------------+
+//
+// $Id: URL.php,v 1.49 2007/06/28 14:43:07 davidc Exp $
+//
+// Net_URL Class
+
+
+class Net_URL
+{
+    var $options = array('encode_query_keys' => false);
+    /**
+    * Full url
+    * @var string
+    */
+    var $url;
+
+    /**
+    * Protocol
+    * @var string
+    */
+    var $protocol;
+
+    /**
+    * Username
+    * @var string
+    */
+    var $username;
+
+    /**
+    * Password
+    * @var string
+    */
+    var $password;
+
+    /**
+    * Host
+    * @var string
+    */
+    var $host;
+
+    /**
+    * Port
+    * @var integer
+    */
+    var $port;
+
+    /**
+    * Path
+    * @var string
+    */
+    var $path;
+
+    /**
+    * Query string
+    * @var array
+    */
+    var $querystring;
+
+    /**
+    * Anchor
+    * @var string
+    */
+    var $anchor;
+
+    /**
+    * Whether to use []
+    * @var bool
+    */
+    var $useBrackets;
+
+    /**
+    * PHP4 Constructor
+    *
+    * @see __construct()
+    */
+    function Net_URL($url = null, $useBrackets = true)
+    {
+        $this->__construct($url, $useBrackets);
+    }
+
+    /**
+    * PHP5 Constructor
+    *
+    * Parses the given url and stores the various parts
+    * Defaults are used in certain cases
+    *
+    * @param string $url         Optional URL
+    * @param bool   $useBrackets Whether to use square brackets when
+    *                            multiple querystrings with the same name
+    *                            exist
+    */
+    function __construct($url = null, $useBrackets = true)
+    {
+        $this->url = $url;
+        $this->useBrackets = $useBrackets;
+
+        $this->initialize();
+    }
+
+    function initialize()
+    {
+        $HTTP_SERVER_VARS  = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS'];
+
+        $this->user        = '';
+        $this->pass        = '';
+        $this->host        = '';
+        $this->port        = 80;
+        $this->path        = '';
+        $this->querystring = array();
+        $this->anchor      = '';
+
+        // Only use defaults if not an absolute URL given
+        if (!preg_match('/^[a-z0-9]+:\/\//i', $this->url)) {
+            $this->protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
+
+            /**
+            * Figure out host/port
+            */
+            if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) && 
+                preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) 
+            {
+                $host = $matches[1];
+                if (!empty($matches[3])) {
+                    $port = $matches[3];
+                } else {
+                    $port = $this->getStandardPort($this->protocol);
+                }
+            }
+
+            $this->user        = '';
+            $this->pass        = '';
+            $this->host        = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost');
+            $this->port        = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol));
+            $this->path        = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/';
+            $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null;
+            $this->anchor      = '';
+        }
+
+        // Parse the url and store the various parts
+        if (!empty($this->url)) {
+            $urlinfo = parse_url($this->url);
+
+            // Default querystring
+            $this->querystring = array();
+
+            foreach ($urlinfo as $key => $value) {
+                switch ($key) {
+                    case 'scheme':
+                        $this->protocol = $value;
+                        $this->port     = $this->getStandardPort($value);
+                        break;
+
+                    case 'user':
+                    case 'pass':
+                    case 'host':
+                    case 'port':
+                        $this->$key = $value;
+                        break;
+
+                    case 'path':
+                        if ($value{0} == '/') {
+                            $this->path = $value;
+                        } else {
+                            $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path);
+                            $this->path = sprintf('%s/%s', $path, $value);
+                        }
+                        break;
+
+                    case 'query':
+                        $this->querystring = $this->_parseRawQueryString($value);
+                        break;
+
+                    case 'fragment':
+                        $this->anchor = $value;
+                        break;
+                }
+            }
+        }
+    }
+    /**
+    * Returns full url
+    *
+    * @return string Full url
+    * @access public
+    */
+    function getURL()
+    {
+        $querystring = $this->getQueryString();
+
+        $this->url = $this->protocol . '://'
+                   . $this->user . (!empty($this->pass) ? ':' : '')
+                   . $this->pass . (!empty($this->user) ? '@' : '')
+                   . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port)
+                   . $this->path
+                   . (!empty($querystring) ? '?' . $querystring : '')
+                   . (!empty($this->anchor) ? '#' . $this->anchor : '');
+
+        return $this->url;
+    }
+
+    /**
+    * Adds or updates a querystring item (URL parameter).
+    * Automatically encodes parameters with rawurlencode() if $preencoded
+    *  is false.
+    * You can pass an array to $value, it gets mapped via [] in the URL if
+    * $this->useBrackets is activated.
+    *
+    * @param  string $name       Name of item
+    * @param  string $value      Value of item
+    * @param  bool   $preencoded Whether value is urlencoded or not, default = not
+    * @access public
+    */
+    function addQueryString($name, $value, $preencoded = false)
+    {
+        if ($this->getOption('encode_query_keys')) {
+            $name = rawurlencode($name);
+        }
+
+        if ($preencoded) {
+            $this->querystring[$name] = $value;
+        } else {
+            $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value);
+        }
+    }
+
+    /**
+    * Removes a querystring item
+    *
+    * @param  string $name Name of item
+    * @access public
+    */
+    function removeQueryString($name)
+    {
+        if ($this->getOption('encode_query_keys')) {
+            $name = rawurlencode($name);
+        }
+
+        if (isset($this->querystring[$name])) {
+            unset($this->querystring[$name]);
+        }
+    }
+
+    /**
+    * Sets the querystring to literally what you supply
+    *
+    * @param  string $querystring The querystring data. Should be of the format foo=bar&x=y etc
+    * @access public
+    */
+    function addRawQueryString($querystring)
+    {
+        $this->querystring = $this->_parseRawQueryString($querystring);
+    }
+
+    /**
+    * Returns flat querystring
+    *
+    * @return string Querystring
+    * @access public
+    */
+    function getQueryString()
+    {
+        if (!empty($this->querystring)) {
+            foreach ($this->querystring as $name => $value) {
+                // Encode var name
+                $name = rawurlencode($name);
+
+                if (is_array($value)) {
+                    foreach ($value as $k => $v) {
+                        $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v);
+                    }
+                } elseif (!is_null($value)) {
+                    $querystring[] = $name . '=' . $value;
+                } else {
+                    $querystring[] = $name;
+                }
+            }
+            $querystring = implode(ini_get('arg_separator.output'), $querystring);
+        } else {
+            $querystring = '';
+        }
+
+        return $querystring;
+    }
+
+    /**
+    * Parses raw querystring and returns an array of it
+    *
+    * @param  string  $querystring The querystring to parse
+    * @return array                An array of the querystring data
+    * @access private
+    */
+    function _parseRawQuerystring($querystring)
+    {
+        $parts  = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY);
+        $return = array();
+
+        foreach ($parts as $part) {
+            if (strpos($part, '=') !== false) {
+                $value = substr($part, strpos($part, '=') + 1);
+                $key   = substr($part, 0, strpos($part, '='));
+            } else {
+                $value = null;
+                $key   = $part;
+            }
+
+            if (!$this->getOption('encode_query_keys')) {
+                $key = rawurldecode($key);
+            }
+
+            if (preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
+                $key = $matches[1];
+                $idx = $matches[2];
+
+                // Ensure is an array
+                if (empty($return[$key]) || !is_array($return[$key])) {
+                    $return[$key] = array();
+                }
+
+                // Add data
+                if ($idx === '') {
+                    $return[$key][] = $value;
+                } else {
+                    $return[$key][$idx] = $value;
+                }
+            } elseif (!$this->useBrackets AND !empty($return[$key])) {
+                $return[$key]   = (array)$return[$key];
+                $return[$key][] = $value;
+            } else {
+                $return[$key] = $value;
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+    * Resolves //, ../ and ./ from a path and returns
+    * the result. Eg:
+    *
+    * /foo/bar/../boo.php    => /foo/boo.php
+    * /foo/bar/../../boo.php => /boo.php
+    * /foo/bar/.././/boo.php => /foo/boo.php
+    *
+    * This method can also be called statically.
+    *
+    * @param  string $path URL path to resolve
+    * @return string      The result
+    */
+    function resolvePath($path)
+    {
+        $path = explode('/', str_replace('//', '/', $path));
+
+        for ($i=0; $i<count($path); $i++) {
+            if ($path[$i] == '.') {
+                unset($path[$i]);
+                $path = array_values($path);
+                $i--;
+
+            } elseif ($path[$i] == '..' AND ($i > 1 OR ($i == 1 AND $path[0] != '') ) ) {
+                unset($path[$i]);
+                unset($path[$i-1]);
+                $path = array_values($path);
+                $i -= 2;
+
+            } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') {
+                unset($path[$i]);
+                $path = array_values($path);
+                $i--;
+
+            } else {
+                continue;
+            }
+        }
+
+        return implode('/', $path);
+    }
+
+    /**
+    * Returns the standard port number for a protocol
+    *
+    * @param  string  $scheme The protocol to lookup
+    * @return integer         Port number or NULL if no scheme matches
+    *
+    * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+    */
+    function getStandardPort($scheme)
+    {
+        switch (strtolower($scheme)) {
+            case 'http':    return 80;
+            case 'https':   return 443;
+            case 'ftp':     return 21;
+            case 'imap':    return 143;
+            case 'imaps':   return 993;
+            case 'pop3':    return 110;
+            case 'pop3s':   return 995;
+            default:        return null;
+       }
+    }
+
+    /**
+    * Forces the URL to a particular protocol
+    *
+    * @param string  $protocol Protocol to force the URL to
+    * @param integer $port     Optional port (standard port is used by default)
+    */
+    function setProtocol($protocol, $port = null)
+    {
+        $this->protocol = $protocol;
+        $this->port     = is_null($port) ? $this->getStandardPort($protocol) : $port;
+    }
+
+    /**
+     * Set an option
+     *
+     * This function set an option
+     * to be used thorough the script.
+     *
+     * @access public
+     * @param  string $optionName  The optionname to set
+     * @param  string $value       The value of this option.
+     */
+    function setOption($optionName, $value)
+    {
+        if (!array_key_exists($optionName, $this->options)) {
+            return false;
+        }
+
+        $this->options[$optionName] = $value;
+        $this->initialize();
+    }
+
+    /**
+     * Get an option
+     *
+     * This function gets an option
+     * from the $this->options array
+     * and return it's value.
+     *
+     * @access public
+     * @param  string $opionName  The name of the option to retrieve
+     * @see    $this->options
+     */
+    function getOption($optionName)
+    {
+        if (!isset($this->options[$optionName])) {
+            return false;
+        }
+
+        return $this->options[$optionName];
+    }
+
+}
+?>
diff --git a/extlib/Net/URL/Mapper.php b/extlib/Net/URL/Mapper.php
new file mode 100644 (file)
index 0000000..65e3881
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Mapper.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+require_once 'Net/URL/Mapper/Path.php';
+require_once 'Net/URL/Mapper/Exception.php';
+
+/**
+ * URL parser and mapper class
+ *
+ * This class takes an URL and a configuration and returns formatted data
+ * about the request according to a configuration parameter
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @version    Release: @package_version@
+ */
+class Net_URL_Mapper
+{
+    /**
+    * Array of Net_URL_Mapper instances
+    * @var array
+    */
+    private static $instances = array();
+
+    /**
+    * Mapped paths collection
+    * @var array
+    */
+    protected $paths = array();
+
+    /**
+    * Prefix used for url mapping
+    * @var string
+    */
+    protected $prefix = '';
+
+    /**
+    * Optional scriptname if mod_rewrite is not available
+    * @var string
+    */
+    protected $scriptname = '';
+
+    /**
+    * Mapper instance id
+    * @var string
+    */
+    protected $id = '__default__';
+
+    /**
+    * Class constructor
+    * Constructor is private, you should use getInstance() instead.
+    */
+    private function __construct() { }
+
+    /**
+    * Returns a singleton object corresponding to the requested instance id
+    * @param  string    Requested instance name
+    * @return Object    Net_URL_Mapper Singleton
+    */
+    public static function getInstance($id = '__default__')
+    {
+        if (!isset(self::$instances[$id])) {
+            $m = new Net_URL_Mapper();
+            $m->id = $id;
+            self::$instances[$id] = $m;
+        }
+        return self::$instances[$id];
+    }
+
+    /**
+    * Returns the instance id
+    * @return   string  Mapper instance id
+    */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+    * Parses a path and creates a connection
+    * @param    string  The path to connect
+    * @param    array   Default values for path parts
+    * @param    array   Regular expressions for path parts
+    * @return   object  Net_URL_Mapper_Path
+    */
+    public function connect($path, $defaults = array(), $rules = array())
+    {
+        $pathObj = new Net_URL_Mapper_Path($path, $defaults, $rules);
+        $this->addPath($pathObj);
+        return $pathObj;
+    }
+
+    /**
+    * Set the url prefix if needed
+    *
+    * Example: using the prefix to differenciate mapper instances
+    * <code>
+    * $fr = Net_URL_Mapper::getInstance('fr');
+    * $fr->setPrefix('/fr');
+    * $en = Net_URL_Mapper::getInstance('en');
+    * $en->setPrefix('/en');
+    * </code>
+    *
+    * @param    string  URL prefix
+    */
+    public function setPrefix($prefix)
+    {
+        $this->prefix = '/'.trim($prefix, '/');
+    }
+
+    /**
+    * Set the scriptname if mod_rewrite not available
+    *
+    * Example: will match and generate url like
+    * - index.php/view/product/1
+    * <code>
+    * $m = Net_URL_Mapper::getInstance();
+    * $m->setScriptname('index.php');
+    * </code>
+    * @param    string  URL prefix
+    */
+    public function setScriptname($scriptname)
+    {
+        $this->scriptname = $scriptname;
+    }
+
+    /**
+    * Will attempt to match an url with a defined path
+    *
+    * If an url corresponds to a path, the resulting values are returned
+    * in an array. If none is found, null is returned. In case an url is
+    * matched but its content doesn't validate the path rules, an exception is
+    * thrown.
+    *
+    * @param    string  URL
+    * @return   array|null   array if match found, null otherwise
+    * @throws   Net_URL_Mapper_InvalidException
+    */
+    public function match($url)
+    {
+        $nurl = '/'.trim($url, '/');
+
+        // Remove scriptname if needed
+        
+        if (!empty($this->scriptname) &&
+            strpos($nurl, $this->scriptname) === 0) {
+            $nurl = substr($nurl, strlen($this->scriptname));
+            if (empty($nurl)) {
+                $nurl = '/';
+            }
+        }
+
+        // Remove prefix
+        
+        if (!empty($this->prefix)) {
+            if (strpos($nurl, $this->prefix) !== 0) {
+                return null;
+            }
+            $nurl = substr($nurl, strlen($this->prefix));
+            if (empty($nurl)) {
+                $nurl = '/';
+            }
+        }
+        
+        // Remove query string
+        
+        if (($pos = strpos($nurl, '?')) !== false) {
+            $nurl = substr($nurl, 0, $pos);
+        }
+
+        $paths = array();
+        $values = null;
+
+        // Make a list of paths that conform to route format
+
+        foreach ($this->paths as $path) {
+            $regex = $path->getFormat();
+            if (preg_match($regex, $nurl)) {
+                $paths[] = $path;
+            }   
+        }
+
+        // Make sure one of the paths found is valid
+
+        foreach ($paths as $path) {
+            $regex = $path->getRule();
+            if (preg_match($regex, $nurl, $matches)) {
+                $values = $path->getDefaults();
+                array_shift($matches);
+                $clean = array();
+                foreach ($matches as $k => $v) {
+                    $v = trim($v, '/');
+                    if (!is_int($k) && $v !== '') {
+                        $values[$k] = $v;
+                    }
+                }
+                break;
+            }
+        }
+
+        // A path conforms but does not validate
+
+        if (is_null($values) && !empty($paths)) {
+            $e = new Net_URL_Mapper_InvalidException('A path was found but is invalid.');
+            $e->setPath($paths[0]);
+            $e->setUrl($url);
+            throw $e;
+        }
+
+        return $values;
+    }
+
+    /**
+    * Generate an url based on given parameters
+    *
+    * Will attempt to find a path definition that matches the given parameters and
+    * will generate an url based on this path.
+    *
+    * @param    array   Values to be used for the url generation
+    * @param    array   Key/value pairs for query string if needed
+    * @param    string  Anchor (fragment) if needed
+    * @return   string|false    String if a rule was found, false otherwise
+    */
+    public function generate($values = array(), $qstring = array(), $anchor = '')
+    {
+        // Use root path if any
+
+        if (empty($values) && isset($this->paths['/'])) {
+            return $this->scriptname.$this->prefix.$this->paths['/']->generate($values, $qstring, $anchor);
+        }
+
+        foreach ($this->paths as $path) {
+            $set = array();
+            foreach ($values as $k => $v) {
+                if ($path->hasKey($k, $v)) {
+                    $set[$k] = $v;
+                }
+            }
+
+            if (count($set) == count($values) &&
+                count($set) <= $path->getMaxKeys()) {
+
+                $req = $path->getRequired();
+                if (count(array_intersect(array_keys($set), $req)) != count($req)) {
+                    continue;
+                }
+                $gen = $path->generate($set, $qstring, $anchor);
+                return $this->scriptname.$this->prefix.$gen;
+            }
+        }
+        return false;
+    }
+
+    /**
+    * Returns defined paths
+    * @return array     Array of paths
+    */
+    public function getPaths()
+    {
+        return $this->paths;
+    }
+
+    /**
+    * Reset all paths
+    * This is probably only useful for testing
+    */
+    public function reset()
+    {
+        $this->paths = array();
+        $this->prefix = '';
+    }
+
+    /**
+    * Add a new path to the mapper
+    * @param object     Net_URL_Mapper_Path object
+    */
+    public function addPath(Net_URL_Mapper_Path $path)
+    {
+        $this->paths[$path->getPath()] = $path;
+    }
+
+}
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Exception.php b/extlib/Net/URL/Mapper/Exception.php
new file mode 100644 (file)
index 0000000..ac3ad17
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Exception classes for Net_URL_Mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com> 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Exception.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+/**
+ * Base class for exceptions in PEAR
+ */
+require_once 'PEAR/Exception.php'; 
+
+/**
+ * Base class for exceptions in Net_URL_Mapper package
+ *
+ * Such a base class is required by the Exception RFC:
+ * http://pear.php.net/pepr/pepr-proposal-show.php?id=132
+ * It will rarely be thrown directly, its specialized subclasses will be
+ * thrown most of the time.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @version    Release: @package_version@
+ */
+class Net_URL_Mapper_Exception extends PEAR_Exception
+{
+}
+
+/**
+ * Exception thrown when a path is invalid
+ *
+ * A path can conform to a given structure, but contain invalid parameters.
+ * <code>
+ * $m = Net_URL_Mapper::getInstance();
+ * $m->connect('hi/:name', null, array('name'=>'[a-z]+'));
+ * $m->match('/hi/FOXY'); // Will throw the exception
+ * </code>
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @version    Release: @package_version@
+ */
+class Net_URL_Mapper_InvalidException extends Net_URL_Mapper_Exception
+{
+    protected $path;
+    protected $url;
+
+    public function setPath($path)
+    {
+        $this->path = $path;
+    }
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    public function setUrl($url)
+    {
+        $this->url = $url;
+    }
+
+    public function getUrl()
+    {
+        return $this->url;
+    }
+} 
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Part.php b/extlib/Net/URL/Mapper/Part.php
new file mode 100644 (file)
index 0000000..2f15b2c
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Part.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+abstract class Net_URL_Mapper_Part
+{
+    protected $defaults;
+    protected $rule;
+    protected $public;
+    protected $type;
+    protected $required = false;
+    
+    /**
+    * Part name if dynamic or content, generated from path
+    * @var string
+    */
+    public $content;
+    
+    const DYNAMIC = 1;
+    const WILDCARD = 2;
+    const FIXED = 3;
+
+    public function __construct($content, $path)
+    {
+        $this->content = $content;
+        $this->path = $path;
+    }
+
+    public function setRule($rule)
+    {
+        $this->rule = $rule;
+    }
+
+    abstract public function getFormat();
+    
+    abstract public function getRule();
+
+    public function addSlash($str)
+    {
+        $str = trim($str, '/');
+        if (($pos = strpos($this->path, '/')) !== false) {
+            if ($pos == 0) {
+                $str = '/'.$str;
+            } else {
+                $str .= '/';
+            }
+        }
+        return $str;
+    }
+
+    public function addSlashRegex($str)
+    {
+        $str = trim($str, '/');
+        if (($pos = strpos($this->path, '/')) !== false) {
+            if ($pos == 0) {
+                $str = '\/'.$str;
+            } else {
+                $str .= '\/';
+            }
+        }
+        if (!$this->isRequired()) {
+            $str = '('.$str.'|)';
+        }
+        return $str;
+    }
+
+    public function setDefaults($defaults)
+    {
+        $this->defaults = (string)$defaults;
+    }
+
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    public function accept($visitor, $method = null)
+    {
+        $args = func_get_args();
+        $visitor->$method($this, $args);
+    }
+
+    public function setRequired($required)
+    {
+        $this->required = $required;
+    }
+
+    public function isRequired()
+    {
+        return $this->required;
+    }
+
+    abstract public function generate($value = null);
+    
+    public function match($value)
+    {
+        $rule = $this->getRule();
+        return preg_match('/^'.$rule.'$/', $this->addSlash($value));
+    }
+
+}
+
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Part/Dynamic.php b/extlib/Net/URL/Mapper/Part/Dynamic.php
new file mode 100644 (file)
index 0000000..349d873
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Dynamic.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+require_once 'Net/URL/Mapper/Part.php';
+
+class Net_URL_Mapper_Part_Dynamic extends Net_URL_Mapper_Part
+{
+    
+    public function __construct($content, $path)
+    {
+        $this->type = Net_URL_Mapper_Part::DYNAMIC;
+        $this->setRequired(true);
+        parent::__construct($content, $path);
+    }
+
+    public function getFormat()
+    {
+        return $this->addSlashRegex('[^\/]+');
+    }
+
+    public function getRule()
+    {
+        if (!empty($this->rule)) {
+            return '(?P<'.$this->content.'>'.$this->addSlashRegex($this->rule).')';
+        }
+        return '(?P<'.$this->content.'>'.$this->addSlashRegex('[^\/]+').')';
+    }
+
+    public function generate($value = null)
+    {
+        if (is_array($value) && isset($value[$this->content])) {
+            $val = $value[$this->content];
+        } elseif (!is_array($value) && !is_null($value)) {
+            $val = $value;
+        } else {
+            $val = $this->defaults;
+        }
+        return $this->addSlash(urlencode($val));
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Part/Fixed.php b/extlib/Net/URL/Mapper/Part/Fixed.php
new file mode 100644 (file)
index 0000000..b315b44
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Fixed.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+require_once 'Net/URL/Mapper/Part.php';
+
+class Net_URL_Mapper_Part_Fixed extends Net_URL_Mapper_Part
+{
+    
+    public function __construct($content, $path)
+    {
+        $this->type = Net_URL_Mapper_Part::FIXED;
+        parent::__construct($content, $path);
+    }
+
+    public function getFormat()
+    {
+        return $this->getRule();
+    }
+
+    public function getRule()
+    {
+        return preg_quote($this->path, '/');
+    }
+
+    public function generate($value = null)
+    {
+        return $this->path;
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Part/Wildcard.php b/extlib/Net/URL/Mapper/Part/Wildcard.php
new file mode 100644 (file)
index 0000000..6085ff6
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Wildcard.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+require_once 'Net/URL/Mapper/Part.php';
+
+class Net_URL_Mapper_Part_Wildcard extends Net_URL_Mapper_Part
+{
+    
+    public function __construct($content, $path)
+    {
+        $this->type = Net_URL_Mapper_Part::WILDCARD;
+        $this->setRequired(true);
+        parent::__construct($content, $path);
+    }
+
+    public function getFormat()
+    {
+        return $this->addSlashRegex('.*');;
+    }
+
+    public function getRule()
+    {
+        return '(?P<'.$this->content.'>'.$this->addSlashRegex('.*').')';
+    }
+
+    public function generate($value = null)
+    {
+        if (is_array($value) && isset($value[$this->content])) {
+            $val = $value[$this->content];
+        } elseif (!is_array($value) && !is_null($value)) {
+            $val = $value;
+        } else {
+            $val = $this->defaults;
+        }
+        return $this->addSlash(str_replace(
+            array('%2F', '%23'), 
+            array('/', '#'), urlencode($val)));
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Net/URL/Mapper/Path.php b/extlib/Net/URL/Mapper/Path.php
new file mode 100644 (file)
index 0000000..b541002
--- /dev/null
@@ -0,0 +1,430 @@
+<?php
+/**
+ * URL parser and mapper
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ * 
+ * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the 
+ *      documentation and/or other materials provided with the distribution.
+ *    * The names of the authors may not be used to endorse or promote products 
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Net
+ * @package    Net_URL_Mapper
+ * @author     Bertrand Mansion <golgote@mamasam.com>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Path.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
+ * @link       http://pear.php.net/package/Net_URL_Mapper
+ */
+
+require_once 'Net/URL.php';
+require_once 'Net/URL/Mapper/Part/Dynamic.php';
+require_once 'Net/URL/Mapper/Part/Wildcard.php';
+require_once 'Net/URL/Mapper/Part/Fixed.php';
+
+class Net_URL_Mapper_Path
+{
+    private $path = '';
+    private $N = 0;
+    public $token;
+    public $value;
+    private $line = 1;
+    private $state = 1;
+
+
+    protected $alias;
+    protected $rules = array();
+    protected $defaults = array();
+    protected $parts = array();
+    protected $rule;
+    protected $format;
+    protected $minKeys;
+    protected $maxKeys;
+    protected $fixed = true;
+    protected $required;
+
+    public function __construct($path = '', $defaults = array(), $rules = array())
+    {
+        $this->path = '/'.trim(Net_URL::resolvePath($path), '/');
+        $this->setDefaults($defaults);
+        $this->setRules($rules);
+
+        try {
+            $this->parsePath();
+        } catch (Exception $e) {
+            // The path could not be parsed correctly, treat it as fixed
+            $this->fixed = true;
+            $part = self::createPart(Net_URL_Mapper_Part::FIXED, $this->path, $this->path);
+            $this->parts = array($part);
+        }
+        $this->getRequired();
+    }
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    protected function parsePath()
+    {
+        while ($this->yylex()) { }
+    }
+
+    /**
+    * Get the path alias
+    * Path aliases can be used instead of full path
+    * @return null|string
+    */
+    public function getAlias()
+    {
+        return $this->alias;
+    }
+
+    /**
+    * Set the path name
+    * @param string Set the path name
+    * @see getAlias()
+    */
+    public function setAlias($alias)
+    {
+        $this->alias = $alias;
+        return $this;
+    }
+
+    /**
+    * Get the path parts default values
+    * @return null|array
+    */
+    public function getDefaults()
+    {
+        return $this->defaults;
+    }
+
+    /**
+    * Set the path parts default values
+    * @param array  Associative array with format partname => value
+    */    
+    public function setDefaults($defaults)
+    {
+        if (is_array($defaults)) {
+            $this->defaults = $defaults;
+        } else {
+            $this->defaults = array();
+        }
+    }
+
+    /**
+    * Set the path parts default values
+    * @param array  Associative array with format partname => value
+    */    
+    public function setRules($rules)
+    {
+        if (is_array($rules)) {
+            $this->rules = $rules;   
+        } else {
+            $this->rules = array();
+        }
+    }
+
+    /**
+    * Returns the regular expression used to match this path
+    * @return string  PERL Regular expression
+    */ 
+    public function getRule()
+    {
+        if (is_null($this->rule)) {
+            $this->rule = '/^';
+            foreach ($this->parts as $path => $part) {
+                $this->rule .= $part->getRule();
+            }
+            $this->rule .= '$/';
+        }
+        return $this->rule;
+    }
+
+    public function getFormat()
+    {
+        if (is_null($this->format)) {
+            $this->format = '/^';
+            foreach ($this->parts as $path => $part) {
+                $this->format .= $part->getFormat();
+            }
+            $this->format .= '$/';
+        }
+        return $this->format;
+    }
+
+    protected function addPart($part)
+    {
+        if (array_key_exists($part->content, $this->defaults)) {
+            $part->setRequired(false);
+            $part->setDefaults($this->defaults[$part->content]);
+        }
+        if (isset($this->rules[$part->content])) {
+            $part->setRule($this->rules[$part->content]);
+        }
+        $this->rule = null;
+        if ($part->getType() != Net_URL_Mapper_Part::FIXED) {
+            $this->fixed = false;
+            $this->parts[$part->content] = $part;
+        } else {
+            $this->parts[] = $part;
+        }
+        return $part;
+    }
+
+    public static function createPart($type, $content, $path)
+    {
+        switch ($type) {
+            case Net_URL_Mapper_Part::DYNAMIC:
+                return new Net_URL_Mapper_Part_Dynamic($content, $path);
+                break;
+            case Net_URL_Mapper_Part::WILDCARD:
+                return new Net_URL_Mapper_Part_Wildcard($content, $path);
+                break;
+            default:
+                return new Net_URL_Mapper_Part_Fixed($content, $path);
+        }
+    }
+
+    /**
+    * Checks whether the path contains the given part by name
+    * If value parameter is given, the part also checks if the 
+    * given value conforms to the part rule.
+    * @param string Part name
+    * @param mixed  The value to check against 
+    */
+    public function hasKey($partName, $value = null)
+    {
+        if (array_key_exists($partName, $this->parts)) {
+            if (!is_null($value) && $value !== false) {
+                return $this->parts[$partName]->match($value);
+            } else {
+                return true;
+            }
+        } elseif (array_key_exists($partName, $this->defaults) &&
+            $value == $this->defaults[$partName]) {
+            return true;
+        }
+        return false;
+    }
+
+    public function generate($values = array(), $qstring = array(), $anchor = '')
+    {
+        $path = '';
+        foreach ($this->parts as $part) {
+            $path .= $part->generate($values);
+        }
+        $path = '/'.trim(Net_URL::resolvePath($path), '/');
+        if (!empty($qstring)) {
+            $path .= '?'.http_build_query($qstring);
+        }
+        if (!empty($anchor)) {
+            $path .= '#'.ltrim($anchor, '#');
+        }
+        return $path;
+    }
+
+    public function getRequired()
+    {
+        if (!isset($this->required)) {
+            $req = array();
+            foreach ($this->parts as $part) {
+                if ($part->isRequired()) {
+                    $req[] = $part->content;
+                }
+            }
+            $this->required = $req;
+        }
+        return $this->required;
+    }
+
+    public function getMaxKeys()
+    {
+        if (is_null($this->maxKeys)) {
+            $this->maxKeys = count($this->required);
+            $this->maxKeys += count($this->defaults);
+        }
+        return $this->maxKeys;
+    }
+
+
+
+
+    private $_yy_state = 1;
+    private $_yy_stack = array();
+
+    function yylex()
+    {
+        return $this->{'yylex' . $this->_yy_state}();
+    }
+
+    function yypushstate($state)
+    {
+        array_push($this->_yy_stack, $this->_yy_state);
+        $this->_yy_state = $state;
+    }
+
+    function yypopstate()
+    {
+        $this->_yy_state = array_pop($this->_yy_stack);
+    }
+
+    function yybegin($state)
+    {
+        $this->_yy_state = $state;
+    }
+
+
+
+    function yylex1()
+    {
+        $tokenMap = array (
+              1 => 1,
+              3 => 1,
+              5 => 1,
+              7 => 1,
+              9 => 1,
+            );
+        if ($this->N >= strlen($this->path)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(\/?:\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))/";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->path, $this->N), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->path,
+                        $this->N, 5) . '... state START');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r1_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->N += strlen($this->value);
+                    $this->line += substr_count("\n", $this->value);
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->N += strlen($this->value);
+                    $this->line += substr_count("\n", $this->value);
+                    if ($this->N >= strlen($this->path)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                } else {                    $yy_yymore_patterns = array(
+        1 => "^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
+        3 => "^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
+        5 => "^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
+        7 => "^(\/?([^\/:*]+))",
+        9 => "",
+    );
+
+                    // yymore is needed
+                    do {
+                        if (!strlen($yy_yymore_patterns[$this->token])) {
+                            throw new Exception('cannot do yymore for the last token');
+                        }
+                        if (preg_match($yy_yymore_patterns[$this->token],
+                              substr($this->path, $this->N), $yymatches)) {
+                            $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                            next($yymatches); // skip global match
+                            $this->token = key($yymatches); // token number
+                            $this->value = current($yymatches); // token value
+                            $this->line = substr_count("\n", $this->value);
+                        }
+                    } while ($this->{'yy_r1_' . $this->token}() !== null);
+                    // accept
+                    $this->N += strlen($this->value);
+                    $this->line += substr_count("\n", $this->value);
+                    return true;
+                }
+            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->path[$this->N]);
+            }
+            break;
+        } while (true);
+    } // end function
+
+
+    const START = 1;
+    function yy_r1_1($yy_subpatterns)
+    {
+
+    $c = $yy_subpatterns[0];
+    $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value);
+    $this->addPart($part);
+    }
+    function yy_r1_3($yy_subpatterns)
+    {
+
+    $c = $yy_subpatterns[0];
+    $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value);
+    $this->addPart($part);
+    }
+    function yy_r1_5($yy_subpatterns)
+    {
+
+    $c = $yy_subpatterns[0];
+    $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value);
+    $this->addPart($part);
+    }
+    function yy_r1_7($yy_subpatterns)
+    {
+
+    $c = $yy_subpatterns[0];
+    $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value);
+    $this->addPart($part);
+    }
+    function yy_r1_9($yy_subpatterns)
+    {
+
+    $c = $yy_subpatterns[0];
+    $part = self::createPart(Net_URL_Mapper_Part::FIXED, $c, $this->value);
+    $this->addPart($part);
+    }
+
+}
+
+?>
\ No newline at end of file
index 5f9827f9630f64f56ddbedf9008c522e5097b92f..634900dbf6a642f24923970de29ba029fe2d56da 100644 (file)
@@ -4,165 +4,9 @@ RewriteEngine On
 
 RewriteBase /mublog/
 
-RewriteRule ^$ index.php?action=public [L,QSA]
-RewriteRule ^rss$ index.php?action=publicrss [L,QSA]
-RewriteRule ^xrds$ index.php?action=publicxrds [L,QSA]
-RewriteRule ^featuredrss$ index.php?action=featuredrss [L,QSA]
-RewriteRule ^favoritedrss$ index.php?action=favoritedrss [L,QSA]
-RewriteRule ^opensearch/people$ index.php?action=opensearch&type=people [L,QSA]
-RewriteRule ^opensearch/notice$ index.php?action=opensearch&type=notice [L,QSA]
-
-RewriteRule ^doc/about$ index.php?action=doc&title=about [L,QSA]
-RewriteRule ^doc/contact$ index.php?action=doc&title=contact [L,QSA]
-RewriteRule ^doc/faq$ index.php?action=doc&title=faq [L,QSA]
-RewriteRule ^doc/help$ index.php?action=doc&title=help [L,QSA]
-RewriteRule ^doc/im$ index.php?action=doc&title=im [L,QSA]
-RewriteRule ^doc/openid$ index.php?action=doc&title=openid [L,QSA]
-RewriteRule ^doc/openmublog$ index.php?action=doc&title=openmublog [L,QSA]
-RewriteRule ^doc/privacy$ index.php?action=doc&title=privacy [L,QSA]
-RewriteRule ^doc/source$ index.php?action=doc&title=source [L,QSA]
-RewriteRule ^doc/tags$ index.php?action=doc&title=tags [L,QSA]
-RewriteRule ^doc/groups$ index.php?action=doc&title=groups [L,QSA]
-RewriteRule ^doc/sms$ index.php?action=doc&title=sms [L,QSA]
-
-RewriteRule ^facebook/$ index.php?action=facebookhome [L,QSA]
-RewriteRule ^facebook/index.php$ index.php?action=facebookhome [L,QSA]
-RewriteRule ^facebook/settings.php$ index.php?action=facebooksettings [L,QSA]
-RewriteRule ^facebook/invite.php$ index.php?action=facebookinvite [L,QSA]
-RewriteRule ^facebook/remove$ index.php?action=facebookremove [L,QSA]
-
-RewriteRule ^main/login$ index.php?action=login [L,QSA]
-RewriteRule ^main/logout$ index.php?action=logout [L,QSA]
-RewriteRule ^main/register/(.*)$ index.php?action=register&code=$1 [L,QSA]
-RewriteRule ^main/register$ index.php?action=register [L,QSA]
-RewriteRule ^main/openid$ index.php?action=openidlogin [L,QSA]
-RewriteRule ^main/remote$ index.php?action=remotesubscribe [L,QSA]
-
-RewriteRule ^main/subscribe$ index.php?action=subscribe [L,QSA]
-RewriteRule ^main/unsubscribe$ index.php?action=unsubscribe [L,QSA]
-RewriteRule ^main/confirmaddress$ index.php?action=confirmaddress [L,QSA]
-RewriteRule ^main/confirmaddress/(.*)$ index.php?action=confirmaddress&code=$1 [L,QSA]
-RewriteRule ^main/recoverpassword$ index.php?action=recoverpassword [L,QSA]
-RewriteRule ^main/recoverpassword/(.*)$ index.php?action=recoverpassword&code=$1 [L,QSA]
-RewriteRule ^main/invite$ index.php?action=invite [L,QSA]
-
-RewriteRule ^main/favor$ index.php?action=favor [L,QSA]
-RewriteRule ^main/disfavor$ index.php?action=disfavor [L,QSA]
-
-RewriteRule ^main/sup$ index.php?action=sup [L,QSA]
-
-RewriteRule ^main/tagother$ index.php?action=tagother [L,QSA]
-
-RewriteRule ^main/block$ index.php?action=block [L,QSA]
-
-RewriteRule ^settings/profile$ index.php?action=profilesettings [L,QSA]
-RewriteRule ^settings/avatar$ index.php?action=avatarsettings [L,QSA]
-RewriteRule ^settings/password$ index.php?action=passwordsettings [L,QSA]
-RewriteRule ^settings/openid$ index.php?action=openidsettings [L,QSA]
-RewriteRule ^settings/im$ index.php?action=imsettings [L,QSA]
-RewriteRule ^settings/email$ index.php?action=emailsettings [L,QSA]
-RewriteRule ^settings/sms$ index.php?action=smssettings [L,QSA]
-RewriteRule ^settings/twitter$ index.php?action=twittersettings [L,QSA]
-RewriteRule ^settings/other$ index.php?action=othersettings [L,QSA]
-
-RewriteRule ^search/group$ index.php?action=groupsearch [L,QSA]
-RewriteRule ^search/people$ index.php?action=peoplesearch [L,QSA]
-RewriteRule ^search/notice$ index.php?action=noticesearch [L,QSA]
-RewriteRule ^search/notice/rss$ index.php?action=noticesearchrss [L,QSA]
-
-RewriteRule ^notice/new$ index.php?action=newnotice [L,QSA]
-RewriteRule ^notice/(\d+)$ index.php?action=shownotice&notice=$1 [L,QSA]
-RewriteRule ^notice/delete/((\d+))?$ index.php?action=deletenotice&notice=$2 [L,QSA]
-RewriteRule ^notice/delete$ index.php?action=deletenotice [L,QSA]
-
-RewriteRule ^message/new$ index.php?action=newmessage [L,QSA]
-RewriteRule ^message/(\d+)$ index.php?action=showmessage&message=$1 [L,QSA]
-
-RewriteRule ^user/(\d+)$ index.php?action=userbyid&id=$1 [L,QSA]
-
-RewriteRule ^tags/?$ index.php?action=publictagcloud [L,QSA]
-RewriteRule ^tag/([a-zA-Z0-9]+)/rss$ index.php?action=tagrss&tag=$1 [L,QSA]
-RewriteRule ^tag(/(.*))?$ index.php?action=tag&tag=$2 [L,QSA]
-
-RewriteRule ^peopletag/([a-zA-Z0-9]+)$ index.php?action=peopletag&tag=$1 [L,QSA]
-
-RewriteRule ^featured/?$ index.php?action=featured [L,QSA]
-RewriteRule ^favorited/?$ index.php?action=favorited [L,QSA]
-
-RewriteRule ^group/new$ index.php?action=newgroup [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/edit$ index.php?action=editgroup&nickname=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/join$ index.php?action=joingroup&nickname=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/leave$ index.php?action=leavegroup&nickname=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/members$ index.php?action=groupmembers&nickname=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/logo$ index.php?action=grouplogo&nickname=$1 [L,QSA]
-RewriteRule ^group/([0-9]+)/id$ index.php?action=groupbyid&id=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)/rss$ index.php?action=grouprss&nickname=$1 [L,QSA]
-RewriteRule ^group/([a-zA-Z0-9]+)$ index.php?action=showgroup&nickname=$1 [L,QSA]
-RewriteRule ^group$ index.php?action=groups [L,QSA]
-
-# Twitter-compatible API rewrites
-# XXX: Surely these can be refactored a little -- Zach
-RewriteRule ^api/statuses/public_timeline(.*)$ index.php?action=api&apiaction=statuses&method=public_timeline$1 [L,QSA]
-RewriteRule ^api/statuses/friends_timeline(.*)$ index.php?action=api&apiaction=statuses&method=friends_timeline$1 [L,QSA]
-RewriteRule ^api/statuses/user_timeline/(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/user_timeline(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline$1 [L,QSA]
-RewriteRule ^api/statuses/show/(.*)$ index.php?action=api&apiaction=statuses&method=show&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/update(.*)$ index.php?action=api&apiaction=statuses&method=update$1 [L,QSA]
-RewriteRule ^api/statuses/replies(.*)$ index.php?action=api&apiaction=statuses&method=replies&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/destroy/(.*)$ index.php?action=api&apiaction=statuses&method=destroy&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/friends/(.*)$ index.php?action=api&apiaction=statuses&method=friends&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/friends(.*)$ index.php?action=api&apiaction=statuses&method=friends$1 [L,QSA]
-RewriteRule ^api/statuses/followers/(.*)$ index.php?action=api&apiaction=statuses&method=followers&argument=$1 [L,QSA]
-RewriteRule ^api/statuses/followers(.*)$ index.php?action=api&apiaction=statuses&method=followers$1 [L,QSA]
-RewriteRule ^api/statuses/featured(.*)$ index.php?action=api&apiaction=statuses&method=featured$1 [L,QSA]
-RewriteRule ^api/users/show/(.*)$ index.php?action=api&apiaction=users&method=show&argument=$1 [L,QSA]
-RewriteRule ^api/users/show(.*)$ index.php?action=api&apiaction=users&method=show$1 [L,QSA]
-RewriteRule ^api/direct_messages/sent(.*)$ index.php?action=api&apiaction=direct_messages&method=sent$1 [L,QSA]
-RewriteRule ^api/direct_messages/destroy/(.*)$ index.php?action=api&apiaction=direct_messages&method=destroy&argument=$1 [L,QSA]
-RewriteRule ^api/direct_messages/new(.*)$ index.php?action=api&apiaction=direct_messages&method=create$1 [L,QSA]
-RewriteRule ^api/direct_messages(.*)$ index.php?action=api&apiaction=direct_messages&method=direct_messages$1 [L,QSA]
-RewriteRule ^api/friendships/create/(.*)$ index.php?action=api&apiaction=friendships&method=create&argument=$1 [L,QSA]
-RewriteRule ^api/friendships/destroy/(.*)$ index.php?action=api&apiaction=friendships&method=destroy&argument=$1 [L,QSA]
-RewriteRule ^api/friendships/exists(.*)$ index.php?action=api&apiaction=friendships&method=exists$1 [L,QSA]
-RewriteRule ^api/account/verify_credentials(.*)$ index.php?action=api&apiaction=account&method=verify_credentials$1 [L,QSA]
-RewriteRule ^api/account/end_session$ index.php?action=api&apiaction=account&method=end_session$1 [L,QSA]
-RewriteRule ^api/account/update_location(.*)$ index.php?action=api&apiaction=account&method=update_location$1 [L,QSA]
-RewriteRule ^api/account/update_delivery_device(.*)$ index.php?action=api&apiaction=account&method=update_delivery_device$1 [L,QSA]
-RewriteRule ^api/account/rate_limit_status(.*)$ index.php?action=api&apiaction=account&method=rate_limit_status$1 [L,QSA]
-RewriteRule ^api/favorites/create/(.*)$ index.php?action=api&apiaction=favorites&method=create&argument=$1 [L,QSA]
-RewriteRule ^api/favorites/destroy/(.*)$ index.php?action=api&apiaction=favorites&method=destroy&argument=$1 [L,QSA]
-RewriteRule ^api/favorites/(.*)$ index.php?action=api&apiaction=favorites&method=favorites&argument=$1 [L,QSA]
-RewriteRule ^api/favorites(.*)$ index.php?action=api&apiaction=favorites&method=favorites$1 [L,QSA]
-RewriteRule ^api/notifications/follow/(.*)$ index.php?action=api&apiaction=notifications&method=follow&argument=$1 [L,QSA]
-RewriteRule ^api/notifications/leave/(.*)$ index.php?action=api&apiaction=notifications&method=leave&argument=$1 [L,QSA]
-RewriteRule ^api/blocks/create/(.*)$ index.php?action=api&apiaction=blocks&method=create&argument=$1 [L,QSA]
-RewriteRule ^api/blocks/destroy/(.*)$ index.php?action=api&apiaction=blocks&method=destroy&argument=$1 [L,QSA]
-RewriteRule ^api/help/(.*)$ index.php?action=api&apiaction=help&method=$1 [L,QSA]
-RewriteRule ^api/laconica/version(.*)$ index.php?action=api&apiaction=laconica&method=version$1 [L,QSA]
-RewriteRule ^api/laconica/config(.*)$ index.php?action=api&apiaction=laconica&method=config$1 [L,QSA]
-RewriteRule ^api/laconica/wadl\.xml$ index.php?action=api&apiaction=laconica&method=wadl.xml [L,QSA]
-
-RewriteRule ^(\w+)/subscriptions$ index.php?action=subscriptions&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/subscriptions/([a-zA-Z0-9]+)$ index.php?action=subscriptions&nickname=$1&tag=$2 [L,QSA]
-RewriteRule ^(\w+)/subscribers/([a-zA-Z0-9]+)$ index.php?action=subscribers&nickname=$1&tag=$2 [L,QSA]
-RewriteRule ^(\w+)/subscribers$ index.php?action=subscribers&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/nudge$ index.php?action=nudge&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/xrds$ index.php?action=xrds&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/rss$ index.php?action=userrss&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/all$ index.php?action=all&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/all/rss$ index.php?action=allrss&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/foaf$ index.php?action=foaf&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/replies$ index.php?action=replies&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/replies/rss$ index.php?action=repliesrss&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/avatar/(original|96|48|24)$ index.php?action=avatarbynickname&nickname=$1&size=$2 [L,QSA]
-RewriteRule ^(\w+)/favorites$ index.php?action=showfavorites&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/favorites/rss$ index.php?action=favoritesrss&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/inbox$ index.php?action=inbox&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/outbox$ index.php?action=outbox&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/microsummary$ index.php?action=microsummary&nickname=$1 [L,QSA]
-RewriteRule ^(\w+)/groups$ index.php?action=usergroups&nickname=$1 [L,QSA]
-
-RewriteRule ^(\w+)$ index.php?action=showstream&nickname=$1 [L,QSA]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule (.*) index.php?p=$1 [L,QSA]
 
 <FilesMatch "\.(ini)">
   Order allow,deny
index 0a79b9731d0aaa97ce321ec47d41bef37c270460..914ba5bde1453e2c21122a0766e8fc95fd8ac6cb 100644 (file)
--- a/index.php
+++ b/index.php
@@ -22,70 +22,131 @@ define('LACONICA', true);
 
 require_once INSTALLDIR . '/lib/common.php';
 
-// get and cache current user
+$user = null;
+$action = null;
+
+function getPath($req)
+{
+    if ((common_config('site', 'fancy') || !array_key_exists('PATH_INFO', $_SERVER))
+        && array_key_exists('p', $req)) {
+        return $req['p'];
+    } else if (array_key_exists('PATH_INFO', $_SERVER)) {
+        return $_SERVER['PATH_INFO'];
+    } else {
+        return null;
+    }
+}
 
-$user = common_current_user();
+function handleError($error)
+{
+    if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
+        return;
+    }
 
-// initialize language env
+    common_log(LOG_ERR, "PEAR error: " . $error->getMessage());
+    $msg = sprintf(_('The database for %s isn\'t responding correctly, '.
+                     'so the site won\'t work properly. '.
+                     'The site admins probably know about the problem, '.
+                     'but you can contact them at %s to make sure. '.
+                     'Otherwise, wait a few minutes and try again.'),
+                   common_config('site', 'name'),
+                   common_config('site', 'email'));
+
+    $dac = new DBErrorAction($msg, 500);
+    $dac->showPage();
+    exit(-1);
+}
 
-common_init_language();
+function main()
+{
+    global $user, $action;
 
-$action = $_REQUEST['action'];
+    // For database errors
 
-if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) {
-    common_redirect(common_local_url('public'));
-}
+    PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError');
 
-// If the site is private, and they're not on one of the "public"
-// parts of the site, redirect to login
+    // XXX: we need a little more structure in this script
 
-if (!$user && common_config('site', 'private') &&
-    !in_array($action, array('login', 'openidlogin', 'finishopenidlogin',
-                             'recoverpassword', 'api', 'doc', 'register'))) {
-    common_redirect(common_local_url('login'));
-}
+    // get and cache current user
+
+    $user = common_current_user();
+
+    // initialize language env
+
+    common_init_language();
+
+    $path = getPath($_REQUEST);
+
+    $r = Router::get();
+
+    $args = $r->map($path);
 
-$actionfile = INSTALLDIR."/actions/$action.php";
+    if (!$args) {
+        $cac = new ClientErrorAction(_('Unknown page'), 404);
+        $cac->showPage();
+        return;
+    }
+
+    $args = array_merge($args, $_REQUEST);
+
+    $action = $args['action'];
+
+    if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) {
+        common_redirect(common_local_url('public'));
+        return;
+    }
 
-if (!file_exists($actionfile)) {
-    $cac = new ClientErrorAction(_('Unknown action'), 404);
-    $cac->showPage();
-} else {
+    // If the site is private, and they're not on one of the "public"
+    // parts of the site, redirect to login
 
-    include_once $actionfile;
+    if (!$user && common_config('site', 'private') &&
+        !in_array($action, array('login', 'openidlogin', 'finishopenidlogin',
+                                 'recoverpassword', 'api', 'doc', 'register'))) {
+        common_redirect(common_local_url('login'));
+        return;
+    }
 
     $action_class = ucfirst($action).'Action';
 
-    $action_obj = new $action_class();
+    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
-            $k = array_rand($config['db']['mirror']);
+        if (common_config('db', 'mirror') && $action_obj->isReadOnly()) {
+            if (is_array(common_config('db', 'mirror'))) {
+                // "load balancing", ha ha
+                $k = array_rand($config['db']['mirror']);
 
-            $mirror = $config['db']['mirror'][$k];
-        } else {
-            $mirror = $config['db']['mirror'];
+                $mirror = $config['db']['mirror'][$k];
+            } else {
+                $mirror = $config['db']['mirror'];
+            }
+            $config['db']['database'] = $mirror;
         }
-        $config['db']['database'] = $mirror;
-    }
 
-    try {
-        if ($action_obj->prepare($_REQUEST)) {
-            $action_obj->handle($_REQUEST);
+        try {
+            if ($action_obj->prepare($args)) {
+                $action_obj->handle($args);
+            }
+        } 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();
         }
-    } 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();
     }
 }
 
+main();
+
 // XXX: cleanup exit() calls or add an exit handler so
 // this always gets called
 
diff --git a/js/flowplayer-3.0.5.min.js b/js/flowplayer-3.0.5.min.js
new file mode 100644 (file)
index 0000000..b1c3315
--- /dev/null
@@ -0,0 +1,24 @@
+/** 
+ * flowplayer.js 3.0.5. The Flowplayer API
+ * 
+ * Copyright 2009 Flowplayer Oy
+ * 
+ * This file is part of Flowplayer.
+ * 
+ * Flowplayer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * Flowplayer 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Flowplayer.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Version: 3.0.5 - Tue Feb 03 2009 13:14:17 GMT-0000 (GMT+00:00)
+ */
+(function(){function log(args){console.log("$f.fireEvent",[].slice.call(args));}function clone(obj){if(!obj||typeof obj!='object'){return obj;}var temp=new obj.constructor();for(var key in obj){if(obj.hasOwnProperty(key)){temp[key]=clone(obj[key]);}}return temp;}function each(obj,fn){if(!obj){return;}var name,i=0,length=obj.length;if(length===undefined){for(name in obj){if(fn.call(obj[name],name,obj[name])===false){break;}}}else{for(var value=obj[0];i<length&&fn.call(value,i,value)!==false;value=obj[++i]){}}return obj;}function el(id){return document.getElementById(id);}function extend(to,from,skipFuncs){if(to&&from){each(from,function(name,value){if(!skipFuncs||typeof value!='function'){to[name]=value;}});}}function select(query){var index=query.indexOf(".");if(index!=-1){var tag=query.substring(0,index)||"*";var klass=query.substring(index+1,query.length);var els=[];each(document.getElementsByTagName(tag),function(){if(this.className&&this.className.indexOf(klass)!=-1){els.push(this);}});return els;}}function stopEvent(e){e=e||window.event;if(e.preventDefault){e.stopPropagation();e.preventDefault();}else{e.returnValue=false;e.cancelBubble=true;}return false;}function bind(to,evt,fn){to[evt]=to[evt]||[];to[evt].push(fn);}function makeId(){return"_"+(""+Math.random()).substring(2,10);}var Clip=function(json,index,player){var self=this;var cuepoints={};var listeners={};self.index=index;if(typeof json=='string'){json={url:json};}extend(this,json,true);each(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),function(){var evt="on"+this;if(evt.indexOf("*")!=-1){evt=evt.substring(0,evt.length-1);var before="onBefore"+evt.substring(2);self[before]=function(fn){bind(listeners,before,fn);return self;};}self[evt]=function(fn){bind(listeners,evt,fn);return self;};if(index==-1){if(self[before]){player[before]=self[before];}if(self[evt]){player[evt]=self[evt];}}});extend(this,{onCuepoint:function(points,fn){if(arguments.length==1){cuepoints.embedded=[null,points];return self;}if(typeof points=='number'){points=[points];}var fnId=makeId();cuepoints[fnId]=[points,fn];if(player.isLoaded()){player._api().fp_addCuepoints(points,index,fnId);}return self;},update:function(json){extend(self,json);if(player.isLoaded()){player._api().fp_updateClip(json,index);}var conf=player.getConfig();var clip=(index==-1)?conf.clip:conf.playlist[index];extend(clip,json,true);},_fireEvent:function(evt,arg1,arg2,target){if(evt=='onLoad'){each(cuepoints,function(key,val){if(val[0]){player._api().fp_addCuepoints(val[0],index,key);}});return false;}target=target||self;if(evt=='onCuepoint'){var fn=cuepoints[arg1];if(fn){return fn[1].call(player,target,arg2);}}if(evt=='onStart'||evt=='onUpdate'){extend(target,arg1);if(!target.duration){target.duration=arg1.metaData.duration;}else{target.fullDuration=arg1.metaData.duration;}}var ret=true;each(listeners[evt],function(){ret=this.call(player,target,arg1,arg2);});return ret;}});if(json.onCuepoint){var arg=json.onCuepoint;self.onCuepoint.apply(self,typeof arg=='function'?[arg]:arg);delete json.onCuepoint;}each(json,function(key,val){if(typeof val=='function'){bind(listeners,key,val);delete json[key];}});if(index==-1){player.onCuepoint=this.onCuepoint;}};var Plugin=function(name,json,player,fn){var listeners={};var self=this;var hasMethods=false;if(fn){extend(listeners,fn);}each(json,function(key,val){if(typeof val=='function'){listeners[key]=val;delete json[key];}});extend(this,{animate:function(props,speed,fn){if(!props){return self;}if(typeof speed=='function'){fn=speed;speed=500;}if(typeof props=='string'){var key=props;props={};props[key]=speed;speed=500;}if(fn){var fnId=makeId();listeners[fnId]=fn;}if(speed===undefined){speed=500;}json=player._api().fp_animate(name,props,speed,fnId);return self;},css:function(props,val){if(val!==undefined){var css={};css[props]=val;props=css;}json=player._api().fp_css(name,props);extend(self,json);return self;},show:function(){this.display='block';player._api().fp_showPlugin(name);return self;},hide:function(){this.display='none';player._api().fp_hidePlugin(name);return self;},toggle:function(){this.display=player._api().fp_togglePlugin(name);return self;},fadeTo:function(o,speed,fn){if(typeof speed=='function'){fn=speed;speed=500;}if(fn){var fnId=makeId();listeners[fnId]=fn;}this.display=player._api().fp_fadeTo(name,o,speed,fnId);this.opacity=o;return self;},fadeIn:function(speed,fn){return self.fadeTo(1,speed,fn);},fadeOut:function(speed,fn){return self.fadeTo(0,speed,fn);},getName:function(){return name;},_fireEvent:function(evt,arg){if(evt=='onUpdate'){var json=player._api().fp_getPlugin(name);if(!json){return;}extend(self,json);delete self.methods;if(!hasMethods){each(json.methods,function(){var method=""+this;self[method]=function(){var a=[].slice.call(arguments);var ret=player._api().fp_invoke(name,method,a);return ret=='undefined'?self:ret;};});hasMethods=true;}}var fn=listeners[evt];if(fn){fn.call(self,arg);if(evt.substring(0,1)=="_"){delete listeners[evt];}}}});};function Player(wrapper,params,conf){var
+self=this,api=null,html,commonClip,playlist=[],plugins={},listeners={},playerId,apiId,playerIndex,activeIndex,swfHeight,wrapperHeight;extend(self,{id:function(){return playerId;},isLoaded:function(){return(api!==null);},getParent:function(){return wrapper;},hide:function(all){if(all){wrapper.style.height="0px";}if(api){api.style.height="0px";}return self;},show:function(){wrapper.style.height=wrapperHeight+"px";if(api){api.style.height=swfHeight+"px";}return self;},isHidden:function(){return api&&parseInt(api.style.height,10)===0;},load:function(fn){if(!api&&self._fireEvent("onBeforeLoad")!==false){each(players,function(){this.unload();});html=wrapper.innerHTML;flashembed(wrapper,params,{config:conf});if(fn){fn.cached=true;bind(listeners,"onLoad",fn);}}return self;},unload:function(){try{if(api&&api.fp_isFullscreen()){}}catch(error){return;}if(api&&html.replace(/\s/g,'')!==''&&!api.fp_isFullscreen()&&self._fireEvent("onBeforeUnload")!==false){api.fp_close();wrapper.innerHTML=html;self._fireEvent("onUnload");api=null;}return self;},getClip:function(index){if(index===undefined){index=activeIndex;}return playlist[index];},getCommonClip:function(){return commonClip;},getPlaylist:function(){return playlist;},getPlugin:function(name){var plugin=plugins[name];if(!plugin&&self.isLoaded()){var json=self._api().fp_getPlugin(name);if(json){plugin=new Plugin(name,json,self);plugins[name]=plugin;}}return plugin;},getScreen:function(){return self.getPlugin("screen");},getControls:function(){return self.getPlugin("controls");},getConfig:function(copy){return copy?clone(conf):conf;},getFlashParams:function(){return params;},loadPlugin:function(name,url,props,fn){if(typeof props=='function'){fn=props;props={};}var fnId=fn?makeId():"_";self._api().fp_loadPlugin(name,url,props,fnId);var arg={};arg[fnId]=fn;var p=new Plugin(name,null,self,arg);plugins[name]=p;return p;},getState:function(){return api?api.fp_getState():-1;},play:function(clip){function play(){if(clip!==undefined){self._api().fp_play(clip);}else{self._api().fp_play();}}if(api){play();}else{self.load(function(){play();});}return self;},getVersion:function(){var js="flowplayer.js 3.0.5";if(api){var ver=api.fp_getVersion();ver.push(js);return ver;}return js;},_api:function(){if(!api){throw"Flowplayer "+self.id()+" not loaded. Try moving your call to player's onLoad event";}return api;},_dump:function(){console.log(listeners);},setClip:function(clip){self.setPlaylist([clip]);},getIndex:function(){return playerIndex;}});each(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,Fullscreen*,FullscreenExit,Error").split(","),function(){var name="on"+this;if(name.indexOf("*")!=-1){name=name.substring(0,name.length-1);var name2="onBefore"+name.substring(2);self[name2]=function(fn){bind(listeners,name2,fn);return self;};}self[name]=function(fn){bind(listeners,name,fn);return self;};});each(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,reset,close,setPlaylist").split(","),function(){var name=this;self[name]=function(arg){if(!api){return self;}var ret=(arg===undefined)?api["fp_"+name]():api["fp_"+name](arg);return ret=='undefined'?self:ret;};});self._fireEvent=function(evt,arg0,arg1,arg2){if(conf.debug){log(arguments);}if(!api&&evt=='onLoad'&&arg0=='player'){api=api||el(apiId);swfHeight=api.clientHeight;each(playlist,function(){this._fireEvent("onLoad");});each(plugins,function(name,p){p._fireEvent("onUpdate");});commonClip._fireEvent("onLoad");}if(evt=='onLoad'&&arg0!='player'){return;}if(evt=='onError'){if(typeof arg0=='string'||(typeof arg0=='number'&&typeof arg1=='number')){arg0=arg1;arg1=arg2;}}if(evt=='onContextMenu'){each(conf.contextMenu[arg0],function(key,fn){fn.call(self);});return;}if(evt=='onPluginEvent'){var name=arg0.name||arg0;var p=plugins[name];if(p){p._fireEvent("onUpdate",arg0);p._fireEvent(arg1);}return;}if(evt=='onPlaylistReplace'){playlist=[];var index=0;each(arg0,function(){playlist.push(new Clip(this,index++,self));});}var ret=true;if(typeof arg0=='number'&&arg0<playlist.length){activeIndex=arg0;var clip=playlist[arg0];if(clip){ret=clip._fireEvent(evt,arg1,arg2);}if(!clip||ret!==false){ret=commonClip._fireEvent(evt,arg1,arg2,clip);}}var i=0;each(listeners[evt],function(){ret=this.call(self,arg0,arg1);if(this.cached){listeners[evt].splice(i,1);}if(ret===false){return false;}i++;});return ret;};function init(){if($f(wrapper)){$f(wrapper).getParent().innerHTML="";playerIndex=$f(wrapper).getIndex();players[playerIndex]=self;}else{players.push(self);playerIndex=players.length-1;}wrapperHeight=parseInt(wrapper.style.height,10)||wrapper.clientHeight;if(typeof params=='string'){params={src:params};}playerId=wrapper.id||"fp"+makeId();apiId=params.id||playerId+"_api";params.id=apiId;conf.playerId=playerId;if(typeof conf=='string'){conf={clip:{url:conf}};}if(typeof conf.clip=='string'){conf.clip={url:conf.clip};}conf.clip=conf.clip||{};if(wrapper.getAttribute("href",2)&&!conf.clip.url){conf.clip.url=wrapper.getAttribute("href",2);}commonClip=new Clip(conf.clip,-1,self);conf.playlist=conf.playlist||[conf.clip];var index=0;each(conf.playlist,function(){var clip=this;if(typeof clip=='object'&&clip.length){clip=""+clip;}if(typeof clip=='string'){clip={url:clip};}each(conf.clip,function(key,val){if(conf.clip[key]!==undefined&&typeof val!='function'){clip[key]=val;}});conf.playlist[index]=clip;clip=new Clip(clip,index,self);playlist.push(clip);index++;});each(conf,function(key,val){if(typeof val=='function'){bind(listeners,key,val);delete conf[key];}});each(conf.plugins,function(name,val){if(val){plugins[name]=new Plugin(name,val,self);}});if(!conf.plugins||conf.plugins.controls===undefined){plugins.controls=new Plugin("controls",null,self);}params.bgcolor=params.bgcolor||"#000000";params.version=params.version||[9,0];params.expressInstall='http://www.flowplayer.org/swf/expressinstall.swf';function doClick(e){if(!self.isLoaded()&&self._fireEvent("onBeforeClick")!==false){self.load();}return stopEvent(e);}html=wrapper.innerHTML;if(html.replace(/\s/g,'')!==''){if(wrapper.addEventListener){wrapper.addEventListener("click",doClick,false);}else if(wrapper.attachEvent){wrapper.attachEvent("onclick",doClick);}}else{if(wrapper.addEventListener){wrapper.addEventListener("click",stopEvent,false);}self.load();}}if(typeof wrapper=='string'){flashembed.domReady(function(){var node=el(wrapper);if(!node){throw"Flowplayer cannot access element: "+wrapper;}else{wrapper=node;init();}});}else{init();}}var players=[];function Iterator(arr){this.length=arr.length;this.each=function(fn){each(arr,fn);};this.size=function(){return arr.length;};}window.flowplayer=window.$f=function(){var instance=null;var arg=arguments[0];if(!arguments.length){each(players,function(){if(this.isLoaded()){instance=this;return false;}});return instance||players[0];}if(arguments.length==1){if(typeof arg=='number'){return players[arg];}else{if(arg=='*'){return new Iterator(players);}each(players,function(){if(this.id()==arg.id||this.id()==arg||this.getParent()==arg){instance=this;return false;}});return instance;}}if(arguments.length>1){var swf=arguments[1];var conf=(arguments.length==3)?arguments[2]:{};if(typeof arg=='string'){if(arg.indexOf(".")!=-1){var instances=[];each(select(arg),function(){instances.push(new Player(this,clone(swf),clone(conf)));});return new Iterator(instances);}else{var node=el(arg);return new Player(node!==null?node:arg,swf,conf);}}else if(arg){return new Player(arg,swf,conf);}}return null;};extend(window.$f,{fireEvent:function(id,evt,a0,a1,a2){var p=$f(id);return p?p._fireEvent(evt,a0,a1,a2):null;},addPlugin:function(name,fn){Player.prototype[name]=fn;return $f;},each:each,extend:extend});if(document.all){window.onbeforeunload=function(){$f("*").each(function(){if(this.isLoaded()){this.close();}});};}if(typeof jQuery=='function'){jQuery.prototype.flowplayer=function(params,conf){if(!arguments.length||typeof arguments[0]=='number'){var arr=[];this.each(function(){var p=$f(this);if(p){arr.push(p);}});return arguments.length?arr[arguments[0]]:new Iterator(arr);}return this.each(function(){$f(this,clone(params),conf?clone(conf):{});});};}})();(function(){var jQ=typeof jQuery=='function';function isDomReady(){if(domReady.done){return false;}var d=document;if(d&&d.getElementsByTagName&&d.getElementById&&d.body){clearInterval(domReady.timer);domReady.timer=null;for(var i=0;i<domReady.ready.length;i++){domReady.ready[i].call();}domReady.ready=null;domReady.done=true;}}var domReady=jQ?jQuery:function(f){if(domReady.done){return f();}if(domReady.timer){domReady.ready.push(f);}else{domReady.ready=[f];domReady.timer=setInterval(isDomReady,13);}};function extend(to,from){if(from){for(key in from){if(from.hasOwnProperty(key)){to[key]=from[key];}}}return to;}function asString(obj){switch(typeOf(obj)){case'string':obj=obj.replace(new RegExp('(["\\\\])','g'),'\\$1');obj=obj.replace(/^\s?(\d+)%/,"$1pct");return'"'+obj+'"';case'array':return'['+map(obj,function(el){return asString(el);}).join(',')+']';case'function':return'"function()"';case'object':var str=[];for(var prop in obj){if(obj.hasOwnProperty(prop)){str.push('"'+prop+'":'+asString(obj[prop]));}}return'{'+str.join(',')+'}';}return String(obj).replace(/\s/g," ").replace(/\'/g,"\"");}function typeOf(obj){if(obj===null||obj===undefined){return false;}var type=typeof obj;return(type=='object'&&obj.push)?'array':type;}if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};});}function map(arr,func){var newArr=[];for(var i in arr){if(arr.hasOwnProperty(i)){newArr[i]=func(arr[i]);}}return newArr;}function getHTML(p,c){var ie=document.all;var html='<object width="'+p.width+'" height="'+p.height+'"';if(ie&&!p.id){p.id="_"+(""+Math.random()).substring(9);}if(p.id){html+=' id="'+p.id+'"';}if(p.w3c||!ie){html+=' data="'+p.src+'" type="application/x-shockwave-flash"';}else{html+=' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';}html+='>';if(p.w3c||ie){html+='<param name="movie" value="'+p.src+'" />';}var e=extend({},p);e.width=e.height=e.id=e.w3c=e.src=null;for(var k in e){if(e[k]!==null){html+='<param name="'+k+'" value="'+e[k]+'" />';}}var vars="";if(c){for(var key in c){if(c[key]!==null){vars+=key+'='+(typeof c[key]=='object'?asString(c[key]):c[key])+'&';}}vars=vars.substring(0,vars.length-1);html+='<param name="flashvars" value=\''+vars+'\' />';}html+="</object>";return html;}function Flash(root,opts,flashvars){var version=flashembed.getVersion();extend(this,{getContainer:function(){return root;},getConf:function(){return conf;},getVersion:function(){return version;},getFlashvars:function(){return flashvars;},getApi:function(){return root.firstChild;},getHTML:function(){return getHTML(opts,flashvars);}});var required=opts.version;var express=opts.expressInstall;var ok=!required||flashembed.isSupported(required);if(ok){opts.onFail=opts.version=opts.expressInstall=null;root.innerHTML=getHTML(opts,flashvars);}else if(required&&express&&flashembed.isSupported([6,65])){extend(opts,{src:express});flashvars={MMredirectURL:location.href,MMplayerType:'PlugIn',MMdoctitle:document.title};root.innerHTML=getHTML(opts,flashvars);}else{if(root.innerHTML.replace(/\s/g,'')!==''){}else{root.innerHTML="<h2>Flash version "+required+" or greater is required</h2>"+"<h3>"+(version[0]>0?"Your version is "+version:"You have no flash plugin installed")+"</h3>"+"<p>Download latest version from <a href='http://www.adobe.com/go/getflashplayer'>here</a></p>";}}if(!ok&&opts.onFail){var ret=opts.onFail.call(this);if(typeof ret=='string'){root.innerHTML=ret;}}}window.flashembed=function(root,conf,flashvars){if(typeof root=='string'){var el=document.getElementById(root);if(el){root=el;}else{domReady(function(){flashembed(root,conf,flashvars);});return;}}if(!root){return;}var opts={width:'100%',height:'100%',allowfullscreen:true,allowscriptaccess:'always',quality:'high',version:null,onFail:null,expressInstall:null,w3c:false};if(typeof conf=='string'){conf={src:conf};}extend(opts,conf);return new Flash(root,opts,flashvars);};extend(window.flashembed,{getVersion:function(){var version=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var _d=navigator.plugins["Shockwave Flash"].description;if(typeof _d!="undefined"){_d=_d.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var _m=parseInt(_d.replace(/^(.*)\..*$/,"$1"),10);var _r=/r/.test(_d)?parseInt(_d.replace(/^.*r(.*)$/,"$1"),10):0;version=[_m,_r];}}else if(window.ActiveXObject){try{var _a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");version=[6,0];_a.AllowScriptAccess="always";}catch(ee){if(version[0]==6){return;}}try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(eee){}}if(typeof _a=="object"){_d=_a.GetVariable("$version");if(typeof _d!="undefined"){_d=_d.replace(/^\S+\s+(.*)$/,"$1").split(",");version=[parseInt(_d[0],10),parseInt(_d[2],10)];}}}return version;},isSupported:function(version){var now=flashembed.getVersion();var ret=(now[0]>version[0])||(now[0]==version[0]&&now[1]>=version[1]);return ret;},domReady:domReady,asString:asString,getHTML:getHTML});if(jQ){jQuery.prototype.flashembed=function(conf,flashvars){return this.each(function(){flashembed(this,conf,flashvars);});};}})();
\ No newline at end of file
index 5c586b5d6a5f0162771091612fab1adca148227f..869230b7a45afd28c222819ba22774b863f77c9b 100644 (file)
@@ -1,4 +1,5 @@
 // identica badge -- updated to work with the native API, 12-4-2008
+// Modified to point to Identi.ca, 2-20-2009 by Zach
 // copyright Kent Brewster 2008
 // see http://kentbrewster.com/identica-badge for info
 ( function() { 
             var a = document.createElement('A');
             a.innerHTML = 'get this';
             a.target = '_blank';
-            a.href = 'http://kentbrewster.com/identica-badge';
+            a.href = 'http://identica/doc/badge';
             $.s.f.appendChild(a);
             $.s.appendChild($.s.f);
             $.f.getUser();
index fc06ace272de81b7c91a19439523c47d722feb32..94e9c1755ef6486ebb7d598355157e12c1937eaa 100644 (file)
@@ -1,13 +1,13 @@
 /*!
- * jQuery JavaScript Library v1.3
+ * jQuery JavaScript Library v1.3.1
  * http://jquery.com/
  *
  * Copyright (c) 2009 John Resig
  * Dual licensed under the MIT and GPL licenses.
  * http://docs.jquery.com/License
  *
- * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009)
- * Revision: 6104
+ * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
+ * Revision: 6158
  */
 (function(){
 
@@ -60,20 +60,16 @@ jQuery.fn = jQuery.prototype = {
                                else {
                                        var elem = document.getElementById( match[3] );
 
-                                       // Make sure an element was located
-                                       if ( elem ){
-                                               // Handle the case where IE and Opera return items
-                                               // by name instead of ID
-                                               if ( elem.id != match[3] )
-                                                       return jQuery().find( selector );
-
-                                               // Otherwise, we inject the element directly into the jQuery object
-                                               var ret = jQuery( elem );
-                                               ret.context = document;
-                                               ret.selector = selector;
-                                               return ret;
-                                       }
-                                       selector = [];
+                                       // Handle the case where IE and Opera return items
+                                       // by name instead of ID
+                                       if ( elem && elem.id != match[3] )
+                                               return jQuery().find( selector );
+
+                                       // Otherwise, we inject the element directly into the jQuery object
+                                       var ret = jQuery( elem || [] );
+                                       ret.context = document;
+                                       ret.selector = selector;
+                                       return ret;
                                }
 
                        // HANDLE: $(expr, [context])
@@ -99,7 +95,7 @@ jQuery.fn = jQuery.prototype = {
        selector: "",
 
        // The current version of jQuery being used
-       jquery: "1.3",
+       jquery: "1.3.1",
 
        // The number of elements contained in the matched element set
        size: function() {
@@ -634,8 +630,8 @@ jQuery.extend({
 
        // check if an element is in a (or is an) XML document
        isXMLDoc: function( elem ) {
-               return elem.documentElement && !elem.body ||
-                       elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+               return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+                       !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
        },
 
        // Evalulates a script in a global context
@@ -725,7 +721,7 @@ jQuery.extend({
 
                // internal only, use hasClass("class")
                has: function( elem, className ) {
-                       return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+                       return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
                }
        },
 
@@ -999,9 +995,11 @@ jQuery.extend({
                                        var attributeNode = elem.getAttributeNode( "tabIndex" );
                                        return attributeNode && attributeNode.specified
                                                ? attributeNode.value
-                                               : elem.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i)
+                                               : elem.nodeName.match(/(button|input|object|select|textarea)/i)
                                                        ? 0
-                                                       : undefined;
+                                                       : elem.nodeName.match(/^(a|area)$/i) && elem.href
+                                                               ? 0
+                                                               : undefined;
                                }
 
                                return elem[ name ];
@@ -1397,14 +1395,14 @@ jQuery.fn.extend({
                });\r
        }\r
 });/*!
- * Sizzle CSS Selector Engine - v0.9.1
+ * Sizzle CSS Selector Engine - v0.9.3
  *  Copyright 2009, The Dojo Foundation
  *  Released under the MIT, BSD, and GPL Licenses.
  *  More information: http://sizzlejs.com/
  */
 (function(){
 
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,
        done = 0,
        toString = Object.prototype.toString;
 
@@ -1433,40 +1431,27 @@ var Sizzle = function(selector, context, results, seed) {
                }
        }
 
-       if ( parts.length > 1 && Expr.match.POS.exec( selector ) ) {
+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
                if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
-                       var later = "", match;
-
-                       // Position selectors must be done after the filter
-                       while ( (match = Expr.match.POS.exec( selector )) ) {
-                               later += match[0];
-                               selector = selector.replace( Expr.match.POS, "" );
-                       }
-
-                       set = Sizzle.filter( later, Sizzle( /\s$/.test(selector) ? selector + "*" : selector, context ) );
+                       set = posProcess( parts[0] + parts[1], context );
                } else {
                        set = Expr.relative[ parts[0] ] ?
                                [ context ] :
                                Sizzle( parts.shift(), context );
 
                        while ( parts.length ) {
-                               var tmpSet = [];
-
                                selector = parts.shift();
+
                                if ( Expr.relative[ selector ] )
                                        selector += parts.shift();
 
-                               for ( var i = 0, l = set.length; i < l; i++ ) {
-                                       Sizzle( selector, set[i], tmpSet );
-                               }
-
-                               set = tmpSet;
+                               set = posProcess( selector, set );
                        }
                }
        } else {
                var ret = seed ?
                        { expr: parts.pop(), set: makeArray(seed) } :
-                       Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context );
+                       Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
                set = Sizzle.filter( ret.expr, ret.set );
 
                if ( parts.length > 0 ) {
@@ -1531,7 +1516,7 @@ Sizzle.matches = function(expr, set){
        return Sizzle(expr, null, null, set);
 };
 
-Sizzle.find = function(expr, context){
+Sizzle.find = function(expr, context, isXML){
        var set, match;
 
        if ( !expr ) {
@@ -1546,7 +1531,7 @@ Sizzle.find = function(expr, context){
 
                        if ( left.substr( left.length - 1 ) !== "\\" ) {
                                match[1] = (match[1] || "").replace(/\\/g, "");
-                               set = Expr.find[ type ]( match, context );
+                               set = Expr.find[ type ]( match, context, isXML );
                                if ( set != null ) {
                                        expr = expr.replace( Expr.match[ type ], "" );
                                        break;
@@ -1568,7 +1553,7 @@ Sizzle.filter = function(expr, set, inplace, not){
        while ( expr && set.length ) {
                for ( var type in Expr.filter ) {
                        if ( (match = Expr.match[ type ].exec( expr )) != null ) {
-                               var filter = Expr.filter[ type ], goodArray = null, goodPos = 0, found, item;
+                               var filter = Expr.filter[ type ], found, item;
                                anyFound = false;
 
                                if ( curLoop == result ) {
@@ -1582,26 +1567,13 @@ Sizzle.filter = function(expr, set, inplace, not){
                                                anyFound = found = true;
                                        } else if ( match === true ) {
                                                continue;
-                                       } else if ( match[0] === true ) {
-                                               goodArray = [];
-                                               var last = null, elem;
-                                               for ( var i = 0; (elem = curLoop[i]) !== undefined; i++ ) {
-                                                       if ( elem && last !== elem ) {
-                                                               goodArray.push( elem );
-                                                               last = elem;
-                                                       }
-                                               }
                                        }
                                }
 
                                if ( match ) {
-                                       for ( var i = 0; (item = curLoop[i]) !== undefined; i++ ) {
+                                       for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
                                                if ( item ) {
-                                                       if ( goodArray && item != goodArray[goodPos] ) {
-                                                               goodPos++;
-                                                       }
-       
-                                                       found = filter( item, match, goodPos, goodArray );
+                                                       found = filter( item, match, i, curLoop );
                                                        var pass = not ^ !!found;
 
                                                        if ( inplace && found != null ) {
@@ -1739,14 +1711,16 @@ var Expr = Sizzle.selectors = {
                }
        },
        find: {
-               ID: function(match, context){
-                       if ( context.getElementById ) {
+               ID: function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
                                var m = context.getElementById(match[1]);
                                return m ? [m] : [];
                        }
                },
-               NAME: function(match, context){
-                       return context.getElementsByName ? context.getElementsByName(match[1]) : null;
+               NAME: function(match, context, isXML){
+                       if ( typeof context.getElementsByName !== "undefined" && !isXML ) {
+                               return context.getElementsByName(match[1]);
+                       }
                },
                TAG: function(match, context){
                        return context.getElementsByTagName(match[1]);
@@ -1756,12 +1730,15 @@ var Expr = Sizzle.selectors = {
                CLASS: function(match, curLoop, inplace, result, not){
                        match = " " + match[1].replace(/\\/g, "") + " ";
 
-                       for ( var i = 0; curLoop[i]; i++ ) {
-                               if ( not ^ (" " + curLoop[i].className + " ").indexOf(match) >= 0 ) {
-                                       if ( !inplace )
-                                               result.push( curLoop[i] );
-                               } else if ( inplace ) {
-                                       curLoop[i] = false;
+                       var elem;
+                       for ( var i = 0; (elem = curLoop[i]) != null; i++ ) {
+                               if ( elem ) {
+                                       if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) {
+                                               if ( !inplace )
+                                                       result.push( elem );
+                                       } else if ( inplace ) {
+                                               curLoop[i] = false;
+                                       }
                                }
                        }
 
@@ -1771,8 +1748,8 @@ var Expr = Sizzle.selectors = {
                        return match[1].replace(/\\/g, "");
                },
                TAG: function(match, curLoop){
-                       for ( var i = 0; !curLoop[i]; i++ ){}
-                       return isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+                       for ( var i = 0; curLoop[i] === false; i++ ){}
+                       return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
                },
                CHILD: function(match){
                        if ( match[1] == "nth" ) {
@@ -1792,7 +1769,7 @@ var Expr = Sizzle.selectors = {
                        return match;
                },
                ATTR: function(match){
-                       var name = match[1];
+                       var name = match[1].replace(/\\/g, "");
                        
                        if ( Expr.attrMap[name] ) {
                                match[1] = Expr.attrMap[name];
@@ -1916,7 +1893,7 @@ var Expr = Sizzle.selectors = {
                CHILD: function(elem, match){
                        var type = match[1], parent = elem.parentNode;
 
-                       var doneName = "child" + parent.childNodes.length;
+                       var doneName = match[0];
                        
                        if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) {
                                var count = 1;
@@ -1985,7 +1962,7 @@ var Expr = Sizzle.selectors = {
                ATTR: function(elem, match){
                        var result = Expr.attrHandle[ match[1] ] ? Expr.attrHandle[ match[1] ]( elem ) : elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4];
                        return result == null ?
-                               false :
+                               type === "!=" :
                                type === "=" ?
                                value === check :
                                type === "*=" ?
@@ -2014,6 +1991,8 @@ var Expr = Sizzle.selectors = {
        }
 };
 
+var origPOS = Expr.match.POS;
+
 for ( var type in Expr.match ) {
        Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
 }
@@ -2072,15 +2051,15 @@ try {
        // The workaround has to do additional checks after a getElementById
        // Which slows things down for other browsers (hence the branching)
        if ( !!document.getElementById( id ) ) {
-               Expr.find.ID = function(match, context){
-                       if ( context.getElementById ) {
+               Expr.find.ID = function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
                                var m = context.getElementById(match[1]);
-                               return m ? m.id === match[1] || m.getAttributeNode && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+                               return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
                        }
                };
 
                Expr.filter.ID = function(elem, match){
-                       var node = elem.getAttributeNode && elem.getAttributeNode("id");
+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
                        return elem.nodeType === 1 && node && node.nodeValue === match;
                };
        }
@@ -2120,7 +2099,7 @@ try {
 
        // Check to see if an attribute returns normalized href attributes
        div.innerHTML = "<a href='#'></a>";
-       if ( div.firstChild.getAttribute("href") !== "#" ) {
+       if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) {
                Expr.attrHandle.href = function(elem){
                        return elem.getAttribute("href", 2);
                };
@@ -2128,12 +2107,21 @@ try {
 })();
 
 if ( document.querySelectorAll ) (function(){
-       var oldSizzle = Sizzle;
+       var oldSizzle = Sizzle, div = document.createElement("div");
+       div.innerHTML = "<p class='TEST'></p>";
+
+       // Safari can't handle uppercase or unicode characters when
+       // in quirks mode.
+       if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+               return;
+       }
        
        Sizzle = function(query, context, extra, seed){
                context = context || document;
 
-               if ( !seed && context.nodeType === 9 ) {
+               // Only use querySelectorAll on non-XML documents
+               // (ID selectors don't work in non-HTML documents)
+               if ( !seed && context.nodeType === 9 && !isXML(context) ) {
                        try {
                                return makeArray( context.querySelectorAll(query), extra );
                        } catch(e){}
@@ -2148,7 +2136,7 @@ if ( document.querySelectorAll ) (function(){
        Sizzle.matches = oldSizzle.matches;
 })();
 
-if ( document.documentElement.getElementsByClassName ) {
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) {
        Expr.order.splice(1, 0, "CLASS");
        Expr.find.CLASS = function(match, context) {
                return context.getElementsByClassName(match[1]);
@@ -2229,8 +2217,28 @@ var contains = document.compareDocumentPosition ?  function(a, b){
 };
 
 var isXML = function(elem){
-       return elem.documentElement && !elem.body ||
-               elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+       return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+               !!elem.ownerDocument && isXML( elem.ownerDocument );
+};
+
+var posProcess = function(selector, context){
+       var tmpSet = [], later = "", match,
+               root = context.nodeType ? [context] : context;
+
+       // Position selectors must be done after the filter
+       // And so must :not(positional) so we move all PSEUDOs to the end
+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+               later += match[0];
+               selector = selector.replace( Expr.match.PSEUDO, "" );
+       }
+
+       selector = Expr.relative[selector] ? selector + "*" : selector;
+
+       for ( var i = 0, l = root.length; i < l; i++ ) {
+               Sizzle( selector, root[i], tmpSet );
+       }
+
+       return Sizzle.filter( later, tmpSet );
 };
 
 // EXPOSE
@@ -2681,13 +2689,13 @@ jQuery.Event = function( src ){
        if( src && src.type ){
                this.originalEvent = src;
                this.type = src.type;
-               this.timeStamp = src.timeStamp;
        // Event type
        }else
                this.type = src;
 
-       if( !this.timeStamp )
-               this.timeStamp = now();
+       // timeStamp is buggy for some events on Firefox(#3843)
+       // So we won't rely on the native value
+       this.timeStamp = now();
        
        // Mark it as fixed
        this[expando] = true;
@@ -2876,9 +2884,8 @@ function liveHandler( event ){
        });
 
        jQuery.each(elems, function(){
-               if ( !event.isImmediatePropagationStopped() &&
-                       this.fn.call(this.elem, event, this.fn.data) === false )
-                               stop = false;
+               if ( this.fn.call(this.elem, event, this.fn.data) === false )
+                       stop = false;
        });
 
        return stop;
@@ -2942,7 +2949,7 @@ function bindReady(){
 
                // If IE and not an iframe
                // continually check to see if the document is ready
-               if ( document.documentElement.doScroll && !window.frameElement ) (function(){
+               if ( document.documentElement.doScroll && typeof window.frameElement === "undefined" ) (function(){
                        if ( jQuery.isReady ) return;
 
                        try {
@@ -3477,6 +3484,9 @@ jQuery.extend({
                                // Fire the complete handlers
                                complete();
 
+                               if ( isTimeout )
+                                       xhr.abort();
+
                                // Stop memory leaks
                                if ( s.async )
                                        xhr = null;
@@ -3491,14 +3501,8 @@ jQuery.extend({
                        if ( s.timeout > 0 )
                                setTimeout(function(){
                                        // Check to see if the request is still happening
-                                       if ( xhr ) {
-                                               if( !requestDone )
-                                                       onreadystatechange( "timeout" );
-
-                                               // Cancel the request
-                                               if ( xhr )
-                                                       xhr.abort();
-                                       }
+                                       if ( xhr && !requestDone )
+                                               onreadystatechange( "timeout" );
                                }, s.timeout);
                }
 
@@ -3637,6 +3641,7 @@ jQuery.extend({
 
 });
 var elemdisplay = {},
+       timerId,
        fxAttrs = [
                // height animations
                [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
@@ -3859,7 +3864,6 @@ jQuery.extend({
        },
 
        timers: [],
-       timerId: null,
 
        fx: function( elem, options, prop ){
                this.options = options;
@@ -3911,10 +3915,8 @@ jQuery.fx.prototype = {
 
                t.elem = this.elem;
 
-               jQuery.timers.push(t);
-
-               if ( t() && jQuery.timerId == null ) {
-                       jQuery.timerId = setInterval(function(){
+               if ( t() && jQuery.timers.push(t) == 1 ) {
+                       timerId = setInterval(function(){
                                var timers = jQuery.timers;
 
                                for ( var i = 0; i < timers.length; i++ )
@@ -3922,8 +3924,7 @@ jQuery.fx.prototype = {
                                                timers.splice(i--, 1);
 
                                if ( !timers.length ) {
-                                       clearInterval( jQuery.timerId );
-                                       jQuery.timerId = null;
+                                       clearInterval( timerId );
                                }
                        }, 13);
                }
@@ -3989,11 +3990,10 @@ jQuery.fx.prototype = {
                                if ( this.options.hide || this.options.show )
                                        for ( var p in this.options.curAnim )
                                                jQuery.attr(this.elem.style, p, this.options.orig[p]);
-                       }
-
-                       if ( done )
+                                       
                                // Execute the complete function
                                this.options.complete.call( this.elem );
+                       }
 
                        return false;
                } else {
@@ -4087,7 +4087,7 @@ jQuery.offset = {
        initialize: function() {
                if ( this.initialized ) return;
                var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
-                       html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"cellpadding="0"cellspacing="0"><tr><td></td></tr></table>';
+                       html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
 
                rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
                for ( prop in rules ) container.style[prop] = rules[prop];
index 396646c84231a63731ab718b8747579bbdff5dfa..c327fae812b1a9420c4423d62b0be0fdc6ca9564 100644 (file)
@@ -1,19 +1,19 @@
 /*
- * jQuery JavaScript Library v1.3
+ * jQuery JavaScript Library v1.3.1
  * http://jquery.com/
  *
  * Copyright (c) 2009 John Resig
  * Dual licensed under the MIT and GPL licenses.
  * http://docs.jquery.com/License
  *
- * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009)
- * Revision: 6104
+ * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
+ * Revision: 6158
  */
-(function(){var l=this,g,x=l.jQuery,o=l.$,n=l.jQuery=l.$=function(D,E){return new n.fn.init(D,E)},C=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;n.fn=n.prototype={init:function(D,G){D=D||document;if(D.nodeType){this[0]=D;this.length=1;this.context=D;return this}if(typeof D==="string"){var F=C.exec(D);if(F&&(F[1]||!G)){if(F[1]){D=n.clean([F[1]],G)}else{var H=document.getElementById(F[3]);if(H){if(H.id!=F[3]){return n().find(D)}var E=n(H);E.context=document;E.selector=D;return E}D=[]}}else{return n(G).find(D)}}else{if(n.isFunction(D)){return n(document).ready(D)}}if(D.selector&&D.context){this.selector=D.selector;this.context=D.context}return this.setArray(n.makeArray(D))},selector:"",jquery:"1.3",size:function(){return this.length},get:function(D){return D===g?n.makeArray(this):this[D]},pushStack:function(E,G,D){var F=n(E);F.prevObject=this;F.context=this.context;if(G==="find"){F.selector=this.selector+(this.selector?" ":"")+D}else{if(G){F.selector=this.selector+"."+G+"("+D+")"}}return F},setArray:function(D){this.length=0;Array.prototype.push.apply(this,D);return this},each:function(E,D){return n.each(this,E,D)},index:function(D){return n.inArray(D&&D.jquery?D[0]:D,this)},attr:function(E,G,F){var D=E;if(typeof E==="string"){if(G===g){return this[0]&&n[F||"attr"](this[0],E)}else{D={};D[E]=G}}return this.each(function(H){for(E in D){n.attr(F?this.style:this,E,n.prop(this,D[E],F,H,E))}})},css:function(D,E){if((D=="width"||D=="height")&&parseFloat(E)<0){E=g}return this.attr(D,E,"curCSS")},text:function(E){if(typeof E!=="object"&&E!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(E))}var D="";n.each(E||this,function(){n.each(this.childNodes,function(){if(this.nodeType!=8){D+=this.nodeType!=1?this.nodeValue:n.fn.text([this])}})});return D},wrapAll:function(D){if(this[0]){var E=n(D,this[0].ownerDocument).clone();if(this[0].parentNode){E.insertBefore(this[0])}E.map(function(){var F=this;while(F.firstChild){F=F.firstChild}return F}).append(this)}return this},wrapInner:function(D){return this.each(function(){n(this).contents().wrapAll(D)})},wrap:function(D){return this.each(function(){n(this).wrapAll(D)})},append:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.appendChild(D)}})},prepend:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.insertBefore(D,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this)})},after:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this.nextSibling)})},end:function(){return this.prevObject||n([])},push:[].push,find:function(D){if(this.length===1&&!/,/.test(D)){var F=this.pushStack([],"find",D);F.length=0;n.find(D,this[0],F);return F}else{var E=n.map(this,function(G){return n.find(D,G)});return this.pushStack(/[^+>] [^+>]/.test(D)?n.unique(E):E,"find",D)}},clone:function(E){var D=this.map(function(){if(!n.support.noCloneEvent&&!n.isXMLDoc(this)){var H=this.cloneNode(true),G=document.createElement("div");G.appendChild(H);return n.clean([G.innerHTML])[0]}else{return this.cloneNode(true)}});var F=D.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(E===true){this.find("*").andSelf().each(function(H){if(this.nodeType==3){return}var G=n.data(this,"events");for(var J in G){for(var I in G[J]){n.event.add(F[H],J,G[J][I],G[J][I].data)}}})}return D},filter:function(D){return this.pushStack(n.isFunction(D)&&n.grep(this,function(F,E){return D.call(F,E)})||n.multiFilter(D,n.grep(this,function(E){return E.nodeType===1})),"filter",D)},closest:function(D){var E=n.expr.match.POS.test(D)?n(D):null;return this.map(function(){var F=this;while(F&&F.ownerDocument){if(E?E.index(F)>-1:n(F).is(D)){return F}F=F.parentNode}})},not:function(D){if(typeof D==="string"){if(f.test(D)){return this.pushStack(n.multiFilter(D,this,true),"not",D)}else{D=n.multiFilter(D,this)}}var E=D.length&&D[D.length-1]!==g&&!D.nodeType;return this.filter(function(){return E?n.inArray(this,D)<0:this!=D})},add:function(D){return this.pushStack(n.unique(n.merge(this.get(),typeof D==="string"?n(D):n.makeArray(D))))},is:function(D){return !!D&&n.multiFilter(D,this).length>0},hasClass:function(D){return !!D&&this.is("."+D)},val:function(J){if(J===g){var D=this[0];if(D){if(n.nodeName(D,"option")){return(D.attributes.value||{}).specified?D.value:D.text}if(n.nodeName(D,"select")){var H=D.selectedIndex,K=[],L=D.options,G=D.type=="select-one";if(H<0){return null}for(var E=G?H:0,I=G?H+1:L.length;E<I;E++){var F=L[E];if(F.selected){J=n(F).val();if(G){return J}K.push(J)}}return K}return(D.value||"").replace(/\r/g,"")}return g}if(typeof J==="number"){J+=""}return this.each(function(){if(this.nodeType!=1){return}if(n.isArray(J)&&/radio|checkbox/.test(this.type)){this.checked=(n.inArray(this.value,J)>=0||n.inArray(this.name,J)>=0)}else{if(n.nodeName(this,"select")){var M=n.makeArray(J);n("option",this).each(function(){this.selected=(n.inArray(this.value,M)>=0||n.inArray(this.text,M)>=0)});if(!M.length){this.selectedIndex=-1}}else{this.value=J}}})},html:function(D){return D===g?(this[0]?this[0].innerHTML:null):this.empty().append(D)},replaceWith:function(D){return this.after(D).remove()},eq:function(D){return this.slice(D,+D+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(D){return this.pushStack(n.map(this,function(F,E){return D.call(F,E,F)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=n.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild,D=this.length>1?I.cloneNode(true):I;if(H){for(var G=0,E=this.length;G<E;G++){L.call(K(this[G],H),G>0?D.cloneNode(true):I)}}if(F){n.each(F,y)}}return this;function K(N,O){return M&&n.nodeName(N,"table")&&n.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};n.fn.init.prototype=n.fn;function y(D,E){if(E.src){n.ajax({url:E.src,async:false,dataType:"script"})}else{n.globalEval(E.text||E.textContent||E.innerHTML||"")}if(E.parentNode){E.parentNode.removeChild(E)}}function e(){return +new Date}n.extend=n.fn.extend=function(){var I=arguments[0]||{},G=1,H=arguments.length,D=false,F;if(typeof I==="boolean"){D=I;I=arguments[1]||{};G=2}if(typeof I!=="object"&&!n.isFunction(I)){I={}}if(H==G){I=this;--G}for(;G<H;G++){if((F=arguments[G])!=null){for(var E in F){var J=I[E],K=F[E];if(I===K){continue}if(D&&K&&typeof K==="object"&&!K.nodeType){I[E]=n.extend(D,J||(K.length!=null?[]:{}),K)}else{if(K!==g){I[E]=K}}}}}return I};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,p=document.defaultView||{},r=Object.prototype.toString;n.extend({noConflict:function(D){l.$=o;if(D){l.jQuery=x}return n},isFunction:function(D){return r.call(D)==="[object Function]"},isArray:function(D){return r.call(D)==="[object Array]"},isXMLDoc:function(D){return D.documentElement&&!D.body||D.tagName&&D.ownerDocument&&!D.ownerDocument.body},globalEval:function(F){F=n.trim(F);if(F){var E=document.getElementsByTagName("head")[0]||document.documentElement,D=document.createElement("script");D.type="text/javascript";if(n.support.scriptEval){D.appendChild(document.createTextNode(F))}else{D.text=F}E.insertBefore(D,E.firstChild);E.removeChild(D)}},nodeName:function(E,D){return E.nodeName&&E.nodeName.toUpperCase()==D.toUpperCase()},each:function(F,J,E){var D,G=0,H=F.length;if(E){if(H===g){for(D in F){if(J.apply(F[D],E)===false){break}}}else{for(;G<H;){if(J.apply(F[G++],E)===false){break}}}}else{if(H===g){for(D in F){if(J.call(F[D],D,F[D])===false){break}}}else{for(var I=F[0];G<H&&J.call(I,G,I)!==false;I=F[++G]){}}}return F},prop:function(G,H,F,E,D){if(n.isFunction(H)){H=H.call(G,E)}return typeof H==="number"&&F=="curCSS"&&!b.test(D)?H+"px":H},className:{add:function(D,E){n.each((E||"").split(/\s+/),function(F,G){if(D.nodeType==1&&!n.className.has(D.className,G)){D.className+=(D.className?" ":"")+G}})},remove:function(D,E){if(D.nodeType==1){D.className=E!==g?n.grep(D.className.split(/\s+/),function(F){return !n.className.has(E,F)}).join(" "):""}},has:function(E,D){return n.inArray(D,(E.className||E).toString().split(/\s+/))>-1}},swap:function(G,F,H){var D={};for(var E in F){D[E]=G.style[E];G.style[E]=F[E]}H.call(G);for(var E in F){G.style[E]=D[E]}},css:function(F,D,H){if(D=="width"||D=="height"){var J,E={position:"absolute",visibility:"hidden",display:"block"},I=D=="width"?["Left","Right"]:["Top","Bottom"];function G(){J=D=="width"?F.offsetWidth:F.offsetHeight;var L=0,K=0;n.each(I,function(){L+=parseFloat(n.curCSS(F,"padding"+this,true))||0;K+=parseFloat(n.curCSS(F,"border"+this+"Width",true))||0});J-=Math.round(L+K)}if(n(F).is(":visible")){G()}else{n.swap(F,E,G)}return Math.max(0,J)}return n.curCSS(F,D,H)},curCSS:function(H,E,F){var K,D=H.style;if(E=="opacity"&&!n.support.opacity){K=n.attr(D,"opacity");return K==""?"1":K}if(E.match(/float/i)){E=v}if(!F&&D&&D[E]){K=D[E]}else{if(p.getComputedStyle){if(E.match(/float/i)){E="float"}E=E.replace(/([A-Z])/g,"-$1").toLowerCase();var L=p.getComputedStyle(H,null);if(L){K=L.getPropertyValue(E)}if(E=="opacity"&&K==""){K="1"}}else{if(H.currentStyle){var I=E.replace(/\-(\w)/g,function(M,N){return N.toUpperCase()});K=H.currentStyle[E]||H.currentStyle[I];if(!/^\d+(px)?$/i.test(K)&&/^\d/.test(K)){var G=D.left,J=H.runtimeStyle.left;H.runtimeStyle.left=H.currentStyle.left;D.left=K||0;K=D.pixelLeft+"px";D.left=G;H.runtimeStyle.left=J}}}}return K},clean:function(E,J,H){J=J||document;if(typeof J.createElement==="undefined"){J=J.ownerDocument||J[0]&&J[0].ownerDocument||document}if(!H&&E.length===1&&typeof E[0]==="string"){var G=/^<(\w+)\s*\/?>$/.exec(E[0]);if(G){return[J.createElement(G[1])]}}var F=[],D=[],K=J.createElement("div");n.each(E,function(O,Q){if(typeof Q==="number"){Q+=""}if(!Q){return}if(typeof Q==="string"){Q=Q.replace(/(<(\w+)[^>]*?)\/>/g,function(S,T,R){return R.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?S:T+"></"+R+">"});var N=n.trim(Q).toLowerCase();var P=!N.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!N.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||N.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!N.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!N.indexOf("<td")||!N.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!N.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!n.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];K.innerHTML=P[1]+Q+P[2];while(P[0]--){K=K.lastChild}if(!n.support.tbody){var M=!N.indexOf("<table")&&N.indexOf("<tbody")<0?K.firstChild&&K.firstChild.childNodes:P[1]=="<table>"&&N.indexOf("<tbody")<0?K.childNodes:[];for(var L=M.length-1;L>=0;--L){if(n.nodeName(M[L],"tbody")&&!M[L].childNodes.length){M[L].parentNode.removeChild(M[L])}}}if(!n.support.leadingWhitespace&&/^\s/.test(Q)){K.insertBefore(J.createTextNode(Q.match(/^\s*/)[0]),K.firstChild)}Q=n.makeArray(K.childNodes)}if(Q.nodeType){F.push(Q)}else{F=n.merge(F,Q)}});if(H){for(var I=0;F[I];I++){if(n.nodeName(F[I],"script")&&(!F[I].type||F[I].type.toLowerCase()==="text/javascript")){D.push(F[I].parentNode?F[I].parentNode.removeChild(F[I]):F[I])}else{if(F[I].nodeType===1){F.splice.apply(F,[I+1,0].concat(n.makeArray(F[I].getElementsByTagName("script"))))}H.appendChild(F[I])}}return D}return F},attr:function(I,F,J){if(!I||I.nodeType==3||I.nodeType==8){return g}var G=!n.isXMLDoc(I),K=J!==g;F=G&&n.props[F]||F;if(I.tagName){var E=/href|src|style/.test(F);if(F=="selected"&&I.parentNode){I.parentNode.selectedIndex}if(F in I&&G&&!E){if(K){if(F=="type"&&n.nodeName(I,"input")&&I.parentNode){throw"type property can't be changed"}I[F]=J}if(n.nodeName(I,"form")&&I.getAttributeNode(F)){return I.getAttributeNode(F).nodeValue}if(F=="tabIndex"){var H=I.getAttributeNode("tabIndex");return H&&H.specified?H.value:I.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i)?0:g}return I[F]}if(!n.support.style&&G&&F=="style"){return n.attr(I.style,"cssText",J)}if(K){I.setAttribute(F,""+J)}var D=!n.support.hrefNormalized&&G&&E?I.getAttribute(F,2):I.getAttribute(F);return D===null?g:D}if(!n.support.opacity&&F=="opacity"){if(K){I.zoom=1;I.filter=(I.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(J)+""=="NaN"?"":"alpha(opacity="+J*100+")")}return I.filter&&I.filter.indexOf("opacity=")>=0?(parseFloat(I.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}F=F.replace(/-([a-z])/ig,function(L,M){return M.toUpperCase()});if(K){I[F]=J}return I[F]},trim:function(D){return(D||"").replace(/^\s+|\s+$/g,"")},makeArray:function(F){var D=[];if(F!=null){var E=F.length;if(E==null||typeof F==="string"||n.isFunction(F)||F.setInterval){D[0]=F}else{while(E){D[--E]=F[E]}}}return D},inArray:function(F,G){for(var D=0,E=G.length;D<E;D++){if(G[D]===F){return D}}return -1},merge:function(G,D){var E=0,F,H=G.length;if(!n.support.getAll){while((F=D[E++])!=null){if(F.nodeType!=8){G[H++]=F}}}else{while((F=D[E++])!=null){G[H++]=F}}return G},unique:function(J){var E=[],D={};try{for(var F=0,G=J.length;F<G;F++){var I=n.data(J[F]);if(!D[I]){D[I]=true;E.push(J[F])}}}catch(H){E=J}return E},grep:function(E,I,D){var F=[];for(var G=0,H=E.length;G<H;G++){if(!D!=!I(E[G],G)){F.push(E[G])}}return F},map:function(D,I){var E=[];for(var F=0,G=D.length;F<G;F++){var H=I(D[F],F);if(H!=null){E[E.length]=H}}return E.concat.apply([],E)}});var B=navigator.userAgent.toLowerCase();n.browser={version:(B.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(B),opera:/opera/.test(B),msie:/msie/.test(B)&&!/opera/.test(B),mozilla:/mozilla/.test(B)&&!/(compatible|webkit)/.test(B)};n.each({parent:function(D){return D.parentNode},parents:function(D){return n.dir(D,"parentNode")},next:function(D){return n.nth(D,2,"nextSibling")},prev:function(D){return n.nth(D,2,"previousSibling")},nextAll:function(D){return n.dir(D,"nextSibling")},prevAll:function(D){return n.dir(D,"previousSibling")},siblings:function(D){return n.sibling(D.parentNode.firstChild,D)},children:function(D){return n.sibling(D.firstChild)},contents:function(D){return n.nodeName(D,"iframe")?D.contentDocument||D.contentWindow.document:n.makeArray(D.childNodes)}},function(D,E){n.fn[D]=function(F){var G=n.map(this,E);if(F&&typeof F=="string"){G=n.multiFilter(F,G)}return this.pushStack(n.unique(G),D,F)}});n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(D,E){n.fn[D]=function(){var F=arguments;return this.each(function(){for(var G=0,H=F.length;G<H;G++){n(F[G])[E](this)}})}});n.each({removeAttr:function(D){n.attr(this,D,"");if(this.nodeType==1){this.removeAttribute(D)}},addClass:function(D){n.className.add(this,D)},removeClass:function(D){n.className.remove(this,D)},toggleClass:function(E,D){if(typeof D!=="boolean"){D=!n.className.has(this,E)}n.className[D?"add":"remove"](this,E)},remove:function(D){if(!D||n.filter(D,[this]).length){n("*",this).add([this]).each(function(){n.event.remove(this);n.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){n(">*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(D,E){n.fn[D]=function(){return this.each(E,arguments)}});function j(D,E){return D[0]&&parseInt(n.curCSS(D[0],E,true),10)||0}var h="jQuery"+e(),u=0,z={};n.extend({cache:{},data:function(E,D,F){E=E==l?z:E;var G=E[h];if(!G){G=E[h]=++u}if(D&&!n.cache[G]){n.cache[G]={}}if(F!==g){n.cache[G][D]=F}return D?n.cache[G][D]:G},removeData:function(E,D){E=E==l?z:E;var G=E[h];if(D){if(n.cache[G]){delete n.cache[G][D];D="";for(D in n.cache[G]){break}if(!D){n.removeData(E)}}}else{try{delete E[h]}catch(F){if(E.removeAttribute){E.removeAttribute(h)}}delete n.cache[G]}},queue:function(E,D,G){if(E){D=(D||"fx")+"queue";var F=n.data(E,D);if(!F||n.isArray(G)){F=n.data(E,D,n.makeArray(G))}else{if(G){F.push(G)}}}return F},dequeue:function(G,F){var D=n.queue(G,F),E=D.shift();if(!F||F==="fx"){E=D[0]}if(E!==g){E.call(G)}}});n.fn.extend({data:function(D,F){var G=D.split(".");G[1]=G[1]?"."+G[1]:"";if(F===g){var E=this.triggerHandler("getData"+G[1]+"!",[G[0]]);if(E===g&&this.length){E=n.data(this[0],D)}return E===g&&G[1]?this.data(G[0]):E}else{return this.trigger("setData"+G[1]+"!",[G[0],F]).each(function(){n.data(this,D,F)})}},removeData:function(D){return this.each(function(){n.removeData(this,D)})},queue:function(D,E){if(typeof D!=="string"){E=D;D="fx"}if(E===g){return n.queue(this[0],D)}return this.each(function(){var F=n.queue(this,D,E);if(D=="fx"&&F.length==1){F[0].call(this)}})},dequeue:function(D){return this.each(function(){n.dequeue(this,D)})}});
+(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H<F;H++){M.call(L(this[H],I),H>0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){G=o.trim(G);if(G){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+"></"+S+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf("<table")&&O.indexOf("<tbody")<0?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&O.indexOf("<tbody")<0?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(){var G=arguments;return this.each(function(){for(var H=0,I=G.length;H<I;H++){o(G[H])[F](this)}})}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(">*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
 /*
- * Sizzle CSS Selector Engine - v0.9.1
+ * Sizzle CSS Selector Engine - v0.9.3
  *  Copyright 2009, The Dojo Foundation
  *  Released under the MIT, BSD, and GPL Licenses.
  *  More information: http://sizzlejs.com/
  */
-(function(){var N=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,I=0,F=Object.prototype.toString;var E=function(ae,S,aa,V){aa=aa||[];S=S||document;if(S.nodeType!==1&&S.nodeType!==9){return[]}if(!ae||typeof ae!=="string"){return aa}var ab=[],ac,Y,ah,ag,Z,R,Q=true;N.lastIndex=0;while((ac=N.exec(ae))!==null){ab.push(ac[1]);if(ac[2]){R=RegExp.rightContext;break}}if(ab.length>1&&G.match.POS.exec(ae)){if(ab.length===2&&G.relative[ab[0]]){var U="",X;while((X=G.match.POS.exec(ae))){U+=X[0];ae=ae.replace(G.match.POS,"")}Y=E.filter(U,E(/\s$/.test(ae)?ae+"*":ae,S))}else{Y=G.relative[ab[0]]?[S]:E(ab.shift(),S);while(ab.length){var P=[];ae=ab.shift();if(G.relative[ae]){ae+=ab.shift()}for(var af=0,ad=Y.length;af<ad;af++){E(ae,Y[af],P)}Y=P}}}else{var ai=V?{expr:ab.pop(),set:D(V)}:E.find(ab.pop(),ab.length===1&&S.parentNode?S.parentNode:S);Y=E.filter(ai.expr,ai.set);if(ab.length>0){ah=D(Y)}else{Q=false}while(ab.length){var T=ab.pop(),W=T;if(!G.relative[T]){T=""}else{W=ab.pop()}if(W==null){W=S}G.relative[T](ah,W,M(S))}}if(!ah){ah=Y}if(!ah){throw"Syntax error, unrecognized expression: "+(T||ae)}if(F.call(ah)==="[object Array]"){if(!Q){aa.push.apply(aa,ah)}else{if(S.nodeType===1){for(var af=0;ah[af]!=null;af++){if(ah[af]&&(ah[af]===true||ah[af].nodeType===1&&H(S,ah[af]))){aa.push(Y[af])}}}else{for(var af=0;ah[af]!=null;af++){if(ah[af]&&ah[af].nodeType===1){aa.push(Y[af])}}}}}else{D(ah,aa)}if(R){E(R,S,aa,V)}return aa};E.matches=function(P,Q){return E(P,null,null,Q)};E.find=function(V,S){var W,Q;if(!V){return[]}for(var R=0,P=G.order.length;R<P;R++){var T=G.order[R],Q;if((Q=G.match[T].exec(V))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){Q[1]=(Q[1]||"").replace(/\\/g,"");W=G.find[T](Q,S);if(W!=null){V=V.replace(G.match[T],"");break}}}}if(!W){W=S.getElementsByTagName("*")}return{set:W,expr:V}};E.filter=function(S,ac,ad,T){var Q=S,Y=[],ah=ac,V,ab;while(S&&ac.length){for(var U in G.filter){if((V=G.match[U].exec(S))!=null){var Z=G.filter[U],R=null,X=0,aa,ag;ab=false;if(ah==Y){Y=[]}if(G.preFilter[U]){V=G.preFilter[U](V,ah,ad,Y,T);if(!V){ab=aa=true}else{if(V===true){continue}else{if(V[0]===true){R=[];var W=null,af;for(var ae=0;(af=ah[ae])!==g;ae++){if(af&&W!==af){R.push(af);W=af}}}}}}if(V){for(var ae=0;(ag=ah[ae])!==g;ae++){if(ag){if(R&&ag!=R[X]){X++}aa=Z(ag,V,X,R);var P=T^!!aa;if(ad&&aa!=null){if(P){ab=true}else{ah[ae]=false}}else{if(P){Y.push(ag);ab=true}}}}}if(aa!==g){if(!ad){ah=Y}S=S.replace(G.match[U],"");if(!ab){return[]}break}}}S=S.replace(/\s*,\s*/,"");if(S==Q){if(ab==null){throw"Syntax error, unrecognized expression: "+S}else{break}}Q=S}return ah};var G=E.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(P){return P.getAttribute("href")}},relative:{"+":function(T,Q){for(var R=0,P=T.length;R<P;R++){var S=T[R];if(S){var U=S.previousSibling;while(U&&U.nodeType!==1){U=U.previousSibling}T[R]=typeof Q==="string"?U||false:U===Q}}if(typeof Q==="string"){E.filter(Q,T,true)}},">":function(U,Q,V){if(typeof Q==="string"&&!/\W/.test(Q)){Q=V?Q:Q.toUpperCase();for(var R=0,P=U.length;R<P;R++){var T=U[R];if(T){var S=T.parentNode;U[R]=S.nodeName===Q?S:false}}}else{for(var R=0,P=U.length;R<P;R++){var T=U[R];if(T){U[R]=typeof Q==="string"?T.parentNode:T.parentNode===Q}}if(typeof Q==="string"){E.filter(Q,U,true)}}},"":function(S,Q,U){var R="done"+(I++),P=O;if(!Q.match(/\W/)){var T=Q=U?Q:Q.toUpperCase();P=L}P("parentNode",Q,R,S,T,U)},"~":function(S,Q,U){var R="done"+(I++),P=O;if(typeof Q==="string"&&!Q.match(/\W/)){var T=Q=U?Q:Q.toUpperCase();P=L}P("previousSibling",Q,R,S,T,U)}},find:{ID:function(Q,R){if(R.getElementById){var P=R.getElementById(Q[1]);return P?[P]:[]}},NAME:function(P,Q){return Q.getElementsByName?Q.getElementsByName(P[1]):null},TAG:function(P,Q){return Q.getElementsByTagName(P[1])}},preFilter:{CLASS:function(S,Q,R,P,U){S=" "+S[1].replace(/\\/g,"")+" ";for(var T=0;Q[T];T++){if(U^(" "+Q[T].className+" ").indexOf(S)>=0){if(!R){P.push(Q[T])}}else{if(R){Q[T]=false}}}return false},ID:function(P){return P[1].replace(/\\/g,"")},TAG:function(Q,P){for(var R=0;!P[R];R++){}return M(P[R])?Q[1]:Q[1].toUpperCase()},CHILD:function(P){if(P[1]=="nth"){var Q=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(P[2]=="even"&&"2n"||P[2]=="odd"&&"2n+1"||!/\D/.test(P[2])&&"0n+"+P[2]||P[2]);P[2]=(Q[1]+(Q[2]||1))-0;P[3]=Q[3]-0}P[0]="done"+(I++);return P},ATTR:function(Q){var P=Q[1];if(G.attrMap[P]){Q[1]=G.attrMap[P]}if(Q[2]==="~="){Q[4]=" "+Q[4]+" "}return Q},PSEUDO:function(T,Q,R,P,U){if(T[1]==="not"){if(T[3].match(N).length>1){T[3]=E(T[3],null,null,Q)}else{var S=E.filter(T[3],Q,R,true^U);if(!R){P.push.apply(P,S)}return false}}else{if(G.match.POS.test(T[0])){return true}}return T},POS:function(P){P.unshift(true);return P}},filters:{enabled:function(P){return P.disabled===false&&P.type!=="hidden"},disabled:function(P){return P.disabled===true},checked:function(P){return P.checked===true},selected:function(P){P.parentNode.selectedIndex;return P.selected===true},parent:function(P){return !!P.firstChild},empty:function(P){return !P.firstChild},has:function(R,Q,P){return !!E(P[3],R).length},header:function(P){return/h\d/i.test(P.nodeName)},text:function(P){return"text"===P.type},radio:function(P){return"radio"===P.type},checkbox:function(P){return"checkbox"===P.type},file:function(P){return"file"===P.type},password:function(P){return"password"===P.type},submit:function(P){return"submit"===P.type},image:function(P){return"image"===P.type},reset:function(P){return"reset"===P.type},button:function(P){return"button"===P.type||P.nodeName.toUpperCase()==="BUTTON"},input:function(P){return/input|select|textarea|button/i.test(P.nodeName)}},setFilters:{first:function(Q,P){return P===0},last:function(R,Q,P,S){return Q===S.length-1},even:function(Q,P){return P%2===0},odd:function(Q,P){return P%2===1},lt:function(R,Q,P){return Q<P[3]-0},gt:function(R,Q,P){return Q>P[3]-0},nth:function(R,Q,P){return P[3]-0==Q},eq:function(R,Q,P){return P[3]-0==Q}},filter:{CHILD:function(P,S){var V=S[1],W=P.parentNode;var U="child"+W.childNodes.length;if(W&&(!W[U]||!P.nodeIndex)){var T=1;for(var Q=W.firstChild;Q;Q=Q.nextSibling){if(Q.nodeType==1){Q.nodeIndex=T++}}W[U]=T-1}if(V=="first"){return P.nodeIndex==1}else{if(V=="last"){return P.nodeIndex==W[U]}else{if(V=="only"){return W[U]==1}else{if(V=="nth"){var Y=false,R=S[2],X=S[3];if(R==1&&X==0){return true}if(R==0){if(P.nodeIndex==X){Y=true}}else{if((P.nodeIndex-X)%R==0&&(P.nodeIndex-X)/R>=0){Y=true}}return Y}}}}},PSEUDO:function(V,R,S,W){var Q=R[1],T=G.filters[Q];if(T){return T(V,S,R,W)}else{if(Q==="contains"){return(V.textContent||V.innerText||"").indexOf(R[3])>=0}else{if(Q==="not"){var U=R[3];for(var S=0,P=U.length;S<P;S++){if(U[S]===V){return false}}return true}}}},ID:function(Q,P){return Q.nodeType===1&&Q.getAttribute("id")===P},TAG:function(Q,P){return(P==="*"&&Q.nodeType===1)||Q.nodeName===P},CLASS:function(Q,P){return P.test(Q.className)},ATTR:function(T,R){var P=G.attrHandle[R[1]]?G.attrHandle[R[1]](T):T[R[1]]||T.getAttribute(R[1]),U=P+"",S=R[2],Q=R[4];return P==null?false:S==="="?U===Q:S==="*="?U.indexOf(Q)>=0:S==="~="?(" "+U+" ").indexOf(Q)>=0:!R[4]?P:S==="!="?U!=Q:S==="^="?U.indexOf(Q)===0:S==="$="?U.substr(U.length-Q.length)===Q:S==="|="?U===Q||U.substr(0,Q.length+1)===Q+"-":false},POS:function(T,Q,R,U){var P=Q[2],S=G.setFilters[P];if(S){return S(T,R,Q,U)}}}};for(var K in G.match){G.match[K]=RegExp(G.match[K].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var D=function(Q,P){Q=Array.prototype.slice.call(Q);if(P){P.push.apply(P,Q);return P}return Q};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(J){D=function(T,S){var Q=S||[];if(F.call(T)==="[object Array]"){Array.prototype.push.apply(Q,T)}else{if(typeof T.length==="number"){for(var R=0,P=T.length;R<P;R++){Q.push(T[R])}}else{for(var R=0;T[R];R++){Q.push(T[R])}}}return Q}}(function(){var Q=document.createElement("form"),R="script"+(new Date).getTime();Q.innerHTML="<input name='"+R+"'/>";var P=document.documentElement;P.insertBefore(Q,P.firstChild);if(!!document.getElementById(R)){G.find.ID=function(T,U){if(U.getElementById){var S=U.getElementById(T[1]);return S?S.id===T[1]||S.getAttributeNode&&S.getAttributeNode("id").nodeValue===T[1]?[S]:g:[]}};G.filter.ID=function(U,S){var T=U.getAttributeNode&&U.getAttributeNode("id");return U.nodeType===1&&T&&T.nodeValue===S}}P.removeChild(Q)})();(function(){var P=document.createElement("div");P.appendChild(document.createComment(""));if(P.getElementsByTagName("*").length>0){G.find.TAG=function(Q,U){var T=U.getElementsByTagName(Q[1]);if(Q[1]==="*"){var S=[];for(var R=0;T[R];R++){if(T[R].nodeType===1){S.push(T[R])}}T=S}return T}}P.innerHTML="<a href='#'></a>";if(P.firstChild.getAttribute("href")!=="#"){G.attrHandle.href=function(Q){return Q.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var P=E;E=function(T,S,Q,R){S=S||document;if(!R&&S.nodeType===9){try{return D(S.querySelectorAll(T),Q)}catch(U){}}return P(T,S,Q,R)};E.find=P.find;E.filter=P.filter;E.selectors=P.selectors;E.matches=P.matches})()}if(document.documentElement.getElementsByClassName){G.order.splice(1,0,"CLASS");G.find.CLASS=function(P,Q){return Q.getElementsByClassName(P[1])}}function L(Q,W,V,Z,X,Y){for(var T=0,R=Z.length;T<R;T++){var P=Z[T];if(P){P=P[Q];var U=false;while(P&&P.nodeType){var S=P[V];if(S){U=Z[S];break}if(P.nodeType===1&&!Y){P[V]=T}if(P.nodeName===W){U=P;break}P=P[Q]}Z[T]=U}}}function O(Q,V,U,Y,W,X){for(var S=0,R=Y.length;S<R;S++){var P=Y[S];if(P){P=P[Q];var T=false;while(P&&P.nodeType){if(P[U]){T=Y[P[U]];break}if(P.nodeType===1){if(!X){P[U]=S}if(typeof V!=="string"){if(P===V){T=true;break}}else{if(E.filter(V,[P]).length>0){T=P;break}}}P=P[Q]}Y[S]=T}}}var H=document.compareDocumentPosition?function(Q,P){return Q.compareDocumentPosition(P)&16}:function(Q,P){return Q!==P&&(Q.contains?Q.contains(P):true)};var M=function(P){return P.documentElement&&!P.body||P.tagName&&P.ownerDocument&&!P.ownerDocument.body};n.find=E;n.filter=E.filter;n.expr=E.selectors;n.expr[":"]=n.expr.filters;E.selectors.filters.hidden=function(P){return"hidden"===P.type||n.css(P,"display")==="none"||n.css(P,"visibility")==="hidden"};E.selectors.filters.visible=function(P){return"hidden"!==P.type&&n.css(P,"display")!=="none"&&n.css(P,"visibility")!=="hidden"};E.selectors.filters.animated=function(P){return n.grep(n.timers,function(Q){return P===Q.elem}).length};n.multiFilter=function(R,P,Q){if(Q){R=":not("+R+")"}return E.matches(R,P)};n.dir=function(R,Q){var P=[],S=R[Q];while(S&&S!=document){if(S.nodeType==1){P.push(S)}S=S[Q]}return P};n.nth=function(T,P,R,S){P=P||1;var Q=0;for(;T;T=T[R]){if(T.nodeType==1&&++Q==P){break}}return T};n.sibling=function(R,Q){var P=[];for(;R;R=R.nextSibling){if(R.nodeType==1&&R!=Q){P.push(R)}}return P};return;l.Sizzle=E})();n.event={add:function(H,E,G,J){if(H.nodeType==3||H.nodeType==8){return}if(H.setInterval&&H!=l){H=l}if(!G.guid){G.guid=this.guid++}if(J!==g){var F=G;G=this.proxy(F);G.data=J}var D=n.data(H,"events")||n.data(H,"events",{}),I=n.data(H,"handle")||n.data(H,"handle",function(){return typeof n!=="undefined"&&!n.event.triggered?n.event.handle.apply(arguments.callee.elem,arguments):g});I.elem=H;n.each(E.split(/\s+/),function(L,M){var N=M.split(".");M=N.shift();G.type=N.slice().sort().join(".");var K=D[M];if(n.event.specialAll[M]){n.event.specialAll[M].setup.call(H,J,N)}if(!K){K=D[M]={};if(!n.event.special[M]||n.event.special[M].setup.call(H,J,N)===false){if(H.addEventListener){H.addEventListener(M,I,false)}else{if(H.attachEvent){H.attachEvent("on"+M,I)}}}}K[G.guid]=G;n.event.global[M]=true});H=null},guid:1,global:{},remove:function(J,G,I){if(J.nodeType==3||J.nodeType==8){return}var F=n.data(J,"events"),E,D;if(F){if(G===g||(typeof G==="string"&&G.charAt(0)==".")){for(var H in F){this.remove(J,H+(G||""))}}else{if(G.type){I=G.handler;G=G.type}n.each(G.split(/\s+/),function(L,N){var P=N.split(".");N=P.shift();var M=RegExp("(^|\\.)"+P.slice().sort().join(".*\\.")+"(\\.|$)");if(F[N]){if(I){delete F[N][I.guid]}else{for(var O in F[N]){if(M.test(F[N][O].type)){delete F[N][O]}}}if(n.event.specialAll[N]){n.event.specialAll[N].teardown.call(J,P)}for(E in F[N]){break}if(!E){if(!n.event.special[N]||n.event.special[N].teardown.call(J,P)===false){if(J.removeEventListener){J.removeEventListener(N,n.data(J,"handle"),false)}else{if(J.detachEvent){J.detachEvent("on"+N,n.data(J,"handle"))}}}E=null;delete F[N]}}})}for(E in F){break}if(!E){var K=n.data(J,"handle");if(K){K.elem=null}n.removeData(J,"events");n.removeData(J,"handle")}}},trigger:function(H,J,G,D){var F=H.type||H;if(!D){H=typeof H==="object"?H[h]?H:n.extend(n.Event(F),H):n.Event(F);if(F.indexOf("!")>=0){H.type=F=F.slice(0,-1);H.exclusive=true}if(!G){H.stopPropagation();if(this.global[F]){n.each(n.cache,function(){if(this.events&&this.events[F]){n.event.trigger(H,J,this.handle.elem)}})}}if(!G||G.nodeType==3||G.nodeType==8){return g}H.result=g;H.target=G;J=n.makeArray(J);J.unshift(H)}H.currentTarget=G;var I=n.data(G,"handle");if(I){I.apply(G,J)}if((!G[F]||(n.nodeName(G,"a")&&F=="click"))&&G["on"+F]&&G["on"+F].apply(G,J)===false){H.result=false}if(!D&&G[F]&&!H.isDefaultPrevented()&&!(n.nodeName(G,"a")&&F=="click")){this.triggered=true;try{G[F]()}catch(K){}}this.triggered=false;if(!H.isPropagationStopped()){var E=G.parentNode||G.ownerDocument;if(E){n.event.trigger(H,J,E,true)}}},handle:function(J){var I,D;J=arguments[0]=n.event.fix(J||l.event);var K=J.type.split(".");J.type=K.shift();I=!K.length&&!J.exclusive;var H=RegExp("(^|\\.)"+K.slice().sort().join(".*\\.")+"(\\.|$)");D=(n.data(this,"events")||{})[J.type];for(var F in D){var G=D[F];if(I||H.test(G.type)){J.handler=G;J.data=G.data;var E=G.apply(this,arguments);if(E!==g){J.result=E;if(E===false){J.preventDefault();J.stopPropagation()}}if(J.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(G){if(G[h]){return G}var E=G;G=n.Event(E);for(var F=this.props.length,I;F;){I=this.props[--F];G[I]=E[I]}if(!G.target){G.target=G.srcElement||document}if(G.target.nodeType==3){G.target=G.target.parentNode}if(!G.relatedTarget&&G.fromElement){G.relatedTarget=G.fromElement==G.target?G.toElement:G.fromElement}if(G.pageX==null&&G.clientX!=null){var H=document.documentElement,D=document.body;G.pageX=G.clientX+(H&&H.scrollLeft||D&&D.scrollLeft||0)-(H.clientLeft||0);G.pageY=G.clientY+(H&&H.scrollTop||D&&D.scrollTop||0)-(H.clientTop||0)}if(!G.which&&((G.charCode||G.charCode===0)?G.charCode:G.keyCode)){G.which=G.charCode||G.keyCode}if(!G.metaKey&&G.ctrlKey){G.metaKey=G.ctrlKey}if(!G.which&&G.button){G.which=(G.button&1?1:(G.button&2?3:(G.button&4?2:0)))}return G},proxy:function(E,D){D=D||function(){return E.apply(this,arguments)};D.guid=E.guid=E.guid||D.guid||this.guid++;return D},special:{ready:{setup:A,teardown:function(){}}},specialAll:{live:{setup:function(D,E){n.event.add(this,E[0],c)},teardown:function(F){if(F.length){var D=0,E=RegExp("(^|\\.)"+F[0]+"(\\.|$)");n.each((n.data(this,"events").live||{}),function(){if(E.test(this.type)){D++}});if(D<1){n.event.remove(this,F[0],c)}}}}}};n.Event=function(D){if(!this.preventDefault){return new n.Event(D)}if(D&&D.type){this.originalEvent=D;this.type=D.type;this.timeStamp=D.timeStamp}else{this.type=D}if(!this.timeStamp){this.timeStamp=e()}this[h]=true};function k(){return false}function t(){return true}n.Event.prototype={preventDefault:function(){this.isDefaultPrevented=t;var D=this.originalEvent;if(!D){return}if(D.preventDefault){D.preventDefault()}D.returnValue=false},stopPropagation:function(){this.isPropagationStopped=t;var D=this.originalEvent;if(!D){return}if(D.stopPropagation){D.stopPropagation()}D.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=t;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(E){var D=E.relatedTarget;while(D&&D!=this){try{D=D.parentNode}catch(F){D=this}}if(D!=this){E.type=E.data;n.event.handle.apply(this,arguments)}};n.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(E,D){n.event.special[D]={setup:function(){n.event.add(this,E,a,D)},teardown:function(){n.event.remove(this,E,a)}}});n.fn.extend({bind:function(E,F,D){return E=="unload"?this.one(E,F,D):this.each(function(){n.event.add(this,E,D||F,D&&F)})},one:function(F,G,E){var D=n.event.proxy(E||G,function(H){n(this).unbind(H,D);return(E||G).apply(this,arguments)});return this.each(function(){n.event.add(this,F,D,E&&G)})},unbind:function(E,D){return this.each(function(){n.event.remove(this,E,D)})},trigger:function(D,E){return this.each(function(){n.event.trigger(D,E,this)})},triggerHandler:function(D,F){if(this[0]){var E=n.Event(D);E.preventDefault();E.stopPropagation();n.event.trigger(E,F,this[0]);return E.result}},toggle:function(F){var D=arguments,E=1;while(E<D.length){n.event.proxy(F,D[E++])}return this.click(n.event.proxy(F,function(G){this.lastToggle=(this.lastToggle||0)%E;G.preventDefault();return D[this.lastToggle++].apply(this,arguments)||false}))},hover:function(D,E){return this.mouseenter(D).mouseleave(E)},ready:function(D){A();if(n.isReady){D.call(document,n)}else{n.readyList.push(D)}return this},live:function(F,E){var D=n.event.proxy(E);D.guid+=this.selector+F;n(document).bind(i(F,this.selector),this.selector,D);return this},die:function(E,D){n(document).unbind(i(E,this.selector),D?{guid:D.guid+this.selector+E}:null);return this}});function c(G){var D=RegExp("(^|\\.)"+G.type+"(\\.|$)"),F=true,E=[];n.each(n.data(this,"events").live||[],function(H,I){if(D.test(I.type)){var J=n(G.target).closest(I.data)[0];if(J){E.push({elem:J,fn:I})}}});n.each(E,function(){if(!G.isImmediatePropagationStopped()&&this.fn.call(this.elem,G,this.fn.data)===false){F=false}});return F}function i(E,D){return["live",E,D.replace(/\./g,"`").replace(/ /g,"|")].join(".")}n.extend({isReady:false,readyList:[],ready:function(){if(!n.isReady){n.isReady=true;if(n.readyList){n.each(n.readyList,function(){this.call(document,n)});n.readyList=null}n(document).triggerHandler("ready")}}});var w=false;function A(){if(w){return}w=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);n.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);n.ready()}});if(document.documentElement.doScroll&&!l.frameElement){(function(){if(n.isReady){return}try{document.documentElement.doScroll("left")}catch(D){setTimeout(arguments.callee,0);return}n.ready()})()}}}n.event.add(l,"load",n.ready)}n.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(E,D){n.fn[D]=function(F){return F?this.bind(D,F):this.trigger(D)}});n(l).bind("unload",function(){for(var D in n.cache){if(D!=1&&n.cache[D].handle){n.event.remove(n.cache[D].handle.elem)}}});(function(){n.support={};var E=document.documentElement,F=document.createElement("script"),J=document.createElement("div"),I="script"+(new Date).getTime();J.style.display="none";J.innerHTML='   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var G=J.getElementsByTagName("*"),D=J.getElementsByTagName("a")[0];if(!G||!G.length||!D){return}n.support={leadingWhitespace:J.firstChild.nodeType==3,tbody:!J.getElementsByTagName("tbody").length,objectAll:!!J.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!J.getElementsByTagName("link").length,style:/red/.test(D.getAttribute("style")),hrefNormalized:D.getAttribute("href")==="/a",opacity:D.style.opacity==="0.5",cssFloat:!!D.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};F.type="text/javascript";try{F.appendChild(document.createTextNode("window."+I+"=1;"))}catch(H){}E.insertBefore(F,E.firstChild);if(l[I]){n.support.scriptEval=true;delete l[I]}E.removeChild(F);if(J.attachEvent&&J.fireEvent){J.attachEvent("onclick",function(){n.support.noCloneEvent=false;J.detachEvent("onclick",arguments.callee)});J.cloneNode(true).fireEvent("onclick")}n(function(){var K=document.createElement("div");K.style.width="1px";K.style.paddingLeft="1px";document.body.appendChild(K);n.boxModel=n.support.boxModel=K.offsetWidth===2;document.body.removeChild(K)})})();var v=n.support.cssFloat?"cssFloat":"styleFloat";n.props={"for":"htmlFor","class":"className","float":v,cssFloat:v,styleFloat:v,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};n.fn.extend({_load:n.fn.load,load:function(F,I,J){if(typeof F!=="string"){return this._load(F)}var H=F.indexOf(" ");if(H>=0){var D=F.slice(H,F.length);F=F.slice(0,H)}var G="GET";if(I){if(n.isFunction(I)){J=I;I=null}else{if(typeof I==="object"){I=n.param(I);G="POST"}}}var E=this;n.ajax({url:F,type:G,dataType:"html",data:I,complete:function(L,K){if(K=="success"||K=="notmodified"){E.html(D?n("<div/>").append(L.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(D):L.responseText)}if(J){E.each(J,[L.responseText,K,L])}}});return this},serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?n.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(D,E){var F=n(this).val();return F==null?null:n.isArray(F)?n.map(F,function(H,G){return{name:E.name,value:H}}):{name:E.name,value:F}}).get()}});n.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(D,E){n.fn[E]=function(F){return this.bind(E,F)}});var q=e();n.extend({get:function(D,F,G,E){if(n.isFunction(F)){G=F;F=null}return n.ajax({type:"GET",url:D,data:F,success:G,dataType:E})},getScript:function(D,E){return n.get(D,null,E,"script")},getJSON:function(D,E,F){return n.get(D,E,F,"json")},post:function(D,F,G,E){if(n.isFunction(F)){G=F;F={}}return n.ajax({type:"POST",url:D,data:F,success:G,dataType:E})},ajaxSetup:function(D){n.extend(n.ajaxSettings,D)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(L){L=n.extend(true,L,n.extend(true,{},n.ajaxSettings,L));var V,E=/=\?(&|$)/g,Q,U,F=L.type.toUpperCase();if(L.data&&L.processData&&typeof L.data!=="string"){L.data=n.param(L.data)}if(L.dataType=="jsonp"){if(F=="GET"){if(!L.url.match(E)){L.url+=(L.url.match(/\?/)?"&":"?")+(L.jsonp||"callback")+"=?"}}else{if(!L.data||!L.data.match(E)){L.data=(L.data?L.data+"&":"")+(L.jsonp||"callback")+"=?"}}L.dataType="json"}if(L.dataType=="json"&&(L.data&&L.data.match(E)||L.url.match(E))){V="jsonp"+q++;if(L.data){L.data=(L.data+"").replace(E,"="+V+"$1")}L.url=L.url.replace(E,"="+V+"$1");L.dataType="script";l[V]=function(W){U=W;H();K();l[V]=g;try{delete l[V]}catch(X){}if(G){G.removeChild(S)}}}if(L.dataType=="script"&&L.cache==null){L.cache=false}if(L.cache===false&&F=="GET"){var D=e();var T=L.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+D+"$2");L.url=T+((T==L.url)?(L.url.match(/\?/)?"&":"?")+"_="+D:"")}if(L.data&&F=="GET"){L.url+=(L.url.match(/\?/)?"&":"?")+L.data;L.data=null}if(L.global&&!n.active++){n.event.trigger("ajaxStart")}var P=/^(\w+:)?\/\/([^\/?#]+)/.exec(L.url);if(L.dataType=="script"&&F=="GET"&&P&&(P[1]&&P[1]!=location.protocol||P[2]!=location.host)){var G=document.getElementsByTagName("head")[0];var S=document.createElement("script");S.src=L.url;if(L.scriptCharset){S.charset=L.scriptCharset}if(!V){var N=false;S.onload=S.onreadystatechange=function(){if(!N&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){N=true;H();K();G.removeChild(S)}}}G.appendChild(S);return g}var J=false;var I=L.xhr();if(L.username){I.open(F,L.url,L.async,L.username,L.password)}else{I.open(F,L.url,L.async)}try{if(L.data){I.setRequestHeader("Content-Type",L.contentType)}if(L.ifModified){I.setRequestHeader("If-Modified-Since",n.lastModified[L.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}I.setRequestHeader("X-Requested-With","XMLHttpRequest");I.setRequestHeader("Accept",L.dataType&&L.accepts[L.dataType]?L.accepts[L.dataType]+", */*":L.accepts._default)}catch(R){}if(L.beforeSend&&L.beforeSend(I,L)===false){if(L.global&&!--n.active){n.event.trigger("ajaxStop")}I.abort();return false}if(L.global){n.event.trigger("ajaxSend",[I,L])}var M=function(W){if(I.readyState==0){if(O){clearInterval(O);O=null;if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}}else{if(!J&&I&&(I.readyState==4||W=="timeout")){J=true;if(O){clearInterval(O);O=null}Q=W=="timeout"?"timeout":!n.httpSuccess(I)?"error":L.ifModified&&n.httpNotModified(I,L.url)?"notmodified":"success";if(Q=="success"){try{U=n.httpData(I,L.dataType,L)}catch(Y){Q="parsererror"}}if(Q=="success"){var X;try{X=I.getResponseHeader("Last-Modified")}catch(Y){}if(L.ifModified&&X){n.lastModified[L.url]=X}if(!V){H()}}else{n.handleError(L,I,Q)}K();if(L.async){I=null}}}};if(L.async){var O=setInterval(M,13);if(L.timeout>0){setTimeout(function(){if(I){if(!J){M("timeout")}if(I){I.abort()}}},L.timeout)}}try{I.send(L.data)}catch(R){n.handleError(L,I,null,R)}if(!L.async){M()}function H(){if(L.success){L.success(U,Q)}if(L.global){n.event.trigger("ajaxSuccess",[I,L])}}function K(){if(L.complete){L.complete(I,Q)}if(L.global){n.event.trigger("ajaxComplete",[I,L])}if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}return I},handleError:function(E,G,D,F){if(E.error){E.error(G,D,F)}if(E.global){n.event.trigger("ajaxError",[G,E,F])}},active:0,httpSuccess:function(E){try{return !E.status&&location.protocol=="file:"||(E.status>=200&&E.status<300)||E.status==304||E.status==1223}catch(D){}return false},httpNotModified:function(F,D){try{var G=F.getResponseHeader("Last-Modified");return F.status==304||G==n.lastModified[D]}catch(E){}return false},httpData:function(I,G,F){var E=I.getResponseHeader("content-type"),D=G=="xml"||!G&&E&&E.indexOf("xml")>=0,H=D?I.responseXML:I.responseText;if(D&&H.documentElement.tagName=="parsererror"){throw"parsererror"}if(F&&F.dataFilter){H=F.dataFilter(H,G)}if(typeof H==="string"){if(G=="script"){n.globalEval(H)}if(G=="json"){H=l["eval"]("("+H+")")}}return H},param:function(D){var F=[];function G(H,I){F[F.length]=encodeURIComponent(H)+"="+encodeURIComponent(I)}if(n.isArray(D)||D.jquery){n.each(D,function(){G(this.name,this.value)})}else{for(var E in D){if(n.isArray(D[E])){n.each(D[E],function(){G(E,this)})}else{G(E,n.isFunction(D[E])?D[E]():D[E])}}}return F.join("&").replace(/%20/g,"+")}});var m={},d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function s(E,D){var F={};n.each(d.concat.apply([],d.slice(0,D)),function(){F[this]=E});return F}n.fn.extend({show:function(I,K){if(I){return this.animate(s("show",3),I,K)}else{for(var G=0,E=this.length;G<E;G++){var D=n.data(this[G],"olddisplay");this[G].style.display=D||"";if(n.css(this[G],"display")==="none"){var F=this[G].tagName,J;if(m[F]){J=m[F]}else{var H=n("<"+F+" />").appendTo("body");J=H.css("display");if(J==="none"){J="block"}H.remove();m[F]=J}this[G].style.display=n.data(this[G],"olddisplay",J)}}return this}},hide:function(G,H){if(G){return this.animate(s("hide",3),G,H)}else{for(var F=0,E=this.length;F<E;F++){var D=n.data(this[F],"olddisplay");if(!D&&D!=="none"){n.data(this[F],"olddisplay",n.css(this[F],"display"))}this[F].style.display="none"}return this}},_toggle:n.fn.toggle,toggle:function(F,E){var D=typeof F==="boolean";return n.isFunction(F)&&n.isFunction(E)?this._toggle.apply(this,arguments):F==null||D?this.each(function(){var G=D?F:n(this).is(":hidden");n(this)[G?"show":"hide"]()}):this.animate(s("toggle",3),F,E)},fadeTo:function(D,F,E){return this.animate({opacity:F},D,E)},animate:function(H,E,G,F){var D=n.speed(E,G,F);return this[D.queue===false?"each":"queue"](function(){var J=n.extend({},D),L,K=this.nodeType==1&&n(this).is(":hidden"),I=this;for(L in H){if(H[L]=="hide"&&K||H[L]=="show"&&!K){return J.complete.call(this)}if((L=="height"||L=="width")&&this.style){J.display=n.css(this,"display");J.overflow=this.style.overflow}}if(J.overflow!=null){this.style.overflow="hidden"}J.curAnim=n.extend({},H);n.each(H,function(N,R){var Q=new n.fx(I,J,N);if(/toggle|show|hide/.test(R)){Q[R=="toggle"?K?"show":"hide":R](H)}else{var P=R.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),S=Q.cur(true)||0;if(P){var M=parseFloat(P[2]),O=P[3]||"px";if(O!="px"){I.style[N]=(M||1)+O;S=((M||1)/Q.cur(true))*S;I.style[N]=S+O}if(P[1]){M=((P[1]=="-="?-1:1)*M)+S}Q.custom(S,M,O)}else{Q.custom(S,R,"")}}});return true})},stop:function(E,D){var F=n.timers;if(E){this.queue([])}this.each(function(){for(var G=F.length-1;G>=0;G--){if(F[G].elem==this){if(D){F[G](true)}F.splice(G,1)}}});if(!D){this.dequeue()}return this}});n.each({slideDown:s("show",1),slideUp:s("hide",1),slideToggle:s("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(D,E){n.fn[D]=function(F,G){return this.animate(E,F,G)}});n.extend({speed:function(F,G,E){var D=typeof F==="object"?F:{complete:E||!E&&G||n.isFunction(F)&&F,duration:F,easing:E&&G||G&&!n.isFunction(G)&&G};D.duration=n.fx.off?0:typeof D.duration==="number"?D.duration:n.fx.speeds[D.duration]||n.fx.speeds._default;D.old=D.complete;D.complete=function(){if(D.queue!==false){n(this).dequeue()}if(n.isFunction(D.old)){D.old.call(this)}};return D},easing:{linear:function(F,G,D,E){return D+E*F},swing:function(F,G,D,E){return((-Math.cos(F*Math.PI)/2)+0.5)*E+D}},timers:[],timerId:null,fx:function(E,D,F){this.options=D;this.elem=E;this.prop=F;if(!D.orig){D.orig={}}}});n.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(n.fx.step[this.prop]||n.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(E){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var D=parseFloat(n.css(this.elem,this.prop,E));return D&&D>-10000?D:parseFloat(n.curCSS(this.elem,this.prop))||0},custom:function(H,G,F){this.startTime=e();this.start=H;this.end=G;this.unit=F||this.unit||"px";this.now=this.start;this.pos=this.state=0;var D=this;function E(I){return D.step(I)}E.elem=this.elem;n.timers.push(E);if(E()&&n.timerId==null){n.timerId=setInterval(function(){var J=n.timers;for(var I=0;I<J.length;I++){if(!J[I]()){J.splice(I--,1)}}if(!J.length){clearInterval(n.timerId);n.timerId=null}},13)}},show:function(){this.options.orig[this.prop]=n.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());n(this.elem).show()},hide:function(){this.options.orig[this.prop]=n.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(G){var F=e();if(G||F>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var D=true;for(var E in this.options.curAnim){if(this.options.curAnim[E]!==true){D=false}}if(D){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(n.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){n(this.elem).hide()}if(this.options.hide||this.options.show){for(var H in this.options.curAnim){n.attr(this.elem.style,H,this.options.orig[H])}}}if(D){this.options.complete.call(this.elem)}return false}else{var I=F-this.startTime;this.state=I/this.options.duration;this.pos=n.easing[this.options.easing||(n.easing.swing?"swing":"linear")](this.state,I,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};n.extend(n.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(D){n.attr(D.elem.style,"opacity",D.now)},_default:function(D){if(D.elem.style&&D.elem.style[D.prop]!=null){D.elem.style[D.prop]=D.now+D.unit}else{D.elem[D.prop]=D.now}}}});if(document.documentElement.getBoundingClientRect){n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}var F=this[0].getBoundingClientRect(),I=this[0].ownerDocument,E=I.body,D=I.documentElement,K=D.clientTop||E.clientTop||0,J=D.clientLeft||E.clientLeft||0,H=F.top+(self.pageYOffset||n.boxModel&&D.scrollTop||E.scrollTop)-K,G=F.left+(self.pageXOffset||n.boxModel&&D.scrollLeft||E.scrollLeft)-J;return{top:H,left:G}}}else{n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}n.offset.initialized||n.offset.initialize();var I=this[0],F=I.offsetParent,E=I,N=I.ownerDocument,L,G=N.documentElement,J=N.body,K=N.defaultView,D=K.getComputedStyle(I,null),M=I.offsetTop,H=I.offsetLeft;while((I=I.parentNode)&&I!==J&&I!==G){L=K.getComputedStyle(I,null);M-=I.scrollTop,H-=I.scrollLeft;if(I===F){M+=I.offsetTop,H+=I.offsetLeft;if(n.offset.doesNotAddBorder&&!(n.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(I.tagName))){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}E=F,F=I.offsetParent}if(n.offset.subtractsBorderForOverflowNotVisible&&L.overflow!=="visible"){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}D=L}if(D.position==="relative"||D.position==="static"){M+=J.offsetTop,H+=J.offsetLeft}if(D.position==="fixed"){M+=Math.max(G.scrollTop,J.scrollTop),H+=Math.max(G.scrollLeft,J.scrollLeft)}return{top:M,left:H}}}n.offset={initialize:function(){if(this.initialized){return}var K=document.body,E=document.createElement("div"),G,F,M,H,L,D,I=K.style.marginTop,J='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"cellpadding="0"cellspacing="0"><tr><td></td></tr></table>';L={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(D in L){E.style[D]=L[D]}E.innerHTML=J;K.insertBefore(E,K.firstChild);G=E.firstChild,F=G.firstChild,H=G.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(F.offsetTop!==5);this.doesAddBorderForTableAndCells=(H.offsetTop===5);G.style.overflow="hidden",G.style.position="relative";this.subtractsBorderForOverflowNotVisible=(F.offsetTop===-5);K.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(K.offsetTop===0);K.style.marginTop=I;K.removeChild(E);this.initialized=true},bodyOffset:function(D){n.offset.initialized||n.offset.initialize();var F=D.offsetTop,E=D.offsetLeft;if(n.offset.doesNotIncludeMarginInBodyOffset){F+=parseInt(n.curCSS(D,"marginTop",true),10)||0,E+=parseInt(n.curCSS(D,"marginLeft",true),10)||0}return{top:F,left:E}}};n.fn.extend({position:function(){var H=0,G=0,E;if(this[0]){var F=this.offsetParent(),I=this.offset(),D=/^body|html$/i.test(F[0].tagName)?{top:0,left:0}:F.offset();I.top-=j(this,"marginTop");I.left-=j(this,"marginLeft");D.top+=j(F,"borderTopWidth");D.left+=j(F,"borderLeftWidth");E={top:I.top-D.top,left:I.left-D.left}}return E},offsetParent:function(){var D=this[0].offsetParent||document.body;while(D&&(!/^body|html$/i.test(D.tagName)&&n.css(D,"position")=="static")){D=D.offsetParent}return n(D)}});n.each(["Left","Top"],function(E,D){var F="scroll"+D;n.fn[F]=function(G){if(!this[0]){return null}return G!==g?this.each(function(){this==l||this==document?l.scrollTo(!E?G:n(l).scrollLeft(),E?G:n(l).scrollTop()):this[F]=G}):this[0]==l||this[0]==document?self[E?"pageYOffset":"pageXOffset"]||n.boxModel&&document.documentElement[F]||document.body[F]:this[0][F]}});n.each(["Height","Width"],function(G,E){var D=G?"Left":"Top",F=G?"Right":"Bottom";n.fn["inner"+E]=function(){return this[E.toLowerCase()]()+j(this,"padding"+D)+j(this,"padding"+F)};n.fn["outer"+E]=function(I){return this["inner"+E]()+j(this,"border"+D+"Width")+j(this,"border"+F+"Width")+(I?j(this,"margin"+D)+j(this,"margin"+F):0)};var H=E.toLowerCase();n.fn[H]=function(I){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+E]||document.body["client"+E]:this[0]==document?Math.max(document.documentElement["client"+E],document.body["scroll"+E],document.documentElement["scroll"+E],document.body["offset"+E],document.documentElement["offset"+E]):I===g?(this.length?n.css(this[0],H):null):this.css(H,typeof I==="string"?I:I+"px")}})})();
\ No newline at end of file
+(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V<U;V++){var X=H.order[V],W;if((W=H.match[X].exec(Z))){var T=RegExp.leftContext;if(T.substr(T.length-1)!=="\\"){W[1]=(W[1]||"").replace(/\\/g,"");Y=H.find[X](W,S,aa);if(Y!=null){Z=Z.replace(H.match[X],"");break}}}}if(!Y){Y=S.getElementsByTagName("*")}return{set:Y,expr:Z}};F.filter=function(ab,aa,ae,V){var U=ab,ag=[],Y=aa,X,S;while(ab&&aa.length){for(var Z in H.filter){if((X=H.match[Z].exec(ab))!=null){var T=H.filter[Z],af,ad;S=false;if(Y==ag){ag=[]}if(H.preFilter[Z]){X=H.preFilter[Z](X,Y,ae,ag,V);if(!X){S=af=true}else{if(X===true){continue}}}if(X){for(var W=0;(ad=Y[W])!=null;W++){if(ad){af=T(ad,X,W,Y);var ac=V^!!af;if(ae&&af!=null){if(ac){S=true}else{Y[W]=false}}else{if(ac){ag.push(ad);S=true}}}}}if(af!==g){if(!ae){Y=ag}ab=ab.replace(H.match[Z],"");if(!S){return[]}break}}}ab=ab.replace(/\s*,\s*/,"");if(ab==U){if(S==null){throw"Syntax error, unrecognized expression: "+ab}else{break}}U=ab}return Y};var H=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(S){return S.getAttribute("href")}},relative:{"+":function(W,T){for(var U=0,S=W.length;U<S;U++){var V=W[U];if(V){var X=V.previousSibling;while(X&&X.nodeType!==1){X=X.previousSibling}W[U]=typeof T==="string"?X||false:X===T}}if(typeof T==="string"){F.filter(T,W,true)}},">":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){var V=W.parentNode;X[U]=V.nodeName===T?V:false}}}else{for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){X[U]=typeof T==="string"?W.parentNode:W.parentNode===T}}if(typeof T==="string"){F.filter(T,X,true)}}},"":function(V,T,X){var U="done"+(K++),S=R;if(!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("parentNode",T,U,V,W,X)},"~":function(V,T,X){var U="done"+(K++),S=R;if(typeof T==="string"&&!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("previousSibling",T,U,V,W,X)}},find:{ID:function(T,U,V){if(typeof U.getElementById!=="undefined"&&!V){var S=U.getElementById(T[1]);return S?[S]:[]}},NAME:function(S,T,U){if(typeof T.getElementsByName!=="undefined"&&!U){return T.getElementsByName(S[1])}},TAG:function(S,T){return T.getElementsByTagName(S[1])}},preFilter:{CLASS:function(V,T,U,S,Y){V=" "+V[1].replace(/\\/g,"")+" ";var X;for(var W=0;(X=T[W])!=null;W++){if(X){if(Y^(" "+X.className+" ").indexOf(V)>=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return T<S[3]-0},gt:function(U,T,S){return T>S[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V<S;V++){if(X[V]===Y){return false}}return true}}}},ID:function(T,S){return T.nodeType===1&&T.getAttribute("id")===S},TAG:function(T,S){return(S==="*"&&T.nodeType===1)||T.nodeName===S},CLASS:function(T,S){return S.test(T.className)},ATTR:function(W,U){var S=H.attrHandle[U[1]]?H.attrHandle[U[1]](W):W[U[1]]||W.getAttribute(U[1]),X=S+"",V=U[2],T=U[4];return S==null?V==="!=":V==="="?X===T:V==="*="?X.indexOf(T)>=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U<S;U++){T.push(W[U])}}else{for(var U=0;W[U];U++){T.push(W[U])}}}return T}}(function(){var T=document.createElement("form"),U="script"+(new Date).getTime();T.innerHTML="<input name='"+U+"'/>";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="<a href='#'></a>";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="<p class='TEST'></p>";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W<U;W++){var S=ac[W];if(S){S=S[T];var X=false;while(S&&S.nodeType){var V=S[Y];if(V){X=ac[V];break}if(S.nodeType===1&&!ab){S[Y]=W}if(S.nodeName===Z){X=S;break}S=S[T]}ac[W]=X}}}function R(T,Y,X,ab,Z,aa){for(var V=0,U=ab.length;V<U;V++){var S=ab[V];if(S){S=S[T];var W=false;while(S&&S.nodeType){if(S[X]){W=ab[S[X]];break}if(S.nodeType===1){if(!aa){S[X]=V}if(typeof Y!=="string"){if(S===Y){W=true;break}}else{if(F.filter(Y,[S]).length>0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y<T;Y++){F(S,U[Y],V)}return F.filter(W,V)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(S){return"hidden"===S.type||o.css(S,"display")==="none"||o.css(S,"visibility")==="hidden"};F.selectors.filters.visible=function(S){return"hidden"!==S.type&&o.css(S,"display")!=="none"&&o.css(S,"visibility")!=="hidden"};F.selectors.filters.animated=function(S){return o.grep(o.timers,function(T){return S===T.elem}).length};o.multiFilter=function(U,S,T){if(T){U=":not("+U+")"}return F.matches(U,S)};o.dir=function(U,T){var S=[],V=U[T];while(V&&V!=document){if(V.nodeType==1){S.push(V)}V=V[T]}return S};o.nth=function(W,S,U,V){S=S||1;var T=0;for(;W;W=W[U]){if(W.nodeType==1&&++T==S){break}}return W};o.sibling=function(U,T){var S=[];for(;U;U=U.nextSibling){if(U.nodeType==1&&U!=T){S.push(U)}}return S};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){G=false}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&typeof l.frameElement==="undefined"){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML='   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width="1px";L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L)})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n)}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})();
\ No newline at end of file
diff --git a/js/jquery.simplemodal-1.2.2.pack.js b/js/jquery.simplemodal-1.2.2.pack.js
new file mode 100644 (file)
index 0000000..b5ad5c2
--- /dev/null
@@ -0,0 +1,8 @@
+/*
+ * SimpleModal 1.2.2 - jQuery Plugin
+ * http://www.ericmmartin.com/projects/simplemodal/
+ * Copyright (c) 2008 Eric Martin
+ * Dual licensed under the MIT and GPL licenses
+ * Revision: $Id: jquery.simplemodal.js 181 2008-12-16 16:51:44Z emartin24 $
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(g($){m f=$.Q.1Q&&1a($.Q.1D)==6&&!10[\'2g\'],1f=$.Q.1Q&&!$.2a,w=[];$.y=g(a,b){I $.y.12.1n(a,b)};$.y.D=g(){$.y.12.D()};$.1P.y=g(a){I $.y.12.1n(3,a)};$.y.1O={V:29,1J:\'r-H\',1B:{},1z:\'r-n\',20:{},1Z:{},v:2t,D:1o,1T:\'<a 2j="2h" 2f="2e"></a>\',X:\'r-D\',l:F,1g:K,1e:F,1d:F,1c:F};$.y.12={7:F,4:{},1n:g(a,b){8(3.4.j){I K}3.7=$.U({},$.y.1O,b);3.v=3.7.v;3.1w=K;8(J a==\'27\'){a=a 25 1A?a:$(a);8(a.1v().1v().23()>0){3.4.T=a.1v();8(!3.7.1g){3.4.21=a.2x(1o)}}}q 8(J a==\'2w\'||J a==\'1r\'){a=$(\'<1q/>\').2s(a)}q{2r(\'2q 2p: 2o j 2l: \'+J a);I K}3.4.j=a.11(\'r-j\').E(3.7.1Z);a=F;3.1S();3.1R();8($.1m(3.7.1d)){3.7.1d.1l(3,[3.4])}I 3},1S:g(){w=3.1k();8(f){3.4.x=$(\'<x 2d="2c:K;"/>\').E($.U(3.7.2b,{1j:\'1i\',V:0,l:\'1h\',A:w[0],z:w[1],v:3.7.v,L:0,B:0})).O(\'u\')}3.4.H=$(\'<1q/>\').1N(\'1M\',3.7.1J).11(\'r-H\').E($.U(3.7.1B,{1j:\'1i\',V:3.7.V/1b,A:w[0],z:w[1],l:\'1h\',B:0,L:0,v:3.7.v+1})).O(\'u\');3.4.n=$(\'<1q/>\').1N(\'1M\',3.7.1z).11(\'r-n\').E($.U(3.7.20,{1j:\'1i\',l:\'1h\',v:3.7.v+2})).1K(3.7.D?$(3.7.1T).11(3.7.X):\'\').O(\'u\');3.19();8(f||1f){3.18()}3.4.n.1K(3.4.j.1I())},1H:g(){m a=3;$(\'.\'+3.7.X).1G(\'1L.r\',g(e){e.28();a.D()});$(10).1G(\'1F.r\',g(){w=a.1k();a.19();8(f||1f){a.18()}q{a.4.x&&a.4.x.E({A:w[0],z:w[1]});a.4.H.E({A:w[0],z:w[1]})}})},1E:g(){$(\'.\'+3.7.X).1C(\'1L.r\');$(10).1C(\'1F.r\')},18:g(){m p=3.7.l;$.26([3.4.x||F,3.4.H,3.4.n],g(i,e){8(e){m a=\'k.u.17\',N=\'k.u.1W\',16=\'k.u.24\',S=\'k.u.1y\',R=\'k.u.1x\',15=\'k.u.22\',1t=\'k.P.17\',1s=\'k.P.1W\',C=\'k.P.1y\',G=\'k.P.1x\',s=e[0].2v;s.l=\'2u\';8(i<2){s.14(\'A\');s.14(\'z\');s.Z(\'A\',\'\'+16+\' > \'+a+\' ? \'+16+\' : \'+a+\' + "o"\');s.Z(\'z\',\'\'+15+\' > \'+N+\' ? \'+15+\' : \'+N+\' + "o"\')}q{m b,W;8(p&&p.1Y==1X){8(p[0]){m c=J p[0]==\'1r\'?p[0].1V():p[0].13(/o/,\'\');b=c.1U(\'%\')==-1?c+\' + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\':1a(c.13(/%/,\'\'))+\' * ((\'+1t+\' || \'+a+\') / 1b) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\'}8(p[1]){m d=J p[1]==\'1r\'?p[1].1V():p[1].13(/o/,\'\');W=d.1U(\'%\')==-1?d+\' + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\':1a(d.13(/%/,\'\'))+\' * ((\'+1s+\' || \'+N+\') / 1b) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}}q{b=\'(\'+1t+\' || \'+a+\') / 2 - (3.2n / 2) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\';W=\'(\'+1s+\' || \'+N+\') / 2 - (3.2m / 2) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}s.14(\'L\');s.14(\'B\');s.Z(\'L\',b);s.Z(\'B\',W)}}})},1k:g(){m a=$(10);m h=$.Q.2k&&$.Q.1D>\'9.5\'&&$.1P.2i<=\'1.2.6\'?k.P[\'17\']:a.A();I[h,a.z()]},19:g(){m a,B,1u=(w[0]/2)-((3.4.n.A()||3.4.j.A())/2),1p=(w[1]/2)-((3.4.n.z()||3.4.j.z())/2);8(3.7.l&&3.7.l.1Y==1X){a=3.7.l[0]||1u;B=3.7.l[1]||1p}q{a=1u;B=1p}3.4.n.E({B:B,L:a})},1R:g(){3.4.x&&3.4.x.Y();8($.1m(3.7.1e)){3.7.1e.1l(3,[3.4])}q{3.4.H.Y();3.4.n.Y();3.4.j.Y()}3.1H()},D:g(){8(!3.4.j){I K}8($.1m(3.7.1c)&&!3.1w){3.1w=1o;3.7.1c.1l(3,[3.4])}q{8(3.4.T){8(3.7.1g){3.4.j.1I().O(3.4.T)}q{3.4.j.M();3.4.21.O(3.4.T)}}q{3.4.j.M()}3.4.n.M();3.4.H.M();3.4.x&&3.4.x.M();3.4={}}3.1E()}}})(1A);',62,158,'|||this|dialog|||opts|if||||||||function|||data|document|position|var|container|px||else|simplemodal|||body|zIndex||iframe|modal|width|height|left|sl|close|css|null|st|overlay|return|typeof|false|top|remove|bcw|appendTo|documentElement|browser|bst|bsl|parentNode|extend|opacity|le|closeClass|show|setExpression|window|addClass|impl|replace|removeExpression|bsw|bsh|clientHeight|fixIE|setPosition|parseInt|100|onClose|onShow|onOpen|ieQuirks|persist|fixed|none|display|getDimensions|apply|isFunction|init|true|vCenter|div|number|cw|ch|hCenter|parent|occb|scrollTop|scrollLeft|containerId|jQuery|overlayCss|unbind|version|unbindEvents|resize|bind|bindEvents|hide|overlayId|append|click|id|attr|defaults|fn|msie|open|create|closeHTML|indexOf|toString|clientWidth|Array|constructor|dataCss|containerCss|orig|scrollWidth|size|scrollHeight|instanceof|each|object|preventDefault|50|boxModel|iframeCss|javascript|src|Close|title|XMLHttpRequest|modalCloseImg|jquery|class|opera|type|offsetWidth|offsetHeight|Unsupported|Error|SimpleModal|alert|html|1000|absolute|style|string|clone'.split('|'),0,{}))
\ No newline at end of file
diff --git a/js/video.js b/js/video.js
new file mode 100644 (file)
index 0000000..936a631
--- /dev/null
@@ -0,0 +1,9 @@
+$('document').ready(function() {
+    $('a.media, a.mediamp3').append(' <sup>[PLAY]</sup>');
+    $('a.mediamp3').html('').css('display', 'block').css('width', '224px').css('height','24px').flowplayer('../bin/flowplayer-3.0.5.swf');
+    $('a.media').click(function() {
+        $('<a id="p1i"></a>').attr('href', $(this).attr('href')).flowplayer('../bin/flowplayer-3.0.5.swf').modal({'closeHTML':'<a class="modalCloseImg" title="Close"><img src="x.png" /></a>'});
+        return false;
+    });
+});
+
index 0628dc70db340f1c6457a340e96a71d905661a43..455ebeff0beb4f3b9898b067243fec04d5ee87d4 100644 (file)
@@ -151,25 +151,46 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showStylesheets()
     {
-        $this->element('link', array('rel' => 'stylesheet',
-                                     'type' => 'text/css',
-                                     'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION,
-                                     'media' => 'screen, projection, tv'));
-        $this->element('link', array('rel' => 'stylesheet',
-                                     'type' => 'text/css',
-                                     'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
-                                     'media' => 'screen, projection, tv'));
-        $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
-                       'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
-        foreach (array(6,7) as $ver) {
-            if (file_exists(theme_file('css/ie'.$ver.'.css', 'base'))) {
-                // Yes, IE people should be put in jail.
-                $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
-                               'href="'.theme_path('css/ie'.$ver.'.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
+        if (Event::handle('StartShowStyles', array($this))) {
+            if (Event::handle('StartShowLaconicaStyles', array($this))) {
+
+                $this->element('link', array('rel' => 'stylesheet',
+                                             'type' => 'text/css',
+                                             'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION,
+                                             'media' => 'screen, projection, tv'));
+                $this->element('link', array('rel' => 'stylesheet',
+                                             'type' => 'text/css',
+                                             'href' => theme_path('css/modal.css', 'base') . '?version=' . LACONICA_VERSION,
+                                             'media' => 'screen, projection, tv'));
+                $this->element('link', array('rel' => 'stylesheet',
+                                             'type' => 'text/css',
+                                             'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
+                                             'media' => 'screen, projection, tv'));
+                if (common_config('site', 'mobile')) {
+                    $this->element('link', array('rel' => 'stylesheet',
+                                                 'type' => 'text/css',
+                                                 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
+                                                 // TODO: "handheld" CSS for other mobile devices
+                                                 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
+                }
+                Event::handle('EndShowLaconicaStyles', array($this));
             }
+            if (Event::handle('StartShowUAStyles', array($this))) {
+                $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
+                               'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
+                foreach (array(6,7) as $ver) {
+                    if (file_exists(theme_file('css/ie'.$ver.'.css', 'base'))) {
+                        // Yes, IE people should be put in jail.
+                        $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
+                                       'href="'.theme_path('css/ie'.$ver.'.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
+                    }
+                }
+                $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
+                               'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
+                Event::handle('EndShowUAStyles', array($this));
+            }
+            Event::handle('EndShowStyles', array($this));
         }
-        $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
-                       'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
     }
 
     /**
@@ -187,6 +208,11 @@ class Action extends HTMLOutputter // lawsuit
                 $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/jquery.simplemodal-1.2.2.pack.js')),
+                               ' ');
+
                 Event::handle('EndShowJQueryScripts', array($this));
             }
             if (Event::handle('StartShowLaconicaScripts', array($this))) {
@@ -196,6 +222,17 @@ class Action extends HTMLOutputter // lawsuit
                 $this->element('script', array('type' => 'text/javascript',
                                                'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
                                ' ');
+                // Frame-busting code to avoid clickjacking attacks.
+                $this->element('script', array('type' => 'text/javascript'),
+                               'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
+
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/flowplayer-3.0.5.min.js')),
+                               ' ');
+
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/video.js')),
+                               ' ');
                 Event::handle('EndShowLaconicaScripts', array($this));
             }
             Event::handle('EndShowScripts', array($this));
@@ -225,9 +262,19 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
+
     function showFeeds()
     {
-        // does nothing by default
+        $feeds = $this->getFeeds();
+
+        if ($feeds) {
+            foreach ($feeds as $feed) {
+                $this->element('link', array('rel' => $feed->rel(),
+                                             'href' => $feed->url,
+                                             'type' => $feed->mimeType(),
+                                             'title' => $feed->title));
+            }
+        }
     }
 
     /**
@@ -265,9 +312,15 @@ class Action extends HTMLOutputter // lawsuit
     {
         $this->elementStart('body', array('id' => $this->trimmed('action')));
         $this->elementStart('div', array('id' => 'wrap'));
-        $this->showHeader();
+        if (Event::handle('StartShowHeader', array($this))) {
+            $this->showHeader();
+            Event::handle('EndShowHeader', array($this));
+        }
         $this->showCore();
-        $this->showFooter();
+        if (Event::handle('StartShowFooter', array($this))) {
+            $this->showFooter();
+            Event::handle('EndShowFooter', array($this));
+        }
         $this->elementEnd('div');
         $this->elementEnd('body');
     }
@@ -421,8 +474,14 @@ class Action extends HTMLOutputter // lawsuit
     function showCore()
     {
         $this->elementStart('div', array('id' => 'core'));
-        $this->showLocalNavBlock();
-        $this->showContentBlock();
+        if (Event::handle('StartShowLocalNavBlock', array($this))) {
+            $this->showLocalNavBlock();
+            Event::handle('EndShowLocalNavBlock', array($this));
+        }
+        if (Event::handle('StartShowContentBlock', array($this))) {
+            $this->showContentBlock();
+            Event::handle('EndShowContentBlock', array($this));
+        }
         $this->showAside();
         $this->elementEnd('div');
     }
@@ -524,27 +583,32 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
+
     function showAside()
     {
         $this->elementStart('div', array('id' => 'aside_primary',
                                          'class' => 'aside'));
         $this->showExportData();
-        $this->showSections();
+        if (Event::handle('StartShowSections', array($this))) {
+            $this->showSections();
+            Event::handle('EndShowSections', array($this));
+        }
         $this->elementEnd('div');
     }
 
     /**
      * Show export data feeds.
      *
-     * MAY overload if there are feeds
-     *
-     * @return nothing
+     * @return void
      */
+
     function showExportData()
     {
-        // is there structure to this?
-        // list of (visible!) feed links
-        // can we reuse list of feeds from showFeeds() ?
+        $feeds = $this->getFeeds();
+        if ($feeds) {
+            $fl = new FeedList($this);
+            $fl->show($feeds);
+        }
     }
 
     /**
@@ -596,6 +660,8 @@ class Action extends HTMLOutputter // lawsuit
                             _('Source'));
             $this->menuItem(common_local_url('doc', array('title' => 'contact')),
                             _('Contact'));
+            $this->menuItem(common_local_url('doc', array('title' => 'badge')),
+                            _('Badge'));
             Event::handle('EndSecondaryNav', array($this));
         }
         $this->elementEnd('ul');
@@ -750,8 +816,10 @@ class Action extends HTMLOutputter // lawsuit
             if ($if_modified_since) {
                 $ims = strtotime($if_modified_since);
                 if ($lm <= $ims) {
-                    if (!$etag ||
-                        $this->_hasEtag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
+                    $if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
+                    if (!$if_none_match ||
+                        !$etag ||
+                        $this->_hasEtag($etag, $if_none_match)) {
                         header('HTTP/1.1 304 Not Modified');
                         // Better way to do this?
                         exit(0);
@@ -769,9 +837,11 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return boolean
      */
+
     function _hasEtag($etag, $if_none_match)
     {
-        return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
+        $etags = explode(',', $if_none_match);
+        return in_array($etag, $etags) || in_array('*', $etags);
     }
 
     /**
@@ -920,4 +990,17 @@ class Action extends HTMLOutputter // lawsuit
             $this->elementEnd('div');
         }
     }
+
+    /**
+     * An array of feeds for this action.
+     *
+     * Returns an array of potential feeds for this action.
+     *
+     * @return array Feed object to show in head and links
+     */
+
+    function getFeeds()
+    {
+        return null;
+    }
 }
diff --git a/lib/channel.php b/lib/channel.php
new file mode 100644 (file)
index 0000000..f1e2055
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class Channel
+{
+    function on($user)
+    {
+        return false;
+    }
+
+    function off($user)
+    {
+        return false;
+    }
+
+    function output($user, $text)
+    {
+        return false;
+    }
+
+    function error($user, $text)
+    {
+        return false;
+    }
+
+    function source()
+    {
+        return null;
+    }
+}
+
+class XMPPChannel extends Channel
+{
+
+    var $conn = null;
+
+    function source()
+    {
+        return 'xmpp';
+    }
+
+    function __construct($conn)
+    {
+        $this->conn = $conn;
+    }
+
+    function on($user)
+    {
+        return $this->set_notify($user, 1);
+    }
+
+    function off($user)
+    {
+        return $this->set_notify($user, 0);
+    }
+
+    function output($user, $text)
+    {
+        $text = '['.common_config('site', 'name') . '] ' . $text;
+        jabber_send_message($user->jabber, $text);
+    }
+
+    function error($user, $text)
+    {
+        $text = '['.common_config('site', 'name') . '] ' . $text;
+        jabber_send_message($user->jabber, $text);
+    }
+
+    function set_notify(&$user, $notify)
+    {
+        $orig = clone($user);
+        $user->jabbernotify = $notify;
+        $result = $user->update($orig);
+        if (!$result) {
+            $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+            common_log(LOG_ERR,
+                       'Could not set notify flag to ' . $notify .
+                       ' for user ' . common_log_objstring($user) .
+                       ': ' . $last_error->message);
+            return false;
+        } else {
+            common_log(LOG_INFO,
+                       'User ' . $user->nickname . ' set notify flag to ' . $notify);
+            return true;
+        }
+    }
+}
+
+class WebChannel extends Channel
+{
+    var $out = null;
+
+    function __construct($out=null)
+    {
+        $this->out = $out;
+    }
+
+    function source()
+    {
+        return 'web';
+    }
+
+    function on($user)
+    {
+        return false;
+    }
+
+    function off($user)
+    {
+        return false;
+    }
+
+    function output($user, $text)
+    {
+        # XXX: buffer all output and send it at the end
+        # XXX: even better, redirect to appropriate page
+        #      depending on what command was run
+        $this->out->startHTML();
+        $this->out->elementStart('head');
+        $this->out->element('title', null, _('Command results'));
+        $this->out->elementEnd('head');
+        $this->out->elementStart('body');
+        $this->out->element('p', array('id' => 'command_result'), $text);
+        $this->out->elementEnd('body');
+        $this->out->endHTML();
+    }
+
+    function error($user, $text)
+    {
+        common_user_error($text);
+    }
+}
+
+class AjaxWebChannel extends WebChannel
+{
+    function output($user, $text)
+    {
+        $this->out->startHTML('text/xml;charset=utf-8');
+        $this->out->elementStart('head');
+        $this->out->element('title', null, _('Command results'));
+        $this->out->elementEnd('head');
+        $this->out->elementStart('body');
+        $this->out->element('p', array('id' => 'command_result'), $text);
+        $this->out->elementEnd('body');
+        $this->out->endHTML();
+    }
+
+    function error($user, $text)
+    {
+        $this->out->startHTML('text/xml;charset=utf-8');
+        $this->out->elementStart('head');
+        $this->out->element('title', null, _('Ajax Error'));
+        $this->out->elementEnd('head');
+        $this->out->elementStart('body');
+        $this->out->element('p', array('id' => 'error'), $text);
+        $this->out->elementEnd('body');
+        $this->out->endHTML();
+    }
+}
+
+class MailChannel extends Channel
+{
+
+    var $addr = null;
+
+    function source()
+    {
+        return 'mail';
+    }
+
+    function __construct($addr=null)
+    {
+        $this->addr = $addr;
+    }
+
+    function on($user)
+    {
+        return $this->set_notify($user, 1);
+    }
+
+    function off($user)
+    {
+        return $this->set_notify($user, 0);
+    }
+
+    function output($user, $text)
+    {
+
+        $headers['From'] = $user->incomingemail;
+        $headers['To'] = $this->addr;
+
+        $headers['Subject'] = _('Command complete');
+
+        return mail_send(array($this->addr), $headers, $text);
+    }
+
+    function error($user, $text)
+    {
+
+        $headers['From'] = $user->incomingemail;
+        $headers['To'] = $this->addr;
+
+        $headers['Subject'] = _('Command failed');
+
+        return mail_send(array($this->addr), $headers, $text);
+    }
+
+    function set_notify($user, $value)
+    {
+        $orig = clone($user);
+        $user->smsnotify = $value;
+        $result = $user->update($orig);
+        if (!$result) {
+            common_log_db_error($user, 'UPDATE', __FILE__);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/lib/command.php b/lib/command.php
new file mode 100644 (file)
index 0000000..507990a
--- /dev/null
@@ -0,0 +1,419 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/channel.php');
+
+class Command
+{
+
+    var $user = null;
+
+    function __construct($user=null)
+    {
+        $this->user = $user;
+    }
+
+    function execute($channel)
+    {
+        return false;
+    }
+}
+
+class UnimplementedCommand extends Command
+{
+    function execute($channel)
+    {
+        $channel->error($this->user, _("Sorry, this command is not yet implemented."));
+    }
+}
+
+class TrackingCommand extends UnimplementedCommand
+{
+}
+
+class TrackOffCommand extends UnimplementedCommand
+{
+}
+
+class TrackCommand extends UnimplementedCommand
+{
+    var $word = null;
+    function __construct($user, $word)
+    {
+        parent::__construct($user);
+        $this->word = $word;
+    }
+}
+
+class UntrackCommand extends UnimplementedCommand
+{
+    var $word = null;
+    function __construct($user, $word)
+    {
+        parent::__construct($user);
+        $this->word = $word;
+    }
+}
+
+class NudgeCommand extends UnimplementedCommand
+{
+    var $other = null;
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+}
+
+class InviteCommand extends UnimplementedCommand
+{
+    var $other = null;
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+}
+
+class StatsCommand extends Command
+{
+    function execute($channel)
+    {
+
+        $subs = new Subscription();
+        $subs->subscriber = $this->user->id;
+        $subs_count = (int) $subs->count() - 1;
+
+        $subbed = new Subscription();
+        $subbed->subscribed = $this->user->id;
+        $subbed_count = (int) $subbed->count() - 1;
+
+        $notices = new Notice();
+        $notices->profile_id = $this->user->id;
+        $notice_count = (int) $notices->count();
+
+        $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
+                                   "Subscribers: %2\$s\n".
+                                   "Notices: %3\$s"),
+                                 $subs_count,
+                                 $subbed_count,
+                                 $notice_count));
+    }
+}
+
+class FavCommand extends Command
+{
+
+    var $other = null;
+
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+
+        $recipient =
+          common_relative_profile($this->user, common_canonical_nickname($this->other));
+
+        if (!$recipient) {
+            $channel->error($this->user, _('No such user.'));
+            return;
+        }
+        $notice = $recipient->getCurrentNotice();
+        if (!$notice) {
+            $channel->error($this->user, _('User has no last notice'));
+            return;
+        }
+
+        $fave = Fave::addNew($this->user, $notice);
+
+        if (!$fave) {
+            $channel->error($this->user, _('Could not create favorite.'));
+            return;
+        }
+
+        $other = User::staticGet('id', $recipient->id);
+
+        if ($other && $other->id != $user->id) {
+            if ($other->email && $other->emailnotifyfav) {
+                mail_notify_fave($other, $this->user, $notice);
+            }
+        }
+
+        $this->user->blowFavesCache();
+
+        $channel->output($this->user, _('Notice marked as fave.'));
+    }
+}
+
+class WhoisCommand extends Command
+{
+    var $other = null;
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+        $recipient =
+          common_relative_profile($this->user, common_canonical_nickname($this->other));
+
+        if (!$recipient) {
+            $channel->error($this->user, _('No such user.'));
+            return;
+        }
+
+        $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
+                         $recipient->profileurl);
+        if ($recipient->fullname) {
+            $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
+        }
+        if ($recipient->location) {
+            $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
+        }
+        if ($recipient->homepage) {
+            $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
+        }
+        if ($recipient->bio) {
+            $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
+        }
+        $channel->output($this->user, $whois);
+    }
+}
+
+class MessageCommand extends Command
+{
+    var $other = null;
+    var $text = null;
+    function __construct($user, $other, $text)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+        $this->text = $text;
+    }
+
+    function execute($channel)
+    {
+        $other = User::staticGet('nickname', common_canonical_nickname($this->other));
+        $len = mb_strlen($this->text);
+        if ($len == 0) {
+            $channel->error($this->user, _('No content!'));
+            return;
+        } else if ($len > 140) {
+            $content = common_shorten_links($content);
+            if (mb_strlen($content) > 140) {
+                $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len));
+                return;
+            }
+        }
+
+        if (!$other) {
+            $channel->error($this->user, _('No such user.'));
+            return;
+        } else if (!$this->user->mutuallySubscribed($other)) {
+            $channel->error($this->user, _('You can\'t send a message to this user.'));
+            return;
+        } else if ($this->user->id == $other->id) {
+            $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.'));
+            return;
+        }
+        $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
+        if ($message) {
+            $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
+        } else {
+            $channel->error($this->user, _('Error sending direct message.'));
+        }
+    }
+}
+
+class GetCommand extends Command
+{
+
+    var $other = null;
+
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+        $target_nickname = common_canonical_nickname($this->other);
+
+        $target =
+          common_relative_profile($this->user, $target_nickname);
+
+        if (!$target) {
+            $channel->error($this->user, _('No such user.'));
+            return;
+        }
+        $notice = $target->getCurrentNotice();
+        if (!$notice) {
+            $channel->error($this->user, _('User has no last notice'));
+            return;
+        }
+        $notice_content = $notice->content;
+
+        $channel->output($this->user, $target_nickname . ": " . $notice_content);
+    }
+}
+
+class SubCommand extends Command
+{
+
+    var $other = null;
+
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+
+        if (!$this->other) {
+            $channel->error($this->user, _('Specify the name of the user to subscribe to'));
+            return;
+        }
+
+        $result = subs_subscribe_user($this->user, $this->other);
+
+        if ($result == 'true') {
+            $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
+        } else {
+            $channel->error($this->user, $result);
+        }
+    }
+}
+
+class UnsubCommand extends Command
+{
+
+    var $other = null;
+
+    function __construct($user, $other)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+        if(!$this->other) {
+            $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
+            return;
+        }
+
+        $result=subs_unsubscribe_user($this->user, $this->other);
+
+        if ($result) {
+            $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
+        } else {
+            $channel->error($this->user, $result);
+        }
+    }
+}
+
+class OffCommand extends Command
+{
+    var $other = null;
+    function __construct($user, $other=null)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+    function execute($channel)
+    {
+        if ($other) {
+            $channel->error($this->user, _("Command not yet implemented."));
+        } else {
+            if ($channel->off($this->user)) {
+                $channel->output($this->user, _('Notification off.'));
+            } else {
+                $channel->error($this->user, _('Can\'t turn off notification.'));
+            }
+        }
+    }
+}
+
+class OnCommand extends Command
+{
+    var $other = null;
+    function __construct($user, $other=null)
+    {
+        parent::__construct($user);
+        $this->other = $other;
+    }
+
+    function execute($channel)
+    {
+        if ($other) {
+            $channel->error($this->user, _("Command not yet implemented."));
+        } else {
+            if ($channel->on($this->user)) {
+                $channel->output($this->user, _('Notification on.'));
+            } else {
+                $channel->error($this->user, _('Can\'t turn on notification.'));
+            }
+        }
+    }
+}
+
+class HelpCommand extends Command
+{
+    function execute($channel)
+    {
+        $channel->output($this->user,
+                         _("Commands:\n".
+                           "on - turn on notifications\n".
+                           "off - turn off notifications\n".
+                           "help - show this help\n".
+                           "follow <nickname> - subscribe to user\n".
+                           "leave <nickname> - unsubscribe from user\n".
+                           "d <nickname> <text> - direct message to user\n".
+                           "get <nickname> - get last notice from user\n".
+                           "whois <nickname> - get profile info on user\n".
+                           "fav <nickname> - add user's last notice as a 'fave'\n".
+                           "stats - get your stats\n".
+                           "stop - same as 'off'\n".
+                           "quit - same as 'off'\n".
+                           "sub <nickname> - same as 'follow'\n".
+                           "unsub <nickname> - same as 'leave'\n".
+                           "last <nickname> - same as 'get'\n".
+                           "on <nickname> - not yet implemented.\n".
+                           "off <nickname> - not yet implemented.\n".
+                           "nudge <nickname> - not yet implemented.\n".
+                           "invite <phone number> - not yet implemented.\n".
+                           "track <word> - not yet implemented.\n".
+                           "untrack <word> - not yet implemented.\n".
+                           "track off - not yet implemented.\n".
+                           "untrack all - not yet implemented.\n".
+                           "tracks - not yet implemented.\n".
+                           "tracking - not yet implemented.\n"));
+    }
+}
diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php
new file mode 100644 (file)
index 0000000..49c733c
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR.'/lib/command.php';
+
+class CommandInterpreter
+{
+    function handle_command($user, $text)
+    {
+        # XXX: localise
+
+        $text = preg_replace('/\s+/', ' ', trim($text));
+        list($cmd, $arg) = explode(' ', $text, 2);
+
+        # We try to support all the same commands as Twitter, see
+        # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
+        # There are a few compatibility commands from earlier versions of
+        # Laconica
+
+        switch(strtolower($cmd)) {
+         case 'help':
+            if ($arg) {
+                return null;
+            }
+            return new HelpCommand($user);
+         case 'on':
+            if ($arg) {
+                list($other, $extra) = explode(' ', $arg, 2);
+                if ($extra) {
+                    return null;
+                } else {
+                    return new OnCommand($user, $other);
+                }
+            } else {
+                return new OnCommand($user);
+            }
+         case 'off':
+            if ($arg) {
+                list($other, $extra) = explode(' ', $arg, 2);
+                if ($extra) {
+                    return null;
+                } else {
+                    return new OffCommand($user, $other);
+                }
+            } else {
+                return new OffCommand($user);
+            }
+         case 'stop':
+         case 'quit':
+            if ($arg) {
+                return null;
+            } else {
+                return new OffCommand($user);
+            }
+         case 'follow':
+         case 'sub':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new SubCommand($user, $other);
+            }
+         case 'leave':
+         case 'unsub':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new UnsubCommand($user, $other);
+            }
+         case 'get':
+         case 'last':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new GetCommand($user, $other);
+            }
+         case 'd':
+         case 'dm':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if (!$extra) {
+                return null;
+            } else {
+                return new MessageCommand($user, $other, $extra);
+            }
+         case 'whois':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new WhoisCommand($user, $other);
+            }
+         case 'fav':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new FavCommand($user, $other);
+            }
+         case 'nudge':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new NudgeCommand($user, $other);
+            }
+         case 'stats':
+            if ($arg) {
+                return null;
+            }
+            return new StatsCommand($user);
+         case 'invite':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else {
+                return new InviteCommand($user, $other);
+            }
+         case 'track':
+            if (!$arg) {
+                return null;
+            }
+            list($word, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else if ($word == 'off') {
+                return new TrackOffCommand($user);
+            } else {
+                return new TrackCommand($user, $word);
+            }
+         case 'untrack':
+            if (!$arg) {
+                return null;
+            }
+            list($word, $extra) = explode(' ', $arg, 2);
+            if ($extra) {
+                return null;
+            } else if ($word == 'all') {
+                return new TrackOffCommand($user);
+            } else {
+                return new UntrackCommand($user, $word);
+            }
+         case 'tracks':
+         case 'tracking':
+            if ($arg) {
+                return null;
+            }
+            return new TrackingCommand($user);
+         default:
+            return false;
+        }
+    }
+}
+
index 041459cf3462b7804bf56042afe290dc1ef41a23..4fc749ca06540095b258e9ab4871a7e842855e00 100644 (file)
@@ -106,7 +106,8 @@ $config =
         array('server' => null),
         'public' =>
         array('localonly' => true,
-              'blacklist' => array()),
+              'blacklist' => array(),
+              'autosource' => array()),
         'theme' =>
         array('server' => null),
         'throttle' =>
@@ -211,6 +212,9 @@ 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');
     }
 }
 
diff --git a/lib/dberroraction.php b/lib/dberroraction.php
new file mode 100644 (file)
index 0000000..0dc9249
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * DB error action.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/servererroraction.php';
+
+/**
+ * Class for displaying DB Errors
+ *
+ * This only occurs if there's been a DB_DataObject_Error that's
+ * reported through PEAR, so we try to avoid doing anything that connects
+ * to the DB, so we don't trigger it again.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class DBErrorAction extends ServerErrorAction
+{
+    function __construct($message='Error', $code=500)
+    {
+        parent::__construct($message, $code);
+    }
+
+    function title()
+    {
+        return _('Database error');
+    }
+
+    function getLanguage()
+    {
+        // Don't try to figure out user's language; just show the page
+        return common_config('site', 'language');
+    }
+
+    function showPrimaryNav()
+    {
+        // don't show primary nav
+    }
+}
index beab51366081b90b77a9775bf0f3cef04a225bd3..ec39872732fa45ae22ed95c88f4f3b34633ee28f 100644 (file)
@@ -25,21 +25,6 @@ define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2
 define("FACEBOOK_NOTICE_PREFIX", 1);
 define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
 
-// Gets all the notices from users with a Facebook link since a given ID
-function getFacebookNotices($since)
-{
-    $qry = 'SELECT notice.* ' .
-        'FROM notice ' .
-        'JOIN foreign_link ' .
-        'WHERE notice.profile_id = foreign_link.user_id ' .
-        'AND foreign_link.service = 2';
-
-    // XXX: What should the limit be?
-    //static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) {
-    
-    return Notice::getStreamDirect($qry, 0, 1000, 0, 0, null, $since);
-}
-
 function getFacebook()
 {
     $apikey = common_config('facebook', 'apikey');
@@ -52,3 +37,97 @@ function updateProfileBox($facebook, $flink, $notice) {
     $fbaction->updateProfileBox($notice);
 }
 
+function isFacebookBound($notice, $flink) {
+
+    // If the user does not want to broadcast to Facebook, move along
+    if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
+        common_log(LOG_INFO, "Skipping notice $notice->id " .
+            'because user has FOREIGN_NOTICE_SEND bit off.');
+        return false;
+    }
+
+    $success = false;
+
+    // If it's not a reply, or if the user WANTS to send @-replies...
+    if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
+        ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+
+        $success = true;
+
+        // The two condition below are deal breakers:
+
+        // Avoid a loop
+        if ($notice->source == 'Facebook') {
+            common_log(LOG_INFO, "Skipping notice $notice->id because its " .
+                'source is Facebook.');
+            $success = false;
+        }
+
+        $facebook = getFacebook();
+        $fbuid = $flink->foreign_id;
+
+        try {
+
+            // Check to see if the user has given the FB app status update perms
+            $result = $facebook->api_client->
+                users_hasAppPermission('status_update', $fbuid);
+
+            if ($result != 1) {
+                $user = $flink->getUser();
+                $msg = "Can't send notice $notice->id to Facebook " .
+                    "because user $user->nickname hasn't given the " .
+                    'Facebook app \'status_update\' permission.';
+                common_log(LOG_INFO, $msg);
+                $success = false;
+            }
+
+        } catch(FacebookRestClientException $e){
+            common_log(LOG_ERR, $e->getMessage());
+            $success = false;
+        }
+
+    }
+
+    return $success;
+
+}
+
+
+function facebookBroadcastNotice($notice)
+{
+    $facebook = getFacebook();
+    $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
+    $fbuid = $flink->foreign_id;
+
+    if (isFacebookBound($notice, $flink)) {
+
+        $status = null;
+
+        // Get the status 'verb' (prefix) the user has set
+        try {
+            $prefix = $facebook->api_client->
+                data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
+
+            $status = "$prefix $notice->content";
+
+        } catch(FacebookRestClientException $e) {
+            common_log(LOG_ERR, $e->getMessage());
+            return false;
+        }
+
+        // Okay, we're good to go!
+
+        try {
+            $facebook->api_client->users_setStatus($status, $fbuid, false, true);
+            updateProfileBox($facebook, $flink, $notice);
+        } catch(FacebookRestClientException $e) {
+            common_log(LOG_ERR, $e->getMessage());
+            return false;
+
+             // Should we remove flink if this fails?
+        }
+
+    }
+
+    return true;
+}
index 2935d83630ff3110ab64f607b675912da3dbe9d6..aed94b1a555e845b36c7fd23513fd90b0a9756cc 100644 (file)
@@ -86,4 +86,9 @@ class FeaturedUsersSection extends ProfileSection
     {
         return 'featured_users';
     }
+
+    function moreUrl()
+    {
+        return common_local_url('featured');
+    }
 }
diff --git a/lib/feed.php b/lib/feed.php
new file mode 100644 (file)
index 0000000..4669268
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Data structure for info about syndication feeds (RSS 1.0, RSS 2.0, Atom)
+ *
+ * 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  Feed
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@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);
+}
+
+/**
+ * Data structure for feeds
+ *
+ * This structure is a helpful container for shipping around information about syndication feeds.
+ *
+ * @category Feed
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@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 Feed
+{
+    const RSS1 = 1;
+    const RSS2 = 2;
+    const ATOM = 3;
+    const FOAF = 4;
+
+    var $type = null;
+    var $url = null;
+    var $title = null;
+
+    function __construct($type, $url, $title)
+    {
+        $this->type  = $type;
+        $this->url   = $url;
+        $this->title = $title;
+    }
+
+    function mimeType()
+    {
+        switch ($this->type) {
+         case Feed::RSS1:
+            return 'application/rdf+xml';
+         case Feed::RSS2:
+            return 'application/rss+xml';
+         case Feed::ATOM:
+            return 'application/atom+xml';
+         case Feed::FOAF:
+            return 'application/rdf+xml';
+         default:
+            return null;
+        }
+    }
+
+    function typeName()
+    {
+        switch ($this->type) {
+         case Feed::RSS1:
+            return _('RSS 1.0');
+         case Feed::RSS2:
+            return _('RSS 2.0');
+         case Feed::ATOM:
+            return _('Atom');
+         case Feed::FOAF:
+            return _('FOAF');
+         default:
+            return null;
+        }
+    }
+
+    function rel()
+    {
+        switch ($this->type) {
+         case Feed::RSS1:
+         case Feed::RSS2:
+         case Feed::ATOM:
+            return 'alternate';
+         case Feed::FOAF:
+            return 'meta';
+         default:
+            return null;
+        }
+    }
+}
index 47d909e969c1ac51986f7448e7c3996630ff4c7d..927e43c330fda3c0ee1754f12d30038a7271b5d2 100644 (file)
@@ -50,7 +50,7 @@ if (!defined('LACONICA')) {
 class FeedList extends Widget
 {
     var $action = null;
-    
+
     function __construct($action=null)
     {
        parent::__construct($action);
@@ -64,8 +64,8 @@ class FeedList extends Widget
         $this->out->element('h2', null, _('Export data'));
         $this->out->elementStart('ul', array('class' => 'xoxo'));
 
-        foreach ($feeds as $key => $value) {
-            $this->feedItem($feeds[$key]);
+        foreach ($feeds as $feed) {
+            $this->feedItem($feed);
         }
 
         $this->out->elementEnd('ul');
@@ -74,85 +74,27 @@ class FeedList extends Widget
 
     function feedItem($feed)
     {
-        $nickname = $this->action->trimmed('nickname');
-
-        switch($feed['item']) {
-         case 'notices': default:
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "$nickname's ".$feed['version']." notice feed";
-            $feed['textContent'] = "RSS";
-            break;
-
-         case 'allrss':
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = $feed['version']." feed for $nickname and friends";
-            $feed['textContent'] = "RSS";
-            break;
-
-         case 'repliesrss':
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = $feed['version']." feed for replies to $nickname";
-            $feed['textContent'] = "RSS";
-            break;
-
-         case 'publicrss':
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "Public timeline ".$feed['version']." feed";
-            $feed['textContent'] = "RSS";
-            break;
+        $classname = null;
 
-         case 'publicatom':
-            $feed_classname = "atom";
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "Public timeline ".$feed['version']." feed";
-            $feed['textContent'] = "Atom";
+        switch ($feed->type) {
+         case Feed::RSS1:
+         case Feed::RSS2:
+            $classname = 'rss';
             break;
-
-         case 'tagrss':
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = $feed['version']." feed for this tag";
-            $feed['textContent'] = "RSS";
+         case Feed::ATOM:
+            $classname = 'atom';
             break;
-
-         case 'favoritedrss':
-            $feed_classname = $feed['type'];
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "Favorited ".$feed['version']." feed";
-            $feed['textContent'] = "RSS";
-            break;
-
-         case 'foaf':
-            $feed_classname = "foaf";
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "$nickname's FOAF file";
-            $feed['textContent'] = "FOAF";
-            break;
-
-         case 'favoritesrss':
-            $feed_classname = "favorites";
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "Feed for favorites of $nickname";
-            $feed['textContent'] = "RSS";
-            break;
-
-         case 'usertimeline':
-            $feed_classname = "atom";
-            $feed_mimetype = "application/".$feed['type']."+xml";
-            $feed_title = "$nickname's ".$feed['version']." notice feed";
-            $feed['textContent'] = "Atom";
+         case Feed::FOAF:
+            $classname = 'foaf';
             break;
         }
+
         $this->out->elementStart('li');
-        $this->out->element('a', array('href' => $feed['href'],
-                                  'class' => $feed_classname,
-                                  'type' => $feed_mimetype,
-                                  'title' => $feed_title),
-                       $feed['textContent']);
+        $this->out->element('a', array('href' => $feed->url,
+                                       'class' => $classname,
+                                       'type' => $feed->mimeType(),
+                                       'title' => $feed->title),
+                            $feed->typeName());
         $this->out->elementEnd('li');
     }
 }
index 4c448e250d5011b13d3d8280fac33c97cf482bd5..1b854749982198e68f89de742d2c1fd306e3ce1c 100644 (file)
@@ -124,7 +124,7 @@ class GroupList extends Widget
         if ($this->group->location) {
             $this->out->elementStart('dl', 'entity_location');
             $this->out->element('dt', null, _('Location'));
-            $this->out->elementStart('dd', 'location');
+            $this->out->elementStart('dd', 'label');
             $this->out->raw($this->highlight($this->group->location));
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
@@ -151,7 +151,7 @@ class GroupList extends Widget
 
         # If we're on a list with an owner (subscriptions or subscribers)...
 
-        if ($user && $user->id == $this->owner->id) {
+        if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) {
             $this->showOwnerControls();
         }
 
index 7780b1c19d0c3af05980860ea1d3ae46efcb8a84..06603ac05485660b206726ad217a769756f9713b 100644 (file)
@@ -101,29 +101,32 @@ class HTMLOutputter extends XMLOutputter
             $type = common_negotiate_type($cp, $sp);
 
             if (!$type) {
-                common_user_error(_('This page is not available in a '.
-                                    'media type you accept'), 406);
-                exit(0);
+                throw new ClientException(_('This page is not available in a '.
+                                            'media type you accept'), 406);
             }
         }
 
         header('Content-Type: '.$type);
-        
+
         $this->extraHeaders();
 
         $this->startXML('html',
                         '-//W3C//DTD XHTML 1.0 Strict//EN',
                         'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
 
-        // FIXME: correct language for interface
-
-        $language = common_language();
+        $language = $this->getLanguage();
 
         $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
                                           'xml:lang' => $language,
                                           'lang' => $language));
     }
 
+    function getLanguage()
+    {
+        // FIXME: correct language for interface
+        return common_language();
+    }
+
     /**
     *  Ends an HTML document
     *
@@ -134,7 +137,7 @@ class HTMLOutputter extends XMLOutputter
         $this->elementEnd('html');
         $this->endXML();
     }
-    
+
     /**
     *  To specify additional HTTP headers for the action
     *
@@ -255,7 +258,7 @@ class HTMLOutputter extends XMLOutputter
         foreach ($content as $value => $option) {
             if ($value == $selected) {
                 $this->element('option', array('value' => $value,
-                                               'selected' => $value),
+                                               'selected' => 'selected'),
                                $option);
             } else {
                 $this->element('option', array('value' => $value), $option);
index db344db8f83d8500c790210df522de5ab8cf80f6..0c93b257ed394d5433e6a2c8f147e18aad480587 100644 (file)
@@ -68,17 +68,17 @@ class ImageFile
     static function fromUpload($param='upload')
     {
         switch ($_FILES[$param]['error']) {
-        case UPLOAD_ERR_OK: // success, jump out
+         case UPLOAD_ERR_OK: // success, jump out
             break;
-        case UPLOAD_ERR_INI_SIZE:
-        case UPLOAD_ERR_FORM_SIZE:
+         case UPLOAD_ERR_INI_SIZE:
+         case UPLOAD_ERR_FORM_SIZE:
             throw new Exception(sprintf(_('That file is too big. The maximum file size is %d.'), $this->maxFileSize()));
             return;
-        case UPLOAD_ERR_PARTIAL:
+         case UPLOAD_ERR_PARTIAL:
             @unlink($_FILES[$param]['tmp_name']);
             throw new Exception(_('Partial upload.'));
             return;
-        default:
+         default:
             throw new Exception(_('System error uploading file.'));
             return;
         }
@@ -113,6 +113,23 @@ class ImageFile
             return;
         }
 
+        // Don't crop/scale if it isn't necessary
+        if ($size === $this->width
+            && $size === $this->height
+            && $x === 0
+            && $y === 0
+            && $w === $this->width
+            && $h === $this->height) {
+
+            $outname = Avatar::filename($this->id,
+                                        image_type_to_extension($this->type),
+                                        $size,
+                                        common_timestamp());
+            $outpath = Avatar::path($outname);
+            @copy($this->filepath, $outpath);
+            return $outname;
+        }
+
         switch ($this->type) {
          case IMAGETYPE_GIF:
             $image_src = imagecreatefromgif($this->filepath);
@@ -154,9 +171,9 @@ class ImageFile
         imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $w, $h);
 
         $outname = Avatar::filename($this->id,
-                                          image_type_to_extension($this->type),
-                                          $size,
-                                          common_timestamp());
+                                    image_type_to_extension($this->type),
+                                    $size,
+                                    common_timestamp());
 
         $outpath = Avatar::path($outname);
 
@@ -165,7 +182,7 @@ class ImageFile
             imagegif($image_dest, $outpath);
             break;
          case IMAGETYPE_JPEG:
-            imagejpeg($image_dest, $outpath);
+            imagejpeg($image_dest, $outpath, 100);
             break;
          case IMAGETYPE_PNG:
             imagepng($image_dest, $outpath);
@@ -175,6 +192,9 @@ class ImageFile
             return;
         }
 
+        imagedestroy($image_src);
+        imagedestroy($image_dest);
+
         return $outname;
     }
 
@@ -209,12 +229,12 @@ class ImageFile
         $num = substr($str, 0, -1);
 
         switch(strtoupper($unit)){
-            case 'G':
-                $num *= 1024;
-            case 'M':
-                $num *= 1024;
-            case 'K':
-                $num *= 1024;
+         case 'G':
+            $num *= 1024;
+         case 'M':
+            $num *= 1024;
+         case 'K':
+            $num *= 1024;
         }
 
         return $num;
index f41d984d6261a46698f11cf27de238d8698e3ce1..3fbb3e1ab9ee0c04c9ad263cc3ba43654b97a39b 100644 (file)
@@ -114,7 +114,7 @@ function jabber_connect($resource=null)
         try {
             $conn->connect(true); // true = persistent connection
         } catch (XMPPHP_Exception $e) {
-            common_log(LOG_ERROR, $e->getMessage());
+            common_log(LOG_ERR, $e->getMessage());
             return false;
         }
 
@@ -186,6 +186,11 @@ function jabber_format_entry($profile, $notice)
     $entry .= "<id>". $notice->uri . "</id>\n";
     $entry .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
     $entry .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
+    if ($notice->reply_to) {
+        $replyurl = common_local_url('shownotice',
+                                     array('notice' => $notice->reply_to));
+        $entry .= "<link rel='related' href='" . $replyurl . "'/>\n";
+    }
     $entry .= "</entry>\n";
 
     $html  = "\n<html xmlns='http://jabber.org/protocol/xhtml-im'>\n";
index a1faefc806576a3a336efc0341cedd912c52a21a..9fa86de5cc81a40643340db104248c79adb40c8e 100644 (file)
@@ -573,3 +573,53 @@ function mail_notify_fave($other, $user, $notice)
     common_init_locale();
     mail_to_user($other, $subject, $body);
 }
+
+/**
+ * notify a user that they have received an "attn:" message AKA "@-reply"
+ *
+ * @param User   $user   The user who recevied the notice
+ * @param Notice $notice The notice that was sent
+ *
+ * @return void
+ */
+
+function mail_notify_attn($user, $notice)
+{
+    if (!$user->email || !$user->emailnotifyattn) {
+        return;
+    }
+
+    $sender = $notice->getProfile();
+
+    $bestname = $sender->getBestName();
+
+    common_init_locale($user->language);
+
+    $subject = sprintf(_('%s sent a notice to your attention'), $bestname);
+
+    $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
+                      "The notice is here:\n\n".
+                      "\t%3\$s\n\n" .
+                      "It reads:\n\n".
+                      "\t%4\$s\n\n" .
+                      "You can reply back here:\n\n".
+                      "\t%5\$s\n\n" .
+                      "The list of all @-replies for you here:\n\n" .
+                      "%6\$s\n\n" .
+                      "Faithfully yours,\n" .
+                      "%2\$s\n\n" .
+                      "P.S. You can turn off these email notifications here: %7\$s\n"),
+                    $bestname,
+                    common_config('site', 'name'),
+                    common_local_url('shownotice',
+                                     array('notice' => $notice->id)),
+                    $notice->content,
+                    common_local_url('newnotice',
+                                     array('replyto' => $sender->nickname)),
+                    common_local_url('replies',
+                                     array('nickname' => $user->nickname)),
+                    common_local_url('emailsettings'));
+
+    common_init_locale();
+    mail_to_user($user, $subject, $body);
+}
index 97b51752965c1092c7bf7867e6d0aade184373ea..b31f18744557be91c42975ca0baaef70139271cb 100644 (file)
@@ -96,7 +96,7 @@ class NoticeSection extends Section
         $this->out->elementStart('p', 'entry-content');
         $this->out->raw($notice->rendered);
         $this->out->elementEnd('p');
-        if ($notice->value) {
+        if (!empty($notice->value)) {
             $this->out->elementStart('p');
             $this->out->text($notice->value);
             $this->out->elementEnd('p');
index f2dbef5ba945f97ed10c172cd5e3a5367c844035..29e14c75f76adff6255e62010b3e5e46b999b0d8 100644 (file)
@@ -239,7 +239,7 @@ function omb_broadcast_profile($profile)
         while ($sub->fetch()) {
             $rp = Remote_profile::staticGet('id', $sub->subscriber);
             if ($rp) {
-                if (!$updated[$rp->updateprofileurl]) {
+                if (!array_key_exists($rp->updateprofileurl, $updated)) {
                     if (omb_update_profile($profile, $rp, $sub)) {
                         $updated[$rp->updateprofileurl] = true;
                     }
@@ -295,7 +295,9 @@ function omb_update_profile($profile, $remote_profile, $subscription)
 
     common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
 
-    if ($result->status == 403) { # not authorized, don't send again
+    if (empty($result) || $result) {
+        common_debug("Unable to contact " . $req->get_normalized_http_url());
+    } else if ($result->status == 403) { # not authorized, don't send again
         common_debug('403 result, deleting subscription', __FILE__);
         $subscription->delete();
         return false;
index 8605737026340cebfbc00e2d87ea615398af83e2..5c3d460dafdb7c2e7814663953f54eb7c5788f8f 100644 (file)
@@ -64,6 +64,9 @@ function oid_set_last($openid_url)
 
 function oid_get_last()
 {
+    if (empty($_COOKIE[OPENID_COOKIE_KEY])) {
+        return null;
+    }
     $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
     if ($openid_url && strlen($openid_url) > 0) {
         return $openid_url;
diff --git a/lib/peoplesearchresults.php b/lib/peoplesearchresults.php
new file mode 100644 (file)
index 0000000..f8ab7cf
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * People search results class
+ *
+ * PHP version 5
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profilelist.php';
+
+/**
+ * People search results class
+ *
+ * Derivative of ProfileList with specialization for highlighting search terms.
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * @see PeoplesearchAction
+ */
+
+class PeopleSearchResults extends ProfileList
+{
+    var $terms = null;
+    var $pattern = null;
+
+    function __construct($profile, $terms, $action)
+    {
+        parent::__construct($profile, $terms, $action);
+        $this->terms = array_map('preg_quote',
+                                 array_map('htmlspecialchars', $terms));
+        $this->pattern = '/('.implode('|',$terms).')/i';
+    }
+
+    function highlight($text)
+    {
+        return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
+    }
+
+    function isReadOnly()
+    {
+        return true;
+    }
+}
+
index 5734d800189d9bdd7be139d85aef94775b8b53c7..c7c7f02150407a850bbcc8a3172cfd8b05f90dfd 100644 (file)
@@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
     exit(1);
 }
 
-define('NOTICES_PER_SECTION', 5);
-
 /**
  * Base class for sections showing lists of notices
  *
@@ -80,4 +78,9 @@ class PopularNoticeSection extends NoticeSection
     {
         return 'popular_notices';
     }
+
+    function moreUrl()
+    {
+        return common_local_url('favorited');
+    }
 }
index 4d924b039e9647fd329240044a9874d11a9ead59..c2040fbc232a980fa2d926d926abbd6850b67614 100644 (file)
@@ -34,8 +34,6 @@ if (!defined('LACONICA')) {
 
 require_once INSTALLDIR.'/lib/widget.php';
 
-define('PROFILES_PER_PAGE', 20);
-
 /**
  * Widget to show a list of profiles
  *
@@ -123,7 +121,7 @@ class ProfileList extends Widget
         if ($this->profile->location) {
             $this->out->elementStart('dl', 'entity_location');
             $this->out->element('dt', null, _('Location'));
-            $this->out->elementStart('dd', 'location');
+            $this->out->elementStart('dd', 'label');
             $this->out->raw($this->highlight($this->profile->location));
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
diff --git a/lib/router.php b/lib/router.php
new file mode 100644 (file)
index 0000000..b18a552
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * URL routing utilities
+ *
+ * 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  URL
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@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);
+}
+
+require_once 'Net/URL/Mapper.php';
+
+/**
+ * URL Router
+ *
+ * Cheap wrapper around Net_URL_Mapper
+ *
+ * @category URL
+ * @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 Router
+{
+    var $m = null;
+    static $inst = null;
+
+    static function get()
+    {
+        if (!Router::$inst) {
+            Router::$inst = new Router();
+        }
+        return Router::$inst;
+    }
+
+    function __construct()
+    {
+        if (!$this->m) {
+            $this->m = $this->initialize();
+        }
+    }
+
+    function initialize() {
+
+        $m = Net_URL_Mapper::getInstance();
+
+        // In the "root"
+
+        $m->connect('', array('action' => 'public'));
+        $m->connect('rss', array('action' => 'publicrss'));
+        $m->connect('xrds', array('action' => 'publicxrds'));
+        $m->connect('featuredrss', array('action' => 'featuredrss'));
+        $m->connect('favoritedrss', array('action' => 'favoritedrss'));
+        $m->connect('opensearch/people', array('action' => 'opensearch',
+                                               'type' => 'people'));
+        $m->connect('opensearch/notice', array('action' => 'opensearch',
+                                               'type' => 'notice'));
+
+        // docs
+
+        $m->connect('doc/:title', array('action' => 'doc'));
+
+        // facebook
+
+        $m->connect('facebook', array('action' => 'facebookhome'));
+        $m->connect('facebook/index.php', array('action' => 'facebookhome'));
+        $m->connect('facebook/settings.php', array('action' => 'facebooksettings'));
+        $m->connect('facebook/invite.php', array('action' => 'facebookinvite'));
+        $m->connect('facebook/remove', array('action' => 'facebookremove'));
+
+        // main stuff is repetitive
+
+        $main = array('login', 'logout', 'register', 'subscribe',
+                      'unsubscribe', 'confirmaddress', 'recoverpassword',
+                      'invite', 'favor', 'disfavor', 'sup',
+                      'block');
+
+        foreach ($main as $a) {
+            $m->connect('main/'.$a, array('action' => $a));
+        }
+
+        $m->connect('main/tagother/:id', array('action' => 'tagother'));
+
+        // these take a code
+
+        foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
+            $m->connect('main/'.$c.'/:code', array('action' => $c));
+        }
+
+        // exceptional
+
+        $m->connect('main/openid', array('action' => 'openidlogin'));
+        $m->connect('main/remote', array('action' => 'remotesubscribe'));
+
+        // settings
+
+        foreach (array('profile', 'avatar', 'password', 'openid', 'im',
+                       'email', 'sms', 'twitter', 'other') as $s) {
+            $m->connect('settings/'.$s, array('action' => $s.'settings'));
+        }
+
+        // search
+
+        foreach (array('group', 'people', 'notice') as $s) {
+            $m->connect('search/'.$s, array('action' => $s.'search'));
+        }
+
+        $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
+
+        // notice
+
+        $m->connect('notice/new', array('action' => 'newnotice'));
+        $m->connect('notice/:notice',
+                    array('action' => 'shownotice'),
+                    array('notice' => '[0-9]+'));
+        $m->connect('notice/delete', array('action' => 'deletenotice'));
+        $m->connect('notice/delete/:notice',
+                    array('action' => 'deletenotice'),
+                    array('notice' => '[0-9]+'));
+
+        $m->connect('message/new', array('action' => 'newmessage'));
+        $m->connect('message/:message',
+                    array('action' => 'showmessage'),
+                    array('message' => '[0-9]+'));
+
+        $m->connect('user/:id',
+                    array('action' => 'userbyid'),
+                    array('id' => '[0-9]+'));
+
+        $m->connect('tags/', array('action' => 'publictagcloud'));
+        $m->connect('tag/', array('action' => 'publictagcloud'));
+        $m->connect('tags', array('action' => 'publictagcloud'));
+        $m->connect('tag', array('action' => 'publictagcloud'));
+        $m->connect('tag/:tag/rss',
+                    array('action' => 'tagrss'),
+                    array('tag' => '[a-zA-Z0-9]+'));
+        $m->connect('tag/:tag',
+                    array('action' => 'tag'),
+                    array('tag' => '[a-zA-Z0-9]+'));
+
+        $m->connect('peopletag/:tag',
+                    array('action' => 'peopletag'),
+                    array('tag' => '[a-zA-Z0-9]+'));
+
+        $m->connect('featured/', array('action' => 'featured'));
+        $m->connect('featured', array('action' => 'featured'));
+        $m->connect('favorited/', array('action' => 'favorited'));
+        $m->connect('favorited', array('action' => 'favorited'));
+
+        // groups
+
+        $m->connect('group/new', array('action' => 'newgroup'));
+
+        foreach (array('edit', 'join', 'leave') as $v) {
+            $m->connect('group/:nickname/'.$v,
+                        array('action' => $v.'group'),
+                        array('nickname' => '[a-zA-Z0-9]+'));
+        }
+
+        foreach (array('members', 'logo', 'rss') as $n) {
+            $m->connect('group/:nickname/'.$n,
+                        array('action' => 'group'.$n),
+                        array('nickname' => '[a-zA-Z0-9]+'));
+        }
+
+        $m->connect('group/:id/id',
+                    array('action' => 'groupbyid'),
+                    array('id' => '[0-9]+'));
+
+        $m->connect('group/:nickname',
+                    array('action' => 'showgroup'),
+                    array('nickname' => '[a-zA-Z0-9]+'));
+
+        $m->connect('group/', array('action' => 'groups'));
+        $m->connect('group', array('action' => 'groups'));
+        $m->connect('groups/', array('action' => 'groups'));
+        $m->connect('groups', array('action' => 'groups'));
+
+        // Twitter-compatible API
+
+        // statuses API
+
+        $m->connect('api/statuses/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'statuses'),
+                    array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|friends|followers|featured)(\.(atom|rss|xml|json))?'));
+
+        $m->connect('api/statuses/:method/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'statuses'),
+                    array('method' => '(user_timeline|friends_timeline|show|destroy|friends|followers)'));
+
+        // users
+
+        $m->connect('api/users/show/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'users'));
+
+        $m->connect('api/users/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'users'),
+                    array('method' => 'show(\.(xml|json|atom|rss))?'));
+
+        // direct messages
+
+        foreach (array('xml', 'json') as $e) {
+            $m->connect('api/direct_messages/new.'.$e,
+                        array('action' => 'api',
+                              'apiaction' => 'direct_messages',
+                              'method' => 'create.'.$e));
+        }
+
+        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+            $m->connect('api/direct_messages.'.$e,
+                        array('action' => 'api',
+                              'apiaction' => 'direct_messages',
+                              'method' => 'direct_messages.'.$e));
+        }
+
+        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+            $m->connect('api/direct_message/sent.'.$e,
+                        array('action' => 'api',
+                        'apiaction' => 'direct_messages',
+                        'method' => 'sent.'.$e));
+        }
+
+        $m->connect('api/direct_messages/destroy/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'direct_messages'));
+
+        // friendships
+
+        $m->connect('api/friendships/:method/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'friendships'),
+                    array('method' => '(create|destroy)'));
+
+        $m->connect('api/friendships/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'friendships'),
+                    array('method' => 'exists(\.(xml|json|rss|atom))'));
+
+
+        // Social graph
+
+        $m->connect('api/friends/ids/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'statuses',
+                          'method' => 'friendsIDs'));
+                                                   
+        foreach (array('xml', 'json') as $e) {
+            $m->connect('api/friends/ids.'.$e,
+                        array('action' => 'api',
+                              'apiaction' => 'statuses',
+                              'method' => 'friendsIDs.'.$e));
+        }
+                                                    
+        $m->connect('api/followers/ids/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'statuses',
+                          'method' => 'followersIDs'));
+
+        foreach (array('xml', 'json') as $e) {
+            $m->connect('api/followers/ids.'.$e,
+                        array('action' => 'api',
+                              'apiaction' => 'statuses',
+                              'method' => 'followersIDs.'.$e));
+        }
+
+        // account
+
+        $m->connect('api/account/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'account'));
+
+        // favorites
+
+        $m->connect('api/favorites/:method/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'favorites'));
+
+        $m->connect('api/favorites/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'favorites',
+                          'method' => 'favorites'));
+
+        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+            $m->connect('api/favorites.'.$e,
+                array('action' => 'api',
+                      'apiaction' => 'favorites',
+                      'method' => 'favorites.'.$e));
+        }
+
+        // notifications
+
+        $m->connect('api/notifications/:method/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'favorites'));
+
+        // blocks
+
+        $m->connect('api/blocks/:method/:argument',
+                    array('action' => 'api',
+                          'apiaction' => 'blocks'));
+
+        // help
+
+        $m->connect('api/help/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'help'));
+
+        // laconica
+
+        $m->connect('api/laconica/:method',
+                    array('action' => 'api',
+                          'apiaction' => 'laconica'));
+
+        // user stuff
+
+        foreach (array('subscriptions', 'subscribers',
+                       'nudge', 'xrds', 'all', 'foaf',
+                       'replies', 'inbox', 'outbox', 'microsummary') as $a) {
+            $m->connect(':nickname/'.$a,
+                        array('action' => $a),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
+        }
+
+        foreach (array('subscriptions', 'subscribers') as $a) {
+            $m->connect(':nickname/'.$a.'/:tag',
+                        array('action' => $a),
+                        array('tag' => '[a-zA-Z0-9]+',
+                              'nickname' => '[a-zA-Z0-9]{1,64}'));
+        }
+
+        foreach (array('rss', 'groups') as $a) {
+            $m->connect(':nickname/'.$a,
+                        array('action' => 'user'.$a),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
+        }
+
+        foreach (array('all', 'replies', 'favorites') as $a) {
+            $m->connect(':nickname/'.$a.'/rss',
+                        array('action' => $a.'rss'),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
+        }
+
+        $m->connect(':nickname/favorites',
+                    array('action' => 'showfavorites'),
+                    array('nickname' => '[a-zA-Z0-9]{1,64}'));
+
+        $m->connect(':nickname/avatar/:size',
+                    array('action' => 'avatarbynickname'),
+                    array('size' => '(original|96|48|24)',
+                          'nickname' => '[a-zA-Z0-9]{1,64}'));
+
+        $m->connect(':nickname',
+                    array('action' => 'showstream'),
+                    array('nickname' => '[a-zA-Z0-9]{1,64}'));
+
+        return $m;
+    }
+
+    function map($path)
+    {
+        try {
+            $match = $this->m->match($path);
+        } catch (Net_URL_Mapper_InvalidException $e) {
+            common_log(LOG_ERR, "Problem getting route for $path - " .
+                $e->getMessage());
+            $cac = new ClientErrorAction("Page not found.", 404);
+            $cac->showPage();
+        }
+
+        return $match;
+    }
+
+    function build($action, $args=null, $params=null, $fragment=null)
+    {
+        $action_arg = array('action' => $action);
+
+        if ($args) {
+            $args = array_merge($action_arg, $args);
+        } else {
+            $args = $action_arg;
+        }
+
+        return $this->m->generate($args, $params, $fragment);
+    }
+}
\ No newline at end of file
index 131e8ac65acf86424b0b251b67527441f9b41966..66c2d9e8cd2399adf22a6311a044ae604fadf458 100644 (file)
@@ -38,6 +38,7 @@ class Rss10Action extends Action
 
     var $creators = array();
     var $limit = DEFAULT_RSS_LIMIT;
+    var $notices = null;
 
     /**
      * Constructor
@@ -93,6 +94,9 @@ class Rss10Action extends Action
 
     function handle($args)
     {
+        // Get the list of notices
+        $this->notices = $this->getNotices();
+        // Parent handling, including cache check
         parent::handle($args);
         $this->showRss($this->limit);
     }
@@ -258,5 +262,25 @@ class Rss10Action extends Action
     {
         $this->elementEnd('rdf:RDF');
     }
+
+    /**
+     * When was this page last modified?
+     *
+     */
+
+    function lastModified()
+    {
+        if (empty($this->notices)) {
+            return null;
+        }
+
+        if (count($this->notices) == 0) {
+            return null;
+        }
+
+        // FIXME: doesn't handle modified profiles, avatars, deleted notices
+
+        return strtotime($this->notices[0]->created);
+    }
 }
 
index fdfb8dc5adac4cc57378b9665c9d0744907a8d5a..df6876445992b8c3838f5c3a51d660dc746805b4 100644 (file)
@@ -79,10 +79,11 @@ class SearchAction extends Action
 
     function showTop($arr=null)
     {
+        $error = null;
         if ($arr) {
             $error = $arr[1];
         }
-        if ($error) {
+        if (!empty($error)) {
             $this->element('p', 'error', $error);
         } else {
             $instr = $this->getInstructions();
index 0c32ddcf84e91a5696e5ea32cb29d4f45108995d..d145750862a504ec10d1e912e1f4a4c709624040 100644 (file)
@@ -103,6 +103,6 @@ class Section extends Widget
 
     function moreTitle()
     {
-        return null;
+        return _('More...');
     }
 }
index 1972985493a92b414d54667506b4983f86e80c44..deb6fd276b6ac71de7fe10280ad33f129c3cd717 100644 (file)
@@ -19,6 +19,8 @@
 
 if (!defined('LACONICA')) { exit(1); }
 
+define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1
+
 function get_twitter_data($uri, $screen_name, $password)
 {
 
@@ -28,14 +30,13 @@ function get_twitter_data($uri, $screen_name, $password)
             CURLOPT_FAILONERROR        => true,
             CURLOPT_HEADER            => false,
             CURLOPT_FOLLOWLOCATION    => true,
-            # CURLOPT_USERAGENT        => "identi.ca",
+            CURLOPT_USERAGENT      => "Laconica",
             CURLOPT_CONNECTTIMEOUT    => 120,
             CURLOPT_TIMEOUT            => 120,
             # Twitter is strict about accepting invalid "Expect" headers
             CURLOPT_HTTPHEADER => array('Expect:')
     );
 
-
     $ch = curl_init($uri);
     curl_setopt_array($ch, $options);
     $data = curl_exec($ch);
@@ -95,7 +96,7 @@ function add_twitter_user($twitter_id, $screen_name)
     $fuser->nickname = $screen_name;
     $fuser->uri = 'http://twitter.com/' . $screen_name;
     $fuser->id = $twitter_id;
-    $fuser->service = 1; // Twitter
+    $fuser->service = TWITTER_SERVICE; // Twitter
     $fuser->created = common_sql_now();
     $result = $fuser->insert();
 
@@ -206,3 +207,93 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
     return true;
 }
 
+function is_twitter_bound($notice, $flink) {
+
+    // Check to see if notice should go to Twitter
+    if (($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) ||
+            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+                return true;
+        }
+    }
+    
+    return false;
+}
+
+function broadcast_twitter($notice)
+{
+    global $config;
+    $success = true;
+
+    $flink = Foreign_link::getByUserID($notice->profile_id, 
+        TWITTER_SERVICE);
+            
+    // XXX: Not sure WHERE to check whether a notice should go to 
+    // Twitter. Should we even put in the queue if it shouldn't? --Zach
+    if (is_twitter_bound($notice, $flink)) {
+
+        $fuser = $flink->getForeignUser();
+        $twitter_user = $fuser->nickname;
+        $twitter_password = $flink->credentials;
+        $uri = 'http://www.twitter.com/statuses/update.json';
+
+        // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+        $statustxt = preg_replace('/^@/', ' @', $notice->content);
+
+        $options = array(
+            CURLOPT_USERPWD        => "$twitter_user:$twitter_password",
+            CURLOPT_POST           => true,
+            CURLOPT_POSTFIELDS     => 
+                array(
+                        'status' => $statustxt,
+                        'source' => $config['integration']['source']
+                     ),
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_FAILONERROR    => true,
+            CURLOPT_HEADER         => false,
+            CURLOPT_FOLLOWLOCATION => true,
+            CURLOPT_USERAGENT      => "Laconica",
+            CURLOPT_CONNECTTIMEOUT => 120,  // XXX: How long should this be?
+            CURLOPT_TIMEOUT        => 120,
+
+            # Twitter is strict about accepting invalid "Expect" headers
+            CURLOPT_HTTPHEADER => array('Expect:')
+            );
+
+        $ch = curl_init($uri);
+        curl_setopt_array($ch, $options);
+        $data = curl_exec($ch);
+        $errmsg = curl_error($ch);
+
+        if ($errmsg) {
+            common_debug("cURL error: $errmsg - " .
+                "trying to send notice for $twitter_user.",
+                         __FILE__);
+            $success = false;
+        }
+
+        curl_close($ch);
+
+        if (!$data) {
+            common_debug("No data returned by Twitter's " .
+                "API trying to send update for $twitter_user",
+                         __FILE__);
+            $success = false;
+        }
+
+        // Twitter should return a status
+        $status = json_decode($data);
+
+        if (!$status->id) {
+            common_debug("Unexpected data returned by Twitter " .
+                " API trying to send update for $twitter_user",
+                         __FILE__);
+            $success = false;
+        }
+    }
+    
+    return $success;
+}
+
index c5a092f6300c4f7c8bdae1bd27b4e8d0cac9f95f..5345a08bba08346e028a55f164fcf609b416e5fd 100644 (file)
@@ -394,32 +394,32 @@ function common_render_text($text)
 
 function common_replace_urls_callback($text, $callback) {
     // Start off with a regex
-    $regex = '#
-    (?:
-        (?:
-            (?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://
-            |
-            (?:mailto|aim|tel):
-        )
-        [^.\s]+\.[^\s]+
-        |
-        (?:[^.\s/:]+\.)+
-        (?:museum|travel|[a-z]{2,4})
-        (?:[:/][^\s]*)?
-    )
-    #ix';
+    $regex = '#'.
+    '(?:'.
+        '(?:'.
+            '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://'.
+            '|'.
+            '(?:mailto|aim|tel):'.
+        ')'.
+        '[^.\s]+\.[^\s]+'.
+        '|'.
+        '(?:[^.\s/:]+\.)+'.
+        '(?:museum|travel|[a-z]{2,4})'.
+        '(?:[:/][^\s]*)?'.
+    ')'.
+    '#ix';
     preg_match_all($regex, $text, $matches);
 
     // Then clean up what the regex left behind
     $offset = 0;
-    foreach($matches[0] as $url) {
-        $url = htmlspecialchars_decode($url);
+    foreach($matches[0] as $orig_url) {
+        $url = htmlspecialchars_decode($orig_url);
 
         // Make sure we didn't pick up an email address
         if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue;
 
-        // Remove trailing punctuation
-        $url = rtrim($url, '.?!,;:\'"`');
+        // Remove surrounding punctuation
+        $url = trim($url, '.?!,;:\'"`([<');
 
         // Remove surrounding parens and the like
         preg_match('/[)\]>]+$/', $url, $trailing);
@@ -446,7 +446,7 @@ function common_replace_urls_callback($text, $callback) {
 
         // If the first part wasn't cap'd but the last part was, we captured too much
         if ((!$prev_part && $last_part)) {
-            $url = substr_replace($url, '', mb_strpos($url, '.'.$url_parts[2], 0));
+            $url = mb_substr($url, 0 , mb_strpos($url, '.'.$url_parts['2'], 0));
         }
 
         // Capture the new TLD
@@ -456,6 +456,9 @@ function common_replace_urls_callback($text, $callback) {
 
         if (!in_array($url_parts[2], $tlds)) continue;
 
+        // Put the url back the way we found it.
+        $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
+
         // Call user specified func
         $modified_url = $callback($url);
 
@@ -469,16 +472,25 @@ function common_replace_urls_callback($text, $callback) {
 }
 
 function common_linkify($url) {
+    // It comes in special'd, so we unspecial it before passing to the stringifying
+    // functions
+    $ext = pathinfo($url, PATHINFO_EXTENSION);
+    $url = htmlspecialchars_decode($url);
+    $video_ext = array('mp4', 'flv', 'avi', 'mpg', 'mp3', 'ogg');
     $display = $url;
-    $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url:$url;
+    $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
+
+    $attrs = array('href' => $url, 'rel' => 'external');
+
+    if (in_array($ext, $video_ext)) {
+        $attrs['class'] = 'media';
+    }
 
     if ($longurl = common_longurl($url)) {
-        $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8');
-        $title = "title=\"$longurl\"";
+        $attrs['title'] = $longurl;
     }
-    else $title = '';
 
-    return "<a href=\"$url\" $title rel=\"external\">$display</a>";
+    return XMLStringer::estring('a', $attrs, $display);
 }
 
 function common_longurl($short_url)
@@ -579,7 +591,13 @@ function common_tag_link($tag)
 {
     $canonical = common_canonical_tag($tag);
     $url = common_local_url('tag', array('tag' => $canonical));
-    return '<span class="tag"><a href="' . htmlspecialchars($url) . '" rel="tag">' . htmlspecialchars($tag) . '</a></span>';
+    $xs = new XMLStringer();
+    $xs->elementStart('span', 'tag');
+    $xs->element('a', array('href' => $url,
+                            'rel' => 'tag'),
+                 $tag);
+    $xs->elementEnd('span');
+    return $xs->getString();
 }
 
 function common_canonical_tag($tag)
@@ -597,7 +615,14 @@ function common_at_link($sender_id, $nickname)
     $sender = Profile::staticGet($sender_id);
     $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
     if ($recipient) {
-        return '<span class="vcard"><a href="'.htmlspecialchars($recipient->profileurl).'" class="url"><span class="fn nickname">'.$nickname.'</span></a></span>';
+        $xs = new XMLStringer(false);
+        $xs->elementStart('span', 'vcard');
+        $xs->elementStart('a', array('href' => $recipient->profileurl,
+                                     'class' => 'url'));
+        $xs->element('span', 'fn nickname', $nickname);
+        $xs->elementEnd('a');
+        $xs->elementEnd('span');
+        return $xs->getString();
     } else {
         return $nickname;
     }
@@ -608,7 +633,14 @@ function common_group_link($sender_id, $nickname)
     $sender = Profile::staticGet($sender_id);
     $group = User_group::staticGet('nickname', common_canonical_nickname($nickname));
     if ($group && $sender->isMember($group)) {
-        return '<span class="vcard"><a href="'.htmlspecialchars($group->permalink()).'" class="url"><span class="fn nickname">'.$nickname.'</span></a></span>';
+        $xs = new XMLStringer();
+        $xs->elementStart('span', 'vcard');
+        $xs->elementStart('a', array('href' => $group->permalink(),
+                                     'class' => 'url'));
+        $xs->element('span', 'fn nickname', $nickname);
+        $xs->elementEnd('a');
+        $xs->elementEnd('span');
+        return $xs->getString();
     } else {
         return $nickname;
     }
@@ -625,7 +657,13 @@ function common_at_hash_link($sender_id, $tag)
         $url = common_local_url('subscriptions',
                                 array('nickname' => $user->nickname,
                                       'tag' => $tag));
-        return '<span class="tag"><a href="'.htmlspecialchars($url).'" rel="tag">'.$tag.'</a></span>';
+        $xs = new XMLStringer();
+        $xs->elementStart('span', 'tag');
+        $xs->element('a', array('href' => $url,
+                                'rel' => $tag),
+                     $tag);
+        $xs->elementEnd('span');
+        return $xs->getString();
     } else {
         return $tag;
     }
@@ -667,277 +705,20 @@ function common_relative_profile($sender, $nickname, $dt=null)
     return null;
 }
 
-function common_local_url($action, $args=null, $fragment=null)
+function common_local_url($action, $args=null, $params=null, $fragment=null)
 {
-    $url = null;
+    $r = Router::get();
+    $path = $r->build($action, $args, $params, $fragment);
+    if ($path) {
+    }
     if (common_config('site','fancy')) {
-        $url = common_fancy_url($action, $args);
+        $url = common_path(mb_substr($path, 1));
     } else {
-        $url = common_simple_url($action, $args);
-    }
-    if (!is_null($fragment)) {
-        $url .= '#'.$fragment;
+        $url = common_path('index.php'.$path);
     }
     return $url;
 }
 
-function common_fancy_url($action, $args=null)
-{
-    switch (strtolower($action)) {
-     case 'public':
-        if ($args && isset($args['page'])) {
-            return common_path('?page=' . $args['page']);
-        } else {
-            return common_path('');
-        }
-     case 'featured':
-        if ($args && isset($args['page'])) {
-            return common_path('featured?page=' . $args['page']);
-        } else {
-            return common_path('featured');
-        }
-     case 'favorited':
-        if ($args && isset($args['page'])) {
-            return common_path('favorited?page=' . $args['page']);
-        } else {
-            return common_path('favorited');
-        }
-     case 'publicrss':
-        return common_path('rss');
-     case 'publicatom':
-        return common_path("api/statuses/public_timeline.atom");
-     case 'publicxrds':
-        return common_path('xrds');
-     case 'tagrss':
-        return common_path('tag/' . $args['tag'] . '/rss');
-     case 'featuredrss':
-        return common_path('featuredrss');
-     case 'favoritedrss':
-        return common_path('favoritedrss');
-     case 'opensearch':
-        if ($args && $args['type']) {
-            return common_path('opensearch/'.$args['type']);
-        } else {
-            return common_path('opensearch/people');
-        }
-     case 'doc':
-        return common_path('doc/'.$args['title']);
-     case 'block':
-     case 'login':
-     case 'logout':
-     case 'subscribe':
-     case 'unsubscribe':
-     case 'invite':
-        return common_path('main/'.$action);
-     case 'tagother':
-        return common_path('main/tagother?id='.$args['id']);
-     case 'register':
-        if ($args && $args['code']) {
-            return common_path('main/register/'.$args['code']);
-        } else {
-            return common_path('main/register');
-        }
-     case 'remotesubscribe':
-        if ($args && $args['nickname']) {
-            return common_path('main/remote?nickname=' . $args['nickname']);
-        } else {
-            return common_path('main/remote');
-        }
-     case 'nudge':
-        return common_path($args['nickname'].'/nudge');
-     case 'openidlogin':
-        return common_path('main/openid');
-     case 'profilesettings':
-        return common_path('settings/profile');
-     case 'passwordsettings':
-        return common_path('settings/password');
-     case 'emailsettings':
-        return common_path('settings/email');
-     case 'openidsettings':
-        return common_path('settings/openid');
-     case 'smssettings':
-        return common_path('settings/sms');
-     case 'twittersettings':
-        return common_path('settings/twitter');
-     case 'othersettings':
-        return common_path('settings/other');
-     case 'deleteprofile':
-        return common_path('settings/delete');
-     case 'newnotice':
-        if ($args && $args['replyto']) {
-            return common_path('notice/new?replyto='.$args['replyto']);
-        } else {
-            return common_path('notice/new');
-        }
-     case 'shownotice':
-        return common_path('notice/'.$args['notice']);
-     case 'deletenotice':
-        if ($args && $args['notice']) {
-            return common_path('notice/delete/'.$args['notice']);
-        } else {
-            return common_path('notice/delete');
-        }
-     case 'microsummary':
-     case 'xrds':
-     case 'foaf':
-        return common_path($args['nickname'].'/'.$action);
-     case 'all':
-     case 'replies':
-     case 'inbox':
-     case 'outbox':
-        if ($args && isset($args['page'])) {
-            return common_path($args['nickname'].'/'.$action.'?page=' . $args['page']);
-        } else {
-            return common_path($args['nickname'].'/'.$action);
-        }
-     case 'subscriptions':
-     case 'subscribers':
-        $nickname = $args['nickname'];
-        unset($args['nickname']);
-        if (isset($args['tag'])) {
-            $tag = $args['tag'];
-            unset($args['tag']);
-        }
-        $params = http_build_query($args);
-        if ($params) {
-            return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '') . '?' . $params);
-        } else {
-            return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : ''));
-        }
-     case 'allrss':
-        return common_path($args['nickname'].'/all/rss');
-     case 'repliesrss':
-        return common_path($args['nickname'].'/replies/rss');
-     case 'userrss':
-        if (isset($args['limit']))
-          return common_path($args['nickname'].'/rss?limit=' . $args['limit']);
-        return common_path($args['nickname'].'/rss');
-     case 'showstream':
-        if ($args && isset($args['page'])) {
-            return common_path($args['nickname'].'?page=' . $args['page']);
-        } else {
-            return common_path($args['nickname']);
-        }
-
-     case 'usertimeline':
-        return common_path("api/statuses/user_timeline/".$args['nickname'].".atom");
-     case 'confirmaddress':
-        return common_path('main/confirmaddress/'.$args['code']);
-     case 'userbyid':
-        return common_path('user/'.$args['id']);
-     case 'recoverpassword':
-        $path = 'main/recoverpassword';
-        if ($args['code']) {
-            $path .= '/' . $args['code'];
-        }
-        return common_path($path);
-     case 'imsettings':
-        return common_path('settings/im');
-     case 'avatarsettings':
-        return common_path('settings/avatar');
-     case 'groupsearch':
-        return common_path('search/group' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'peoplesearch':
-        return common_path('search/people' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'noticesearch':
-        return common_path('search/notice' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'noticesearchrss':
-        return common_path('search/notice/rss' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'avatarbynickname':
-        return common_path($args['nickname'].'/avatar/'.$args['size']);
-     case 'tag':
-        $path = 'tag/' . $args['tag'];
-        unset($args['tag']);
-        return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'publictagcloud':
-        return common_path('tags');
-     case 'peopletag':
-        $path = 'peopletag/' . $args['tag'];
-        unset($args['tag']);
-        return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'tags':
-        return common_path('tags' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'favor':
-        return common_path('main/favor');
-     case 'disfavor':
-        return common_path('main/disfavor');
-     case 'showfavorites':
-        if ($args && isset($args['page'])) {
-            return common_path($args['nickname'].'/favorites?page=' . $args['page']);
-        } else {
-            return common_path($args['nickname'].'/favorites');
-        }
-     case 'favoritesrss':
-        return common_path($args['nickname'].'/favorites/rss');
-     case 'showmessage':
-        return common_path('message/' . $args['message']);
-     case 'newmessage':
-        return common_path('message/new' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'api':
-        // XXX: do fancy URLs for all the API methods
-        switch (strtolower($args['apiaction'])) {
-         case 'statuses':
-            switch (strtolower($args['method'])) {
-             case 'user_timeline.rss':
-                return common_path('api/statuses/user_timeline/'.$args['argument'].'.rss');
-             case 'user_timeline.atom':
-                return common_path('api/statuses/user_timeline/'.$args['argument'].'.atom');
-             case 'user_timeline.json':
-                return common_path('api/statuses/user_timeline/'.$args['argument'].'.json');
-             case 'user_timeline.xml':
-                return common_path('api/statuses/user_timeline/'.$args['argument'].'.xml');
-             default: return common_simple_url($action, $args);
-            }
-         default: return common_simple_url($action, $args);
-        }
-     case 'sup':
-        if ($args && isset($args['seconds'])) {
-            return common_path('main/sup?seconds='.$args['seconds']);
-        } else {
-            return common_path('main/sup');
-        }
-     case 'newgroup':
-        return common_path('group/new');
-     case 'showgroup':
-        return common_path('group/'.$args['nickname'] . (($args['page']) ? ('?page=' . $args['page']) : ''));
-     case 'editgroup':
-        return common_path('group/'.$args['nickname'].'/edit');
-     case 'joingroup':
-        return common_path('group/'.$args['nickname'].'/join');
-     case 'leavegroup':
-        return common_path('group/'.$args['nickname'].'/leave');
-     case 'groupbyid':
-        return common_path('group/'.$args['id'].'/id');
-     case 'grouprss':
-        return common_path('group/'.$args['nickname'].'/rss');
-     case 'groupmembers':
-        return common_path('group/'.$args['nickname'].'/members' . (($args['page']) ? ('?page=' . $args['page']) : ''));
-     case 'grouplogo':
-        return common_path('group/'.$args['nickname'].'/logo');
-     case 'usergroups':
-        $nickname = $args['nickname'];
-        unset($args['nickname']);
-        return common_path($nickname.'/groups' . (($args) ? ('?' . http_build_query($args)) : ''));
-     case 'groups':
-        return common_path('group' . (($args) ? ('?' . http_build_query($args)) : ''));
-     default:
-        return common_simple_url($action, $args);
-    }
-}
-
-function common_simple_url($action, $args=null)
-{
-    global $config;
-    /* XXX: pretty URLs */
-    $extra = '';
-    if ($args) {
-        foreach ($args as $key => $value) {
-            $extra .= "&${key}=${value}";
-        }
-    }
-    return common_path("index.php?action=${action}${extra}");
-}
-
 function common_path($relative)
 {
     global $config;
@@ -1046,24 +827,6 @@ function common_redirect($url, $code=307)
 
 function common_broadcast_notice($notice, $remote=false)
 {
-
-    // Check to see if notice should go to Twitter
-    $flink = Foreign_link::getByUserID($notice->profile_id, 1); // 1 == Twitter
-    if (($flink->noticesync & FOREIGN_NOTICE_SEND) == 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) ||
-            (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) {
-
-            $result = common_twitter_broadcast($notice, $flink);
-
-            if (!$result) {
-                common_debug('Unable to send notice: ' . $notice->id . ' to Twitter.', __FILE__);
-            }
-        }
-    }
-
     if (common_config('queue', 'enabled')) {
         // Do it later!
         return common_enqueue_notice($notice);
@@ -1072,73 +835,11 @@ function common_broadcast_notice($notice, $remote=false)
     }
 }
 
-function common_twitter_broadcast($notice, $flink)
-{
-    global $config;
-    $success = true;
-    $fuser = $flink->getForeignUser();
-    $twitter_user = $fuser->nickname;
-    $twitter_password = $flink->credentials;
-    $uri = 'http://www.twitter.com/statuses/update.json';
-
-    // XXX: Hack to get around PHP cURL's use of @ being a a meta character
-    $statustxt = preg_replace('/^@/', ' @', $notice->content);
-
-    $options = array(
-                     CURLOPT_USERPWD         => "$twitter_user:$twitter_password",
-                     CURLOPT_POST            => true,
-                     CURLOPT_POSTFIELDS        => array(
-                                                        'status'    => $statustxt,
-                                                        'source'    => $config['integration']['source']
-                                                        ),
-                     CURLOPT_RETURNTRANSFER    => true,
-                     CURLOPT_FAILONERROR        => true,
-                     CURLOPT_HEADER            => false,
-                     CURLOPT_FOLLOWLOCATION    => true,
-                     CURLOPT_USERAGENT        => "Laconica",
-                     CURLOPT_CONNECTTIMEOUT    => 120,  // XXX: Scary!!!! How long should this be?
-                     CURLOPT_TIMEOUT            => 120,
-
-                     # Twitter is strict about accepting invalid "Expect" headers
-                     CURLOPT_HTTPHEADER => array('Expect:')
-                     );
-
-    $ch = curl_init($uri);
-    curl_setopt_array($ch, $options);
-    $data = curl_exec($ch);
-    $errmsg = curl_error($ch);
-
-    if ($errmsg) {
-        common_debug("cURL error: $errmsg - trying to send notice for $twitter_user.",
-                     __FILE__);
-        $success = false;
-    }
-
-    curl_close($ch);
-
-    if (!$data) {
-        common_debug("No data returned by Twitter's API trying to send update for $twitter_user",
-                     __FILE__);
-        $success = false;
-    }
-
-    // Twitter should return a status
-    $status = json_decode($data);
-
-    if (!$status->id) {
-        common_debug("Unexpected data returned by Twitter API trying to send update for $twitter_user",
-                     __FILE__);
-        $success = false;
-    }
-
-    return $success;
-}
-
 // Stick the notice on the queue
 
 function common_enqueue_notice($notice)
 {
-    foreach (array('jabber', 'omb', 'sms', 'public') as $transport) {
+    foreach (array('jabber', 'omb', 'sms', 'public', 'twitter', 'facebook') as $transport) {
         $qi = new Queue_item();
         $qi->notice_id = $notice->id;
         $qi->transport = $transport;
@@ -1185,6 +886,15 @@ function common_real_broadcast($notice, $remote=false)
             common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
         }
     }
+    if ($success) {
+        $success = broadcast_twitter($notice);
+        if (!$success) {
+            common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id);
+        }
+    }
+
+    // XXX: Do a real-time FB broadcast here?
+
     // XXX: broadcast notices to other IM
     return $success;
 }
diff --git a/lib/xmlstringer.php b/lib/xmlstringer.php
new file mode 100644 (file)
index 0000000..951b13b
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Generator for in-memory XML
+ *
+ * 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  Output
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@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);
+}
+
+/**
+ * Create in-memory XML
+ *
+ * @category Output
+ * @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      Action
+ * @see      HTMLOutputter
+ */
+
+class XMLStringer extends XMLOutputter
+{
+    function __construct($indent=false)
+    {
+        $this->xw = new XMLWriter();
+        $this->xw->openMemory();
+        $this->xw->setIndent($indent);
+    }
+
+    function getString()
+    {
+        return $this->xw->outputMemory();
+    }
+
+    // utility for quickly creating XML-strings
+
+    static function estring($tag, $attrs=null, $content=null)
+    {
+        $xs = new XMLStringer();
+        $xs->element($tag, $attrs, $content);
+        return $xs->getString();
+    }
+}
\ No newline at end of file
diff --git a/plugins/BlogspamNetPlugin.php b/plugins/BlogspamNetPlugin.php
new file mode 100644 (file)
index 0000000..d9372bc
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to check submitted notices with blogspam.net
+ *
+ * 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 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);
+}
+
+define('BLOGSPAMNETPLUGIN_VERSION', '0.1');
+
+/**
+ * Plugin to check submitted notices with blogspam.net
+ *
+ * When new notices are saved, we check their text with blogspam.net (or
+ * a compatible service).
+ *
+ * Blogspam.net is supposed to catch blog comment spam, and I found that
+ * some of its tests (min/max size, bayesian match) gave a lot of false positives.
+ * So, I've turned those tests off by default. This may not get as many
+ * hits, but it's better than nothing.
+ *
+ * @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 BlogspamNetPlugin extends Plugin
+{
+    var $baseUrl = 'http://test.blogspam.net:8888/';
+
+    function __construct($url=null)
+    {
+        parent::__construct();
+        if ($url) {
+            $this->baseUrl = $url;
+        }
+    }
+
+    function onStartNoticeSave($notice)
+    {
+        $args = $this->testArgs($notice);
+        common_debug("Blogspamnet args = " . print_r($args, TRUE));
+        $request = xmlrpc_encode_request('testComment', array($args));
+        $context = stream_context_create(array('http' => array('method' => "POST",
+                                                               'header' =>
+                                                               "Content-Type: text/xml\r\n".
+                                                               "User-Agent: " . $this->userAgent(),
+                                                               'content' => $request)));
+        $file = file_get_contents($this->baseUrl, false, $context);
+        $response = xmlrpc_decode($file);
+        if (xmlrpc_is_fault($response)) {
+            throw new ServerException("$response[faultString] ($response[faultCode])", 500);
+        } else {
+            common_debug("Blogspamnet results = " . $response);
+            if (preg_match('/^ERROR(:(.*))?$/', $response, $match)) {
+                throw new ServerException(sprintf(_("Error from %s: %s"), $this->baseUrl, $match[2]), 500);
+            } else if (preg_match('/^SPAM(:(.*))?$/', $response, $match)) {
+                throw new ClientException(sprintf(_("Spam checker results: %s"), $match[2]), 400);
+            } else if (preg_match('/^OK$/', $response)) {
+                // don't do anything
+            } else {
+                throw new ServerException(sprintf(_("Unexpected response from %s: %s"), $this->baseUrl, $response), 500);
+            }
+        }
+        return true;
+    }
+
+    function testArgs($notice)
+    {
+        $args = array();
+        $args['comment'] = $notice->content;
+        $args['ip'] = $this->getClientIP();
+
+        if (isset($_SERVER) && array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
+            $args['agent'] = $_SERVER['HTTP_USER_AGENT'];
+        }
+
+        $profile = $notice->getProfile();
+
+        if ($profile && $profile->homepage) {
+            $args['link'] = $profile->homepage;
+        }
+
+        if ($profile && $profile->fullname) {
+            $args['name'] = $profile->fullname;
+        } else {
+            $args['name'] = $profile->nickname;
+        }
+
+        $args['site'] = common_root_url();
+        $args['version'] = $this->userAgent();
+
+        $args['options'] = "max-size=140,min-size=0,min-words=0,exclude=bayasian";
+
+        return $args;
+    }
+
+    function getClientIP()
+    {
+        if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+            // Note: order matters here; use proxy-forwarded stuff first
+            foreach (array('HTTP_X_FORWARDED_FOR', 'CLIENT-IP', 'REMOTE_ADDR') as $k) {
+                if (isset($_SERVER[$k])) {
+                    return $_SERVER[$k];
+                }
+            }
+        }
+        return '127.0.0.1';
+    }
+
+    function userAgent()
+    {
+        return 'BlogspamNetPlugin/'.BLOGSPAMNETPLUGIN_VERSION . ' Laconica/' . LACONICA_VERSION;
+    }
+}
index 87a70e31ea9acf9f90f25e5f43b8091869add2b1..1ecbb664e078352fbbe7285f590bc03c13af58c0 100644 (file)
@@ -37,7 +37,7 @@ if (!defined('LACONICA')) {
  * 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!
+ * Piwik (http://www.piwik.org/) instead!
  *
  * @category Plugin
  * @package  Laconica
diff --git a/scripts/facebookqueuehandler.php b/scripts/facebookqueuehandler.php
new file mode 100755 (executable)
index 0000000..c6859cb
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+    print "This script must be run from the command line\n";
+    exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/facebookutil.php');
+require_once(INSTALLDIR . '/lib/queuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class FacebookQueueHandler extends QueueHandler
+{
+    
+    function transport()
+    {
+        return 'facebook';
+    }
+    
+    function start()
+    {
+        $this->log(LOG_INFO, "INITIALIZE");
+        return true;
+    }
+
+    function handle_notice($notice)
+    {
+        return facebookBroadcastNotice($notice);
+    }
+    
+    function finish()
+    {
+    }
+
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+
+mb_internal_encoding('UTF-8');
+
+$id = ($argc > 1) ? $argv[1] : null;
+
+$handler = new FacebookQueueHandler($id);
+
+$handler->runOnce();
index 51a9bbd7573629d74803b446611783880b6f1ca0..39eb859bbad1f10e678d604cb87f08b42f6c5749 100755 (executable)
@@ -61,7 +61,8 @@ function standard_map()
                                     )
                               );
 
-    $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', 'privacy', 'source');
+    $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', 
+        'privacy', 'source', 'badge');
 
     foreach($docs as $title) {
         $standard_map_urls .= url(
index 685bd938fa289862898fd2357b7f0f32a29e452c..a3256966d5a27231dff79499eef69f92f6e01079 100755 (executable)
@@ -23,7 +23,8 @@
 DIR=`dirname $0`
 
 for f in xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php \
-         xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php; do
+         xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php \
+         twitterqueuehandler.php facebookqueuehandler.php; do
 
          echo -n "Starting $f...";
         php $DIR/$f
index 08e1d4714ff8643549864575fe5cbc333f0c4d5f..fd4406d415b35f766c200c5e3dc85cf2705edde5 100755 (executable)
@@ -24,7 +24,7 @@ SDIR=`dirname $0`
 DIR=`php $SDIR/getpiddir.php`
 
 for f in jabberhandler ombhandler publichandler smshandler \
-        xmppconfirmhandler xmppdaemon; do
+        xmppconfirmhandler xmppdaemon twitterhandler facebookhandler ; do
 
        FILES="$DIR/$f.*.pid"
        for ff in "$FILES" ; do
diff --git a/scripts/twitterqueuehandler.php b/scripts/twitterqueuehandler.php
new file mode 100755 (executable)
index 0000000..7da4f1e
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * 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/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+    print "This script must be run from the command line\n";
+    exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/twitter.php');
+require_once(INSTALLDIR . '/lib/queuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class TwitterQueueHandler extends QueueHandler
+{
+    
+    function transport()
+    {
+        return 'twitter';
+    }
+    
+    function start()
+    {
+        $this->log(LOG_INFO, "INITIALIZE");
+        return true;
+    }
+
+    function handle_notice($notice)
+    {
+        return broadcast_twitter($notice);
+    }
+    
+    function finish()
+    {
+    }
+
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+
+mb_internal_encoding('UTF-8');
+
+$id = ($argc > 1) ? $argv[1] : null;
+
+$handler = new TwitterQueueHandler($id);
+
+$handler->runOnce();
diff --git a/scripts/update_facebook.php b/scripts/update_facebook.php
deleted file mode 100755 (executable)
index 60e1041..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * 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/>.
- */
-
-# Abort if called from a web server
-if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
-    print "This script must be run from the command line\n";
-    exit();
-}
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
-
-require_once INSTALLDIR . '/lib/common.php';
-require_once INSTALLDIR . '/lib/facebookutil.php';
-
-// For storing the last run date-time
-$last_updated_file = INSTALLDIR . '/scripts/facebook_last_updated';
-
-// Lock file name
-$lock_file = INSTALLDIR . '/scripts/update_facebook.lock';
-
-// Make sure only one copy of the script is running at a time
-$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()) {
-
-    $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
-    $user = $flink->getUser();
-    $fbuid = $flink->foreign_id;
-    
-    if (!userCanUpdate($fbuid)) {
-        continue;
-    }
-
-    $prefix = $facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
-    $content = "$prefix $notice->content";
-    
-    if (($flink->noticesync & FOREIGN_NOTICE_SEND) == FOREIGN_NOTICE_SEND) {
-
-        // If it's not a reply, or if the user WANTS to send replies...
-        if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $content) ||
-            (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) {
-             
-                // Avoid a Loop
-                if ($notice->source != 'Facebook') {
-                    
-                    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 for Facebook since last run at " . 
-     date('r', $since) . "\n";
-}
-
-fclose($lock_file);
-exit(0);
-
-
-function userCanUpdate($fbuid) {
-    
-    global $facebook;
-
-    $result = false;
-    
-    try {
-        $result = $facebook->api_client->users_hasAppPermission('status_update', $fbuid);
-    } catch(FacebookRestClientException $e){
-        print_r($e);
-    }
-
-    return $result;
-}
-
-function getLastUpdated(){
-    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);
-}
-
index 01fe8914f1aae18bcc0874878b5507bc0d520864..ef3f8c63d862472d52d3af9e725e7c1262797ce9 100755 (executable)
@@ -208,6 +208,8 @@ class XMPPDaemon extends Daemon
     {
         if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
             return true;
+        } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
+            return true;
         } else {
             return false;
         }
index 3b72d00ceeb85442ea428e59a1d17dd820fd47a5..be124f43308ca3c91017d891abd194aaced93eb5 100644 (file)
@@ -22,13 +22,11 @@ line-height:1.65;
 position:relative;
 }
 h1,h2,h3,h4,h5,h6 {
-text-transform:uppercase;
 margin-bottom:7px;
 overflow:hidden;
 }
 h1 {
 font-size:1.4em;
-line-height:1;
 margin-bottom:18px;
 }
 h2 { font-size:1.3em; }
@@ -43,7 +41,6 @@ font-weight:bold;
 legend {
 font-weight:bold;
 font-size:1.3em;
-text-transform:uppercase;
 }
 input, textarea, select, option {
 padding:4px;
@@ -367,7 +364,6 @@ margin-right:4px;
 
 #wrap {
 margin:0 auto;
-width:71.714em;
 width:1003px;
 overflow:hidden;
 }
@@ -904,7 +900,7 @@ left:0;
 left:29px;
 }
 .notice-options .notice_delete {
-left:76px;
+right:0;
 }
 .notice-options .notice_reply dt {
 display:none;
diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css
new file mode 100644 (file)
index 0000000..3d0455a
--- /dev/null
@@ -0,0 +1,72 @@
+/** theme: base
+ *
+ * @package   Laconica
+ * @author    Meitar Moscovitz <meitar@maymay.net>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+/* Go linear. */
+#header,
+#header address,
+#site_nav_global_primary,
+#anon_notice,
+#site_nav_local_views .nav,
+#form_notice,
+#form_notice .form_data li,
+#core,
+#content_inner,
+#notices_primary,
+.notice,
+.notice .entry-title,
+.notice div.entry-content,
+.notice-options,
+.notice .notice-options a,
+.pagination,
+.pagination .nav,
+.aside .section { float: none; }
+
+.notice-options .notice_reply,
+.notice-options .notice_delete,
+.notice-options .form_favor,
+.notice-options .form_disfavor { position: static; }
+
+#form_notice,
+#anon_notice,
+#content_inner,
+#footer { width: auto; }
+
+/* And liquid. */
+#wrap { width: 95%; }
+
+/* Make things bigger on smaller screens. */
+body { font-size: 2em; }
+.notices { font-size: 1.5em; }
+
+#site_nav_global_primary, #site_nav_global_secondary { text-align: center; }
+
+.notice div.entry-content { margin-left: 0; }
+address { margin: 0; }
+
+#anon_notice, #footer { clear: left; font-size: .5em; }
+
+#form_notice textarea { width: 80%; height: 5em; }
+#form_notice .form_note { right: 20%; top: 6em; }
+#form_notice .form_actions input.submit { width: auto; }
+
+#content { padding: 18px 0; width: 100%; }
+#content h1, #page_notice, #content_inner { padding: 0 18px; }
+.notices .entry-title, .notices div.entry-content { width: 90%; }
+.notice .author .photo { height: 4.5em; width: 4.5em; } /* about double physical size; TODO: do this scaling better */
+.notice-options { position: absolute; top: 0; right: 0; padding-left: 7%; width: 3%; }
+.notice-options .notice_delete a { float: left; } /* Works, but feels like it shouldn't. */
+/* TODO: Make the icons of the notice options bigger. Probably with mobile-specific images. */
+.pagination .nav { overflow: auto; }
+
+#aside_primary { margin: 10px 0 0 0; border: none; padding: 0; width: 100%; }
+#popular_notices { float: none; width: auto; }
+/* Columns for supplemental info. */
+.aside .section { clear: none; padding: 9px; width: 45%; }
+#top_groups_by_post { float: left; }
+#featured_users { float: right; }
+#export_data { display: none; }
diff --git a/theme/base/css/modal.css b/theme/base/css/modal.css
new file mode 100644 (file)
index 0000000..985e4ad
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * SimpleModal Basic Modal Dialog
+ * http://www.ericmmartin.com/projects/simplemodal/
+ * http://code.google.com/p/simplemodal/
+ *
+ * Copyright (c) 2008 Eric Martin - http://ericmmartin.com
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Revision: $Id: basic.css 162 2008-12-01 23:36:58Z emartin24 $
+ *
+ */
+
+
+/* Overlay */
+#simplemodal-overlay {background-color:#000; cursor:wait;}
+
+/* Container */
+#simplemodal-container {height:240px; width:320px; background-color:#fff; border:3px solid #ccc;}
+#simplemodal-container a.modalCloseImg {background:url(../images/x.png) no-repeat; width:25px; height:29px; display:inline; z-index:3200; position:absolute; top:-15px; right:-18px; cursor:pointer;}
+#simplemodal-container #basicModalContent {padding:8px;}
diff --git a/theme/base/css/modal_ie.css b/theme/base/css/modal_ie.css
new file mode 100644 (file)
index 0000000..eab4637
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * SimpleModal Basic Modal Dialog
+ * http://www.ericmmartin.com/projects/simplemodal/
+ * http://code.google.com/p/simplemodal/
+ *
+ * Copyright (c) 2008 Eric Martin - http://ericmmartin.com
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Revision: $Id: basic_ie.css 162 2008-12-01 23:36:58Z emartin24 $
+ *
+ */
+
+/* IE 6 hacks*/
+#simplemodal-container a.modalCloseImg {background:none; right:-14px; width:22px; height:26px; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/x.png',sizingMethod='scale');}
diff --git a/theme/base/images/x.png b/theme/base/images/x.png
new file mode 100644 (file)
index 0000000..c11f7af
Binary files /dev/null and b/theme/base/images/x.png differ
index 335801385d0d31aa64f2fd0eebf48a6502226eb2..4998b3c988b48b553c1fe4de0df8425f5a5ca5ba 100644 (file)
@@ -31,3 +31,6 @@ cp -r ./default ./mytheme
 nano ./mytheme/css/display.css
 
 3. Search and replace a colour or a path to the background image of your choice.
+
+4. Set /config.php to load 'mytheme':
+$config['site']['theme'] = 'mytheme';