]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge commit 'refs/merge-requests/1900' of git://gitorious.org/statusnet/mainline...
authorBrion Vibber <brion@pobox.com>
Mon, 2 Nov 2009 18:16:06 +0000 (10:16 -0800)
committerBrion Vibber <brion@pobox.com>
Mon, 2 Nov 2009 18:16:06 +0000 (10:16 -0800)
115 files changed:
.gitignore
EVENTS.txt
README
actions/all.php
actions/apistatusesupdate.php
actions/bookmarklet.php [new file with mode: 0644]
actions/emailsettings.php
actions/getfile.php [new file with mode: 0644]
actions/invite.php
actions/newmessage.php
actions/newnotice.php
actions/othersettings.php
actions/profilesettings.php
actions/public.php
actions/publicxrds.php [new file with mode: 0644]
actions/register.php
actions/replies.php
actions/showfavorites.php
actions/showgroup.php
actions/shownotice.php
actions/showstream.php
actions/tag.php
actions/xrds.php
classes/File_redirection.php
classes/Location_namespace.php [new file with mode: 0644]
classes/Notice.php
classes/Profile.php
classes/User.php
classes/statusnet.ini
config.php.sample
db/location_namespace.sql [new file with mode: 0644]
db/statusnet.sql
doc-src/bookmarklet
extlib/Auth/OpenID.php
extlib/Auth/OpenID/BigMath.php
extlib/Auth/OpenID/Consumer.php
extlib/Auth/OpenID/Message.php
extlib/Auth/Yadis/HTTPFetcher.php
extlib/Auth/Yadis/ParanoidHTTPFetcher.php
extlib/Auth/Yadis/PlainHTTPFetcher.php
extlib/Auth/Yadis/XML.php
extlib/HTTP/Request2.php [new file with mode: 0644]
extlib/HTTP/Request2/Adapter.php [new file with mode: 0644]
extlib/HTTP/Request2/Adapter/Curl.php [new file with mode: 0644]
extlib/HTTP/Request2/Adapter/Mock.php [new file with mode: 0644]
extlib/HTTP/Request2/Adapter/Socket.php [new file with mode: 0644]
extlib/HTTP/Request2/Exception.php [new file with mode: 0644]
extlib/HTTP/Request2/MultipartBody.php [new file with mode: 0644]
extlib/HTTP/Request2/Observer/Log.php [new file with mode: 0644]
extlib/HTTP/Request2/Response.php [new file with mode: 0644]
extlib/Net/URL2.php
extlib/README [new file with mode: 0644]
htaccess.sample
index.php
install.php
js/util.js
lib/Shorturl_api.php
lib/command.php
lib/commandinterpreter.php
lib/common.php
lib/curlclient.php [deleted file]
lib/default.php
lib/httpclient.php
lib/imagefile.php
lib/jabber.php
lib/location.php [new file with mode: 0644]
lib/mediafile.php [new file with mode: 0644]
lib/messageform.php
lib/noticeform.php
lib/noticelist.php
lib/oauthclient.php
lib/ping.php
lib/router.php
lib/snapshot.php
lib/util.php
lib/xrdsoutputter.php [new file with mode: 0644]
plugins/BlogspamNetPlugin.php
plugins/Facebook/FacebookPlugin.php
plugins/Facebook/facebookaction.php
plugins/Facebook/facebookapp.css [new file with mode: 0644]
plugins/Facebook/facebookinvite.php
plugins/GeonamesPlugin.php [new file with mode: 0644]
plugins/LilUrl/LilUrlPlugin.php
plugins/LinkbackPlugin.php
plugins/OpenID/OpenIDPlugin.php
plugins/OpenID/User_openid_trustroot.php [new file with mode: 0644]
plugins/OpenID/finishopenidlogin.php
plugins/OpenID/openid.php
plugins/OpenID/openidserver.php [new file with mode: 0644]
plugins/OpenID/openidtrust.php [new file with mode: 0644]
plugins/OpenID/publicxrds.php [deleted file]
plugins/PubSubHubBub/PubSubHubBubPlugin.php
plugins/SimpleUrl/SimpleUrlPlugin.php
plugins/TemplatePlugin.php
plugins/TwitterBridge/README
plugins/TwitterBridge/TwitterBridgePlugin.php
plugins/TwitterBridge/daemons/synctwitterfriends.php
plugins/TwitterBridge/daemons/twitterstatusfetcher.php
plugins/TwitterBridge/twitter.php
plugins/TwitterBridge/twitterauthorization.php
plugins/TwitterBridge/twitterbasicauthclient.php
plugins/WikiHashtagsPlugin.php
scripts/enjitqueuehandler.php
scripts/maildaemon.php
tests/LocationTest.php [new file with mode: 0644]
tests/URLDetectionTest.php
theme/base/css/display.css
theme/base/css/facebookapp.css [deleted file]
theme/base/css/ie.css
theme/base/images/icons/icons-01.png
theme/base/images/icons/twotone/green/x.gif [new file with mode: 0644]
theme/default/css/display.css
theme/default/css/ie.css
theme/identica/css/display.css
theme/identica/css/ie.css

index 1cde3a6254ad0ce63fd1f8593caa4088184a2c8e..217622c84dcaacd1dc5b08d6d79aea460bbaf548 100644 (file)
@@ -24,4 +24,4 @@ config-*.php
 good-config.php
 lac08.log
 php.log
-
+.DS_Store
index 5d34a9e13b75ef7ab3730037af4277cfdbd67216..a8a77390f6c2ccfbcc45a55593d8c5df2b42445c 100644 (file)
@@ -129,6 +129,9 @@ StartSubGroupNav: Showing the subscriptions group nav menu
 EndSubGroupNav: At the end of the subscriptions group nav menu
 - $action: the current action
 
+StartInitializeRouter: Before the router instance has been initialized; good place to add routes
+- $m: the Net_URL_Mapper that has just been set up
+
 RouterInitialized: After the router instance has been initialized
 - $m: the Net_URL_Mapper that has just been set up
 
@@ -455,3 +458,19 @@ StartProfileListItemActionElements: Showing the profile list actions (prepend a
 
 EndProfileListItemActionElements: Showing profile list actions (append a button here)
 - $item: ProfileListItem widget
+
+StartUserXRDS: Start XRDS output (right after the opening XRDS tag)
+- $action: the current action
+- &$xrdsoutputter - XRDSOutputter object to write to
+
+EndUserXRDS: End XRDS output (right before the closing XRDS tag)
+- $action: the current action
+- &$xrdsoutputter - XRDSOutputter object to write to
+
+StartPublicXRDS: Start XRDS output (right after the opening XRDS tag)
+- $action: the current action
+- &$xrdsoutputter - XRDSOutputter object to write to
+
+EndPublicXRDS: End XRDS output (right before the closing XRDS tag)
+- $action: the current action
+- &$xrdsoutputter - XRDSOutputter object to write to
diff --git a/README b/README
index 03064ba184dc1abaa55a07581012450044c05f14..952c914fec371693ade5d84bd903b443863617bf 100644 (file)
--- a/README
+++ b/README
@@ -3,7 +3,7 @@ README
 ------
 
 StatusNet 0.8.2 ("Life and How to Live It")
-26 Aug 2009
+1 Nov 2009
 
 This is the README file for StatusNet (formerly Laconica), the Open
 Source microblogging platform. It includes installation instructions,
@@ -77,49 +77,80 @@ for additional terms.
 New this version
 ================
 
-This is a minor feature and bugfix release since version 0.8.0,
-released Jul 15 2009. Notable changes this version:
-
-- Laconica has been renamed StatusNet. With a few minor compatibility
-  exceptions, all references to "Laconica" in code, documentation
-  and comments were changed to "StatusNet".
-- A new plugin to support "infinite scroll".
-- A new plugin to support reCaptcha <http://recaptcha.net>.
-- Better logging of server errors.
-- Add an Openid-only mode for authentication.
-- 'lite' parameter for some Twitter API methods.
-- A new plugin to auto-complete nicknames for @-replies.
-- Configuration options to disable OpenID, SMS, Twitter, post-by-email, and IM.
-- Support for lighttpd <http://lighttpd.org/> using 404-based
-  rewrites.
-- Support for using Twitter's OAuth authentication as a client.
-- First version of the groups API.
-- Can configure a site-wide design, including background image and
-  colors.
-- Improved algorithm for replies and conversations, making
-  conversation trees more accurate and useful.
-- Add a script to create a simulation database for testing/debugging.
-- Sanitize HTML for OEmbed.
-- Improved queue management for DB-based queuing.
-- More complete URL detection.
-- Hashtags now support full Unicode character set.
-- Notice inboxes are now garbage-collected on a regular basis
-  at notice-write time.
-- PiwikAnalyticsPlugin updated for latest Piwik interface.
-- Attachment and notice pages can be embedded with OEmbed
-  <http://www.oembed.com>.
-- Failed authentication is logged.
-- PostgreSQL schema and support brought up-to-date with 0.8.x features.
-- The installer works with PostgreSQL as well as MySQL.
-- RSS 1.0 feeds use HTTP Basic authentication in private mode.
-- Many, many bug fixes, particularly with performance.
-- Better (=working) garbage collection for old sessions.
-- Better (=working) search queries.
-- Some cleanup of HTML output.
-- Better error handling when updating Facebook.
-- Considerably better performance when using replication for API
-  calls.
-- Initial unit tests.
+This is a minor feature and bugfix release since version 0.8.1,
+released Aug 26 2009. Notable changes this version:
+
+- New script for deleting user accounts. Not particularly safe or
+  community-friendly. Better for deleting abusive accounts than for
+  users who are 'retiring'.
+- Improved detection of URLs in notices, specifically for punctuation
+  chars like ~, :, $, _, -, +, !, @, and %.
+- Removed some extra <dl> semantic HTML code.
+- Correct error in status-network database ini file (having multiple
+  statusnet sites with a single codebase)
+- Fixed error output for Twitter posting failures.
+- Fixed bug in Twitter queue handler that requeued inapplicable
+  notices ad infinitum.
+- Improve FOAF output for remote users.
+- new commands to join and leave groups.
+- Fixed bug in which you cannot turn off importing friends timelines
+  flag.
+- Better error handling in Twitter posting.
+- Show oEmbed data for XHTML files as well as plain HTML.
+- Updated bug database link in README.
+- require HTML tidy extension.
+- add support for HTTP Basic Auth in PHP CGI or FastCGI (e.g. GoDaddy).
+- autofocus input to selected entry elements depending on page.
+- updated layout for filter-by-tag form.
+- better layout for inbox and outbox pages.
+- fix highlighting search terms in attributes of notice list elements.
+- Correctly handle errors in linkback plugin.
+- Updated biz theme.
+- Updated cloudy theme.
+- Don't match '::' as an IPv6 address.
+- Use the same decision logic for deciding whether to mark an
+  attachment as an enclosure in RSS or as a paperclip item in Web
+  output.
+- Fixed a bug in the Piwik plugin that hard-coded the site ID.
+- Add a param, inreplyto, to notice/new to allow an explicit response
+  to another notice.
+- Show username in subject of emails.
+- Check if avatar exists before trying to delete it.
+- Correctly add omb_version to response for request token in OMB.
+- Add a few more SMS carriers.
+- Add a few more notice sources.
+- Vary: header.
+- Improvements to the AutoCompletePlugin.
+- Check for 'dl' before using it.
+- Make it impossible to delete self-subscriptions via the API.
+- Fix pagination of tagged user pages.
+- Make PiwikAnalyticsPlugin work with addPlugin().
+- Removed trailing single space in user nicknames in notice lists.
+- Show context link if a notice starts a conversation.
+- blacklist all files and directories in install dir.
+- handle GoDaddy-style PATH_INFO, including script name.
+- add home_timeline synonym for friends_timeline.
+- Add a popup window for the realtime plugin.
+- Add some more streams for the realtime plugin.
+- Fix a bug that overwrote group creation timestamp on every edit.
+- Moved HTTP error code strings to a class variable.
+- The Twitter API now returns server errors in the correct format.
+- Reset the doctype for HTML output.
+- Fixed a number of notices.
+- Don't show search suggestions for private sites.
+- Some corrections to FBConnect nav overrides.
+- Slightly less database-intensive session management.
+- Updated name of software in installer script.
+- Include long-form attachment URLs if url-shortener is disabled.
+- Include updated localisations for Polish, Greek, Hebrew, Icelandic,
+  Norwegian, and Chinese.
+- Include upstream fixes to gettext.php.
+- Correct for regression in Facebook API for updates.
+- Ignore "Sent from my iPhone" (and similar) in mail updates.
+- Use the NICKNAME_FMT constant for detecting nicknames.
+- Check for site servername config'd.
+- Compatibility fix for empty status updates with Twitter API.
+- Option to show files privately (EXPERIMENTAL! Use with caution.)
 
 Prerequisites
 =============
@@ -225,9 +256,9 @@ especially if you've previously installed PHP/MySQL packages.
 1. Unpack the tarball you downloaded on your Web server. Usually a
    command like this will work:
 
-          tar zxf statusnet-0.8.1.tar.gz
+          tar zxf statusnet-0.8.2.tar.gz
 
-   ...which will make a statusnet-0.8.1 subdirectory in your current
+   ...which will make a statusnet-0.8.2 subdirectory in your current
    directory. (If you don't have shell access on your Web server, you
    may have to unpack the tarball on your local computer and FTP the
    files to the server.)
@@ -235,7 +266,7 @@ especially if you've previously installed PHP/MySQL packages.
 2. Move the tarball to a directory of your choosing in your Web root
    directory. Usually something like this will work:
 
-          mv statusnet-0.8.1 /var/www/mublog
+          mv statusnet-0.8.2 /var/www/mublog
 
    This will make your StatusNet instance available in the mublog path of
    your server, like "http://example.net/mublog". "microblog" or
@@ -656,6 +687,16 @@ private site, but users of the private site may be able to subscribe
 to users on a remote site. (Or not... it's not well tested.) The
 "proper behaviour" hasn't been defined here, so handle with care.
 
+If fancy URLs is enabled, access to file attachments can also be
+restricted to logged-in users only. Uncomment the appropriate rewrite
+<<<<<<< HEAD:README
+rule in .htaccess or your server's httpd.conf. (This most likely will
+not work if you are using a virtual server for attachments, so consider
+the performance/security tradeoff.)
+=======
+rule in .htaccess or your server's httpd.conf.
+>>>>>>> 446de62... Revert "Added some explanatory text to README":README
+
 Upgrading
 =========
 
@@ -669,7 +710,7 @@ with this situation.
 If you've been using StatusNet 0.7, 0.6, 0.5 or lower, or if you've
 been tracking the "git" version of the software, you will probably
 want to upgrade and keep your existing data. There is no automated
-upgrade procedure in StatusNet 0.8.1. Try these step-by-step
+upgrade procedure in StatusNet 0.8.2. Try these step-by-step
 instructions; read to the end first before trying them.
 
 0. Download StatusNet and set up all the prerequisites as if you were
@@ -690,7 +731,7 @@ instructions; read to the end first before trying them.
 5. Once all writing processes to your site are turned off, make a
    final backup of the Web directory and database.
 6. Move your StatusNet directory to a backup spot, like "mublog.bak".
-7. Unpack your StatusNet 0.8.1 tarball and move it to "mublog" or
+7. Unpack your StatusNet 0.8.2 tarball and move it to "mublog" or
    wherever your code used to be.
 8. Copy the config.php file and avatar directory from your old
    directory to your new directory.
@@ -1432,7 +1473,7 @@ repository (see below), and you get a compilation error ("unexpected
 T_STRING") in the browser, check to see that you don't have any
 conflicts in your code.
 
-If you upgraded to StatusNet 0.8.1 without reading the "Notice
+If you upgraded to StatusNet 0.8.2 without reading the "Notice
 inboxes" section above, and all your users' 'Personal' tabs are empty,
 read the "Notice inboxes" section above.
 
@@ -1540,6 +1581,7 @@ if anyone's been overlooked in error.
 * Jeffery To
 * Federico Marani
 * Craig Andrews
+* mEDI
 
 Thanks also to the developers of our upstream library code and to the
 thousands of people who have tried out Identi.ca, installed StatusNet,
index f1786462e161e9d55cbadbb806a3362ee09269cb..61cedce7490cd08ed76d2568d85426179044242e 100644 (file)
@@ -99,19 +99,17 @@ class AllAction extends ProfileAction
                 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_timeline',
-                        'argument' => $this->user->nickname.'.rss'
+                    'ApiTimelineFriends', array(
+                        'format' => 'rss',
+                        'id' => $this->user->nickname
                     )
                 ),
                 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_timeline',
-                        'argument' => $this->user->nickname.'.atom'
+                    'ApiTimelineFriends', array(
+                        'format' => 'atom',
+                        'id' => $this->user->nickname
                     )
                 ),
                 sprintf(_('Feed for friends of %s (Atom)'), $this->user->nickname))
index 0d71e1512828bf143c4b8c2cb864c822964694e4..898a4bd723cef157ad3e768096ac5c1134db62ce 100644 (file)
@@ -38,6 +38,7 @@ if (!defined('STATUSNET')) {
 }
 
 require_once INSTALLDIR . '/lib/apiauth.php';
+require_once INSTALLDIR . '/lib/mediafile.php';
 
 /**
  * Updates the authenticating user's status (posts a notice).
@@ -60,7 +61,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction
     var $source                = null;
     var $status                = null;
     var $in_reply_to_status_id = null;
-
     static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
 
     /**
@@ -76,25 +76,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
     {
         parent::prepare($args);
 
-        $this->user = $this->auth_user;
-
-        if (empty($this->user)) {
-            $this->clientError(_('No such user!'), 404, $this->format);
-            return false;
-        }
-
+        $this->user   = $this->auth_user;
         $this->status = $this->trimmed('status');
-
-        if (empty($this->status)) {
-            $this->clientError(
-                'Client must provide a \'status\' parameter with a value.',
-                400,
-                $this->format
-            );
-
-            return false;
-        }
-
         $this->source = $this->trimmed('source');
 
         if (empty($this->source) || in_array($source, $this->reserved_sources)) {
@@ -129,6 +112,27 @@ class ApiStatusesUpdateAction extends ApiAuthAction
             return;
         }
 
+        if (empty($this->status)) {
+            $this->clientError(
+                'Client must provide a \'status\' parameter with a value.',
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        // Workaround for PHP returning empty $_FILES when POST length > PHP settings
+
+        if (empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0)) {
+            $this->clientError(_('Unable to handle that much POST data!'));
+            return;
+        }
+
         $status_shortened = common_shorten_links($this->status);
 
         if (Notice::contentTooLong($status_shortened)) {
@@ -187,14 +191,40 @@ class ApiStatusesUpdateAction extends ApiAuthAction
                 }
             }
 
+            $upload = null;
+
+            try {
+                $upload = MediaFile::fromUpload('media', $this->user);
+            } catch (ClientException $ce) {
+                $this->clientError($ce->getMessage());
+                return;
+            }
+
+            if (isset($upload)) {
+                $status_shortened .= ' ' . $upload->shortUrl();
+
+                if (Notice::contentTooLong($status_shortened)) {
+                    $upload->delete();
+                    $msg = _(
+                        'Max notice size is %d chars, ' .
+                        'including attachment URL.'
+                    );
+                    $this->clientError(sprintf($msg, Notice::maxContent()));
+                }
+            }
+
             $this->notice = Notice::saveNew(
                 $this->user->id,
-                html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'),
+                html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8'),
                 $this->source,
                 1,
                 $reply_to
             );
 
+            if (isset($upload)) {
+                $upload->attachToNotice($this->notice);
+            }
+
             common_broadcast_notice($this->notice);
         }
 
diff --git a/actions/bookmarklet.php b/actions/bookmarklet.php
new file mode 100644 (file)
index 0000000..0603a74
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Handler for posting new notices
+ *
+ * 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  Bookmarklet
+ * @package   StatusNet
+ * @author    Sarven Capadisli <csarven@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/actions/newnotice.php';
+
+/**
+ * Action for posting a notice 
+ *
+ * @category Bookmarklet
+ * @package  StatusNet
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class BookmarkletAction extends NewnoticeAction
+{
+    function showTitle()
+    {
+        $this->element('title', null, _('Post to ').common_config('site', 'name'));
+    }
+
+    function showHeader()
+    {
+        $this->elementStart('div', array('id' => 'header'));
+        $this->elementStart('address');
+        $this->element('a', array('class' => 'url',
+                                  'href' => common_local_url('public')),
+                         '');
+        $this->elementEnd('address');
+        if (common_logged_in()) {
+            $this->showNoticeForm();
+        }
+        $this->elementEnd('div');
+    }
+
+    function showCore()
+    {
+    }
+
+    function showFooter()
+    {
+    }
+}
+
index 6eff06c0d667b66fc6056ed706a83a7141efb89a..67b991cdc8b3d09afffe2349c3f9d925d2719898 100644 (file)
@@ -326,7 +326,7 @@ class EmailsettingsAction extends AccountSettingsAction
             $this->showForm(_('Cannot normalize that email address'));
             return;
         }
-        if (!Validate::email($email, true)) {
+        if (!Validate::email($email, common_config('email', 'check_domain'))) {
             $this->showForm(_('Not a valid email address'));
             return;
         } else if ($user->email == $email) {
diff --git a/actions/getfile.php b/actions/getfile.php
new file mode 100644 (file)
index 0000000..ecda34c
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Returns a given file attachment, allowing private sites to only allow
+ * access to file attachments after login.
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   StatusNet
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once 'MIME/Type.php';
+
+/**
+ * Action for getting a file attachment
+ *
+ * @category Personal
+ * @package  StatusNet
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class GetfileAction extends Action
+{
+    /**
+     * Path of file to return
+     */
+
+    var $path = null;
+
+    /**
+     * Get file name
+     *
+     * @param array $args $_REQUEST array
+     *
+     * @return success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $filename = $this->trimmed('filename');
+        $path = null;
+
+        if ($filename) {
+            $path = common_config('attachments', 'dir') . $filename;
+        }
+
+        if (empty($path) or !file_exists($path)) {
+            $this->clientError(_('No such file.'), 404);
+            return false;
+        }
+        if (!is_readable($path)) {
+            $this->clientError(_('Cannot read file.'), 403);
+            return false;
+        }
+
+        $this->path = $path;
+        return true;
+    }
+
+    /**
+     * Is this page read-only?
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Last-modified date for file
+     *
+     * @return int last-modified date as unix timestamp
+     */
+
+    function lastModified()
+    {
+        return filemtime($this->path);
+    }
+
+    /**
+     * etag for file
+     *
+     * This returns the same data (inode, size, mtime) as Apache would,
+     * but in decimal instead of hex.
+     *
+     * @return string etag http header
+     */
+    function etag()
+    {
+        $stat = stat($this->path);
+        return '"' . $stat['ino'] . '-' . $stat['size'] . '-' . $stat['mtime'] . '"';
+    }
+
+    /**
+     * Handle input, produce output
+     *
+     * @param array $args $_REQUEST contents
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        // undo headers set by PHP sessions
+        $sec = session_cache_expire() * 60;
+        header('Expires: ' . date(DATE_RFC1123, time() + $sec));
+        header('Cache-Control: public, max-age=' . $sec);
+        header('Pragma: public');
+
+        parent::handle($args);
+
+        $path = $this->path;
+        header('Content-Type: ' . MIME_Type::autoDetect($path));
+        readfile($path);
+    }
+}
index 788130c5825e4989a9e7f93fc6baf0270fe184bd..3015202e9e69b39fcc59563bd16f42c34d0a727f 100644 (file)
@@ -68,7 +68,7 @@ class InviteAction extends CurrentUserDesignAction
 
         foreach ($addresses as $email) {
             $email = trim($email);
-            if (!Validate::email($email, true)) {
+            if (!Validate::email($email, common_config('email', 'check_domain'))) {
                 $this->showForm(sprintf(_('Invalid email address: %s'), $email));
                 return;
             }
index a0b17fc18a5ec315eac3c25b13c5c7f1c271dcb7..095a7d1d34bed2685ebff113064a2decf5853020 100644 (file)
@@ -99,7 +99,9 @@ class NewmessageAction extends Action
         $user = common_current_user();
 
         if (!$user) {
-            $this->clientError(_('Only logged-in users can send direct messages.'), 403);
+            /* Go log in, and then come back. */
+            common_set_returnto($_SERVER['REQUEST_URI']);
+            common_redirect(common_local_url('login'));
             return false;
         }
 
@@ -221,7 +223,21 @@ class NewmessageAction extends Action
         }
 
         $this->msg = $msg;
-        $this->showPage();
+        if ($this->trimmed('ajax')) {
+            header('Content-Type: text/xml;charset=utf-8');
+            $this->xw->startDocument('1.0', 'UTF-8');
+            $this->elementStart('html');
+            $this->elementStart('head');
+            $this->element('title', null, _('New message'));
+            $this->elementEnd('head');
+            $this->elementStart('body');
+            $this->showNoticeForm();
+            $this->elementEnd('body');
+            $this->endHTML();
+        }
+        else {
+            $this->showPage();
+        }
     }
 
     function showPageNotice()
index 9ee031f93636b0003f5ae6937c4bc9e892f64d48..fbd7ab6bce9c25da6514ed75ba476c44646edda0 100644 (file)
@@ -33,7 +33,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/noticelist.php';
+require_once INSTALLDIR . '/lib/noticelist.php';
+require_once INSTALLDIR . '/lib/mediafile.php';
 
 /**
  * Action for posting new notices
@@ -113,33 +114,6 @@ class NewnoticeAction extends Action
         }
     }
 
-    function getUploadedFileType() {
-        require_once 'MIME/Type.php';
-
-        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
-        $cmd = common_config('attachments', 'filecommand');
-
-        $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
-        if (in_array($filetype, common_config('attachments', 'supported'))) {
-            return $filetype;
-        }
-        $media = MIME_Type::getMedia($filetype);
-        if ('application' !== $media) {
-            $hint = sprintf(_(' Try using another %s format.'), $media);
-        } else {
-            $hint = '';
-        }
-        $this->clientError(sprintf(
-                                   _('%s is not a supported filetype on this server.'), $filetype) . $hint);
-    }
-
-    function isRespectsQuota($user) {
-        $file = new File;
-        $ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
-        if (true === $ret) return true;
-        $this->clientError($ret);
-    }
-
     /**
      * Save a new notice, based on arguments
      *
@@ -160,18 +134,12 @@ class NewnoticeAction extends Action
 
         if (!$content) {
             $this->clientError(_('No content!'));
-        } else {
-            $content_shortened = common_shorten_links($content);
-            if (Notice::contentTooLong($content_shortened)) {
-                $this->clientError(sprintf(_('That\'s too long. '.
-                                             'Max notice size is %d chars.'),
-                                           Notice::maxContent()));
-            }
+            return;
         }
 
         $inter = new CommandInterpreter();
 
-        $cmd = $inter->handle_command($user, $content_shortened);
+        $cmd = $inter->handle_command($user, $content);
 
         if ($cmd) {
             if ($this->boolean('ajax')) {
@@ -182,6 +150,13 @@ class NewnoticeAction extends Action
             return;
         }
 
+        $content_shortened = common_shorten_links($content);
+        if (Notice::contentTooLong($content_shortened)) {
+            $this->clientError(sprintf(_('That\'s too long. '.
+                                         'Max notice size is %d chars.'),
+                                       Notice::maxContent()));
+        }
+
         $replyto = $this->trimmed('inreplyto');
         #If an ID of 0 is wrongly passed here, it will cause a database error,
         #so override it...
@@ -189,84 +164,37 @@ class NewnoticeAction extends Action
             $replyto = 'false';
         }
 
-        if (isset($_FILES['attach']['error'])) {
-            switch ($_FILES['attach']['error']) {
-             case UPLOAD_ERR_NO_FILE:
-                // no file uploaded, nothing to do
-                break;
-
-             case UPLOAD_ERR_OK:
-                $mimetype = $this->getUploadedFileType();
-                if (!$this->isRespectsQuota($user)) {
-                    die('clientError() should trigger an exception before reaching here.');
-                }
-                break;
-
-             case UPLOAD_ERR_INI_SIZE:
-                $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
-
-             case UPLOAD_ERR_FORM_SIZE:
-                $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
+        $upload = null;
+        $upload = MediaFile::fromUpload('attach');
 
-             case UPLOAD_ERR_PARTIAL:
-                $this->clientError(_('The uploaded file was only partially uploaded.'));
+        if (isset($upload)) {
 
-             case  UPLOAD_ERR_NO_TMP_DIR:
-                $this->clientError(_('Missing a temporary folder.'));
-
-             case UPLOAD_ERR_CANT_WRITE:
-                $this->clientError(_('Failed to write file to disk.'));
-
-             case UPLOAD_ERR_EXTENSION:
-                $this->clientError(_('File upload stopped by extension.'));
-
-             default:
-                die('Should never reach here.');
-            }
-        }
-
-        if (isset($mimetype)) {
-            $filename = $this->saveFile($mimetype);
-            if (empty($filename)) {
-                $this->clientError(_('Couldn\'t save file.'));
-            }
-
-            $fileRecord = $this->storeFile($filename, $mimetype);
-
-            $fileurl = common_local_url('attachment',
-                                        array('attachment' => $fileRecord->id));
-
-            // not sure this is necessary -- Zach
-            $this->maybeAddRedir($fileRecord->id, $fileurl);
-
-            $short_fileurl = common_shorten_url($fileurl);
-            if (!$short_fileurl) {
-                // todo -- Consider forcing default shortener if none selected?
-                $short_fileurl = $fileurl;
-            }
-            $content_shortened .= ' ' . $short_fileurl;
+            $content_shortened .= ' ' . $upload->shortUrl();
 
             if (Notice::contentTooLong($content_shortened)) {
-                $this->deleteFile($filename);
-                $this->clientError(sprintf(_('Max notice size is %d chars, including attachment URL.'),
-                                           Notice::maxContent()));
+                $upload->delete();
+                $this->clientError(
+                    sprintf(
+                        _('Max notice size is %d chars, including attachment URL.'),
+                          Notice::maxContent()
+                    )
+                );
             }
-
-            // Also, not sure this is necessary -- Zach
-            $this->maybeAddRedir($fileRecord->id, $short_fileurl);
         }
 
         $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
                                   ($replyto == 'false') ? null : $replyto);
 
-        if (isset($mimetype)) {
-            $this->attachFile($notice, $fileRecord);
+        if (isset($upload)) {
+            $upload->attachToNotice($notice);
         }
 
         common_broadcast_notice($notice);
 
         if ($this->boolean('ajax')) {
-            $this->startHTML('text/xml;charset=utf-8');
+            header('Content-Type: text/xml;charset=utf-8');
+            $this->xw->startDocument('1.0', 'UTF-8');
+            $this->elementStart('html');
             $this->elementStart('head');
             $this->element('title', null, _('Notice posted'));
             $this->elementEnd('head');
@@ -288,87 +216,6 @@ class NewnoticeAction extends Action
         }
     }
 
-    function saveFile($mimetype) {
-
-        $cur = common_current_user();
-
-        if (empty($cur)) {
-            $this->serverError(_('Somehow lost the login in saveFile'));
-        }
-
-        $basename = basename($_FILES['attach']['name']);
-
-        $filename = File::filename($cur->getProfile(), $basename, $mimetype);
-
-        $filepath = File::path($filename);
-
-        if (move_uploaded_file($_FILES['attach']['tmp_name'], $filepath)) {
-            return $filename;
-        } else {
-            $this->clientError(_('File could not be moved to destination directory.'));
-        }
-    }
-
-    function deleteFile($filename)
-    {
-        $filepath = File::path($filename);
-        @unlink($filepath);
-    }
-
-    function storeFile($filename, $mimetype) {
-
-        $file = new File;
-        $file->filename = $filename;
-
-        $file->url = File::url($filename);
-
-        $filepath = File::path($filename);
-
-        $file->size = filesize($filepath);
-        $file->date = time();
-        $file->mimetype = $mimetype;
-
-        $file_id = $file->insert();
-
-        if (!$file_id) {
-            common_log_db_error($file, "INSERT", __FILE__);
-            $this->clientError(_('There was a database error while saving your file. Please try again.'));
-        }
-
-        return $file;
-    }
-
-    function rememberFile($file, $short)
-    {
-        $this->maybeAddRedir($file->id, $short);
-    }
-
-    function maybeAddRedir($file_id, $url)
-    {
-        $file_redir = File_redirection::staticGet('url', $url);
-
-        if (empty($file_redir)) {
-            $file_redir = new File_redirection;
-            $file_redir->url = $url;
-            $file_redir->file_id = $file_id;
-
-            $result = $file_redir->insert();
-
-            if (!$result) {
-                common_log_db_error($file_redir, "INSERT", __FILE__);
-                $this->clientError(_('There was a database error while saving your file. Please try again.'));
-            }
-        }
-    }
-
-    function attachFile($notice, $filerec)
-    {
-        File_to_post::processNew($filerec->id, $notice->id);
-
-        $this->maybeAddRedir($filerec->id,
-                             common_local_url('file', array('notice' => $notice->id)));
-    }
-
     /**
      * Show an Ajax-y error message
      *
index 011b4fc83832e7c39de789855ea44067318a871f..d32a2d651cb390b2d248064d43543f0a233dbb9f 100644 (file)
@@ -103,7 +103,7 @@ class OthersettingsAction extends AccountSettingsAction
             foreach($_shorteners as $name=>$value)
             {
                 $services[$name]=$name;
-                if($value['info']['freeService']){
+                if(!empty($value['info']['freeService'])){
                     // I18N
                     $services[$name].=' (free service)';
                 }
index 5445d9bb257ee0c513ead7c2799a1f11958cbe81..0a0cc59973a3709c272a93e74bf1e9176f46c5d5 100644 (file)
@@ -306,6 +306,16 @@ class ProfilesettingsAction extends AccountSettingsAction
             $profile->homepage = $homepage;
             $profile->bio = $bio;
             $profile->location = $location;
+
+            $loc = Location::fromName($location);
+
+            if (!empty($loc)) {
+                $profile->lat         = $loc->lat;
+                $profile->lon         = $loc->lon;
+                $profile->location_id = $loc->location_id;
+                $profile->location_ns = $loc->location_ns;
+            }
+
             $profile->profileurl = common_profile_url($nickname);
 
             common_debug('Old profile: ' . common_log_objstring($orig_profile), __FILE__);
index 73fad182a3f2ab71e7452f349637826dfb95d2d9..982dfde15700863f6fff8da111a7f04376fdb070 100644 (file)
@@ -131,6 +131,13 @@ class PublicAction extends Action
             return _('Public timeline');
         }
     }
+    
+    function extraHead()
+    {
+        parent::extraHead();
+        $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
+                                           'content' => common_local_url('publicxrds')));
+    }
 
     /**
      * Output <head> elements for RSS and Atom feeds
@@ -143,14 +150,12 @@ class PublicAction extends Action
         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')),
+                              common_local_url('ApiTimelinePublic',
+                                               array('format' => 'rss')),
                               _('Public Stream Feed (RSS 2.0)')),
                      new Feed(Feed::ATOM,
-                              common_local_url('api',
-                                               array('apiaction' => 'statuses',
-                                                     'method' => 'public_timeline.atom')),
+                              common_local_url('ApiTimelinePublic',
+                                               array('format' => 'atom')),
                               _('Public Stream Feed (Atom)')));
     }
 
diff --git a/actions/publicxrds.php b/actions/publicxrds.php
new file mode 100644 (file)
index 0000000..5fd4eea
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Public XRDS for OpenID
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
+require_once INSTALLDIR.'/lib/xrdsoutputter.php';
+
+/**
+ * Public XRDS
+ *
+ * @category Action
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@status.net>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @todo factor out similarities with XrdsAction
+ */
+class PublicxrdsAction extends Action
+{
+    /**
+     * Is read only?
+     *
+     * @return boolean true
+     */
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Class handler.
+     *
+     * @param array $args array of arguments
+     *
+     * @return nothing
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+        $xrdsOutputter = new XRDSOutputter();
+        $xrdsOutputter->startXRDS();
+        Event::handle('StartPublicXRDS', array($this,&$xrdsOutputter));
+        Event::handle('EndPublicXRDS', array($this,&$xrdsOutputter));
+        $xrdsOutputter->endXRDS();
+    }
+}
+
index 100ab74242c74a4244739a3fcc46a56ca937c027..a6c1a903aab33811e019176ae88d011aa79e056d 100644 (file)
@@ -191,7 +191,7 @@ class RegisterAction extends Action
             if (!$this->boolean('license')) {
                 $this->showForm(_('You can\'t register if you don\'t '.
                                   'agree to the license.'));
-            } else if ($email && !Validate::email($email, true)) {
+            } else if ($email && !Validate::email($email, common_config('email', 'check_domain'))) {
                 $this->showForm(_('Not a valid email address.'));
             } else if (!Validate::string($nickname, array('min_length' => 1,
                                                           'max_length' => 64,
index 6003ad30bde3e60b5a7fb5b9f953d20d1d634583..a13b5a22734934408c069ca26a4c8c00b1da5bf6 100644 (file)
@@ -138,11 +138,25 @@ class RepliesAction extends OwnerDesignAction
 
     function getFeeds()
     {
-        $rssurl   = common_local_url('repliesrss',
-                                     array('nickname' => $this->user->nickname));
-        $rsstitle = sprintf(_('Feed for replies to %s'), $this->user->nickname);
-
-        return array(new Feed(Feed::RSS1, $rssurl, $rsstitle));
+        return array(new Feed(Feed::RSS1,
+                              common_local_url('repliesrss',
+                                               array('nickname' => $this->user->nickname)),
+                              sprintf(_('Replies feed for %s (RSS 1.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::RSS2,
+                              common_local_url('ApiTimelineMentions',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => 'rss')),
+                              sprintf(_('Replies feed for %s (RSS 2.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::ATOM,
+                              common_local_url('ApiTimelineMentions',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => 'atom')),
+                              sprintf(_('Replies feed for %s (Atom)'),
+                                    $this->user->nickname)));
     }
 
     /**
index b96d2af37fc568a672132bba2a32280b488ecfc8..b12fcdd9a0c5e31e29da6db692ae99974d0fdc8a 100644 (file)
@@ -164,13 +164,25 @@ class ShowfavoritesAction extends OwnerDesignAction
 
     function getFeeds()
     {
-        $feedurl   = common_local_url('favoritesrss',
-                                      array('nickname' =>
-                                            $this->user->nickname));
-        $feedtitle = sprintf(_('Feed for favorites of %s'),
-                             $this->user->nickname);
-
-        return array(new Feed(Feed::RSS1, $feedurl, $feedtitle));
+        return array(new Feed(Feed::RSS1,
+                              common_local_url('favoritesrss',
+                                               array('nickname' => $this->user->nickname)),
+                              sprintf(_('Feed for favorites of %s (RSS 1.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::RSS2,
+                              common_local_url('ApiTimelineFavorites',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => 'rss')),
+                              sprintf(_('Feed for favorites of %s (RSS 2.0)'),
+                                      $this->user->nickname)),
+                     new Feed(Feed::ATOM,
+                              common_local_url('ApiTimelineFavorites',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => 'atom')),
+                              sprintf(_('Feed for favorites of %s (Atom)'),
+                                      $this->user->nickname)));
     }
 
     /**
index bfe45ddad79a8d8194b679f510c058d7c2b68b71..a4af29391d0f7a7a7c38f2d673c9b4b6fc1afb0e 100644 (file)
@@ -328,17 +328,15 @@ class ShowgroupAction extends GroupDesignAction
                               sprintf(_('Notice feed for %s group (RSS 1.0)'),
                                       $this->group->nickname)),
                      new Feed(Feed::RSS2,
-                              common_local_url('api',
-                                               array('apiaction' => 'groups',
-                                                     'method' => 'timeline',
-                                                     'argument' => $this->group->nickname.'.rss')),
+                              common_local_url('ApiTimelineGroup',
+                                               array('format' => 'rss',
+                                                     'id' => $this->group->nickname)),
                               sprintf(_('Notice feed for %s group (RSS 2.0)'),
                                       $this->group->nickname)),
                      new Feed(Feed::ATOM,
-                              common_local_url('api',
-                                               array('apiaction' => 'groups',
-                                                     'method' => 'timeline',
-                                                     'argument' => $this->group->nickname.'.atom')),
+                              common_local_url('ApiTimelineGroup',
+                                               array('format' => 'atom',
+                                                     'id' => $this->group->nickname)),
                               sprintf(_('Notice feed for %s group (Atom)'),
                                       $this->group->nickname)),
                      new Feed(Feed::FOAF,
index 41408c23ccb4a396d92d814366824a5871475ae5..5d16fdad9ed84a4706d759c04f9d681ab37ac760 100644 (file)
@@ -172,9 +172,9 @@ class ShownoticeAction extends OwnerDesignAction
     function title()
     {
         if (!empty($this->profile->fullname)) {
-            $base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
+            $base = $this->profile->fullname . ' (' . $this->profile->nickname . ') ';
         } else {
-            $base = $this->user->nickname;
+            $base = $this->profile->nickname;
         }
 
         return sprintf(_('%1$s\'s status on %2$s'),
index b3a9b1f05c75da9fbf3ef8969c3fc093cda63494..4f480603785231de454e51f3c8856fb22a4537ef 100644 (file)
@@ -128,17 +128,17 @@ class ShowstreamAction extends ProfileAction
                               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')),
+                              common_local_url('ApiTimelineUser',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => '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')),
+                              common_local_url('ApiTimelineUser',
+                                               array(
+                                                    'id' => $this->user->nickname,
+                                                    'format' => 'atom')),
                               sprintf(_('Notice feed for %s (Atom)'),
                                       $this->user->nickname)),
                      new Feed(Feed::FOAF,
@@ -348,6 +348,8 @@ class ShowstreamAction extends ProfileAction
     {
         if (Event::handle('StartProfilePageActionsSection', array(&$this, $this->profile))) {
 
+            $cur = common_current_user();
+
             $this->elementStart('div', 'entity_actions');
             $this->element('h2', null, _('User actions'));
             $this->elementStart('ul');
@@ -379,21 +381,21 @@ class ShowstreamAction extends ProfileAction
                         }
                         $this->elementEnd('li');
 
-                        if ($cur->mutuallySubscribed($user)) {
+                        if ($cur->mutuallySubscribed($this->user)) {
 
                             // message
 
                             $this->elementStart('li', 'entity_send-a-message');
-                            $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)),
+                            $this->element('a', array('href' => common_local_url('newmessage', array('to' => $this->user->id)),
                                                       'title' => _('Send a direct message to this user')),
                                            _('Message'));
                             $this->elementEnd('li');
 
                             // nudge
 
-                            if ($user->email && $user->emailnotifynudge) {
+                            if ($this->user->email && $this->user->emailnotifynudge) {
                                 $this->elementStart('li', 'entity_nudge');
-                                $nf = new NudgeForm($this, $user);
+                                $nf = new NudgeForm($this, $this->user);
                                 $nf->show();
                                 $this->elementEnd('li');
                             }
index f0ab30308cb3b5f2803b2e096189554677c3879f..3a88c1229dd217857bb386a787f66d89b14d436b 100644 (file)
@@ -86,17 +86,15 @@ class TagAction extends Action
                               sprintf(_('Notice feed for tag %s (RSS 1.0)'),
                                       $this->tag)),
                      new Feed(Feed::RSS2,   
-                              common_local_url('api',
-                                               array('apiaction' => 'tags',
-                                                     'method' => 'timeline',
-                                                     'argument' => $this->tag.'.rss')),
-                              sprintf(_('Notice feed for %s group (RSS 2.0)'),
+                              common_local_url('ApiTimelineTag',
+                                               array('format' => 'rss',
+                                                     'tag' => $this->tag)),
+                              sprintf(_('Notice feed for tag %s (RSS 2.0)'),
                                       $this->tag)),
                      new Feed(Feed::ATOM,
-                              common_local_url('api',
-                                               array('apiaction' => 'tags',
-                                                     'method' => 'timeline',
-                                                     'argument' => $this->tag.'.atom')),
+                              common_local_url('ApiTimelineTag',
+                                               array('format' => 'atom',
+                                                     'tag' => $this->tag)),
                               sprintf(_('Notice feed for tag %s (Atom)'),
                                       $this->tag)));
     }
index 8ba89fec0ff7c56a262ebaa29374ba9a8c0b8359..8f09557d185ce34ca3d2a566ddffc76860961f05 100644 (file)
@@ -36,6 +36,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 require_once INSTALLDIR.'/lib/omb.php';
 require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php';
+require_once INSTALLDIR.'/lib/xrdsoutputter.php';
 
 /**
  * XRDS for OpenMicroBlogging
@@ -49,6 +50,8 @@ require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php';
  */
 class XrdsAction extends Action
 {
+    var $user;
+
     /**
      * Is read only?
      *
@@ -58,60 +61,87 @@ class XrdsAction extends Action
     {
         return true;
     }
-
-    /**
-     * Class handler.
-     *
-     * @param array $args query arguments
-     *
-     * @return void
-     */
-    function handle($args)
+    
+    function prepare($args)
     {
-        parent::handle($args);
+        parent::prepare($args);
         $nickname = $this->trimmed('nickname');
-        $user     = User::staticGet('nickname', $nickname);
-        if (!$user) {
+        $this->user     = User::staticGet('nickname', $nickname);
+        if (!$this->user) {
             $this->clientError(_('No such user.'));
             return;
         }
-        $this->showXrds($user);
+        return true;
     }
 
     /**
-     * Show XRDS for a user.
+     * Class handler.
      *
-     * @param class $user XRDS for this user.
+     * @param array $args query arguments
      *
      * @return void
      */
-    function showXrds($user)
+    function handle($args)
     {
-        $srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri,
-                                        $user->getProfile()));
-        /* Use libomb’s default XRDS Writer. */
-        $xrds_writer = null;
-        $srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer);
-    }
-}
+        parent::handle($args);
+        $xrdsOutputter = new XRDSOutputter();
+        $xrdsOutputter->startXRDS();
 
-class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper
-{
-    protected $urls;
+        Event::handle('StartUserXRDS', array($this,&$xrdsOutputter));
 
-    public function __construct()
-    {
-        $this->urls = array(
-            OAUTH_ENDPOINT_REQUEST => 'requesttoken',
-            OAUTH_ENDPOINT_AUTHORIZE => 'userauthorization',
-            OAUTH_ENDPOINT_ACCESS => 'accesstoken',
-            OMB_ENDPOINT_POSTNOTICE => 'postnotice',
-            OMB_ENDPOINT_UPDATEPROFILE => 'updateprofile');
-    }
+        //oauth
+        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'oauth',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'));
+        $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
+        $xrdsOutputter->showXrdsService(OAUTH_ENDPOINT_REQUEST,
+                            common_local_url('requesttoken'),
+                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY, OAUTH_HMAC_SHA1));
+        $xrdsOutputter->showXrdsService( OAUTH_ENDPOINT_AUTHORIZE,
+                            common_local_url('userauthorization'),
+                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY, OAUTH_HMAC_SHA1),
+                            null,
+                            $this->user->getIdentifierURI());
+        $xrdsOutputter->showXrdsService(OAUTH_ENDPOINT_ACCESS,
+                            common_local_url('accesstoken'),
+                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY, OAUTH_HMAC_SHA1),
+                            null,
+                            $this->user->getIdentifierURI());
+        $xrdsOutputter->showXrdsService(OAUTH_ENDPOINT_RESOURCE,
+                            null,
+                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY, OAUTH_HMAC_SHA1),
+                            null,
+                            $this->user->getIdentifierURI());
+        $xrdsOutputter->elementEnd('XRD');
+        
+        //omb
+        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'oauth',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'));
+        $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
+        $xrdsOutputter->showXrdsService(OMB_ENDPOINT_POSTNOTICE,
+                            common_local_url('postnotice'));
+        $xrdsOutputter->showXrdsService(OMB_ENDPOINT_UPDATEPROFILE,
+                            common_local_url('updateprofile'));
+        $xrdsOutputter->elementEnd('XRD');
+        
+        //misc
+        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'oauth',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'));
+        $xrdsOutputter->showXrdsService(OAUTH_DISCOVERY,
+                            '#oauth');
+        $xrdsOutputter->showXrdsService(OMB_VERSION,
+                            '#omb');
+        $xrdsOutputter->elementEnd('XRD');
 
-    public function getURL($action)
-    {
-        return common_local_url($this->urls[$action]);
+        Event::handle('EndUserXRDS', array($this,&$xrdsOutputter));
+
+        $xrdsOutputter->endXRDS();
+        
     }
 }
 ?>
index 79052bf7d3e80ca6e5b9347ee2062e5e9cc8ad22..08a6e8d8beb56d0ccd75c51b9e58c32f57e6c18a 100644 (file)
@@ -47,18 +47,15 @@ class File_redirection extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function _commonCurl($url, $redirs) {
-        $curlh = curl_init();
-        curl_setopt($curlh, CURLOPT_URL, $url);
-        curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects
-        curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait
-        curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow
-        curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT);
-        curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
-        curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-        curl_setopt($curlh, CURLOPT_FILETIME, true);
-        curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output
-        return $curlh;
+    static function _commonHttp($url, $redirs) {
+        $request = new HTTPClient($url);
+        $request->setConfig(array(
+            'connect_timeout' => 10, // # seconds to wait
+            'max_redirs' => $redirs, // # max number of http redirections to follow
+            'follow_redirects' => true, // Follow redirects
+            'store_body' => false, // We won't need body content here.
+        ));
+        return $request;
     }
 
     function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
@@ -82,32 +79,39 @@ class File_redirection extends Memcached_DataObject
         if(strpos($short_url,'://') === false){
             return $short_url;
         }
-        $curlh = File_redirection::_commonCurl($short_url, $redirs);
-        // Don't include body in output
-        curl_setopt($curlh, CURLOPT_NOBODY, true);
-        curl_exec($curlh);
-        $info = curl_getinfo($curlh);
-        curl_close($curlh);
-
-        if (405 == $info['http_code']) {
-            $curlh = File_redirection::_commonCurl($short_url, $redirs);
-            curl_exec($curlh);
-            $info = curl_getinfo($curlh);
-            curl_close($curlh);
+        try {
+            $request = self::_commonHttp($short_url, $redirs);
+            // Don't include body in output
+            $request->setMethod(HTTP_Request2::METHOD_HEAD);
+            $response = $request->send();
+
+            if (405 == $response->getStatus()) {
+                // Server doesn't support HEAD method? Can this really happen?
+                // We'll try again as a GET and ignore the response data.
+                $request = self::_commonHttp($short_url, $redirs);
+                $response = $request->send();
+            }
+        } catch (Exception $e) {
+            // Invalid URL or failure to reach server
+            return $short_url;
         }
 
-        if (!empty($info['redirect_count']) && File::isProtected($info['url'])) {
-            return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true);
+        if ($response->getRedirectCount() && File::isProtected($response->getUrl())) {
+            // Bump back up the redirect chain until we find a non-protected URL
+            return self::_redirectWhere_imp($short_url, $response->getRedirectCount() - 1, true);
         }
 
-        $ret = array('code' => $info['http_code']
-                , 'redirects' => $info['redirect_count']
-                , 'url' => $info['url']);
+        $ret = array('code' => $response->getStatus()
+                , 'redirects' => $response->getRedirectCount()
+                , 'url' => $response->getUrl());
 
-        if (!empty($info['content_type'])) $ret['type'] = $info['content_type'];
+        $type = $response->getHeader('Content-Type');
+        if ($type) $ret['type'] = $type;
         if ($protected) $ret['protected'] = true;
-        if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length'];
-        if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime'];
+        $size = $response->getHeader('Content-Length'); // @fixme bytes?
+        if ($size) $ret['size'] = $size;
+        $time = $response->getHeader('Last-Modified');
+        if ($time) $ret['time'] = strtotime($time);
         return $ret;
     }
 
diff --git a/classes/Location_namespace.php b/classes/Location_namespace.php
new file mode 100644 (file)
index 0000000..5ab95d9
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for location_namespace
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Location_namespace extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'location_namespace';              // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $description;                     // varchar(255)
+    public $created;                         // datetime()   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) {
+        return Memcached_DataObject::staticGet('Location_namespace',$k,$v);
+    }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+}
index cd3906ca17fb2a7f201056495ac9647392eb3beb..a9dbaa461ba806593efbbeb40d4d6e3726aa1bc7 100644 (file)
@@ -66,9 +66,15 @@ class Notice extends Memcached_DataObject
     public $is_local;                        // tinyint(1)
     public $source;                          // varchar(32)
     public $conversation;                    // int(4)
+    public $lat;                             // decimal(10,7)
+    public $lon;                             // decimal(10,7)
+    public $location_id;                     // int(4)
+    public $location_ns;                     // int(4)
 
     /* 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
@@ -162,7 +168,8 @@ class Notice extends Memcached_DataObject
     }
 
     static function saveNew($profile_id, $content, $source=null,
-                            $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null) {
+                            $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null,
+                            $lat=null, $lon=null, $location_id=null, $location_ns=null) {
 
         $profile = Profile::staticGet($profile_id);
 
@@ -172,7 +179,7 @@ class Notice extends Memcached_DataObject
             throw new ClientException(_('Problem saving notice. Too long.'));
         }
 
-        if (!$profile) {
+        if (empty($profile)) {
             throw new ClientException(_('Problem saving notice. Unknown user.'));
         }
 
@@ -228,6 +235,26 @@ class Notice extends Memcached_DataObject
             $notice->conversation = $reply->conversation;
         }
 
+        if (!empty($lat) && !empty($lon)) {
+            $notice->lat = $lat;
+            $notice->lon = $lon;
+            $notice->location_id = $location_id;
+            $notice->location_ns = $location_ns;
+        } else if (!empty($location_ns) && !empty($location_id)) {
+            $location = Location::fromId($location_id, $location_ns);
+            if (!empty($location)) {
+                $notice->lat = $location->lat;
+                $notice->lon = $location->lon;
+                $notice->location_id = $location_id;
+                $notice->location_ns = $location_ns;
+            }
+        } else {
+            $notice->lat         = $profile->lat;
+            $notice->lon         = $profile->lon;
+            $notice->location_id = $profile->location_id;
+            $notice->location_ns = $profile->location_ns;
+        }
+
         if (Event::handle('StartNoticeSave', array(&$notice))) {
 
             // XXX: some of these functions write to the DB
@@ -269,7 +296,6 @@ class Notice extends Memcached_DataObject
 
             // XXX: do we need to change this for remote users?
 
-            $notice->saveReplies();
             $notice->saveTags();
 
             $notice->addToInboxes();
@@ -307,11 +333,11 @@ class Notice extends Memcached_DataObject
 
     static function checkDupes($profile_id, $content) {
         $profile = Profile::staticGet($profile_id);
-        if (!$profile) {
+        if (empty($profile)) {
             return false;
         }
         $notice = $profile->getNotices(0, NOTICE_CACHE_WINDOW);
-        if ($notice) {
+        if (!empty($notice)) {
             $last = 0;
             while ($notice->fetch()) {
                 if (time() - strtotime($notice->created) >= common_config('site', 'dupelimit')) {
@@ -337,7 +363,7 @@ class Notice extends Memcached_DataObject
 
     static function checkEditThrottle($profile_id) {
         $profile = Profile::staticGet($profile_id);
-        if (!$profile) {
+        if (empty($profile)) {
             return false;
         }
         # Get the Nth notice
@@ -658,7 +684,7 @@ class Notice extends Memcached_DataObject
 
         $cache = common_memcache();
 
-        if (!$cache) {
+        if (empty($cache)) {
             return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
         }
 
@@ -719,7 +745,7 @@ class Notice extends Memcached_DataObject
 
         # If there are no hits, just return the value
 
-        if (!$notice) {
+        if (empty($notice)) {
             return $notice;
         }
 
@@ -909,6 +935,18 @@ class Notice extends Memcached_DataObject
             }
         }
 
+        $recipients = $this->saveReplies();
+
+        foreach ($recipients as $recipient) {
+
+            if (!array_key_exists($recipient, $ni)) {
+                $recipientUser = User::staticGet('id', $recipient);
+                if (!empty($recipientUser)) {
+                    $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY;
+                }
+            }
+        }
+
         $cnt = 0;
 
         $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
@@ -1061,12 +1099,12 @@ class Notice extends Memcached_DataObject
         for ($i=0; $i<count($names); $i++) {
             $nickname = $names[$i];
             $recipient = common_relative_profile($sender, $nickname, $this->created);
-            if (!$recipient) {
+            if (empty($recipient)) {
                 continue;
             }
             // Don't save replies from blocked profile to local user
             $recipient_user = User::staticGet('id', $recipient->id);
-            if ($recipient_user && $recipient_user->hasBlocked($sender)) {
+            if (!empty($recipient_user) && $recipient_user->hasBlocked($sender)) {
                 continue;
             }
             $reply = new Reply();
@@ -1077,7 +1115,7 @@ class Notice extends Memcached_DataObject
                 $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
                 common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message);
                 common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message));
-                return;
+                return array();
             } else {
                 $replied[$recipient->id] = 1;
             }
@@ -1101,7 +1139,7 @@ class Notice extends Memcached_DataObject
                         $id = $reply->insert();
                         if (!$id) {
                             common_log_db_error($reply, 'INSERT', __FILE__);
-                            return;
+                            return array();
                         } else {
                             $replied[$recipient->id] = 1;
                         }
@@ -1110,12 +1148,16 @@ class Notice extends Memcached_DataObject
             }
         }
 
-        foreach (array_keys($replied) as $recipient) {
+        $recipientIds = array_keys($replied);
+
+        foreach ($recipientIds as $recipient) {
             $user = User::staticGet('id', $recipient);
             if ($user) {
                 mail_notify_attn($user, $this);
             }
         }
+
+        return $recipientIds;
     }
 
     function asAtomEntry($namespace=false, $source=false)
@@ -1139,10 +1181,9 @@ class Notice extends Memcached_DataObject
             $xs->element('link', array('href' => $profile->profileurl));
             $user = User::staticGet('id', $profile->id);
             if (!empty($user)) {
-                $atom_feed = common_local_url('api',
-                                              array('apiaction' => 'statuses',
-                                                    'method' => 'user_timeline',
-                                                    'argument' => $profile->nickname.'.atom'));
+                $atom_feed = common_local_url('ApiTimelineUser',
+                                              array('format' => 'atom',
+                                                    'id' => $profile->nickname));
                 $xs->element('link', array('rel' => 'self',
                                            'type' => 'application/atom+xml',
                                            'href' => $profile->profileurl));
@@ -1370,4 +1411,21 @@ class Notice extends Memcached_DataObject
         $contentlimit = self::maxContent();
         return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
     }
+
+    function getLocation()
+    {
+        $location = null;
+
+        if (!empty($this->location_id) && !empty($this->location_ns)) {
+            $location = Location::fromId($this->location_id, $this->location_ns);
+        }
+
+        if (is_null($location)) { // no ID, or Location::fromId() failed
+            if (!empty($this->lat) && !empty($this->lon)) {
+                $location = Location::fromLatLon($this->lat, $this->lon);
+            }
+        }
+
+        return $location;
+    }
 }
index a78a27f4a2b35e3e490d991b56e12b0f1c79d322..7c1e9db332cb420b970b866e2efa62e3fc54c56b 100644 (file)
@@ -37,11 +37,17 @@ class Profile extends Memcached_DataObject
     public $homepage;                        // varchar(255)  multiple_key
     public $bio;                             // text()  multiple_key
     public $location;                        // varchar(255)  multiple_key
+    public $lat;                             // decimal(10,7)
+    public $lon;                             // decimal(10,7)
+    public $location_id;                     // int(4)
+    public $location_ns;                     // int(4)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
     /* Static get */
-    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile',$k,$v); }
+    function staticGet($k,$v=NULL) {
+        return Memcached_DataObject::staticGet('Profile',$k,$v);
+    }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
@@ -556,4 +562,29 @@ class Profile extends Memcached_DataObject
         $block->blocked = $this->id;
         $block->delete();
     }
+
+    // XXX: identical to Notice::getLocation.
+
+    function getLocation()
+    {
+        $location = null;
+
+        if (!empty($this->location_id) && !empty($this->location_ns)) {
+            $location = Location::fromId($this->location_id, $this->location_ns);
+        }
+
+        if (is_null($location)) { // no ID, or Location::fromId() failed
+            if (!empty($this->lat) && !empty($this->lon)) {
+                $location = Location::fromLatLon($this->lat, $this->lon);
+            }
+        }
+
+        if (is_null($location)) { // still haven't found it!
+            if (!empty($this->location)) {
+                $location = Location::fromName($this->location);
+            }
+        }
+
+        return $location;
+    }
 }
index 0a70c9801402f8038d1dc59bb72bf0204ce1f3c7..3fa9cc15262420f9b6a05721eac472f90c2829b4 100644 (file)
@@ -198,6 +198,15 @@ class User extends Memcached_DataObject
         }
         if (!empty($location)) {
             $profile->location = $location;
+
+            $loc = Location::fromName($location);
+
+            if (!empty($loc)) {
+                $profile->lat         = $loc->lat;
+                $profile->lon         = $loc->lon;
+                $profile->location_id = $loc->location_id;
+                $profile->location_ns = $loc->location_ns;
+            }
         }
 
         $profile->created = common_sql_now();
@@ -319,6 +328,7 @@ class User extends Memcached_DataObject
                                                   common_config('site', 'name'),
                                                   $user->nickname),
                                           'system');
+                common_broadcast_notice($notice);
             }
         }
 
index 453981cd64f7ad34f1c74891b1e734330ad100e3..623790b100f60443b208bf6c12dcd1de57417924 100644 (file)
@@ -1,3 +1,4 @@
+
 [avatar]
 profile_id = 129
 original = 17
@@ -243,6 +244,15 @@ created = 142
 [invitation__keys]
 code = K
 
+[location_namespace]
+id = 129
+description = 2
+created = 142
+modified = 384
+
+[location_namespace__keys]
+id = K
+
 [message]
 id = 129
 uri = 2
@@ -284,6 +294,10 @@ reply_to = 1
 is_local = 17
 source = 2
 conversation = 1
+lat = 1
+lon = 1
+location_id = 1
+location_ns = 1
 
 [notice__keys]
 id = N
@@ -325,6 +339,10 @@ profileurl = 2
 homepage = 2
 bio = 34
 location = 2
+lat = 1
+lon = 1
+location_id = 1
+location_ns = 1
 created = 142
 modified = 384
 
@@ -519,6 +537,16 @@ modified = 384
 canonical = K
 display = U
 
+[user_openid_trustroot]
+trustroot = 130
+user_id = 129
+created = 142
+modified = 384
+
+[user_openid__keys]
+trustroot = K
+user_id = K
+
 [user_role]
 user_id = 129
 role = 130
index 997c9d6b0bccb971157c6cc50f5689fc17419f87..9fccb84f3b76f3816130759e74dac69b15d11c7f 100644 (file)
@@ -104,6 +104,10 @@ $config['sphinx']['port'] = 3312;
 // $config['site']['timezone'] = 'Pacific/Auckland';
 // $config['site']['language'] = 'en_NZ';
 
+// When validating user supplied email addresses, validate if the domain
+// is running an SMTP server.
+// $config['mail']['check_domain'] = true;
+
 // Email info, used for all outbound email
 // $config['mail']['notifyfrom'] = 'microblog@example.net';
 // $config['mail']['domain'] = 'microblog.example.net';
diff --git a/db/location_namespace.sql b/db/location_namespace.sql
new file mode 100644 (file)
index 0000000..59b2ce6
--- /dev/null
@@ -0,0 +1,5 @@
+insert into location_namespace
+    (id, description, created)
+values
+    (1, 'Geonames', now()),
+    (2, 'Where on Earth', now());
index dfcddb643c8b8004d1043c73053329c69c45852a..1524d83957c27815fd2f83986b2b8938f1a80d5a 100644 (file)
@@ -1,6 +1,7 @@
 /* local and remote users have profiles */
 
 create table profile (
+
     id integer auto_increment primary key comment 'unique identifier',
     nickname varchar(64) not null comment 'nickname or username',
     fullname varchar(255) comment 'display name',
@@ -8,6 +9,11 @@ create table profile (
     homepage varchar(255) comment 'identifying URL',
     bio text comment 'descriptive biography',
     location varchar(255) comment 'physical location',
+    lat decimal(10,7) comment 'latitude',
+    lon decimal(10,7) comment 'longitude',
+    location_id integer comment 'location id if possible',
+    location_ns integer comment 'namespace for location',
+
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
 
@@ -119,6 +125,10 @@ create table notice (
     is_local tinyint default 0 comment 'notice was generated by a user',
     source varchar(32) comment 'source of comment, like "web", "im", or "clientname"',
     conversation integer comment 'id of root notice in this conversation' references notice (id),
+    lat decimal(10,7) comment 'latitude',
+    lon decimal(10,7) comment 'longitude',
+    location_id integer comment 'location id if possible',
+    location_ns integer comment 'namespace for location',
 
     index notice_profile_id_idx (profile_id),
     index notice_conversation_idx (conversation),
@@ -556,3 +566,12 @@ create table user_role (
     constraint primary key (user_id, role)
 
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table location_namespace (
+
+    id integer primary key comment 'identity for this namespace',
+    description varchar(255) comment 'description of the namespace',
+    created datetime not null comment 'date the record was created',
+    modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
index e5ded770234bbbfcc3b2494375ef1622b1d10cb5..6ce23bd0b67d2429d4de25cd69d11219fe6beab3 100644 (file)
@@ -2,4 +2,4 @@ A bookmarklet is a small piece of javascript code used as a bookmark. This one w
 
 Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy.
 
-<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&amp;status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a>
+<a href="javascript:(function(){var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=bookmarklet',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22%20%E2%80%94%20'+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=450,height=200')){l.href=g;}}a();})()">Post to %%site.name%%</a>
index 6556b5b01e8bb43ef315991932b370ce30535bdf..db6164256f801412c6b2c23faebcec9673d39cef 100644 (file)
@@ -20,7 +20,7 @@
 /**
  * The library version string
  */
-define('Auth_OpenID_VERSION', '2.1.2');
+define('Auth_OpenID_VERSION', '2.1.3');
 
 /**
  * Require the fetcher code.
index b5fc627a096cee6247daaf495b2d9b1e29d86e61..45104947d6da44412671f6e319f0dcf817131893 100644 (file)
@@ -376,7 +376,7 @@ function Auth_OpenID_detectMathLibrary($exts)
         // Try to load dynamic modules.
         if (!$loaded) {
             foreach ($extension['modules'] as $module) {
-                if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($module . "." . PHP_SHLIB_SUFFIX)) {
+                if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
                     $loaded = true;
                     break;
                 }
index a72684c6b8b76fc19d779068f224a4d55f15b17b..500890b6568def082e94b3afdc28ac893cd3592c 100644 (file)
@@ -1295,7 +1295,8 @@ class Auth_OpenID_GenericConsumer {
             Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
                                                   array('response_nonce',
                                                         'claimed_id',
-                                                        'assoc_handle')),
+                                                        'assoc_handle',
+                                                        'op_endpoint')),
             Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
                                                   array('nonce'))
             );
index fd23e67a3c2c7c7939540a9eb94e427c651f8d79..5ab115a86e0670d15dd32a9c75be6ef907309565 100644 (file)
@@ -887,6 +887,11 @@ class Auth_OpenID_Message {
 
     function getAliasedArg($aliased_key, $default = null)
     {
+        if ($aliased_key == 'ns') {
+            // Return the namespace URI for the OpenID namespace
+            return $this->getOpenIDNamespace();
+        }
+
         $parts = explode('.', $aliased_key, 2);
 
         if (count($parts) != 2) {
index a1825403d67b9684b577035dd29f5e960df63a06..963b9a49a48f50495e561be4863957b61aa377a3 100644 (file)
@@ -138,7 +138,7 @@ class Auth_Yadis_HTTPFetcher {
      * pass the URLHasAllowedScheme check or if the server's response
      * is malformed.
      */
-    function get($url, $headers)
+    function get($url, $headers = null)
     {
         trigger_error("not implemented", E_USER_ERROR);
     }
index 8975d7f89ed6e5b035fb2e1db0eaa977dd68bfe0..6a418260eefebfa409768d5d750e1026457ff4dd 100644 (file)
@@ -127,8 +127,6 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
                         Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
             curl_setopt($c, CURLOPT_TIMEOUT, $off);
             curl_setopt($c, CURLOPT_URL, $url);
-            curl_setopt($c, CURLOPT_RANGE, 
-                        "0-".(1024 * Auth_OpenID_FETCHER_MAX_RESPONSE_KB));
 
             curl_exec($c);
 
index 8882e3cef7e7c3992d72d92d7e6104ad96affe2e..3e0ca2bb0c7302f5496244f4d9b36bd3fc1ce56a 100644 (file)
@@ -83,8 +83,6 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
                              "User-Agent: $user_agent",
                              "Host: ".$parts['host'].
                                 ($specify_port ? ":".$parts['port'] : ""),
-                             "Range: 0-".
-                                (1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB),
                              "Port: ".$parts['port']);
 
             $errno = 0;
index 7232d6cbdd85dbcfe140b0d70818aecf9645cdd2..81b2ce2210a2332a7a82b202b7e3c244542ab3f9 100644 (file)
@@ -91,7 +91,7 @@ class Auth_Yadis_XMLParser {
      * @return array $node_list An array of matching opaque node
      * objects to be used with other methods of this parser class.
      */
-    function evalXPath($xpath, $node = null)
+    function &evalXPath($xpath, $node = null)
     {
         // Not implemented.
     }
@@ -349,7 +349,7 @@ function &Auth_Yadis_getXMLParser()
     foreach ($extensions as $name => $params) {
         if (!extension_loaded($name)) {
             foreach ($params['libname'] as $libname) {
-                if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($libname)) {
+                if (@dl($libname)) {
                     $classname = $params['classname'];
                 }
             }
diff --git a/extlib/HTTP/Request2.php b/extlib/HTTP/Request2.php
new file mode 100644 (file)
index 0000000..e06bb86
--- /dev/null
@@ -0,0 +1,844 @@
+<?php\r
+/**\r
+ * Class representing a HTTP request\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * A class representing an URL as per RFC 3986.\r
+ */\r
+require_once 'Net/URL2.php';\r
+\r
+/**\r
+ * Exception class for HTTP_Request2 package\r
+ */ \r
+require_once 'HTTP/Request2/Exception.php';\r
+\r
+/**\r
+ * Class representing a HTTP request\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @version    Release: 0.4.1\r
+ * @link       http://tools.ietf.org/html/rfc2616#section-5\r
+ */\r
+class HTTP_Request2 implements SplSubject\r
+{\r
+   /**#@+\r
+    * Constants for HTTP request methods\r
+    *\r
+    * @link http://tools.ietf.org/html/rfc2616#section-5.1.1\r
+    */\r
+    const METHOD_OPTIONS = 'OPTIONS';\r
+    const METHOD_GET     = 'GET';\r
+    const METHOD_HEAD    = 'HEAD';\r
+    const METHOD_POST    = 'POST';\r
+    const METHOD_PUT     = 'PUT';\r
+    const METHOD_DELETE  = 'DELETE';\r
+    const METHOD_TRACE   = 'TRACE';\r
+    const METHOD_CONNECT = 'CONNECT';\r
+   /**#@-*/\r
+\r
+   /**#@+\r
+    * Constants for HTTP authentication schemes \r
+    *\r
+    * @link http://tools.ietf.org/html/rfc2617\r
+    */\r
+    const AUTH_BASIC  = 'basic';\r
+    const AUTH_DIGEST = 'digest';\r
+   /**#@-*/\r
+\r
+   /**\r
+    * Regular expression used to check for invalid symbols in RFC 2616 tokens\r
+    * @link http://pear.php.net/bugs/bug.php?id=15630\r
+    */\r
+    const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';\r
+\r
+   /**\r
+    * Regular expression used to check for invalid symbols in cookie strings\r
+    * @link http://pear.php.net/bugs/bug.php?id=15630\r
+    * @link http://cgi.netscape.com/newsref/std/cookie_spec.html\r
+    */\r
+    const REGEXP_INVALID_COOKIE = '/[\s,;]/';\r
+\r
+   /**\r
+    * Fileinfo magic database resource\r
+    * @var  resource\r
+    * @see  detectMimeType()\r
+    */\r
+    private static $_fileinfoDb;\r
+\r
+   /**\r
+    * Observers attached to the request (instances of SplObserver)\r
+    * @var  array\r
+    */\r
+    protected $observers = array();\r
+\r
+   /**\r
+    * Request URL\r
+    * @var  Net_URL2\r
+    */\r
+    protected $url;\r
+\r
+   /**\r
+    * Request method\r
+    * @var  string\r
+    */\r
+    protected $method = self::METHOD_GET;\r
+\r
+   /**\r
+    * Authentication data\r
+    * @var  array\r
+    * @see  getAuth()\r
+    */\r
+    protected $auth;\r
+\r
+   /**\r
+    * Request headers\r
+    * @var  array\r
+    */\r
+    protected $headers = array();\r
+\r
+   /**\r
+    * Configuration parameters\r
+    * @var  array\r
+    * @see  setConfig()\r
+    */\r
+    protected $config = array(\r
+        'adapter'           => 'HTTP_Request2_Adapter_Socket',\r
+        'connect_timeout'   => 10,\r
+        'timeout'           => 0,\r
+        'use_brackets'      => true,\r
+        'protocol_version'  => '1.1',\r
+        'buffer_size'       => 16384,\r
+        'store_body'        => true,\r
+\r
+        'proxy_host'        => '',\r
+        'proxy_port'        => '',\r
+        'proxy_user'        => '',\r
+        'proxy_password'    => '',\r
+        'proxy_auth_scheme' => self::AUTH_BASIC,\r
+\r
+        'ssl_verify_peer'   => true,\r
+        'ssl_verify_host'   => true,\r
+        'ssl_cafile'        => null,\r
+        'ssl_capath'        => null,\r
+        'ssl_local_cert'    => null,\r
+        'ssl_passphrase'    => null,\r
+\r
+        'digest_compat_ie'  => false\r
+    );\r
+\r
+   /**\r
+    * Last event in request / response handling, intended for observers\r
+    * @var  array\r
+    * @see  getLastEvent()\r
+    */\r
+    protected $lastEvent = array(\r
+        'name' => 'start',\r
+        'data' => null\r
+    );\r
+\r
+   /**\r
+    * Request body\r
+    * @var  string|resource\r
+    * @see  setBody()\r
+    */\r
+    protected $body = '';\r
+\r
+   /**\r
+    * Array of POST parameters\r
+    * @var  array\r
+    */\r
+    protected $postParams = array();\r
+\r
+   /**\r
+    * Array of file uploads (for multipart/form-data POST requests) \r
+    * @var  array\r
+    */\r
+    protected $uploads = array();\r
+\r
+   /**\r
+    * Adapter used to perform actual HTTP request\r
+    * @var  HTTP_Request2_Adapter\r
+    */\r
+    protected $adapter;\r
+\r
+\r
+   /**\r
+    * Constructor. Can set request URL, method and configuration array.\r
+    *\r
+    * Also sets a default value for User-Agent header. \r
+    *\r
+    * @param    string|Net_Url2     Request URL\r
+    * @param    string              Request method\r
+    * @param    array               Configuration for this Request instance\r
+    */\r
+    public function __construct($url = null, $method = self::METHOD_GET, array $config = array())\r
+    {\r
+        if (!empty($url)) {\r
+            $this->setUrl($url);\r
+        }\r
+        if (!empty($method)) {\r
+            $this->setMethod($method);\r
+        }\r
+        $this->setConfig($config);\r
+        $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' .\r
+                         '(http://pear.php.net/package/http_request2) ' .\r
+                         'PHP/' . phpversion());\r
+    }\r
+\r
+   /**\r
+    * Sets the URL for this request\r
+    *\r
+    * If the URL has userinfo part (username & password) these will be removed\r
+    * and converted to auth data. If the URL does not have a path component,\r
+    * that will be set to '/'.\r
+    *\r
+    * @param    string|Net_URL2 Request URL\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function setUrl($url)\r
+    {\r
+        if (is_string($url)) {\r
+            $url = new Net_URL2($url);\r
+        }\r
+        if (!$url instanceof Net_URL2) {\r
+            throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL');\r
+        }\r
+        // URL contains username / password?\r
+        if ($url->getUserinfo()) {\r
+            $username = $url->getUser();\r
+            $password = $url->getPassword();\r
+            $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');\r
+            $url->setUserinfo('');\r
+        }\r
+        if ('' == $url->getPath()) {\r
+            $url->setPath('/');\r
+        }\r
+        $this->url = $url;\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the request URL\r
+    *\r
+    * @return   Net_URL2\r
+    */\r
+    public function getUrl()\r
+    {\r
+        return $this->url;\r
+    }\r
+\r
+   /**\r
+    * Sets the request method\r
+    *\r
+    * @param    string\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception if the method name is invalid\r
+    */\r
+    public function setMethod($method)\r
+    {\r
+        // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1\r
+        if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {\r
+            throw new HTTP_Request2_Exception("Invalid request method '{$method}'");\r
+        }\r
+        $this->method = $method;\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the request method\r
+    *\r
+    * @return   string\r
+    */\r
+    public function getMethod()\r
+    {\r
+        return $this->method;\r
+    }\r
+\r
+   /**\r
+    * Sets the configuration parameter(s)\r
+    *\r
+    * The following parameters are available:\r
+    * <ul>\r
+    *   <li> 'adapter'           - adapter to use (string)</li>\r
+    *   <li> 'connect_timeout'   - Connection timeout in seconds (integer)</li>\r
+    *   <li> 'timeout'           - Total number of seconds a request can take.\r
+    *                              Use 0 for no limit, should be greater than \r
+    *                              'connect_timeout' if set (integer)</li>\r
+    *   <li> 'use_brackets'      - Whether to append [] to array variable names (bool)</li>\r
+    *   <li> 'protocol_version'  - HTTP Version to use, '1.0' or '1.1' (string)</li>\r
+    *   <li> 'buffer_size'       - Buffer size to use for reading and writing (int)</li>\r
+    *   <li> 'store_body'        - Whether to store response body in response object.\r
+    *                              Set to false if receiving a huge response and\r
+    *                              using an Observer to save it (boolean)</li>\r
+    *   <li> 'proxy_host'        - Proxy server host (string)</li>\r
+    *   <li> 'proxy_port'        - Proxy server port (integer)</li>\r
+    *   <li> 'proxy_user'        - Proxy auth username (string)</li>\r
+    *   <li> 'proxy_password'    - Proxy auth password (string)</li>\r
+    *   <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>\r
+    *   <li> 'ssl_verify_peer'   - Whether to verify peer's SSL certificate (bool)</li>\r
+    *   <li> 'ssl_verify_host'   - Whether to check that Common Name in SSL\r
+    *                              certificate matches host name (bool)</li>\r
+    *   <li> 'ssl_cafile'        - Cerificate Authority file to verify the peer\r
+    *                              with (use with 'ssl_verify_peer') (string)</li>\r
+    *   <li> 'ssl_capath'        - Directory holding multiple Certificate \r
+    *                              Authority files (string)</li>\r
+    *   <li> 'ssl_local_cert'    - Name of a file containing local cerificate (string)</li>\r
+    *   <li> 'ssl_passphrase'    - Passphrase with which local certificate\r
+    *                              was encoded (string)</li>\r
+    *   <li> 'digest_compat_ie'  - Whether to imitate behaviour of MSIE 5 and 6\r
+    *                              in using URL without query string in digest\r
+    *                              authentication (boolean)</li>\r
+    * </ul>\r
+    *\r
+    * @param    string|array    configuration parameter name or array\r
+    *                           ('parameter name' => 'parameter value')\r
+    * @param    mixed           parameter value if $nameOrConfig is not an array\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception If the parameter is unknown\r
+    */\r
+    public function setConfig($nameOrConfig, $value = null)\r
+    {\r
+        if (is_array($nameOrConfig)) {\r
+            foreach ($nameOrConfig as $name => $value) {\r
+                $this->setConfig($name, $value);\r
+            }\r
+\r
+        } else {\r
+            if (!array_key_exists($nameOrConfig, $this->config)) {\r
+                throw new HTTP_Request2_Exception(\r
+                    "Unknown configuration parameter '{$nameOrConfig}'"\r
+                );\r
+            }\r
+            $this->config[$nameOrConfig] = $value;\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the value(s) of the configuration parameter(s)\r
+    *\r
+    * @param    string  parameter name\r
+    * @return   mixed   value of $name parameter, array of all configuration \r
+    *                   parameters if $name is not given\r
+    * @throws   HTTP_Request2_Exception If the parameter is unknown\r
+    */\r
+    public function getConfig($name = null)\r
+    {\r
+        if (null === $name) {\r
+            return $this->config;\r
+        } elseif (!array_key_exists($name, $this->config)) {\r
+            throw new HTTP_Request2_Exception(\r
+                "Unknown configuration parameter '{$name}'"\r
+            );\r
+        }\r
+        return $this->config[$name];\r
+    }\r
+\r
+   /**\r
+    * Sets the autentification data\r
+    *\r
+    * @param    string  user name\r
+    * @param    string  password\r
+    * @param    string  authentication scheme\r
+    * @return   HTTP_Request2\r
+    */ \r
+    public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)\r
+    {\r
+        if (empty($user)) {\r
+            $this->auth = null;\r
+        } else {\r
+            $this->auth = array(\r
+                'user'     => (string)$user,\r
+                'password' => (string)$password,\r
+                'scheme'   => $scheme\r
+            );\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the authentication data\r
+    *\r
+    * The array has the keys 'user', 'password' and 'scheme', where 'scheme'\r
+    * is one of the HTTP_Request2::AUTH_* constants.\r
+    *\r
+    * @return   array\r
+    */\r
+    public function getAuth()\r
+    {\r
+        return $this->auth;\r
+    }\r
+\r
+   /**\r
+    * Sets request header(s)\r
+    *\r
+    * The first parameter may be either a full header string 'header: value' or\r
+    * header name. In the former case $value parameter is ignored, in the latter \r
+    * the header's value will either be set to $value or the header will be\r
+    * removed if $value is null. The first parameter can also be an array of\r
+    * headers, in that case method will be called recursively.\r
+    *\r
+    * Note that headers are treated case insensitively as per RFC 2616.\r
+    * \r
+    * <code>\r
+    * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'\r
+    * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'\r
+    * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'\r
+    * $req->setHeader('FOO'); // removes 'Foo' header from request\r
+    * </code>\r
+    *\r
+    * @param    string|array    header name, header string ('Header: value')\r
+    *                           or an array of headers\r
+    * @param    string|null     header value, header will be removed if null\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function setHeader($name, $value = null)\r
+    {\r
+        if (is_array($name)) {\r
+            foreach ($name as $k => $v) {\r
+                if (is_string($k)) {\r
+                    $this->setHeader($k, $v);\r
+                } else {\r
+                    $this->setHeader($v);\r
+                }\r
+            }\r
+        } else {\r
+            if (null === $value && strpos($name, ':')) {\r
+                list($name, $value) = array_map('trim', explode(':', $name, 2));\r
+            }\r
+            // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2\r
+            if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {\r
+                throw new HTTP_Request2_Exception("Invalid header name '{$name}'");\r
+            }\r
+            // Header names are case insensitive anyway\r
+            $name = strtolower($name);\r
+            if (null === $value) {\r
+                unset($this->headers[$name]);\r
+            } else {\r
+                $this->headers[$name] = $value;\r
+            }\r
+        }\r
+        \r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the request headers\r
+    *\r
+    * The array is of the form ('header name' => 'header value'), header names\r
+    * are lowercased\r
+    *\r
+    * @return   array\r
+    */\r
+    public function getHeaders()\r
+    {\r
+        return $this->headers;\r
+    }\r
+\r
+   /**\r
+    * Appends a cookie to "Cookie:" header\r
+    *\r
+    * @param    string  cookie name\r
+    * @param    string  cookie value\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function addCookie($name, $value)\r
+    {\r
+        $cookie = $name . '=' . $value;\r
+        if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {\r
+            throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'");\r
+        }\r
+        $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';\r
+        $this->setHeader('cookie', $cookies . $cookie);\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Sets the request body\r
+    *\r
+    * @param    string  Either a string with the body or filename containing body\r
+    * @param    bool    Whether first parameter is a filename\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function setBody($body, $isFilename = false)\r
+    {\r
+        if (!$isFilename) {\r
+            $this->body = (string)$body;\r
+        } else {\r
+            if (!($fp = @fopen($body, 'rb'))) {\r
+                throw new HTTP_Request2_Exception("Cannot open file {$body}");\r
+            }\r
+            $this->body = $fp;\r
+            if (empty($this->headers['content-type'])) {\r
+                $this->setHeader('content-type', self::detectMimeType($body));\r
+            }\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Returns the request body\r
+    *\r
+    * @return   string|resource|HTTP_Request2_MultipartBody\r
+    */\r
+    public function getBody()\r
+    {\r
+        if (self::METHOD_POST == $this->method && \r
+            (!empty($this->postParams) || !empty($this->uploads))\r
+        ) {\r
+            if ('application/x-www-form-urlencoded' == $this->headers['content-type']) {\r
+                $body = http_build_query($this->postParams, '', '&');\r
+                if (!$this->getConfig('use_brackets')) {\r
+                    $body = preg_replace('/%5B\d+%5D=/', '=', $body);\r
+                }\r
+                // support RFC 3986 by not encoding '~' symbol (request #15368)\r
+                return str_replace('%7E', '~', $body);\r
+\r
+            } elseif ('multipart/form-data' == $this->headers['content-type']) {\r
+                require_once 'HTTP/Request2/MultipartBody.php';\r
+                return new HTTP_Request2_MultipartBody(\r
+                    $this->postParams, $this->uploads, $this->getConfig('use_brackets')\r
+                );\r
+            }\r
+        }\r
+        return $this->body;\r
+    }\r
+\r
+   /**\r
+    * Adds a file to form-based file upload\r
+    *\r
+    * Used to emulate file upload via a HTML form. The method also sets\r
+    * Content-Type of HTTP request to 'multipart/form-data'.\r
+    *\r
+    * If you just want to send the contents of a file as the body of HTTP\r
+    * request you should use setBody() method.\r
+    *\r
+    * @param    string  name of file-upload field\r
+    * @param    mixed   full name of local file\r
+    * @param    string  filename to send in the request \r
+    * @param    string  content-type of file being uploaded\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function addUpload($fieldName, $filename, $sendFilename = null,\r
+                              $contentType = null)\r
+    {\r
+        if (!is_array($filename)) {\r
+            if (!($fp = @fopen($filename, 'rb'))) {\r
+                throw new HTTP_Request2_Exception("Cannot open file {$filename}");\r
+            }\r
+            $this->uploads[$fieldName] = array(\r
+                'fp'        => $fp,\r
+                'filename'  => empty($sendFilename)? basename($filename): $sendFilename,\r
+                'size'      => filesize($filename),\r
+                'type'      => empty($contentType)? self::detectMimeType($filename): $contentType\r
+            );\r
+        } else {\r
+            $fps = $names = $sizes = $types = array();\r
+            foreach ($filename as $f) {\r
+                if (!is_array($f)) {\r
+                    $f = array($f);\r
+                }\r
+                if (!($fp = @fopen($f[0], 'rb'))) {\r
+                    throw new HTTP_Request2_Exception("Cannot open file {$f[0]}");\r
+                }\r
+                $fps[]   = $fp;\r
+                $names[] = empty($f[1])? basename($f[0]): $f[1];\r
+                $sizes[] = filesize($f[0]);\r
+                $types[] = empty($f[2])? self::detectMimeType($f[0]): $f[2];\r
+            }\r
+            $this->uploads[$fieldName] = array(\r
+                'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types\r
+            );\r
+        }\r
+        if (empty($this->headers['content-type']) ||\r
+            'application/x-www-form-urlencoded' == $this->headers['content-type']\r
+        ) {\r
+            $this->setHeader('content-type', 'multipart/form-data');\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Adds POST parameter(s) to the request.\r
+    *\r
+    * @param    string|array    parameter name or array ('name' => 'value')\r
+    * @param    mixed           parameter value (can be an array)\r
+    * @return   HTTP_Request2\r
+    */\r
+    public function addPostParameter($name, $value = null)\r
+    {\r
+        if (!is_array($name)) {\r
+            $this->postParams[$name] = $value;\r
+        } else {\r
+            foreach ($name as $k => $v) {\r
+                $this->addPostParameter($k, $v);\r
+            }\r
+        }\r
+        if (empty($this->headers['content-type'])) {\r
+            $this->setHeader('content-type', 'application/x-www-form-urlencoded');\r
+        }\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Attaches a new observer\r
+    *\r
+    * @param    SplObserver\r
+    */\r
+    public function attach(SplObserver $observer)\r
+    {\r
+        foreach ($this->observers as $attached) {\r
+            if ($attached === $observer) {\r
+                return;\r
+            }\r
+        }\r
+        $this->observers[] = $observer;\r
+    }\r
+\r
+   /**\r
+    * Detaches an existing observer\r
+    *\r
+    * @param    SplObserver\r
+    */\r
+    public function detach(SplObserver $observer)\r
+    {\r
+        foreach ($this->observers as $key => $attached) {\r
+            if ($attached === $observer) {\r
+                unset($this->observers[$key]);\r
+                return;\r
+            }\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Notifies all observers\r
+    */\r
+    public function notify()\r
+    {\r
+        foreach ($this->observers as $observer) {\r
+            $observer->update($this);\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Sets the last event\r
+    *\r
+    * Adapters should use this method to set the current state of the request\r
+    * and notify the observers.\r
+    *\r
+    * @param    string  event name\r
+    * @param    mixed   event data\r
+    */\r
+    public function setLastEvent($name, $data = null)\r
+    {\r
+        $this->lastEvent = array(\r
+            'name' => $name,\r
+            'data' => $data\r
+        );\r
+        $this->notify();\r
+    }\r
+\r
+   /**\r
+    * Returns the last event\r
+    *\r
+    * Observers should use this method to access the last change in request.\r
+    * The following event names are possible:\r
+    * <ul>\r
+    *   <li>'connect'                 - after connection to remote server,\r
+    *                                   data is the destination (string)</li>\r
+    *   <li>'disconnect'              - after disconnection from server</li>\r
+    *   <li>'sentHeaders'             - after sending the request headers,\r
+    *                                   data is the headers sent (string)</li>\r
+    *   <li>'sentBodyPart'            - after sending a part of the request body, \r
+    *                                   data is the length of that part (int)</li>\r
+    *   <li>'receivedHeaders'         - after receiving the response headers,\r
+    *                                   data is HTTP_Request2_Response object</li>\r
+    *   <li>'receivedBodyPart'        - after receiving a part of the response\r
+    *                                   body, data is that part (string)</li>\r
+    *   <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still\r
+    *                                   encoded by Content-Encoding</li>\r
+    *   <li>'receivedBody'            - after receiving the complete response\r
+    *                                   body, data is HTTP_Request2_Response object</li>\r
+    * </ul>\r
+    * Different adapters may not send all the event types. Mock adapter does\r
+    * not send any events to the observers.\r
+    *\r
+    * @return   array   The array has two keys: 'name' and 'data'\r
+    */\r
+    public function getLastEvent()\r
+    {\r
+        return $this->lastEvent;\r
+    }\r
+\r
+   /**\r
+    * Sets the adapter used to actually perform the request\r
+    *\r
+    * You can pass either an instance of a class implementing HTTP_Request2_Adapter\r
+    * or a class name. The method will only try to include a file if the class\r
+    * name starts with HTTP_Request2_Adapter_, it will also try to prepend this\r
+    * prefix to the class name if it doesn't contain any underscores, so that\r
+    * <code>\r
+    * $request->setAdapter('curl');\r
+    * </code>\r
+    * will work.\r
+    *\r
+    * @param    string|HTTP_Request2_Adapter\r
+    * @return   HTTP_Request2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function setAdapter($adapter)\r
+    {\r
+        if (is_string($adapter)) {\r
+            if (!class_exists($adapter, false)) {\r
+                if (false === strpos($adapter, '_')) {\r
+                    $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);\r
+                }\r
+                if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) {\r
+                    include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';\r
+                }\r
+                if (!class_exists($adapter, false)) {\r
+                    throw new HTTP_Request2_Exception("Class {$adapter} not found");\r
+                }\r
+            }\r
+            $adapter = new $adapter;\r
+        }\r
+        if (!$adapter instanceof HTTP_Request2_Adapter) {\r
+            throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter');\r
+        }\r
+        $this->adapter = $adapter;\r
+\r
+        return $this;\r
+    }\r
+\r
+   /**\r
+    * Sends the request and returns the response\r
+    *\r
+    * @throws   HTTP_Request2_Exception\r
+    * @return   HTTP_Request2_Response\r
+    */\r
+    public function send()\r
+    {\r
+        // Sanity check for URL\r
+        if (!$this->url instanceof Net_URL2) {\r
+            throw new HTTP_Request2_Exception('No URL given');\r
+        } elseif (!$this->url->isAbsolute()) {\r
+            throw new HTTP_Request2_Exception('Absolute URL required');\r
+        } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) {\r
+            throw new HTTP_Request2_Exception('Not a HTTP URL');\r
+        }\r
+        if (empty($this->adapter)) {\r
+            $this->setAdapter($this->getConfig('adapter'));\r
+        }\r
+        // magic_quotes_runtime may break file uploads and chunked response\r
+        // processing; see bug #4543\r
+        if ($magicQuotes = ini_get('magic_quotes_runtime')) {\r
+            ini_set('magic_quotes_runtime', false);\r
+        }\r
+        // force using single byte encoding if mbstring extension overloads\r
+        // strlen() and substr(); see bug #1781, bug #10605\r
+        if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {\r
+            $oldEncoding = mb_internal_encoding();\r
+            mb_internal_encoding('iso-8859-1');\r
+        }\r
+\r
+        try {\r
+            $response = $this->adapter->sendRequest($this);\r
+        } catch (Exception $e) {\r
+        }\r
+        // cleanup in either case (poor man's "finally" clause)\r
+        if ($magicQuotes) {\r
+            ini_set('magic_quotes_runtime', true);\r
+        }\r
+        if (!empty($oldEncoding)) {\r
+            mb_internal_encoding($oldEncoding);\r
+        }\r
+        // rethrow the exception\r
+        if (!empty($e)) {\r
+            throw $e;\r
+        }\r
+        return $response;\r
+    }\r
+\r
+   /**\r
+    * Tries to detect MIME type of a file\r
+    *\r
+    * The method will try to use fileinfo extension if it is available,\r
+    * deprecated mime_content_type() function in the other case. If neither\r
+    * works, default 'application/octet-stream' MIME type is returned\r
+    *\r
+    * @param    string  filename\r
+    * @return   string  file MIME type\r
+    */\r
+    protected static function detectMimeType($filename)\r
+    {\r
+        // finfo extension from PECL available \r
+        if (function_exists('finfo_open')) {\r
+            if (!isset(self::$_fileinfoDb)) {\r
+                self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);\r
+            }\r
+            if (self::$_fileinfoDb) { \r
+                $info = finfo_file(self::$_fileinfoDb, $filename);\r
+            }\r
+        }\r
+        // (deprecated) mime_content_type function available\r
+        if (empty($info) && function_exists('mime_content_type')) {\r
+            return mime_content_type($filename);\r
+        }\r
+        return empty($info)? 'application/octet-stream': $info;\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/extlib/HTTP/Request2/Adapter.php b/extlib/HTTP/Request2/Adapter.php
new file mode 100644 (file)
index 0000000..39b092b
--- /dev/null
@@ -0,0 +1,152 @@
+<?php\r
+/**\r
+ * Base class for HTTP_Request2 adapters\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Adapter.php 274684 2009-01-26 23:07:27Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Class representing a HTTP response\r
+ */\r
+require_once 'HTTP/Request2/Response.php';\r
+\r
+/**\r
+ * Base class for HTTP_Request2 adapters\r
+ *\r
+ * HTTP_Request2 class itself only defines methods for aggregating the request\r
+ * data, all actual work of sending the request to the remote server and \r
+ * receiving its response is performed by adapters.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @version    Release: 0.4.1\r
+ */\r
+abstract class HTTP_Request2_Adapter\r
+{\r
+   /**\r
+    * A list of methods that MUST NOT have a request body, per RFC 2616\r
+    * @var  array\r
+    */\r
+    protected static $bodyDisallowed = array('TRACE');\r
+\r
+   /**\r
+    * Methods having defined semantics for request body\r
+    *\r
+    * Content-Length header (indicating that the body follows, section 4.3 of\r
+    * RFC 2616) will be sent for these methods even if no body was added\r
+    *\r
+    * @var  array\r
+    * @link http://pear.php.net/bugs/bug.php?id=12900\r
+    * @link http://pear.php.net/bugs/bug.php?id=14740\r
+    */\r
+    protected static $bodyRequired = array('POST', 'PUT');\r
+\r
+   /**\r
+    * Request being sent\r
+    * @var  HTTP_Request2\r
+    */\r
+    protected $request;\r
+\r
+   /**\r
+    * Request body\r
+    * @var  string|resource|HTTP_Request2_MultipartBody\r
+    * @see  HTTP_Request2::getBody()\r
+    */\r
+    protected $requestBody;\r
+\r
+   /**\r
+    * Length of the request body\r
+    * @var  integer\r
+    */\r
+    protected $contentLength;\r
+\r
+   /**\r
+    * Sends request to the remote server and returns its response\r
+    *\r
+    * @param    HTTP_Request2\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    abstract public function sendRequest(HTTP_Request2 $request);\r
+\r
+   /**\r
+    * Calculates length of the request body, adds proper headers\r
+    *\r
+    * @param    array   associative array of request headers, this method will \r
+    *                   add proper 'Content-Length' and 'Content-Type' headers \r
+    *                   to this array (or remove them if not needed)\r
+    */\r
+    protected function calculateRequestLength(&$headers)\r
+    {\r
+        $this->requestBody = $this->request->getBody();\r
+\r
+        if (is_string($this->requestBody)) {\r
+            $this->contentLength = strlen($this->requestBody);\r
+        } elseif (is_resource($this->requestBody)) {\r
+            $stat = fstat($this->requestBody);\r
+            $this->contentLength = $stat['size'];\r
+            rewind($this->requestBody);\r
+        } else {\r
+            $this->contentLength = $this->requestBody->getLength();\r
+            $headers['content-type'] = 'multipart/form-data; boundary=' .\r
+                                       $this->requestBody->getBoundary();\r
+            $this->requestBody->rewind();\r
+        }\r
+\r
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||\r
+            0 == $this->contentLength\r
+        ) {\r
+            unset($headers['content-type']);\r
+            // No body: send a Content-Length header nonetheless (request #12900),\r
+            // but do that only for methods that require a body (bug #14740)\r
+            if (in_array($this->request->getMethod(), self::$bodyRequired)) {\r
+                $headers['content-length'] = 0;\r
+            } else {\r
+                unset($headers['content-length']);\r
+            }\r
+        } else {\r
+            if (empty($headers['content-type'])) {\r
+                $headers['content-type'] = 'application/x-www-form-urlencoded';\r
+            }\r
+            $headers['content-length'] = $this->contentLength;\r
+        }\r
+    }\r
+}\r
+?>\r
diff --git a/extlib/HTTP/Request2/Adapter/Curl.php b/extlib/HTTP/Request2/Adapter/Curl.php
new file mode 100644 (file)
index 0000000..4d4de0d
--- /dev/null
@@ -0,0 +1,383 @@
+<?php\r
+/**\r
+ * Adapter for HTTP_Request2 wrapping around cURL extension\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Curl.php 278226 2009-04-03 21:32:48Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Base class for HTTP_Request2 adapters\r
+ */\r
+require_once 'HTTP/Request2/Adapter.php';\r
+\r
+/**\r
+ * Adapter for HTTP_Request2 wrapping around cURL extension\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request2\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @version     Release: 0.4.1\r
+ */\r
+class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter\r
+{\r
+   /**\r
+    * Mapping of header names to cURL options\r
+    * @var  array\r
+    */\r
+    protected static $headerMap = array(\r
+        'accept-encoding' => CURLOPT_ENCODING,\r
+        'cookie'          => CURLOPT_COOKIE,\r
+        'referer'         => CURLOPT_REFERER,\r
+        'user-agent'      => CURLOPT_USERAGENT\r
+    );\r
+\r
+   /**\r
+    * Mapping of SSL context options to cURL options\r
+    * @var  array\r
+    */\r
+    protected static $sslContextMap = array(\r
+        'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,\r
+        'ssl_cafile'      => CURLOPT_CAINFO,\r
+        'ssl_capath'      => CURLOPT_CAPATH,\r
+        'ssl_local_cert'  => CURLOPT_SSLCERT,\r
+        'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD\r
+   );\r
+\r
+   /**\r
+    * Response being received\r
+    * @var  HTTP_Request2_Response\r
+    */\r
+    protected $response;\r
+\r
+   /**\r
+    * Whether 'sentHeaders' event was sent to observers\r
+    * @var  boolean\r
+    */\r
+    protected $eventSentHeaders = false;\r
+\r
+   /**\r
+    * Whether 'receivedHeaders' event was sent to observers\r
+    * @var boolean\r
+    */\r
+    protected $eventReceivedHeaders = false;\r
+\r
+   /**\r
+    * Position within request body\r
+    * @var  integer\r
+    * @see  callbackReadBody()\r
+    */\r
+    protected $position = 0;\r
+\r
+   /**\r
+    * Information about last transfer, as returned by curl_getinfo()\r
+    * @var  array\r
+    */\r
+    protected $lastInfo;\r
+\r
+   /**\r
+    * Sends request to the remote server and returns its response\r
+    *\r
+    * @param    HTTP_Request2\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function sendRequest(HTTP_Request2 $request)\r
+    {\r
+        if (!extension_loaded('curl')) {\r
+            throw new HTTP_Request2_Exception('cURL extension not available');\r
+        }\r
+\r
+        $this->request              = $request;\r
+        $this->response             = null;\r
+        $this->position             = 0;\r
+        $this->eventSentHeaders     = false;\r
+        $this->eventReceivedHeaders = false;\r
+\r
+        try {\r
+            if (false === curl_exec($ch = $this->createCurlHandle())) {\r
+                $errorMessage = 'Error sending request: #' . curl_errno($ch) .\r
+                                                       ' ' . curl_error($ch);\r
+            }\r
+        } catch (Exception $e) {\r
+        }\r
+        $this->lastInfo = curl_getinfo($ch);\r
+        curl_close($ch);\r
+\r
+        if (!empty($e)) {\r
+            throw $e;\r
+        } elseif (!empty($errorMessage)) {\r
+            throw new HTTP_Request2_Exception($errorMessage);\r
+        }\r
+\r
+        if (0 < $this->lastInfo['size_download']) {\r
+            $this->request->setLastEvent('receivedBody', $this->response);\r
+        }\r
+        return $this->response;\r
+    }\r
+\r
+   /**\r
+    * Returns information about last transfer\r
+    *\r
+    * @return   array   associative array as returned by curl_getinfo()\r
+    */\r
+    public function getInfo()\r
+    {\r
+        return $this->lastInfo;\r
+    }\r
+\r
+   /**\r
+    * Creates a new cURL handle and populates it with data from the request\r
+    *\r
+    * @return   resource    a cURL handle, as created by curl_init()\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function createCurlHandle()\r
+    {\r
+        $ch = curl_init();\r
+\r
+        curl_setopt_array($ch, array(\r
+            // setup callbacks\r
+            CURLOPT_READFUNCTION   => array($this, 'callbackReadBody'),\r
+            CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),\r
+            CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),\r
+            // disallow redirects\r
+            CURLOPT_FOLLOWLOCATION => false,\r
+            // buffer size\r
+            CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),\r
+            // connection timeout\r
+            CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),\r
+            // save full outgoing headers, in case someone is interested\r
+            CURLINFO_HEADER_OUT    => true,\r
+            // request url\r
+            CURLOPT_URL            => $this->request->getUrl()->getUrl()\r
+        ));\r
+\r
+        // request timeout\r
+        if ($timeout = $this->request->getConfig('timeout')) {\r
+            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\r
+        }\r
+\r
+        // set HTTP version\r
+        switch ($this->request->getConfig('protocol_version')) {\r
+            case '1.0':\r
+                curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);\r
+                break;\r
+            case '1.1':\r
+                curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);\r
+        }\r
+\r
+        // set request method\r
+        switch ($this->request->getMethod()) {\r
+            case HTTP_Request2::METHOD_GET:\r
+                curl_setopt($ch, CURLOPT_HTTPGET, true);\r
+                break;\r
+            case HTTP_Request2::METHOD_POST:\r
+                curl_setopt($ch, CURLOPT_POST, true);\r
+                break;\r
+            default:\r
+                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());\r
+        }\r
+\r
+        // set proxy, if needed\r
+        if ($host = $this->request->getConfig('proxy_host')) {\r
+            if (!($port = $this->request->getConfig('proxy_port'))) {\r
+                throw new HTTP_Request2_Exception('Proxy port not provided');\r
+            }\r
+            curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);\r
+            if ($user = $this->request->getConfig('proxy_user')) {\r
+                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' .\r
+                            $this->request->getConfig('proxy_password'));\r
+                switch ($this->request->getConfig('proxy_auth_scheme')) {\r
+                    case HTTP_Request2::AUTH_BASIC:\r
+                        curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);\r
+                        break;\r
+                    case HTTP_Request2::AUTH_DIGEST:\r
+                        curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);\r
+                }\r
+            }\r
+        }\r
+\r
+        // set authentication data\r
+        if ($auth = $this->request->getAuth()) {\r
+            curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);\r
+            switch ($auth['scheme']) {\r
+                case HTTP_Request2::AUTH_BASIC:\r
+                    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\r
+                    break;\r
+                case HTTP_Request2::AUTH_DIGEST:\r
+                    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);\r
+            }\r
+        }\r
+\r
+        // set SSL options\r
+        if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) {\r
+            foreach ($this->request->getConfig() as $name => $value) {\r
+                if ('ssl_verify_host' == $name && null !== $value) {\r
+                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);\r
+                } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {\r
+                    curl_setopt($ch, self::$sslContextMap[$name], $value);\r
+                }\r
+            }\r
+        }\r
+\r
+        $headers = $this->request->getHeaders();\r
+        // make cURL automagically send proper header\r
+        if (!isset($headers['accept-encoding'])) {\r
+            $headers['accept-encoding'] = '';\r
+        }\r
+\r
+        // set headers having special cURL keys\r
+        foreach (self::$headerMap as $name => $option) {\r
+            if (isset($headers[$name])) {\r
+                curl_setopt($ch, $option, $headers[$name]);\r
+                unset($headers[$name]);\r
+            }\r
+        }\r
+\r
+        $this->calculateRequestLength($headers);\r
+\r
+        // set headers not having special keys\r
+        $headersFmt = array();\r
+        foreach ($headers as $name => $value) {\r
+            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));\r
+            $headersFmt[]  = $canonicalName . ': ' . $value;\r
+        }\r
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);\r
+\r
+        return $ch;\r
+    }\r
+\r
+   /**\r
+    * Callback function called by cURL for reading the request body\r
+    *\r
+    * @param    resource    cURL handle\r
+    * @param    resource    file descriptor (not used)\r
+    * @param    integer     maximum length of data to return\r
+    * @return   string      part of the request body, up to $length bytes \r
+    */\r
+    protected function callbackReadBody($ch, $fd, $length)\r
+    {\r
+        if (!$this->eventSentHeaders) {\r
+            $this->request->setLastEvent(\r
+                'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)\r
+            );\r
+            $this->eventSentHeaders = true;\r
+        }\r
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||\r
+            0 == $this->contentLength || $this->position >= $this->contentLength\r
+        ) {\r
+            return '';\r
+        }\r
+        if (is_string($this->requestBody)) {\r
+            $string = substr($this->requestBody, $this->position, $length);\r
+        } elseif (is_resource($this->requestBody)) {\r
+            $string = fread($this->requestBody, $length);\r
+        } else {\r
+            $string = $this->requestBody->read($length);\r
+        }\r
+        $this->request->setLastEvent('sentBodyPart', strlen($string));\r
+        $this->position += strlen($string);\r
+        return $string;\r
+    }\r
+\r
+   /**\r
+    * Callback function called by cURL for saving the response headers\r
+    *\r
+    * @param    resource    cURL handle\r
+    * @param    string      response header (with trailing CRLF)\r
+    * @return   integer     number of bytes saved\r
+    * @see      HTTP_Request2_Response::parseHeaderLine()\r
+    */\r
+    protected function callbackWriteHeader($ch, $string)\r
+    {\r
+        // we may receive a second set of headers if doing e.g. digest auth\r
+        if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {\r
+            // don't bother with 100-Continue responses (bug #15785)\r
+            if (!$this->eventSentHeaders ||\r
+                $this->response->getStatus() >= 200\r
+            ) {\r
+                $this->request->setLastEvent(\r
+                    'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)\r
+                );\r
+            }\r
+            $this->eventSentHeaders = true;\r
+            // we'll need a new response object\r
+            if ($this->eventReceivedHeaders) {\r
+                $this->eventReceivedHeaders = false;\r
+                $this->response             = null;\r
+            }\r
+        }\r
+        if (empty($this->response)) {\r
+            $this->response = new HTTP_Request2_Response($string, false);\r
+        } else {\r
+            $this->response->parseHeaderLine($string);\r
+            if ('' == trim($string)) {\r
+                // don't bother with 100-Continue responses (bug #15785)\r
+                if (200 <= $this->response->getStatus()) {\r
+                    $this->request->setLastEvent('receivedHeaders', $this->response);\r
+                }\r
+                $this->eventReceivedHeaders = true;\r
+            }\r
+        }\r
+        return strlen($string);\r
+    }\r
+\r
+   /**\r
+    * Callback function called by cURL for saving the response body\r
+    *\r
+    * @param    resource    cURL handle (not used)\r
+    * @param    string      part of the response body\r
+    * @return   integer     number of bytes saved\r
+    * @see      HTTP_Request2_Response::appendBody()\r
+    */\r
+    protected function callbackWriteBody($ch, $string)\r
+    {\r
+        // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if \r
+        // response doesn't start with proper HTTP status line (see bug #15716)\r
+        if (empty($this->response)) {\r
+            throw new HTTP_Request2_Exception("Malformed response: {$string}");\r
+        }\r
+        if ($this->request->getConfig('store_body')) {\r
+            $this->response->appendBody($string);\r
+        }\r
+        $this->request->setLastEvent('receivedBodyPart', $string);\r
+        return strlen($string);\r
+    }\r
+}\r
+?>\r
diff --git a/extlib/HTTP/Request2/Adapter/Mock.php b/extlib/HTTP/Request2/Adapter/Mock.php
new file mode 100644 (file)
index 0000000..8968800
--- /dev/null
@@ -0,0 +1,171 @@
+<?php\r
+/**\r
+ * Mock adapter intended for testing\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Mock.php 274406 2009-01-23 18:01:57Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Base class for HTTP_Request2 adapters\r
+ */\r
+require_once 'HTTP/Request2/Adapter.php';\r
+\r
+/**\r
+ * Mock adapter intended for testing\r
+ *\r
+ * Can be used to test applications depending on HTTP_Request2 package without\r
+ * actually performing any HTTP requests. This adapter will return responses\r
+ * previously added via addResponse()\r
+ * <code>\r
+ * $mock = new HTTP_Request2_Adapter_Mock();\r
+ * $mock->addResponse("HTTP/1.1 ... ");\r
+ * \r
+ * $request = new HTTP_Request2();\r
+ * $request->setAdapter($mock);\r
+ * \r
+ * // This will return the response set above\r
+ * $response = $req->send();\r
+ * </code> \r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @version    Release: 0.4.1\r
+ */\r
+class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter\r
+{\r
+   /**\r
+    * A queue of responses to be returned by sendRequest()\r
+    * @var  array \r
+    */\r
+    protected $responses = array();\r
+\r
+   /**\r
+    * Returns the next response from the queue built by addResponse()\r
+    *\r
+    * If the queue is empty will return default empty response with status 400,\r
+    * if an Exception object was added to the queue it will be thrown.\r
+    *\r
+    * @param    HTTP_Request2\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   Exception\r
+    */\r
+    public function sendRequest(HTTP_Request2 $request)\r
+    {\r
+        if (count($this->responses) > 0) {\r
+            $response = array_shift($this->responses);\r
+            if ($response instanceof HTTP_Request2_Response) {\r
+                return $response;\r
+            } else {\r
+                // rethrow the exception,\r
+                $class   = get_class($response);\r
+                $message = $response->getMessage();\r
+                $code    = $response->getCode();\r
+                throw new $class($message, $code);\r
+            }\r
+        } else {\r
+            return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Adds response to the queue\r
+    *\r
+    * @param    mixed   either a string, a pointer to an open file,\r
+    *                   a HTTP_Request2_Response or Exception object\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function addResponse($response)\r
+    {\r
+        if (is_string($response)) {\r
+            $response = self::createResponseFromString($response);\r
+        } elseif (is_resource($response)) {\r
+            $response = self::createResponseFromFile($response);\r
+        } elseif (!$response instanceof HTTP_Request2_Response &&\r
+                  !$response instanceof Exception\r
+        ) {\r
+            throw new HTTP_Request2_Exception('Parameter is not a valid response');\r
+        }\r
+        $this->responses[] = $response;\r
+    }\r
+\r
+   /**\r
+    * Creates a new HTTP_Request2_Response object from a string\r
+    *\r
+    * @param    string\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public static function createResponseFromString($str)\r
+    {\r
+        $parts       = preg_split('!(\r?\n){2}!m', $str, 2);\r
+        $headerLines = explode("\n", $parts[0]); \r
+        $response    = new HTTP_Request2_Response(array_shift($headerLines));\r
+        foreach ($headerLines as $headerLine) {\r
+            $response->parseHeaderLine($headerLine);\r
+        }\r
+        $response->parseHeaderLine('');\r
+        if (isset($parts[1])) {\r
+            $response->appendBody($parts[1]);\r
+        }\r
+        return $response;\r
+    }\r
+\r
+   /**\r
+    * Creates a new HTTP_Request2_Response object from a file\r
+    *\r
+    * @param    resource    file pointer returned by fopen()\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public static function createResponseFromFile($fp)\r
+    {\r
+        $response = new HTTP_Request2_Response(fgets($fp));\r
+        do {\r
+            $headerLine = fgets($fp);\r
+            $response->parseHeaderLine($headerLine);\r
+        } while ('' != trim($headerLine));\r
+\r
+        while (!feof($fp)) {\r
+            $response->appendBody(fread($fp, 8192));\r
+        }\r
+        return $response;\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/extlib/HTTP/Request2/Adapter/Socket.php b/extlib/HTTP/Request2/Adapter/Socket.php
new file mode 100644 (file)
index 0000000..ff44d49
--- /dev/null
@@ -0,0 +1,971 @@
+<?php\r
+/**\r
+ * Socket-based adapter for HTTP_Request2\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Socket.php 279760 2009-05-03 10:46:42Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Base class for HTTP_Request2 adapters\r
+ */\r
+require_once 'HTTP/Request2/Adapter.php';\r
+\r
+/**\r
+ * Socket-based adapter for HTTP_Request2\r
+ *\r
+ * This adapter uses only PHP sockets and will work on almost any PHP\r
+ * environment. Code is based on original HTTP_Request PEAR package.\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request2\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @version     Release: 0.4.1\r
+ */\r
+class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter\r
+{\r
+   /**\r
+    * Regular expression for 'token' rule from RFC 2616\r
+    */ \r
+    const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';\r
+\r
+   /**\r
+    * Regular expression for 'quoted-string' rule from RFC 2616\r
+    */\r
+    const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"';\r
+\r
+   /**\r
+    * Connected sockets, needed for Keep-Alive support\r
+    * @var  array\r
+    * @see  connect()\r
+    */\r
+    protected static $sockets = array();\r
+\r
+   /**\r
+    * Data for digest authentication scheme\r
+    *\r
+    * The keys for the array are URL prefixes. \r
+    *\r
+    * The values are associative arrays with data (realm, nonce, nonce-count, \r
+    * opaque...) needed for digest authentication. Stored here to prevent making \r
+    * duplicate requests to digest-protected resources after we have already \r
+    * received the challenge.\r
+    *\r
+    * @var  array\r
+    */\r
+    protected static $challenges = array();\r
+\r
+   /**\r
+    * Connected socket\r
+    * @var  resource\r
+    * @see  connect()\r
+    */\r
+    protected $socket;\r
+\r
+   /**\r
+    * Challenge used for server digest authentication\r
+    * @var  array\r
+    */\r
+    protected $serverChallenge;\r
+\r
+   /**\r
+    * Challenge used for proxy digest authentication\r
+    * @var  array\r
+    */\r
+    protected $proxyChallenge;\r
+\r
+   /**\r
+    * Global timeout, exception will be raised if request continues past this time\r
+    * @var  integer\r
+    */\r
+    protected $timeout = null;\r
+\r
+   /**\r
+    * Remaining length of the current chunk, when reading chunked response\r
+    * @var  integer\r
+    * @see  readChunked()\r
+    */ \r
+    protected $chunkLength = 0;\r
+\r
+   /**\r
+    * Sends request to the remote server and returns its response\r
+    *\r
+    * @param    HTTP_Request2\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public function sendRequest(HTTP_Request2 $request)\r
+    {\r
+        $this->request = $request;\r
+        $keepAlive     = $this->connect();\r
+        $headers       = $this->prepareHeaders();\r
+\r
+        // Use global request timeout if given, see feature requests #5735, #8964 \r
+        if ($timeout = $request->getConfig('timeout')) {\r
+            $this->timeout = time() + $timeout;\r
+        } else {\r
+            $this->timeout = null;\r
+        }\r
+\r
+        try {\r
+            if (false === @fwrite($this->socket, $headers, strlen($headers))) {\r
+                throw new HTTP_Request2_Exception('Error writing request');\r
+            }\r
+            // provide request headers to the observer, see request #7633\r
+            $this->request->setLastEvent('sentHeaders', $headers);\r
+            $this->writeBody();\r
+\r
+            if ($this->timeout && time() > $this->timeout) {\r
+                throw new HTTP_Request2_Exception(\r
+                    'Request timed out after ' . \r
+                    $request->getConfig('timeout') . ' second(s)'\r
+                );\r
+            }\r
+\r
+            $response = $this->readResponse();\r
+\r
+            if (!$this->canKeepAlive($keepAlive, $response)) {\r
+                $this->disconnect();\r
+            }\r
+\r
+            if ($this->shouldUseProxyDigestAuth($response)) {\r
+                return $this->sendRequest($request);\r
+            }\r
+            if ($this->shouldUseServerDigestAuth($response)) {\r
+                return $this->sendRequest($request);\r
+            }\r
+            if ($authInfo = $response->getHeader('authentication-info')) {\r
+                $this->updateChallenge($this->serverChallenge, $authInfo);\r
+            }\r
+            if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {\r
+                $this->updateChallenge($this->proxyChallenge, $proxyInfo);\r
+            }\r
+\r
+        } catch (Exception $e) {\r
+            $this->disconnect();\r
+            throw $e;\r
+        }\r
+\r
+        return $response;\r
+    }\r
+\r
+   /**\r
+    * Connects to the remote server\r
+    *\r
+    * @return   bool    whether the connection can be persistent\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function connect()\r
+    {\r
+        $secure  = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');\r
+        $tunnel  = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();\r
+        $headers = $this->request->getHeaders();\r
+        $reqHost = $this->request->getUrl()->getHost();\r
+        if (!($reqPort = $this->request->getUrl()->getPort())) {\r
+            $reqPort = $secure? 443: 80;\r
+        }\r
+\r
+        if ($host = $this->request->getConfig('proxy_host')) {\r
+            if (!($port = $this->request->getConfig('proxy_port'))) {\r
+                throw new HTTP_Request2_Exception('Proxy port not provided');\r
+            }\r
+            $proxy = true;\r
+        } else {\r
+            $host  = $reqHost;\r
+            $port  = $reqPort;\r
+            $proxy = false;\r
+        }\r
+\r
+        if ($tunnel && !$proxy) {\r
+            throw new HTTP_Request2_Exception(\r
+                "Trying to perform CONNECT request without proxy"\r
+            );\r
+        }\r
+        if ($secure && !in_array('ssl', stream_get_transports())) {\r
+            throw new HTTP_Request2_Exception(\r
+                'Need OpenSSL support for https:// requests'\r
+            );\r
+        }\r
+\r
+        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive\r
+        // connection token to a proxy server...\r
+        if ($proxy && !$secure && \r
+            !empty($headers['connection']) && 'Keep-Alive' == $headers['connection']\r
+        ) {\r
+            $this->request->setHeader('connection');\r
+        }\r
+\r
+        $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') && \r
+                      empty($headers['connection'])) ||\r
+                     (!empty($headers['connection']) &&\r
+                      'Keep-Alive' == $headers['connection']);\r
+        $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host;\r
+\r
+        $options = array();\r
+        if ($secure || $tunnel) {\r
+            foreach ($this->request->getConfig() as $name => $value) {\r
+                if ('ssl_' == substr($name, 0, 4) && null !== $value) {\r
+                    if ('ssl_verify_host' == $name) {\r
+                        if ($value) {\r
+                            $options['CN_match'] = $reqHost;\r
+                        }\r
+                    } else {\r
+                        $options[substr($name, 4)] = $value;\r
+                    }\r
+                }\r
+            }\r
+            ksort($options);\r
+        }\r
+\r
+        // Changing SSL context options after connection is established does *not*\r
+        // work, we need a new connection if options change\r
+        $remote    = $host . ':' . $port;\r
+        $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') .\r
+                     (empty($options)? '': ':' . serialize($options));\r
+        unset($this->socket);\r
+\r
+        // We use persistent connections and have a connected socket?\r
+        // Ensure that the socket is still connected, see bug #16149\r
+        if ($keepAlive && !empty(self::$sockets[$socketKey]) &&\r
+            !feof(self::$sockets[$socketKey])\r
+        ) {\r
+            $this->socket =& self::$sockets[$socketKey];\r
+\r
+        } elseif ($secure && $proxy && !$tunnel) {\r
+            $this->establishTunnel();\r
+            $this->request->setLastEvent(\r
+                'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}"\r
+            );\r
+            self::$sockets[$socketKey] =& $this->socket;\r
+\r
+        } else {\r
+            // Set SSL context options if doing HTTPS request or creating a tunnel\r
+            $context = stream_context_create();\r
+            foreach ($options as $name => $value) {\r
+                if (!stream_context_set_option($context, 'ssl', $name, $value)) {\r
+                    throw new HTTP_Request2_Exception(\r
+                        "Error setting SSL context option '{$name}'"\r
+                    );\r
+                }\r
+            }\r
+            $this->socket = @stream_socket_client(\r
+                $remote, $errno, $errstr,\r
+                $this->request->getConfig('connect_timeout'),\r
+                STREAM_CLIENT_CONNECT, $context\r
+            );\r
+            if (!$this->socket) {\r
+                throw new HTTP_Request2_Exception(\r
+                    "Unable to connect to {$remote}. Error #{$errno}: {$errstr}"\r
+                );\r
+            }\r
+            $this->request->setLastEvent('connect', $remote);\r
+            self::$sockets[$socketKey] =& $this->socket;\r
+        }\r
+        return $keepAlive;\r
+    }\r
+\r
+   /**\r
+    * Establishes a tunnel to a secure remote server via HTTP CONNECT request\r
+    *\r
+    * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP\r
+    * sees that we are connected to a proxy server (duh!) rather than the server\r
+    * that presents its certificate.\r
+    *\r
+    * @link     http://tools.ietf.org/html/rfc2817#section-5.2\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function establishTunnel()\r
+    {\r
+        $donor   = new self;\r
+        $connect = new HTTP_Request2(\r
+            $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,\r
+            array_merge($this->request->getConfig(),\r
+                        array('adapter' => $donor))\r
+        );\r
+        $response = $connect->send();\r
+        // Need any successful (2XX) response\r
+        if (200 > $response->getStatus() || 300 <= $response->getStatus()) {\r
+            throw new HTTP_Request2_Exception(\r
+                'Failed to connect via HTTPS proxy. Proxy response: ' .\r
+                $response->getStatus() . ' ' . $response->getReasonPhrase()\r
+            );\r
+        }\r
+        $this->socket = $donor->socket;\r
+\r
+        $modes = array(\r
+            STREAM_CRYPTO_METHOD_TLS_CLIENT, \r
+            STREAM_CRYPTO_METHOD_SSLv3_CLIENT,\r
+            STREAM_CRYPTO_METHOD_SSLv23_CLIENT,\r
+            STREAM_CRYPTO_METHOD_SSLv2_CLIENT \r
+        );\r
+\r
+        foreach ($modes as $mode) {\r
+            if (stream_socket_enable_crypto($this->socket, true, $mode)) {\r
+                return;\r
+            }\r
+        }\r
+        throw new HTTP_Request2_Exception(\r
+            'Failed to enable secure connection when connecting through proxy'\r
+        );\r
+    }\r
+\r
+   /**\r
+    * Checks whether current connection may be reused or should be closed\r
+    *\r
+    * @param    boolean                 whether connection could be persistent \r
+    *                                   in the first place\r
+    * @param    HTTP_Request2_Response  response object to check\r
+    * @return   boolean\r
+    */\r
+    protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)\r
+    {\r
+        // Do not close socket on successful CONNECT request\r
+        if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&\r
+            200 <= $response->getStatus() && 300 > $response->getStatus()\r
+        ) {\r
+            return true;\r
+        }\r
+\r
+        $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) ||\r
+                       null !== $response->getHeader('content-length');\r
+        $persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||\r
+                       (null === $response->getHeader('connection') &&\r
+                        '1.1' == $response->getVersion());\r
+        return $requestKeepAlive && $lengthKnown && $persistent;\r
+    }\r
+\r
+   /**\r
+    * Disconnects from the remote server\r
+    */\r
+    protected function disconnect()\r
+    {\r
+        if (is_resource($this->socket)) {\r
+            fclose($this->socket);\r
+            $this->socket = null;\r
+            $this->request->setLastEvent('disconnect');\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Checks whether another request should be performed with server digest auth\r
+    *\r
+    * Several conditions should be satisfied for it to return true:\r
+    *   - response status should be 401\r
+    *   - auth credentials should be set in the request object\r
+    *   - response should contain WWW-Authenticate header with digest challenge\r
+    *   - there is either no challenge stored for this URL or new challenge\r
+    *     contains stale=true parameter (in other case we probably just failed \r
+    *     due to invalid username / password)\r
+    *\r
+    * The method stores challenge values in $challenges static property\r
+    *\r
+    * @param    HTTP_Request2_Response  response to check\r
+    * @return   boolean whether another request should be performed\r
+    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters\r
+    */\r
+    protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)\r
+    {\r
+        // no sense repeating a request if we don't have credentials\r
+        if (401 != $response->getStatus() || !$this->request->getAuth()) {\r
+            return false;\r
+        }\r
+        if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {\r
+            return false;\r
+        }\r
+\r
+        $url    = $this->request->getUrl();\r
+        $scheme = $url->getScheme();\r
+        $host   = $scheme . '://' . $url->getHost();\r
+        if ($port = $url->getPort()) {\r
+            if ((0 == strcasecmp($scheme, 'http') && 80 != $port) ||\r
+                (0 == strcasecmp($scheme, 'https') && 443 != $port)\r
+            ) {\r
+                $host .= ':' . $port;\r
+            }\r
+        }\r
+\r
+        if (!empty($challenge['domain'])) {\r
+            $prefixes = array();\r
+            foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {\r
+                // don't bother with different servers\r
+                if ('/' == substr($prefix, 0, 1)) {\r
+                    $prefixes[] = $host . $prefix;\r
+                }\r
+            }\r
+        }\r
+        if (empty($prefixes)) {\r
+            $prefixes = array($host . '/');\r
+        }\r
+\r
+        $ret = true;\r
+        foreach ($prefixes as $prefix) {\r
+            if (!empty(self::$challenges[$prefix]) &&\r
+                (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))\r
+            ) {\r
+                // probably credentials are invalid\r
+                $ret = false;\r
+            }\r
+            self::$challenges[$prefix] =& $challenge;\r
+        }\r
+        return $ret;\r
+    }\r
+\r
+   /**\r
+    * Checks whether another request should be performed with proxy digest auth\r
+    *\r
+    * Several conditions should be satisfied for it to return true:\r
+    *   - response status should be 407\r
+    *   - proxy auth credentials should be set in the request object\r
+    *   - response should contain Proxy-Authenticate header with digest challenge\r
+    *   - there is either no challenge stored for this proxy or new challenge\r
+    *     contains stale=true parameter (in other case we probably just failed \r
+    *     due to invalid username / password)\r
+    *\r
+    * The method stores challenge values in $challenges static property\r
+    *\r
+    * @param    HTTP_Request2_Response  response to check\r
+    * @return   boolean whether another request should be performed\r
+    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters\r
+    */\r
+    protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)\r
+    {\r
+        if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {\r
+            return false;\r
+        }\r
+        if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {\r
+            return false;\r
+        }\r
+\r
+        $key = 'proxy://' . $this->request->getConfig('proxy_host') .\r
+               ':' . $this->request->getConfig('proxy_port');\r
+\r
+        if (!empty(self::$challenges[$key]) &&\r
+            (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))\r
+        ) {\r
+            $ret = false;\r
+        } else {\r
+            $ret = true;\r
+        }\r
+        self::$challenges[$key] = $challenge;\r
+        return $ret;\r
+    }\r
+\r
+   /**\r
+    * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value\r
+    *\r
+    * There is a problem with implementation of RFC 2617: several of the parameters\r
+    * here are defined as quoted-string and thus may contain backslash escaped\r
+    * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as\r
+    * just value of quoted-string X without surrounding quotes, it doesn't speak\r
+    * about removing backslash escaping.\r
+    *\r
+    * Now realm parameter is user-defined and human-readable, strange things\r
+    * happen when it contains quotes:\r
+    *   - Apache allows quotes in realm, but apparently uses realm value without\r
+    *     backslashes for digest computation\r
+    *   - Squid allows (manually escaped) quotes there, but it is impossible to\r
+    *     authorize with either escaped or unescaped quotes used in digest,\r
+    *     probably it can't parse the response (?)\r
+    *   - Both IE and Firefox display realm value with backslashes in \r
+    *     the password popup and apparently use the same value for digest\r
+    *\r
+    * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in\r
+    * quoted-string handling, unfortunately that means failure to authorize \r
+    * sometimes\r
+    *\r
+    * @param    string  value of WWW-Authenticate or Proxy-Authenticate header\r
+    * @return   mixed   associative array with challenge parameters, false if\r
+    *                   no challenge is present in header value\r
+    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters\r
+    */\r
+    protected function parseDigestChallenge($headerValue)\r
+    {\r
+        $authParam   = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .\r
+                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';\r
+        $challenge   = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";\r
+        if (!preg_match($challenge, $headerValue, $matches)) {\r
+            return false;\r
+        }\r
+\r
+        preg_match_all('!' . $authParam . '!', $matches[0], $params);\r
+        $paramsAry   = array();\r
+        $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',\r
+                             'algorithm', 'qop');\r
+        for ($i = 0; $i < count($params[0]); $i++) {\r
+            // section 3.2.1: Any unrecognized directive MUST be ignored.\r
+            if (in_array($params[1][$i], $knownParams)) {\r
+                if ('"' == substr($params[2][$i], 0, 1)) {\r
+                    $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);\r
+                } else {\r
+                    $paramsAry[$params[1][$i]] = $params[2][$i];\r
+                }\r
+            }\r
+        }\r
+        // we only support qop=auth\r
+        if (!empty($paramsAry['qop']) && \r
+            !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))\r
+        ) {\r
+            throw new HTTP_Request2_Exception(\r
+                "Only 'auth' qop is currently supported in digest authentication, " .\r
+                "server requested '{$paramsAry['qop']}'"\r
+            );\r
+        }\r
+        // we only support algorithm=MD5\r
+        if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {\r
+            throw new HTTP_Request2_Exception(\r
+                "Only 'MD5' algorithm is currently supported in digest authentication, " .\r
+                "server requested '{$paramsAry['algorithm']}'"\r
+            );\r
+        }\r
+\r
+        return $paramsAry; \r
+    }\r
+\r
+   /**\r
+    * Parses [Proxy-]Authentication-Info header value and updates challenge\r
+    *\r
+    * @param    array   challenge to update\r
+    * @param    string  value of [Proxy-]Authentication-Info header\r
+    * @todo     validate server rspauth response\r
+    */ \r
+    protected function updateChallenge(&$challenge, $headerValue)\r
+    {\r
+        $authParam   = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .\r
+                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';\r
+        $paramsAry   = array();\r
+\r
+        preg_match_all($authParam, $headerValue, $params);\r
+        for ($i = 0; $i < count($params[0]); $i++) {\r
+            if ('"' == substr($params[2][$i], 0, 1)) {\r
+                $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);\r
+            } else {\r
+                $paramsAry[$params[1][$i]] = $params[2][$i];\r
+            }\r
+        }\r
+        // for now, just update the nonce value\r
+        if (!empty($paramsAry['nextnonce'])) {\r
+            $challenge['nonce'] = $paramsAry['nextnonce'];\r
+            $challenge['nc']    = 1;\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Creates a value for [Proxy-]Authorization header when using digest authentication\r
+    *\r
+    * @param    string  user name\r
+    * @param    string  password\r
+    * @param    string  request URL\r
+    * @param    array   digest challenge parameters\r
+    * @return   string  value of [Proxy-]Authorization request header\r
+    * @link     http://tools.ietf.org/html/rfc2617#section-3.2.2\r
+    */ \r
+    protected function createDigestResponse($user, $password, $url, &$challenge)\r
+    {\r
+        if (false !== ($q = strpos($url, '?')) && \r
+            $this->request->getConfig('digest_compat_ie')\r
+        ) {\r
+            $url = substr($url, 0, $q);\r
+        }\r
+\r
+        $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);\r
+        $a2 = md5($this->request->getMethod() . ':' . $url);\r
+\r
+        if (empty($challenge['qop'])) {\r
+            $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);\r
+        } else {\r
+            $challenge['cnonce'] = 'Req2.' . rand();\r
+            if (empty($challenge['nc'])) {\r
+                $challenge['nc'] = 1;\r
+            }\r
+            $nc     = sprintf('%08x', $challenge['nc']++);\r
+            $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .\r
+                          $challenge['cnonce'] . ':auth:' . $a2);\r
+        }\r
+        return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .\r
+               'realm="' . $challenge['realm'] . '", ' .\r
+               'nonce="' . $challenge['nonce'] . '", ' .\r
+               'uri="' . $url . '", ' .\r
+               'response="' . $digest . '"' .\r
+               (!empty($challenge['opaque'])? \r
+                ', opaque="' . $challenge['opaque'] . '"':\r
+                '') .\r
+               (!empty($challenge['qop'])?\r
+                ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':\r
+                '');\r
+    }\r
+\r
+   /**\r
+    * Adds 'Authorization' header (if needed) to request headers array\r
+    *\r
+    * @param    array   request headers\r
+    * @param    string  request host (needed for digest authentication)\r
+    * @param    string  request URL (needed for digest authentication)\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)\r
+    {\r
+        if (!($auth = $this->request->getAuth())) {\r
+            return;\r
+        }\r
+        switch ($auth['scheme']) {\r
+            case HTTP_Request2::AUTH_BASIC:\r
+                $headers['authorization'] = \r
+                    'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']);\r
+                break;\r
+\r
+            case HTTP_Request2::AUTH_DIGEST:\r
+                unset($this->serverChallenge);\r
+                $fullUrl = ('/' == $requestUrl[0])?\r
+                           $this->request->getUrl()->getScheme() . '://' .\r
+                            $requestHost . $requestUrl:\r
+                           $requestUrl;\r
+                foreach (array_keys(self::$challenges) as $key) {\r
+                    if ($key == substr($fullUrl, 0, strlen($key))) {\r
+                        $headers['authorization'] = $this->createDigestResponse(\r
+                            $auth['user'], $auth['password'], \r
+                            $requestUrl, self::$challenges[$key]\r
+                        );\r
+                        $this->serverChallenge =& self::$challenges[$key];\r
+                        break;\r
+                    }\r
+                }\r
+                break;\r
+\r
+            default:\r
+                throw new HTTP_Request2_Exception(\r
+                    "Unknown HTTP authentication scheme '{$auth['scheme']}'"\r
+                );\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Adds 'Proxy-Authorization' header (if needed) to request headers array\r
+    *\r
+    * @param    array   request headers\r
+    * @param    string  request URL (needed for digest authentication)\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function addProxyAuthorizationHeader(&$headers, $requestUrl)\r
+    {\r
+        if (!$this->request->getConfig('proxy_host') ||\r
+            !($user = $this->request->getConfig('proxy_user')) ||\r
+            (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) &&\r
+             HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())\r
+        ) {\r
+            return;\r
+        }\r
+\r
+        $password = $this->request->getConfig('proxy_password');\r
+        switch ($this->request->getConfig('proxy_auth_scheme')) {\r
+            case HTTP_Request2::AUTH_BASIC:\r
+                $headers['proxy-authorization'] =\r
+                    'Basic ' . base64_encode($user . ':' . $password);\r
+                break;\r
+\r
+            case HTTP_Request2::AUTH_DIGEST:\r
+                unset($this->proxyChallenge);\r
+                $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .\r
+                            ':' . $this->request->getConfig('proxy_port');\r
+                if (!empty(self::$challenges[$proxyUrl])) {\r
+                    $headers['proxy-authorization'] = $this->createDigestResponse(\r
+                        $user, $password,\r
+                        $requestUrl, self::$challenges[$proxyUrl]\r
+                    );\r
+                    $this->proxyChallenge =& self::$challenges[$proxyUrl];\r
+                }\r
+                break;\r
+\r
+            default:\r
+                throw new HTTP_Request2_Exception(\r
+                    "Unknown HTTP authentication scheme '" .\r
+                    $this->request->getConfig('proxy_auth_scheme') . "'"\r
+                );\r
+        }\r
+    }\r
+\r
+\r
+   /**\r
+    * Creates the string with the Request-Line and request headers\r
+    *\r
+    * @return   string\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function prepareHeaders()\r
+    {\r
+        $headers = $this->request->getHeaders();\r
+        $url     = $this->request->getUrl();\r
+        $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();\r
+        $host    = $url->getHost();\r
+\r
+        $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;\r
+        if (($port = $url->getPort()) && $port != $defaultPort || $connect) {\r
+            $host .= ':' . (empty($port)? $defaultPort: $port);\r
+        }\r
+        // Do not overwrite explicitly set 'Host' header, see bug #16146\r
+        if (!isset($headers['host'])) {\r
+            $headers['host'] = $host;\r
+        }\r
+\r
+        if ($connect) {\r
+            $requestUrl = $host;\r
+\r
+        } else {\r
+            if (!$this->request->getConfig('proxy_host') ||\r
+                0 == strcasecmp($url->getScheme(), 'https')\r
+            ) {\r
+                $requestUrl = '';\r
+            } else {\r
+                $requestUrl = $url->getScheme() . '://' . $host;\r
+            }\r
+            $path        = $url->getPath();\r
+            $query       = $url->getQuery();\r
+            $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);\r
+        }\r
+\r
+        if ('1.1' == $this->request->getConfig('protocol_version') &&\r
+            extension_loaded('zlib') && !isset($headers['accept-encoding'])\r
+        ) {\r
+            $headers['accept-encoding'] = 'gzip, deflate';\r
+        }\r
+\r
+        $this->addAuthorizationHeader($headers, $host, $requestUrl);\r
+        $this->addProxyAuthorizationHeader($headers, $requestUrl);\r
+        $this->calculateRequestLength($headers);\r
+\r
+        $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .\r
+                      $this->request->getConfig('protocol_version') . "\r\n";\r
+        foreach ($headers as $name => $value) {\r
+            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));\r
+            $headersStr   .= $canonicalName . ': ' . $value . "\r\n";\r
+        }\r
+        return $headersStr . "\r\n";\r
+    }\r
+\r
+   /**\r
+    * Sends the request body\r
+    *\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function writeBody()\r
+    {\r
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||\r
+            0 == $this->contentLength\r
+        ) {\r
+            return;\r
+        }\r
+\r
+        $position   = 0;\r
+        $bufferSize = $this->request->getConfig('buffer_size');\r
+        while ($position < $this->contentLength) {\r
+            if (is_string($this->requestBody)) {\r
+                $str = substr($this->requestBody, $position, $bufferSize);\r
+            } elseif (is_resource($this->requestBody)) {\r
+                $str = fread($this->requestBody, $bufferSize);\r
+            } else {\r
+                $str = $this->requestBody->read($bufferSize);\r
+            }\r
+            if (false === @fwrite($this->socket, $str, strlen($str))) {\r
+                throw new HTTP_Request2_Exception('Error writing request');\r
+            }\r
+            // Provide the length of written string to the observer, request #7630\r
+            $this->request->setLastEvent('sentBodyPart', strlen($str));\r
+            $position += strlen($str); \r
+        }\r
+    }\r
+\r
+   /**\r
+    * Reads the remote server's response\r
+    *\r
+    * @return   HTTP_Request2_Response\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function readResponse()\r
+    {\r
+        $bufferSize = $this->request->getConfig('buffer_size');\r
+\r
+        do {\r
+            $response = new HTTP_Request2_Response($this->readLine($bufferSize), true);\r
+            do {\r
+                $headerLine = $this->readLine($bufferSize);\r
+                $response->parseHeaderLine($headerLine);\r
+            } while ('' != $headerLine);\r
+        } while (in_array($response->getStatus(), array(100, 101)));\r
+\r
+        $this->request->setLastEvent('receivedHeaders', $response);\r
+\r
+        // No body possible in such responses\r
+        if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() ||\r
+            (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&\r
+             200 <= $response->getStatus() && 300 > $response->getStatus()) ||\r
+            in_array($response->getStatus(), array(204, 304))\r
+        ) {\r
+            return $response;\r
+        }\r
+\r
+        $chunked = 'chunked' == $response->getHeader('transfer-encoding');\r
+        $length  = $response->getHeader('content-length');\r
+        $hasBody = false;\r
+        if ($chunked || null === $length || 0 < intval($length)) {\r
+            // RFC 2616, section 4.4:\r
+            // 3. ... If a message is received with both a\r
+            // Transfer-Encoding header field and a Content-Length header field,\r
+            // the latter MUST be ignored.\r
+            $toRead = ($chunked || null === $length)? null: $length;\r
+            $this->chunkLength = 0;\r
+\r
+            while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) {\r
+                if ($chunked) {\r
+                    $data = $this->readChunked($bufferSize);\r
+                } elseif (is_null($toRead)) {\r
+                    $data = $this->fread($bufferSize);\r
+                } else {\r
+                    $data    = $this->fread(min($toRead, $bufferSize));\r
+                    $toRead -= strlen($data);\r
+                }\r
+                if ('' == $data && (!$this->chunkLength || feof($this->socket))) {\r
+                    break;\r
+                }\r
+\r
+                $hasBody = true;\r
+                if ($this->request->getConfig('store_body')) {\r
+                    $response->appendBody($data);\r
+                }\r
+                if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {\r
+                    $this->request->setLastEvent('receivedEncodedBodyPart', $data);\r
+                } else {\r
+                    $this->request->setLastEvent('receivedBodyPart', $data);\r
+                }\r
+            }\r
+        }\r
+\r
+        if ($hasBody) {\r
+            $this->request->setLastEvent('receivedBody', $response);\r
+        }\r
+        return $response;\r
+    }\r
+\r
+   /**\r
+    * Reads until either the end of the socket or a newline, whichever comes first \r
+    *\r
+    * Strips the trailing newline from the returned data, handles global \r
+    * request timeout. Method idea borrowed from Net_Socket PEAR package. \r
+    *\r
+    * @param    int     buffer size to use for reading\r
+    * @return   Available data up to the newline (not including newline)\r
+    * @throws   HTTP_Request2_Exception     In case of timeout\r
+    */\r
+    protected function readLine($bufferSize)\r
+    {\r
+        $line = '';\r
+        while (!feof($this->socket)) {\r
+            if ($this->timeout) {\r
+                stream_set_timeout($this->socket, max($this->timeout - time(), 1));\r
+            }\r
+            $line .= @fgets($this->socket, $bufferSize);\r
+            $info  = stream_get_meta_data($this->socket);\r
+            if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {\r
+                throw new HTTP_Request2_Exception(\r
+                    'Request timed out after ' . \r
+                    $this->request->getConfig('timeout') . ' second(s)'\r
+                );\r
+            }\r
+            if (substr($line, -1) == "\n") {\r
+                return rtrim($line, "\r\n");\r
+            }\r
+        }\r
+        return $line;\r
+    }\r
+\r
+   /**\r
+    * Wrapper around fread(), handles global request timeout\r
+    *\r
+    * @param    int     Reads up to this number of bytes\r
+    * @return   Data read from socket\r
+    * @throws   HTTP_Request2_Exception     In case of timeout\r
+    */\r
+    protected function fread($length)\r
+    {\r
+        if ($this->timeout) {\r
+            stream_set_timeout($this->socket, max($this->timeout - time(), 1));\r
+        }\r
+        $data = fread($this->socket, $length);\r
+        $info = stream_get_meta_data($this->socket);\r
+        if ($info['timed_out'] || $this->timeout && time() > $this->timeout) {\r
+            throw new HTTP_Request2_Exception(\r
+                'Request timed out after ' . \r
+                $this->request->getConfig('timeout') . ' second(s)'\r
+            );\r
+        }\r
+        return $data;\r
+    }\r
+\r
+   /**\r
+    * Reads a part of response body encoded with chunked Transfer-Encoding\r
+    *\r
+    * @param    int     buffer size to use for reading\r
+    * @return   string\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    protected function readChunked($bufferSize)\r
+    {\r
+        // at start of the next chunk?\r
+        if (0 == $this->chunkLength) {\r
+            $line = $this->readLine($bufferSize);\r
+            if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {\r
+                throw new HTTP_Request2_Exception(\r
+                    "Cannot decode chunked response, invalid chunk length '{$line}'"\r
+                );\r
+            } else {\r
+                $this->chunkLength = hexdec($matches[1]);\r
+                // Chunk with zero length indicates the end\r
+                if (0 == $this->chunkLength) {\r
+                    $this->readLine($bufferSize);\r
+                    return '';\r
+                }\r
+            }\r
+        }\r
+        $data = $this->fread(min($this->chunkLength, $bufferSize));\r
+        $this->chunkLength -= strlen($data);\r
+        if (0 == $this->chunkLength) {\r
+            $this->readLine($bufferSize); // Trailing CRLF\r
+        }\r
+        return $data;\r
+    }\r
+}\r
+\r
+?>
\ No newline at end of file
diff --git a/extlib/HTTP/Request2/Exception.php b/extlib/HTTP/Request2/Exception.php
new file mode 100644 (file)
index 0000000..bfef7d6
--- /dev/null
@@ -0,0 +1,62 @@
+<?php\r
+/**\r
+ * Exception class for HTTP_Request2 package\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Exception.php 273003 2009-01-07 19:28:22Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Base class for exceptions in PEAR\r
+ */\r
+require_once 'PEAR/Exception.php';\r
+\r
+/**\r
+ * Exception class for HTTP_Request2 package\r
+ *\r
+ * Such a class is required by the Exception RFC:\r
+ * http://pear.php.net/pepr/pepr-proposal-show.php?id=132\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @version    Release: 0.4.1\r
+ */\r
+class HTTP_Request2_Exception extends PEAR_Exception\r
+{\r
+}\r
+?>
\ No newline at end of file
diff --git a/extlib/HTTP/Request2/MultipartBody.php b/extlib/HTTP/Request2/MultipartBody.php
new file mode 100644 (file)
index 0000000..d8afd83
--- /dev/null
@@ -0,0 +1,274 @@
+<?php\r
+/**\r
+ * Helper class for building multipart/form-data request body\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: MultipartBody.php 287306 2009-08-14 15:22:52Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Class for building multipart/form-data request body\r
+ *\r
+ * The class helps to reduce memory consumption by streaming large file uploads\r
+ * from disk, it also allows monitoring of upload progress (see request #7630)\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @version    Release: 0.4.1\r
+ * @link       http://tools.ietf.org/html/rfc1867\r
+ */\r
+class HTTP_Request2_MultipartBody\r
+{\r
+   /**\r
+    * MIME boundary\r
+    * @var  string\r
+    */\r
+    private $_boundary;\r
+\r
+   /**\r
+    * Form parameters added via {@link HTTP_Request2::addPostParameter()}\r
+    * @var  array\r
+    */\r
+    private $_params = array();\r
+\r
+   /**\r
+    * File uploads added via {@link HTTP_Request2::addUpload()}\r
+    * @var  array\r
+    */\r
+    private $_uploads = array();\r
+\r
+   /**\r
+    * Header for parts with parameters\r
+    * @var  string\r
+    */\r
+    private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";\r
+\r
+   /**\r
+    * Header for parts with uploads\r
+    * @var  string\r
+    */\r
+    private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";\r
+\r
+   /**\r
+    * Current position in parameter and upload arrays\r
+    *\r
+    * First number is index of "current" part, second number is position within\r
+    * "current" part\r
+    *\r
+    * @var  array\r
+    */\r
+    private $_pos = array(0, 0);\r
+\r
+\r
+   /**\r
+    * Constructor. Sets the arrays with POST data.\r
+    *\r
+    * @param    array   values of form fields set via {@link HTTP_Request2::addPostParameter()}\r
+    * @param    array   file uploads set via {@link HTTP_Request2::addUpload()}\r
+    * @param    bool    whether to append brackets to array variable names\r
+    */\r
+    public function __construct(array $params, array $uploads, $useBrackets = true)\r
+    {\r
+        $this->_params = self::_flattenArray('', $params, $useBrackets);\r
+        foreach ($uploads as $fieldName => $f) {\r
+            if (!is_array($f['fp'])) {\r
+                $this->_uploads[] = $f + array('name' => $fieldName);\r
+            } else {\r
+                for ($i = 0; $i < count($f['fp']); $i++) {\r
+                    $upload = array(\r
+                        'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)\r
+                    );\r
+                    foreach (array('fp', 'filename', 'size', 'type') as $key) {\r
+                        $upload[$key] = $f[$key][$i];\r
+                    }\r
+                    $this->_uploads[] = $upload;\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Returns the length of the body to use in Content-Length header\r
+    *\r
+    * @return   integer\r
+    */\r
+    public function getLength()\r
+    {\r
+        $boundaryLength     = strlen($this->getBoundary());\r
+        $headerParamLength  = strlen($this->_headerParam) - 4 + $boundaryLength;\r
+        $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;\r
+        $length             = $boundaryLength + 6;\r
+        foreach ($this->_params as $p) {\r
+            $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;\r
+        }\r
+        foreach ($this->_uploads as $u) {\r
+            $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +\r
+                       strlen($u['filename']) + $u['size'] + 2;\r
+        }\r
+        return $length;\r
+    }\r
+\r
+   /**\r
+    * Returns the boundary to use in Content-Type header\r
+    *\r
+    * @return   string\r
+    */\r
+    public function getBoundary()\r
+    {\r
+        if (empty($this->_boundary)) {\r
+            $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());\r
+        }\r
+        return $this->_boundary;\r
+    }\r
+\r
+   /**\r
+    * Returns next chunk of request body\r
+    *\r
+    * @param    integer Amount of bytes to read\r
+    * @return   string  Up to $length bytes of data, empty string if at end\r
+    */\r
+    public function read($length)\r
+    {\r
+        $ret         = '';\r
+        $boundary    = $this->getBoundary();\r
+        $paramCount  = count($this->_params);\r
+        $uploadCount = count($this->_uploads);\r
+        while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {\r
+            $oldLength = $length;\r
+            if ($this->_pos[0] < $paramCount) {\r
+                $param = sprintf($this->_headerParam, $boundary, \r
+                                 $this->_params[$this->_pos[0]][0]) .\r
+                         $this->_params[$this->_pos[0]][1] . "\r\n";\r
+                $ret    .= substr($param, $this->_pos[1], $length);\r
+                $length -= min(strlen($param) - $this->_pos[1], $length);\r
+\r
+            } elseif ($this->_pos[0] < $paramCount + $uploadCount) {\r
+                $pos    = $this->_pos[0] - $paramCount;\r
+                $header = sprintf($this->_headerUpload, $boundary,\r
+                                  $this->_uploads[$pos]['name'],\r
+                                  $this->_uploads[$pos]['filename'],\r
+                                  $this->_uploads[$pos]['type']);\r
+                if ($this->_pos[1] < strlen($header)) {\r
+                    $ret    .= substr($header, $this->_pos[1], $length);\r
+                    $length -= min(strlen($header) - $this->_pos[1], $length);\r
+                }\r
+                $filePos  = max(0, $this->_pos[1] - strlen($header));\r
+                if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) {\r
+                    $ret     .= fread($this->_uploads[$pos]['fp'], $length);\r
+                    $length  -= min($length, $this->_uploads[$pos]['size'] - $filePos);\r
+                }\r
+                if ($length > 0) {\r
+                    $start   = $this->_pos[1] + ($oldLength - $length) -\r
+                               strlen($header) - $this->_uploads[$pos]['size'];\r
+                    $ret    .= substr("\r\n", $start, $length);\r
+                    $length -= min(2 - $start, $length);\r
+                }\r
+\r
+            } else {\r
+                $closing  = '--' . $boundary . "--\r\n";\r
+                $ret     .= substr($closing, $this->_pos[1], $length);\r
+                $length  -= min(strlen($closing) - $this->_pos[1], $length);\r
+            }\r
+            if ($length > 0) {\r
+                $this->_pos     = array($this->_pos[0] + 1, 0);\r
+            } else {\r
+                $this->_pos[1] += $oldLength;\r
+            }\r
+        }\r
+        return $ret;\r
+    }\r
+\r
+   /**\r
+    * Sets the current position to the start of the body\r
+    *\r
+    * This allows reusing the same body in another request\r
+    */\r
+    public function rewind()\r
+    {\r
+        $this->_pos = array(0, 0);\r
+        foreach ($this->_uploads as $u) {\r
+            rewind($u['fp']);\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Returns the body as string\r
+    *\r
+    * Note that it reads all file uploads into memory so it is a good idea not\r
+    * to use this method with large file uploads and rely on read() instead.\r
+    *\r
+    * @return   string\r
+    */\r
+    public function __toString()\r
+    {\r
+        $this->rewind();\r
+        return $this->read($this->getLength());\r
+    }\r
+\r
+\r
+   /**\r
+    * Helper function to change the (probably multidimensional) associative array\r
+    * into the simple one.\r
+    *\r
+    * @param    string  name for item\r
+    * @param    mixed   item's values\r
+    * @param    bool    whether to append [] to array variables' names\r
+    * @return   array   array with the following items: array('item name', 'item value');\r
+    */\r
+    private static function _flattenArray($name, $values, $useBrackets)\r
+    {\r
+        if (!is_array($values)) {\r
+            return array(array($name, $values));\r
+        } else {\r
+            $ret = array();\r
+            foreach ($values as $k => $v) {\r
+                if (empty($name)) {\r
+                    $newName = $k;\r
+                } elseif ($useBrackets) {\r
+                    $newName = $name . '[' . $k . ']';\r
+                } else {\r
+                    $newName = $name;\r
+                }\r
+                $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));\r
+            }\r
+            return $ret;\r
+        }\r
+    }\r
+}\r
+?>\r
diff --git a/extlib/HTTP/Request2/Observer/Log.php b/extlib/HTTP/Request2/Observer/Log.php
new file mode 100644 (file)
index 0000000..b1a0552
--- /dev/null
@@ -0,0 +1,215 @@
+<?php\r
+/**\r
+ * An observer useful for debugging / testing.\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category HTTP\r
+ * @package  HTTP_Request2\r
+ * @author   David Jean Louis <izi@php.net>\r
+ * @author   Alexey Borzov <avb@php.net>\r
+ * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version  CVS: $Id: Log.php 272593 2009-01-02 16:27:14Z avb $\r
+ * @link     http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Exception class for HTTP_Request2 package\r
+ */ \r
+require_once 'HTTP/Request2/Exception.php';\r
+\r
+/**\r
+ * A debug observer useful for debugging / testing.\r
+ *\r
+ * This observer logs to a log target data corresponding to the various request \r
+ * and response events, it logs by default to php://output but can be configured\r
+ * to log to a file or via the PEAR Log package.\r
+ *\r
+ * A simple example:\r
+ * <code>\r
+ * require_once 'HTTP/Request2.php';\r
+ * require_once 'HTTP/Request2/Observer/Log.php';\r
+ *\r
+ * $request  = new HTTP_Request2('http://www.example.com');\r
+ * $observer = new HTTP_Request2_Observer_Log();\r
+ * $request->attach($observer);\r
+ * $request->send();\r
+ * </code>\r
+ *\r
+ * A more complex example with PEAR Log:\r
+ * <code>\r
+ * require_once 'HTTP/Request2.php';\r
+ * require_once 'HTTP/Request2/Observer/Log.php';\r
+ * require_once 'Log.php';\r
+ *\r
+ * $request  = new HTTP_Request2('http://www.example.com');\r
+ * // we want to log with PEAR log\r
+ * $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));\r
+ *\r
+ * // we only want to log received headers\r
+ * $observer->events = array('receivedHeaders');\r
+ *\r
+ * $request->attach($observer);\r
+ * $request->send();\r
+ * </code>\r
+ *\r
+ * @category HTTP\r
+ * @package  HTTP_Request2\r
+ * @author   David Jean Louis <izi@php.net>\r
+ * @author   Alexey Borzov <avb@php.net>\r
+ * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version  Release: 0.4.1\r
+ * @link     http://pear.php.net/package/HTTP_Request2\r
+ */\r
+class HTTP_Request2_Observer_Log implements SplObserver\r
+{\r
+    // properties {{{\r
+\r
+    /**\r
+     * The log target, it can be a a resource or a PEAR Log instance.\r
+     *\r
+     * @var resource|Log $target\r
+     */\r
+    protected $target = null;\r
+\r
+    /**\r
+     * The events to log.\r
+     *\r
+     * @var array $events\r
+     */\r
+    public $events = array(\r
+        'connect',\r
+        'sentHeaders',\r
+        'sentBodyPart',\r
+        'receivedHeaders',\r
+        'receivedBody',\r
+        'disconnect',\r
+    );\r
+\r
+    // }}}\r
+    // __construct() {{{\r
+\r
+    /**\r
+     * Constructor.\r
+     *\r
+     * @param mixed $target Can be a file path (default: php://output), a resource,\r
+     *                      or an instance of the PEAR Log class.\r
+     * @param array $events Array of events to listen to (default: all events)\r
+     *\r
+     * @return void\r
+     */\r
+    public function __construct($target = 'php://output', array $events = array())\r
+    {\r
+        if (!empty($events)) {\r
+            $this->events = $events;\r
+        }\r
+        if (is_resource($target) || $target instanceof Log) {\r
+            $this->target = $target;\r
+        } elseif (false === ($this->target = @fopen($target, 'w'))) {\r
+            throw new HTTP_Request2_Exception("Unable to open '{$target}'");\r
+        }\r
+    }\r
+\r
+    // }}}\r
+    // update() {{{\r
+\r
+    /**\r
+     * Called when the request notify us of an event.\r
+     *\r
+     * @param HTTP_Request2 $subject The HTTP_Request2 instance\r
+     *\r
+     * @return void\r
+     */\r
+    public function update(SplSubject $subject)\r
+    {\r
+        $event = $subject->getLastEvent();\r
+        if (!in_array($event['name'], $this->events)) {\r
+            return;\r
+        }\r
+\r
+        switch ($event['name']) {\r
+        case 'connect':\r
+            $this->log('* Connected to ' . $event['data']);\r
+            break;\r
+        case 'sentHeaders':\r
+            $headers = explode("\r\n", $event['data']);\r
+            array_pop($headers);\r
+            foreach ($headers as $header) {\r
+                $this->log('> ' . $header);\r
+            }\r
+            break;\r
+        case 'sentBodyPart':\r
+            $this->log('> ' . $event['data']);\r
+            break;\r
+        case 'receivedHeaders':\r
+            $this->log(sprintf('< HTTP/%s %s %s',\r
+                $event['data']->getVersion(),\r
+                $event['data']->getStatus(),\r
+                $event['data']->getReasonPhrase()));\r
+            $headers = $event['data']->getHeader();\r
+            foreach ($headers as $key => $val) {\r
+                $this->log('< ' . $key . ': ' . $val);\r
+            }\r
+            $this->log('< ');\r
+            break;\r
+        case 'receivedBody':\r
+            $this->log($event['data']->getBody());\r
+            break;\r
+        case 'disconnect':\r
+            $this->log('* Disconnected');\r
+            break;\r
+        }\r
+    }\r
+    \r
+    // }}}\r
+    // log() {{{\r
+\r
+    /**\r
+     * Log the given message to the configured target.\r
+     *\r
+     * @param string $message Message to display\r
+     *\r
+     * @return void\r
+     */\r
+    protected function log($message)\r
+    {\r
+        if ($this->target instanceof Log) {\r
+            $this->target->debug($message);\r
+        } elseif (is_resource($this->target)) {\r
+            fwrite($this->target, $message . "\r\n");\r
+        }\r
+    }\r
+\r
+    // }}}\r
+}\r
+\r
+?>
\ No newline at end of file
diff --git a/extlib/HTTP/Request2/Response.php b/extlib/HTTP/Request2/Response.php
new file mode 100644 (file)
index 0000000..c7c1021
--- /dev/null
@@ -0,0 +1,549 @@
+<?php\r
+/**\r
+ * Class representing a HTTP response\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ *    * Redistributions of source code must retain the above copyright\r
+ *      notice, this list of conditions and the following disclaimer.\r
+ *    * Redistributions in binary form must reproduce the above copyright\r
+ *      notice, this list of conditions and the following disclaimer in the\r
+ *      documentation and/or other materials provided with the distribution.\r
+ *    * The names of the authors may not be used to endorse or promote products\r
+ *      derived from this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version    CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z avb $\r
+ * @link       http://pear.php.net/package/HTTP_Request2\r
+ */\r
+\r
+/**\r
+ * Exception class for HTTP_Request2 package\r
+ */ \r
+require_once 'HTTP/Request2/Exception.php';\r
+\r
+/**\r
+ * Class representing a HTTP response\r
+ *\r
+ * The class is designed to be used in "streaming" scenario, building the\r
+ * response as it is being received:\r
+ * <code>\r
+ * $statusLine = read_status_line();\r
+ * $response = new HTTP_Request2_Response($statusLine);\r
+ * do {\r
+ *     $headerLine = read_header_line();\r
+ *     $response->parseHeaderLine($headerLine);\r
+ * } while ($headerLine != '');\r
+ * \r
+ * while ($chunk = read_body()) {\r
+ *     $response->appendBody($chunk);\r
+ * }\r
+ * \r
+ * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());\r
+ * </code>\r
+ *\r
+ *\r
+ * @category   HTTP\r
+ * @package    HTTP_Request2\r
+ * @author     Alexey Borzov <avb@php.net>\r
+ * @version    Release: 0.4.1\r
+ * @link       http://tools.ietf.org/html/rfc2616#section-6\r
+ */\r
+class HTTP_Request2_Response\r
+{\r
+   /**\r
+    * HTTP protocol version (e.g. 1.0, 1.1)\r
+    * @var  string\r
+    */\r
+    protected $version;\r
+\r
+   /**\r
+    * Status code\r
+    * @var  integer\r
+    * @link http://tools.ietf.org/html/rfc2616#section-6.1.1\r
+    */\r
+    protected $code;\r
+\r
+   /**\r
+    * Reason phrase\r
+    * @var  string\r
+    * @link http://tools.ietf.org/html/rfc2616#section-6.1.1\r
+    */\r
+    protected $reasonPhrase;\r
+\r
+   /**\r
+    * Associative array of response headers\r
+    * @var  array\r
+    */\r
+    protected $headers = array();\r
+\r
+   /**\r
+    * Cookies set in the response\r
+    * @var  array\r
+    */\r
+    protected $cookies = array();\r
+\r
+   /**\r
+    * Name of last header processed by parseHederLine()\r
+    *\r
+    * Used to handle the headers that span multiple lines\r
+    *\r
+    * @var  string\r
+    */\r
+    protected $lastHeader = null;\r
+\r
+   /**\r
+    * Response body\r
+    * @var  string\r
+    */\r
+    protected $body = '';\r
+\r
+   /**\r
+    * Whether the body is still encoded by Content-Encoding\r
+    *\r
+    * cURL provides the decoded body to the callback; if we are reading from\r
+    * socket the body is still gzipped / deflated\r
+    *\r
+    * @var  bool\r
+    */\r
+    protected $bodyEncoded;\r
+\r
+   /**\r
+    * Associative array of HTTP status code / reason phrase.\r
+    *\r
+    * @var  array\r
+    * @link http://tools.ietf.org/html/rfc2616#section-10\r
+    */\r
+    protected static $phrases = array(\r
+\r
+        // 1xx: Informational - Request received, continuing process\r
+        100 => 'Continue',\r
+        101 => 'Switching Protocols',\r
+\r
+        // 2xx: Success - The action was successfully received, understood and\r
+        // accepted\r
+        200 => 'OK',\r
+        201 => 'Created',\r
+        202 => 'Accepted',\r
+        203 => 'Non-Authoritative Information',\r
+        204 => 'No Content',\r
+        205 => 'Reset Content',\r
+        206 => 'Partial Content',\r
+\r
+        // 3xx: Redirection - Further action must be taken in order to complete\r
+        // the request\r
+        300 => 'Multiple Choices',\r
+        301 => 'Moved Permanently',\r
+        302 => 'Found',  // 1.1\r
+        303 => 'See Other',\r
+        304 => 'Not Modified',\r
+        305 => 'Use Proxy',\r
+        307 => 'Temporary Redirect',\r
+\r
+        // 4xx: Client Error - The request contains bad syntax or cannot be \r
+        // fulfilled\r
+        400 => 'Bad Request',\r
+        401 => 'Unauthorized',\r
+        402 => 'Payment Required',\r
+        403 => 'Forbidden',\r
+        404 => 'Not Found',\r
+        405 => 'Method Not Allowed',\r
+        406 => 'Not Acceptable',\r
+        407 => 'Proxy Authentication Required',\r
+        408 => 'Request Timeout',\r
+        409 => 'Conflict',\r
+        410 => 'Gone',\r
+        411 => 'Length Required',\r
+        412 => 'Precondition Failed',\r
+        413 => 'Request Entity Too Large',\r
+        414 => 'Request-URI Too Long',\r
+        415 => 'Unsupported Media Type',\r
+        416 => 'Requested Range Not Satisfiable',\r
+        417 => 'Expectation Failed',\r
+\r
+        // 5xx: Server Error - The server failed to fulfill an apparently\r
+        // valid request\r
+        500 => 'Internal Server Error',\r
+        501 => 'Not Implemented',\r
+        502 => 'Bad Gateway',\r
+        503 => 'Service Unavailable',\r
+        504 => 'Gateway Timeout',\r
+        505 => 'HTTP Version Not Supported',\r
+        509 => 'Bandwidth Limit Exceeded',\r
+\r
+    );\r
+\r
+   /**\r
+    * Constructor, parses the response status line\r
+    *\r
+    * @param    string  Response status line (e.g. "HTTP/1.1 200 OK")\r
+    * @param    bool    Whether body is still encoded by Content-Encoding\r
+    * @throws   HTTP_Request2_Exception if status line is invalid according to spec\r
+    */\r
+    public function __construct($statusLine, $bodyEncoded = true)\r
+    {\r
+        if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {\r
+            throw new HTTP_Request2_Exception("Malformed response: {$statusLine}");\r
+        }\r
+        $this->version = $m[1];\r
+        $this->code    = intval($m[2]);\r
+        if (!empty($m[3])) {\r
+            $this->reasonPhrase = trim($m[3]);\r
+        } elseif (!empty(self::$phrases[$this->code])) {\r
+            $this->reasonPhrase = self::$phrases[$this->code];\r
+        }\r
+        $this->bodyEncoded = (bool)$bodyEncoded;\r
+    }\r
+\r
+   /**\r
+    * Parses the line from HTTP response filling $headers array\r
+    *\r
+    * The method should be called after reading the line from socket or receiving \r
+    * it into cURL callback. Passing an empty string here indicates the end of\r
+    * response headers and triggers additional processing, so be sure to pass an\r
+    * empty string in the end.\r
+    *\r
+    * @param    string  Line from HTTP response\r
+    */\r
+    public function parseHeaderLine($headerLine)\r
+    {\r
+        $headerLine = trim($headerLine, "\r\n");\r
+\r
+        // empty string signals the end of headers, process the received ones\r
+        if ('' == $headerLine) {\r
+            if (!empty($this->headers['set-cookie'])) {\r
+                $cookies = is_array($this->headers['set-cookie'])?\r
+                           $this->headers['set-cookie']:\r
+                           array($this->headers['set-cookie']);\r
+                foreach ($cookies as $cookieString) {\r
+                    $this->parseCookie($cookieString);\r
+                }\r
+                unset($this->headers['set-cookie']);\r
+            }\r
+            foreach (array_keys($this->headers) as $k) {\r
+                if (is_array($this->headers[$k])) {\r
+                    $this->headers[$k] = implode(', ', $this->headers[$k]);\r
+                }\r
+            }\r
+\r
+        // string of the form header-name: header value\r
+        } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {\r
+            $name  = strtolower($m[1]);\r
+            $value = trim($m[2]);\r
+            if (empty($this->headers[$name])) {\r
+                $this->headers[$name] = $value;\r
+            } else {\r
+                if (!is_array($this->headers[$name])) {\r
+                    $this->headers[$name] = array($this->headers[$name]);\r
+                }\r
+                $this->headers[$name][] = $value;\r
+            }\r
+            $this->lastHeader = $name;\r
+\r
+        // string \r
+        } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {\r
+            if (!is_array($this->headers[$this->lastHeader])) {\r
+                $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);\r
+            } else {\r
+                $key = count($this->headers[$this->lastHeader]) - 1;\r
+                $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);\r
+            }\r
+        }\r
+    } \r
+\r
+   /**\r
+    * Parses a Set-Cookie header to fill $cookies array\r
+    *\r
+    * @param    string    value of Set-Cookie header\r
+    * @link     http://cgi.netscape.com/newsref/std/cookie_spec.html\r
+    */\r
+    protected function parseCookie($cookieString)\r
+    {\r
+        $cookie = array(\r
+            'expires' => null,\r
+            'domain'  => null,\r
+            'path'    => null,\r
+            'secure'  => false\r
+        );\r
+\r
+        // Only a name=value pair\r
+        if (!strpos($cookieString, ';')) {\r
+            $pos = strpos($cookieString, '=');\r
+            $cookie['name']  = trim(substr($cookieString, 0, $pos));\r
+            $cookie['value'] = trim(substr($cookieString, $pos + 1));\r
+\r
+        // Some optional parameters are supplied\r
+        } else {\r
+            $elements = explode(';', $cookieString);\r
+            $pos = strpos($elements[0], '=');\r
+            $cookie['name']  = trim(substr($elements[0], 0, $pos));\r
+            $cookie['value'] = trim(substr($elements[0], $pos + 1));\r
+\r
+            for ($i = 1; $i < count($elements); $i++) {\r
+                if (false === strpos($elements[$i], '=')) {\r
+                    $elName  = trim($elements[$i]);\r
+                    $elValue = null;\r
+                } else {\r
+                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));\r
+                }\r
+                $elName = strtolower($elName);\r
+                if ('secure' == $elName) {\r
+                    $cookie['secure'] = true;\r
+                } elseif ('expires' == $elName) {\r
+                    $cookie['expires'] = str_replace('"', '', $elValue);\r
+                } elseif ('path' == $elName || 'domain' == $elName) {\r
+                    $cookie[$elName] = urldecode($elValue);\r
+                } else {\r
+                    $cookie[$elName] = $elValue;\r
+                }\r
+            }\r
+        }\r
+        $this->cookies[] = $cookie;\r
+    }\r
+\r
+   /**\r
+    * Appends a string to the response body\r
+    * @param    string\r
+    */\r
+    public function appendBody($bodyChunk)\r
+    {\r
+        $this->body .= $bodyChunk;\r
+    }\r
+\r
+   /**\r
+    * Returns the status code\r
+    * @return   integer \r
+    */\r
+    public function getStatus()\r
+    {\r
+        return $this->code;\r
+    }\r
+\r
+   /**\r
+    * Returns the reason phrase\r
+    * @return   string\r
+    */\r
+    public function getReasonPhrase()\r
+    {\r
+        return $this->reasonPhrase;\r
+    }\r
+\r
+   /**\r
+    * Returns either the named header or all response headers\r
+    *\r
+    * @param    string          Name of header to return\r
+    * @return   string|array    Value of $headerName header (null if header is\r
+    *                           not present), array of all response headers if\r
+    *                           $headerName is null\r
+    */\r
+    public function getHeader($headerName = null)\r
+    {\r
+        if (null === $headerName) {\r
+            return $this->headers;\r
+        } else {\r
+            $headerName = strtolower($headerName);\r
+            return isset($this->headers[$headerName])? $this->headers[$headerName]: null;\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Returns cookies set in response\r
+    *\r
+    * @return   array\r
+    */\r
+    public function getCookies()\r
+    {\r
+        return $this->cookies;\r
+    }\r
+\r
+   /**\r
+    * Returns the body of the response\r
+    *\r
+    * @return   string\r
+    * @throws   HTTP_Request2_Exception if body cannot be decoded\r
+    */\r
+    public function getBody()\r
+    {\r
+        if (!$this->bodyEncoded ||\r
+            !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))\r
+        ) {\r
+            return $this->body;\r
+\r
+        } else {\r
+            if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {\r
+                $oldEncoding = mb_internal_encoding();\r
+                mb_internal_encoding('iso-8859-1');\r
+            }\r
+\r
+            try {\r
+                switch (strtolower($this->getHeader('content-encoding'))) {\r
+                    case 'gzip':\r
+                        $decoded = self::decodeGzip($this->body);\r
+                        break;\r
+                    case 'deflate':\r
+                        $decoded = self::decodeDeflate($this->body);\r
+                }\r
+            } catch (Exception $e) {\r
+            }\r
+\r
+            if (!empty($oldEncoding)) {\r
+                mb_internal_encoding($oldEncoding);\r
+            }\r
+            if (!empty($e)) {\r
+                throw $e;\r
+            }\r
+            return $decoded;\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Get the HTTP version of the response\r
+    *\r
+    * @return   string\r
+    */ \r
+    public function getVersion()\r
+    {\r
+        return $this->version;\r
+    }\r
+\r
+   /**\r
+    * Decodes the message-body encoded by gzip\r
+    *\r
+    * The real decoding work is done by gzinflate() built-in function, this\r
+    * method only parses the header and checks data for compliance with\r
+    * RFC 1952\r
+    *\r
+    * @param    string  gzip-encoded data\r
+    * @return   string  decoded data\r
+    * @throws   HTTP_Request2_Exception\r
+    * @link     http://tools.ietf.org/html/rfc1952\r
+    */\r
+    public static function decodeGzip($data)\r
+    {\r
+        $length = strlen($data);\r
+        // If it doesn't look like gzip-encoded data, don't bother\r
+        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {\r
+            return $data;\r
+        }\r
+        if (!function_exists('gzinflate')) {\r
+            throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+        }\r
+        $method = ord(substr($data, 2, 1));\r
+        if (8 != $method) {\r
+            throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method');\r
+        }\r
+        $flags = ord(substr($data, 3, 1));\r
+        if ($flags & 224) {\r
+            throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set');\r
+        }\r
+\r
+        // header is 10 bytes minimum. may be longer, though.\r
+        $headerLength = 10;\r
+        // extra fields, need to skip 'em\r
+        if ($flags & 4) {\r
+            if ($length - $headerLength - 2 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $extraLength = unpack('v', substr($data, 10, 2));\r
+            if ($length - $headerLength - 2 - $extraLength[1] < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $headerLength += $extraLength[1] + 2;\r
+        }\r
+        // file name, need to skip that\r
+        if ($flags & 8) {\r
+            if ($length - $headerLength - 1 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $filenameLength = strpos(substr($data, $headerLength), chr(0));\r
+            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $headerLength += $filenameLength + 1;\r
+        }\r
+        // comment, need to skip that also\r
+        if ($flags & 16) {\r
+            if ($length - $headerLength - 1 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $commentLength = strpos(substr($data, $headerLength), chr(0));\r
+            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $headerLength += $commentLength + 1;\r
+        }\r
+        // have a CRC for header. let's check\r
+        if ($flags & 2) {\r
+            if ($length - $headerLength - 2 < 8) {\r
+                throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+            }\r
+            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));\r
+            $crcStored = unpack('v', substr($data, $headerLength, 2));\r
+            if ($crcReal != $crcStored[1]) {\r
+                throw new HTTP_Request2_Exception('Header CRC check failed');\r
+            }\r
+            $headerLength += 2;\r
+        }\r
+        // unpacked data CRC and size at the end of encoded data\r
+        $tmp = unpack('V2', substr($data, -8));\r
+        $dataCrc  = $tmp[1];\r
+        $dataSize = $tmp[2];\r
+\r
+        // finally, call the gzinflate() function\r
+        // don't pass $dataSize to gzinflate, see bugs #13135, #14370\r
+        $unpacked = gzinflate(substr($data, $headerLength, -8));\r
+        if (false === $unpacked) {\r
+            throw new HTTP_Request2_Exception('gzinflate() call failed');\r
+        } elseif ($dataSize != strlen($unpacked)) {\r
+            throw new HTTP_Request2_Exception('Data size check failed');\r
+        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {\r
+            throw new HTTP_Request2_Exception('Data CRC check failed');\r
+        }\r
+        return $unpacked;\r
+    }\r
+\r
+   /**\r
+    * Decodes the message-body encoded by deflate\r
+    *\r
+    * @param    string  deflate-encoded data\r
+    * @return   string  decoded data\r
+    * @throws   HTTP_Request2_Exception\r
+    */\r
+    public static function decodeDeflate($data)\r
+    {\r
+        if (!function_exists('gzuncompress')) {\r
+            throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+        }\r
+        // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,\r
+        // while many applications send raw deflate stream from RFC 1951.\r
+        // We should check for presence of zlib header and use gzuncompress() or\r
+        // gzinflate() as needed. See bug #15305\r
+        $header = unpack('n', substr($data, 0, 2));\r
+        return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);\r
+    }\r
+}\r
+?>
\ No newline at end of file
index 7a654aed8f642e9c1df7e3bd7f82142ef70faaf4..f7fbcd9ce76ef21ba4243eff055d224ee0129d08 100644 (file)
@@ -1,44 +1,58 @@
 <?php
-// +-----------------------------------------------------------------------+
-// | Copyright (c) 2007-2008, Christian Schmidt, Peytz & Co. A/S           |
-// | 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: Christian Schmidt <schmidt at php dot net>                    |
-// +-----------------------------------------------------------------------+
-//
-// $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $
-//
-// Net_URL2 Class (PHP5 Only)
-
-// This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.php
 /**
- * @license BSD License
+ * Net_URL2, a class representing a URL as per RFC 3986.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2007-2009, Peytz & Co. A/S
+ * 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.
+ *   * Neither the name of the PHP_LexerGenerator nor the names of its
+ *     contributors may 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  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <chsc@peytz.dk>
+ * @copyright 2007-2008 Peytz & Co. A/S
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   CVS: $Id: URL2.php 286661 2009-08-02 12:50:54Z schmidt $
+ * @link      http://www.rfc-editor.org/rfc/rfc3986.txt
+ */
+
+/**
+ * Represents a URL as per RFC 3986.
+ *
+ * @category  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <chsc@peytz.dk>
+ * @copyright 2007-2008 Peytz & Co. ApS
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   Release: @package_version@
+ * @link      http://pear.php.net/package/Net_URL2
  */
 class Net_URL2
 {
@@ -46,24 +60,24 @@ class Net_URL2
      * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
      * is true.
      */
-    const OPTION_STRICT           = 'strict';
+    const OPTION_STRICT = 'strict';
 
     /**
      * Represent arrays in query using PHP's [] notation. Default is true.
      */
-    const OPTION_USE_BRACKETS     = 'use_brackets';
+    const OPTION_USE_BRACKETS = 'use_brackets';
 
     /**
      * URL-encode query variable keys. Default is true.
      */
-    const OPTION_ENCODE_KEYS      = 'encode_keys';
+    const OPTION_ENCODE_KEYS = 'encode_keys';
 
     /**
      * Query variable separators when parsing the query string. Every character
      * is considered a separator. Default is specified by the
      * arg_separator.input php.ini setting (this defaults to "&").
      */
-    const OPTION_SEPARATOR_INPUT  = 'input_separator';
+    const OPTION_SEPARATOR_INPUT = 'input_separator';
 
     /**
      * Query variable separator used when generating the query string. Default
@@ -75,7 +89,7 @@ class Net_URL2
     /**
      * Default options corresponds to how PHP handles $_GET.
      */
-    private $options = array(
+    private $_options = array(
         self::OPTION_STRICT           => true,
         self::OPTION_USE_BRACKETS     => true,
         self::OPTION_ENCODE_KEYS      => true,
@@ -86,41 +100,43 @@ class Net_URL2
     /**
      * @var  string|bool
      */
-    private $scheme = false;
+    private $_scheme = false;
 
     /**
      * @var  string|bool
      */
-    private $userinfo = false;
+    private $_userinfo = false;
 
     /**
      * @var  string|bool
      */
-    private $host = false;
+    private $_host = false;
 
     /**
      * @var  int|bool
      */
-    private $port = false;
+    private $_port = false;
 
     /**
      * @var  string
      */
-    private $path = '';
+    private $_path = '';
 
     /**
      * @var  string|bool
      */
-    private $query = false;
+    private $_query = false;
 
     /**
      * @var  string|bool
      */
-    private $fragment = false;
+    private $_fragment = false;
 
     /**
+     * Constructor.
+     *
      * @param string $url     an absolute or relative URL
-     * @param array  $options
+     * @param array  $options an array of OPTION_xxx constants
      */
     public function __construct($url, $options = null)
     {
@@ -130,12 +146,12 @@ class Net_URL2
                          ini_get('arg_separator.output'));
         if (is_array($options)) {
             foreach ($options as $optionName => $value) {
-                $this->setOption($optionName);
+                $this->setOption($optionName, $value);
             }
         }
 
         if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) {
-            $this->scheme = $reg[1];
+            $this->_scheme = $reg[1];
             $url = substr($url, strlen($reg[0]));
         }
 
@@ -145,19 +161,58 @@ class Net_URL2
         }
 
         $i = strcspn($url, '?#');
-        $this->path = substr($url, 0, $i);
+        $this->_path = substr($url, 0, $i);
         $url = substr($url, $i);
 
         if (preg_match('@^\?([^#]*)@', $url, $reg)) {
-            $this->query = $reg[1];
+            $this->_query = $reg[1];
             $url = substr($url, strlen($reg[0]));
         }
 
         if ($url) {
-            $this->fragment = substr($url, 1);
+            $this->_fragment = substr($url, 1);
         }
     }
 
+    /**
+     * Magic Setter.
+     *
+     * This method will magically set the value of a private variable ($var)
+     * with the value passed as the args
+     *
+     * @param  string $var      The private variable to set.
+     * @param  mixed  $arg      An argument of any type.
+     * @return void
+     */
+    public function __set($var, $arg)
+    {
+        $method = 'set' . $var;
+        if (method_exists($this, $method)) {
+            $this->$method($arg);
+        }
+    }
+    
+    /**
+     * Magic Getter.
+     *
+     * This is the magic get method to retrieve the private variable 
+     * that was set by either __set() or it's setter...
+     * 
+     * @param  string $var         The property name to retrieve.
+     * @return mixed  $this->$var  Either a boolean false if the
+     *                             property is not set or the value
+     *                             of the private property.
+     */
+    public function __get($var)
+    {
+        $method = 'get' . $var;
+        if (method_exists($this, $method)) {
+            return $this->$method();
+        }
+        
+        return false;
+    }
+    
     /**
      * Returns the scheme, e.g. "http" or "urn", or false if there is no
      * scheme specified, i.e. if this is a relative URL.
@@ -166,18 +221,23 @@ class Net_URL2
      */
     public function getScheme()
     {
-        return $this->scheme;
+        return $this->_scheme;
     }
 
     /**
-     * @param string|bool $scheme
+     * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
+     *                            scheme specified, i.e. if this is a relative
+     *                            URL
      *
      * @return void
      * @see    getScheme()
      */
     public function setScheme($scheme)
     {
-        $this->scheme = $scheme;
+        $this->_scheme = $scheme;
     }
 
     /**
@@ -188,7 +248,9 @@ class Net_URL2
      */
     public function getUser()
     {
-        return $this->userinfo !== false ? preg_replace('@:.*$@', '', $this->userinfo) : false;
+        return $this->_userinfo !== false
+            ? preg_replace('@:.*$@', '', $this->_userinfo)
+            : false;
     }
 
     /**
@@ -201,7 +263,9 @@ class Net_URL2
      */
     public function getPassword()
     {
-        return $this->userinfo !== false ? substr(strstr($this->userinfo, ':'), 1) : false;
+        return $this->_userinfo !== false
+            ? substr(strstr($this->_userinfo, ':'), 1)
+            : false;
     }
 
     /**
@@ -212,7 +276,7 @@ class Net_URL2
      */
     public function getUserinfo()
     {
-        return $this->userinfo;
+        return $this->_userinfo;
     }
 
     /**
@@ -220,15 +284,15 @@ class Net_URL2
      * in the userinfo part as username ":" password.
      *
      * @param string|bool $userinfo userinfo or username
-     * @param string|bool $password
+     * @param string|bool $password optional password, or false
      *
      * @return void
      */
     public function setUserinfo($userinfo, $password = false)
     {
-        $this->userinfo = $userinfo;
+        $this->_userinfo = $userinfo;
         if ($password !== false) {
-            $this->userinfo .= ':' . $password;
+            $this->_userinfo .= ':' . $password;
         }
     }
 
@@ -236,21 +300,24 @@ class Net_URL2
      * Returns the host part, or false if there is no authority part, e.g.
      * relative URLs.
      *
-     * @return  string|bool
+     * @return  string|bool a hostname, an IP address, or false
      */
     public function getHost()
     {
-        return $this->host;
+        return $this->_host;
     }
 
     /**
-     * @param string|bool $host
+     * Sets the host part. Specify false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @param string|bool $host a hostname, an IP address, or false
      *
      * @return void
      */
     public function setHost($host)
     {
-        $this->host = $host;
+        $this->_host = $host;
     }
 
     /**
@@ -261,65 +328,72 @@ class Net_URL2
      */
     public function getPort()
     {
-        return $this->port;
+        return $this->_port;
     }
 
     /**
-     * @param int|bool $port
+     * Sets the port number. Specify false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @param int|bool $port a port number, or false
      *
      * @return void
      */
     public function setPort($port)
     {
-        $this->port = intval($port);
+        $this->_port = intval($port);
     }
 
     /**
      * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
-     * false if there is no authority none.
+     * false if there is no authority.
      *
      * @return string|bool
      */
     public function getAuthority()
     {
-        if (!$this->host) {
+        if (!$this->_host) {
             return false;
         }
 
         $authority = '';
 
-        if ($this->userinfo !== false) {
-            $authority .= $this->userinfo . '@';
+        if ($this->_userinfo !== false) {
+            $authority .= $this->_userinfo . '@';
         }
 
-        $authority .= $this->host;
+        $authority .= $this->_host;
 
-        if ($this->port !== false) {
-            $authority .= ':' . $this->port;
+        if ($this->_port !== false) {
+            $authority .= ':' . $this->_port;
         }
 
         return $authority;
     }
 
     /**
-     * @param string|false $authority
+     * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
+     * false if there is no authority.
+     *
+     * @param string|false $authority a hostname or an IP addresse, possibly
+     *                                with userinfo prefixed and port number
+     *                                appended, e.g. "foo:bar@example.org:81".
      *
      * @return void
      */
     public function setAuthority($authority)
     {
-        $this->user = false;
-        $this->pass = false;
-        $this->host = false;
-        $this->port = false;
-        if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
+        $this->_userinfo = false;
+        $this->_host     = false;
+        $this->_port     = false;
+        if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
             if ($reg[1]) {
-                $this->userinfo = $reg[2];
+                $this->_userinfo = $reg[2];
             }
 
-            $this->host = $reg[3];
+            $this->_host = $reg[3];
             if (isset($reg[5])) {
-                $this->port = intval($reg[5]);
+                $this->_port = intval($reg[5]);
             }
         }
     }
@@ -331,65 +405,74 @@ class Net_URL2
      */
     public function getPath()
     {
-        return $this->path;
+        return $this->_path;
     }
 
     /**
-     * @param string $path
+     * Sets the path part (possibly an empty string).
+     *
+     * @param string $path a path
      *
      * @return void
      */
     public function setPath($path)
     {
-        $this->path = $path;
+        $this->_path = $path;
     }
 
     /**
      * Returns the query string (excluding the leading "?"), or false if "?"
-     * isn't present in the URL.
+     * is not present in the URL.
      *
      * @return  string|bool
      * @see     self::getQueryVariables()
      */
     public function getQuery()
     {
-        return $this->query;
+        return $this->_query;
     }
 
     /**
-     * @param string|bool $query
+     * Sets the query string (excluding the leading "?"). Specify false if "?"
+     * is not present in the URL.
+     *
+     * @param string|bool $query a query string, e.g. "foo=1&bar=2"
      *
      * @return void
      * @see   self::setQueryVariables()
      */
     public function setQuery($query)
     {
-        $this->query = $query;
+        $this->_query = $query;
     }
 
     /**
-     * Returns the fragment name, or false if "#" isn't present in the URL.
+     * Returns the fragment name, or false if "#" is not present in the URL.
      *
      * @return  string|bool
      */
     public function getFragment()
     {
-        return $this->fragment;
+        return $this->_fragment;
     }
 
     /**
-     * @param string|bool $fragment
+     * Sets the fragment name. Specify false if "#" is not present in the URL.
+     *
+     * @param string|bool $fragment a fragment excluding the leading "#", or
+     *                              false
      *
      * @return void
      */
     public function setFragment($fragment)
     {
-        $this->fragment = $fragment;
+        $this->_fragment = $fragment;
     }
 
     /**
      * Returns the query string like an array as the variables would appear in
-     * $_GET in a PHP script.
+     * $_GET in a PHP script. If the URL does not contain a "?", an empty array
+     * is returned.
      *
      * @return  array
      */
@@ -398,7 +481,7 @@ class Net_URL2
         $pattern = '/[' .
                    preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
                    ']/';
-        $parts   = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY);
+        $parts   = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
         $return  = array();
 
         foreach ($parts as $part) {
@@ -445,6 +528,8 @@ class Net_URL2
     }
 
     /**
+     * Sets the query string to the specified variable in the query string.
+     *
      * @param array $array (name => value) array
      *
      * @return void
@@ -452,11 +537,11 @@ class Net_URL2
     public function setQueryVariables(array $array)
     {
         if (!$array) {
-            $this->query = false;
+            $this->_query = false;
         } else {
             foreach ($array as $name => $value) {
                 if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
-                    $name = rawurlencode($name);
+                    $name = self::urlencode($name);
                 }
 
                 if (is_array($value)) {
@@ -466,19 +551,21 @@ class Net_URL2
                             : ($name . '=' . $v);
                     }
                 } elseif (!is_null($value)) {
-                    $parts[] = $name . '=' . $value;
+                    $parts[] = $name . '=' . self::urlencode($value);
                 } else {
                     $parts[] = $name;
                 }
             }
-            $this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
-                                   $parts);
+            $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
+                                    $parts);
         }
     }
 
     /**
-     * @param string $name
-     * @param mixed  $value
+     * Sets the specified variable in the query string.
+     *
+     * @param string $name  variable name
+     * @param mixed  $value variable value
      *
      * @return  array
      */
@@ -490,7 +577,9 @@ class Net_URL2
     }
 
     /**
-     * @param string $name
+     * Removes the specifed variable from the query string.
+     *
+     * @param string $name a query string variable, e.g. "foo" in "?foo=1"
      *
      * @return void
      */
@@ -511,27 +600,38 @@ class Net_URL2
         // See RFC 3986, section 5.3
         $url = "";
 
-        if ($this->scheme !== false) {
-            $url .= $this->scheme . ':';
+        if ($this->_scheme !== false) {
+            $url .= $this->_scheme . ':';
         }
 
         $authority = $this->getAuthority();
         if ($authority !== false) {
             $url .= '//' . $authority;
         }
-        $url .= $this->path;
+        $url .= $this->_path;
 
-        if ($this->query !== false) {
-            $url .= '?' . $this->query;
+        if ($this->_query !== false) {
+            $url .= '?' . $this->_query;
         }
 
-        if ($this->fragment !== false) {
-            $url .= '#' . $this->fragment;
+        if ($this->_fragment !== false) {
+            $url .= '#' . $this->_fragment;
         }
     
         return $url;
     }
 
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     * @see toString()
+     */
+    public function __toString()
+    {
+        return $this->getURL();
+    }
+
     /** 
      * Returns a normalized string representation of this URL. This is useful
      * for comparison of URLs.
@@ -555,36 +655,38 @@ class Net_URL2
         // See RFC 3886, section 6
 
         // Schemes are case-insensitive
-        if ($this->scheme) {
-            $this->scheme = strtolower($this->scheme);
+        if ($this->_scheme) {
+            $this->_scheme = strtolower($this->_scheme);
         }
 
         // Hostnames are case-insensitive
-        if ($this->host) {
-            $this->host = strtolower($this->host);
+        if ($this->_host) {
+            $this->_host = strtolower($this->_host);
         }
 
         // Remove default port number for known schemes (RFC 3986, section 6.2.3)
-        if ($this->port &&
-            $this->scheme &&
-            $this->port == getservbyname($this->scheme, 'tcp')) {
+        if ($this->_port &&
+            $this->_scheme &&
+            $this->_port == getservbyname($this->_scheme, 'tcp')) {
 
-            $this->port = false;
+            $this->_port = false;
         }
 
         // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
-        foreach (array('userinfo', 'host', 'path') as $part) {
+        foreach (array('_userinfo', '_host', '_path') as $part) {
             if ($this->$part) {
-                $this->$part  = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$part);
+                $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
+                                            'strtoupper("\0")',
+                                            $this->$part);
             }
         }
 
         // Path segment normalization (RFC 3986, section 6.2.2.3)
-        $this->path = self::removeDotSegments($this->path);
+        $this->_path = self::removeDotSegments($this->_path);
 
         // Scheme based normalization (RFC 3986, section 6.2.3)
-        if ($this->host && !$this->path) {
-            $this->path = '/';
+        if ($this->_host && !$this->_path) {
+            $this->_path = '/';
         }
     }
 
@@ -595,7 +697,7 @@ class Net_URL2
      */
     public function isAbsolute()
     {
-        return (bool) $this->scheme;
+        return (bool) $this->_scheme;
     }
 
     /**
@@ -608,7 +710,7 @@ class Net_URL2
      */
     public function resolve($reference)
     {
-        if (is_string($reference)) {
+        if (!$reference instanceof Net_URL2) {
             $reference = new self($reference);
         }
         if (!$this->isAbsolute()) {
@@ -617,54 +719,54 @@ class Net_URL2
 
         // A non-strict parser may ignore a scheme in the reference if it is
         // identical to the base URI's scheme.
-        if (!$this->getOption(self::OPTION_STRICT) && $reference->scheme == $this->scheme) {
-            $reference->scheme = false;
+        if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
+            $reference->_scheme = false;
         }
 
         $target = new self('');
-        if ($reference->scheme !== false) {
-            $target->scheme = $reference->scheme;
+        if ($reference->_scheme !== false) {
+            $target->_scheme = $reference->_scheme;
             $target->setAuthority($reference->getAuthority());
-            $target->path  = self::removeDotSegments($reference->path);
-            $target->query = $reference->query;
+            $target->_path  = self::removeDotSegments($reference->_path);
+            $target->_query = $reference->_query;
         } else {
             $authority = $reference->getAuthority();
             if ($authority !== false) {
                 $target->setAuthority($authority);
-                $target->path  = self::removeDotSegments($reference->path);
-                $target->query = $reference->query;
+                $target->_path  = self::removeDotSegments($reference->_path);
+                $target->_query = $reference->_query;
             } else {
-                if ($reference->path == '') {
-                    $target->path = $this->path;
-                    if ($reference->query !== false) {
-                        $target->query = $reference->query;
+                if ($reference->_path == '') {
+                    $target->_path = $this->_path;
+                    if ($reference->_query !== false) {
+                        $target->_query = $reference->_query;
                     } else {
-                        $target->query = $this->query;
+                        $target->_query = $this->_query;
                     }
                 } else {
-                    if (substr($reference->path, 0, 1) == '/') {
-                        $target->path = self::removeDotSegments($reference->path);
+                    if (substr($reference->_path, 0, 1) == '/') {
+                        $target->_path = self::removeDotSegments($reference->_path);
                     } else {
                         // Merge paths (RFC 3986, section 5.2.3)
-                        if ($this->host !== false && $this->path == '') {
-                            $target->path = '/' . $this->path;
+                        if ($this->_host !== false && $this->_path == '') {
+                            $target->_path = '/' . $this->_path;
                         } else {
-                            $i = strrpos($this->path, '/');
+                            $i = strrpos($this->_path, '/');
                             if ($i !== false) {
-                                $target->path = substr($this->path, 0, $i + 1);
+                                $target->_path = substr($this->_path, 0, $i + 1);
                             }
-                            $target->path .= $reference->path;
+                            $target->_path .= $reference->_path;
                         }
-                        $target->path = self::removeDotSegments($target->path);
+                        $target->_path = self::removeDotSegments($target->_path);
                     }
-                    $target->query = $reference->query;
+                    $target->_query = $reference->_query;
                 }
                 $target->setAuthority($this->getAuthority());
             }
-            $target->scheme = $this->scheme;
+            $target->_scheme = $this->_scheme;
         }
 
-        $target->fragment = $reference->fragment;
+        $target->_fragment = $reference->_fragment;
 
         return $target;
     }
@@ -677,7 +779,7 @@ class Net_URL2
      *
      * @return string a path
      */
-    private static function removeDotSegments($path)
+    public static function removeDotSegments($path)
     {
         $output = '';
 
@@ -685,28 +787,25 @@ class Net_URL2
         // method
         $j = 0; 
         while ($path && $j++ < 100) {
-            // Step A
             if (substr($path, 0, 2) == './') {
+                // Step 2.A
                 $path = substr($path, 2);
             } elseif (substr($path, 0, 3) == '../') {
+                // Step 2.A
                 $path = substr($path, 3);
-
-            // Step B
             } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+                // Step 2.B
                 $path = '/' . substr($path, 3);
-
-            // Step C
             } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
-                $path = '/' . substr($path, 4);
-                $i = strrpos($output, '/');
+                // Step 2.C
+                $path   = '/' . substr($path, 4);
+                $i      = strrpos($output, '/');
                 $output = $i === false ? '' : substr($output, 0, $i);
-
-            // Step D
             } elseif ($path == '.' || $path == '..') {
+                // Step 2.D
                 $path = '';
-
-            // Step E
             } else {
+                // Step 2.E
                 $i = strpos($path, '/');
                 if ($i === 0) {
                     $i = strpos($path, '/', 1);
@@ -722,6 +821,22 @@ class Net_URL2
         return $output;
     }
 
+    /**
+     * Percent-encodes all non-alphanumeric characters except these: _ . - ~
+     * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
+     * 5.2.x and earlier.
+     *
+     * @param  $raw the string to encode
+     * @return string
+     */
+    public static function urlencode($string)
+    {
+       $encoded = rawurlencode($string);
+       // This is only necessary in PHP < 5.3.
+       $encoded = str_replace('%7E', '~', $encoded);
+       return $encoded;
+    }
+
     /**
      * Returns a Net_URL2 instance representing the canonical URL of the
      * currently executing PHP script.
@@ -737,13 +852,13 @@ class Net_URL2
 
         // Begin with a relative URL
         $url = new self($_SERVER['PHP_SELF']);
-        $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
-        $url->host = $_SERVER['SERVER_NAME'];
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        $url->_host   = $_SERVER['SERVER_NAME'];
         $port = intval($_SERVER['SERVER_PORT']);
-        if ($url->scheme == 'http' && $port != 80 ||
-            $url->scheme == 'https' && $port != 443) {
+        if ($url->_scheme == 'http' && $port != 80 ||
+            $url->_scheme == 'https' && $port != 443) {
 
-            $url->port = $port;
+            $url->_port = $port;
         }
         return $url;
     }
@@ -773,7 +888,7 @@ class Net_URL2
 
         // Begin with a relative URL
         $url = new self($_SERVER['REQUEST_URI']);
-        $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
         // Set host and possibly port
         $url->setAuthority($_SERVER['HTTP_HOST']);
         return $url;
@@ -792,10 +907,10 @@ class Net_URL2
      */
     function setOption($optionName, $value)
     {
-        if (!array_key_exists($optionName, $this->options)) {
+        if (!array_key_exists($optionName, $this->_options)) {
             return false;
         }
-        $this->options[$optionName] = $value;
+        $this->_options[$optionName] = $value;
     }
 
     /**
@@ -807,7 +922,7 @@ class Net_URL2
      */
     function getOption($optionName)
     {
-        return isset($this->options[$optionName])
-            ? $this->options[$optionName] : false;
+        return isset($this->_options[$optionName])
+            ? $this->_options[$optionName] : false;
     }
 }
diff --git a/extlib/README b/extlib/README
new file mode 100644 (file)
index 0000000..cfc2f9c
--- /dev/null
@@ -0,0 +1,58 @@
+DO NOT "FIX" CODE IN THIS DIRECTORY.
+
+ONLY UPSTREAM VERSIONS OF SOFTWARE GO IN THIS DIRECTORY.
+
+This directory is provided as a courtesy to our users who might be
+unable or unwilling to find and install libraries we depend on.
+
+If we "fix" software in this directory, we hamstring users who do the
+right thing and keep a single version of upstream libraries in a
+system-wide library. We introduce subtle and maddening bugs where
+our code is "accidentally" using the "wrong" library version. We may
+unwittingly interfere with other software that depends on the
+canonical release versions of those same libraries!
+
+Forking upstream software for trivial reasons makes us bad citizens in
+the Open Source community and adds unnecessary heartache for our
+users. Don't make us "that" project.
+
+FAQ:
+
+Q: What should we do when we find a bug in upstream software?
+
+A: First and foremost, REPORT THE BUG, and if possible send in a patch.
+
+   Watch for a release of the upstream software and integrate with it
+   when it's released.
+
+   In the meantime, work around the bug, if at all possible. Usually,
+   it's quite possible, if slightly harder or less efficient.
+
+Q: What if the bug can't be worked around?
+
+A: If the upstream developers have accepted a bug patch, it's
+   undesirable but acceptable to apply that patch to the library in
+   the extlib dir. Ideally, use a release version for upstream or a
+   version control system snapshot.
+
+   Note that this is a last resort.
+
+Q: What if upstream is unresponsive or won't accept a patch?
+
+A: Try again.
+
+Q: I tried again, and upstream is still unresponsive and nobody's
+   checked on my patch. Now what?
+
+A: If the upstream project is moribund and there's a way to adopt it,
+   propose having the StatusNet dev team adopt the project. Or, adopt
+   it yourself.
+
+Q: What if there's no upstream authority and it can't be adopted?
+
+A: Then we fork it. Make a new name and a new version. Include it in
+   lib/ instead of extlib/, and use the StatusNet_* prefix to change
+   the namespace to avoid collisions.
+
+   This is a last resort; consult with the rest of the dev group
+   before taking this radical step.
index 37eb8e01ec01f1169159a1b920c265c00b9bfdc2..91ae9da9be19b7613abb596b85a015be947ab59b 100644 (file)
@@ -5,6 +5,14 @@
 
   RewriteBase /mublog/
 
+  # If your site is private and want to only allow logged-in users to
+  # be able to download file attachments, uncomment this rule.
+  #
+  # If you have a custom attachment path
+  # ($config['attachments']['path']), change "file/" to match.
+  #
+  #RewriteRule ^file/(.*) getfile/$1
+
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteRule (.*) index.php?p=$1 [L,QSA]
index 644812bd55ac1c8b1549fd4446189266f3c121af..3acdba3754a7c095618efe6eacbc96dffd98e9c4 100644 (file)
--- a/index.php
+++ b/index.php
@@ -143,7 +143,7 @@ function checkMirror($action_obj, $args)
 
 function isLoginAction($action)
 {
-    static $loginActions =  array('login', 'recoverpassword', 'api', 'doc', 'register');
+    static $loginActions =  array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds');
 
     $login = null;
 
index 319c261e418514f890e87152c709bc652945accc..d34e92dab4f72cb208d9f3041e2c7305e6517f99 100644 (file)
@@ -93,6 +93,13 @@ $external_libraries=array(
         'include'=>'HTTP/Request.php',
         'check_class'=>'HTTP_Request'
     ),
+    array(
+        'name'=>'HTTP_Request2',
+        'pear'=>'HTTP_Request2',
+        'url'=>'http://pear.php.net/package/HTTP_Request2',
+        'include'=>'HTTP/Request2.php',
+        'check_class'=>'HTTP_Request2'
+    ),
     array(
         'name'=>'Mail',
         'pear'=>'Mail',
@@ -692,9 +699,7 @@ function writeConf($sitename, $server, $path, $fancy, $db)
             // database
             "\$config['db']['database'] = '{$db['database']}';\n\n".
             ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
-            "\$config['db']['type'] = '{$db['type']}';\n\n".
-
-            "?>";
+            "\$config['db']['type'] = '{$db['type']}';\n\n";
     // write configuration file out to install directory
     $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
 
index 0a943512f2a33c4bd7c551a43c6f71d07e47c278..b90f33ec7d01424539e79c9a1738e4ebc45132a6 100644 (file)
  *
  * 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  UI interaction
+ * @package   StatusNet
+ * @author    Sarven Capadisli <csarven@status.net>
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
  */
 
-$(document).ready(function(){
-       var counterBlackout = false;
-       
-       // count character on keyup
-       function counter(event){
-         if (maxLength <= 0) {
-              return;
-         }
-               var currentLength = $("#notice_data-text").val().length;
-               var remaining = maxLength - currentLength;
-               var counter = $("#notice_text-count");
-               
-               if (remaining.toString() != counter.text()) {
-                   if (!counterBlackout || remaining == 0) {
-                        if (counter.text() != String(remaining)) {
-                            counter.text(remaining);
-                       }
+var SN = { // StatusNet
+    C: { // Config
+        I: { // Init
+            CounterBlackout: false,
+            MaxLength: 140,
+            PatternUsername: /^[0-9a-zA-Z\-_.]*$/,
+            HTTP20x30x: [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307]
+        },
 
-                        if (remaining < 0) {
-                            $("#form_notice").addClass("warning");
-                        } else {
-                            $("#form_notice").removeClass("warning");
-                        }
-                        // Skip updates for the next 500ms.
-                        // On slower hardware, updating on every keypress is unpleasant.
-                        if (!counterBlackout) {
-                            counterBlackout = true;
-                            window.setTimeout(clearCounterBlackout, 500);
-                        }
-                    }
-                }
-       }
-       
-       function clearCounterBlackout() {
-               // Allow keyup events to poke the counter again
-               counterBlackout = false;
-               // Check if the string changed since we last looked
-               counter(null);
-       }
+        S: { // Selector
+            Disabled: 'disabled',
+            Warning: 'warning',
+            Error: 'error',
+            Success: 'success',
+            Processing: 'processing',
+            CommandResult: 'command_result',
+            FormNotice: 'form_notice',
+            NoticeDataText: 'notice_data-text',
+            NoticeTextCount: 'notice_text-count',
+            NoticeInReplyTo: 'notice_in-reply-to',
+            NoticeDataAttach: 'notice_data-attach',
+            NoticeDataAttachSelected: 'notice_data-attach_selected',
+            NoticeActionSubmit: 'notice_action-submit'
+        }
+    },
 
-       function submitonreturn(event) {
-               if (event.keyCode == 13 || event.keyCode == 10) {
-                       // iPhone sends \n not \r for 'return'
-                       $("#form_notice").submit();
-                       event.preventDefault();
-                       event.stopPropagation();
-                       $("#notice_data-text").blur();
-                       $("body").focus();
-                       return false;
-               }
-               return true;
-       }
+    U: { // Utils
+        FormNoticeEnhancements: function(form) {
+            form_id = form.attr('id');
+            $('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keyup');
+            $('#'+form_id+' #'+SN.C.S.NoticeDataText).unbind('keydown');
+            if (maxLength > 0) {
+                $('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keyup', function(e) {
+                    SN.U.Counter(form);
+                });
+                // run once in case there's something in there
+                SN.U.Counter(form);
+            }
 
-     // define maxLength if it wasn't defined already
+            $('#'+form_id+' #'+SN.C.S.NoticeDataText).bind('keydown', function(e) {
+                SN.U.SubmitOnReturn(e, form);
+            });
 
-    if (typeof(maxLength) == "undefined") {
-         maxLength = 140;
-    }
+            if($('body')[0].id != 'conversation') {
+                $('#'+form_id+' textarea').focus();
+            }
+        },
 
-       if ($("#notice_data-text").length) {
-         if (maxLength > 0) {
-              $("#notice_data-text").bind("keyup", counter);
-              // run once in case there's something in there
-              counter();
-         }
+        SubmitOnReturn: function(event, el) {
+            if (event.keyCode == 13 || event.keyCode == 10) {
+                el.submit();
+                event.preventDefault();
+                event.stopPropagation();
+                $('#'+el[0].id+' #'+SN.C.S.NoticeDataText).blur();
+                $('body').focus();
+                return false;
+            }
+            return true;
+        },
 
-               $("#notice_data-text").bind("keydown", submitonreturn);
+        Counter: function(form) {
+            SN.C.I.FormNoticeCurrent = form;
+            form_id = form.attr('id');
+            if (typeof(maxLength) == "undefined") {
+                 maxLength = SN.C.I.MaxLength;
+            }
 
-        if($('body')[0].id != 'conversation') {
-            $("#notice_data-text").focus();
-        }
-       }
+            if (maxLength <= 0) {
+                return;
+            }
 
-       // XXX: refactor this code
+            var remaining = maxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length;
+            var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount);
 
-       var favoptions = { dataType: 'xml',
-                                          beforeSubmit: function(data, target, options) {
-                                                                                               $(target).addClass('processing');
-                                                                                               return true;
-                                                                                         },
-                                          success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
-                                                                                               var dis = new_form.id;
-                                                                                               var fav = dis.replace('disfavor', 'favor');
-                                                                                               $('form#'+fav).replaceWith(new_form);
-                                                                                               $('form#'+dis).ajaxForm(disoptions).each(addAjaxHidden);
-                                                                                         }
-                                        };
+            if (remaining.toString() != counter.text()) {
+                if (!SN.C.I.CounterBlackout || remaining === 0) {
+                    if (counter.text() != String(remaining)) {
+                        counter.text(remaining);
+                    }
+                    if (remaining < 0) {
+                        form.addClass(SN.C.S.Warning);
+                    } else {
+                        form.removeClass(SN.C.S.Warning);
+                    }
+                    // Skip updates for the next 500ms.
+                    // On slower hardware, updating on every keypress is unpleasant.
+                    if (!SN.C.I.CounterBlackout) {
+                        SN.C.I.CounterBlackout = true;
+                        SN.C.I.FormNoticeCurrent = form;
+                        window.setTimeout("SN.U.ClearCounterBlackout(SN.C.I.FormNoticeCurrent);", 500);
+                    }
+                }
+            }
+        },
 
-       var disoptions = { dataType: 'xml',
-                                          beforeSubmit: function(data, target, options) {
-                                                                                               $(target).addClass('processing');
-                                                                                               return true;
-                                                                                         },
-                                          success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
-                                                                                               var fav = new_form.id;
-                                                                                               var dis = fav.replace('favor', 'disfavor');
-                                                                                               $('form#'+dis).replaceWith(new_form);
-                                                                                               $('form#'+fav).ajaxForm(favoptions).each(addAjaxHidden);
-                                                                                         }
-                                        };
+        ClearCounterBlackout: function(form) {
+            // Allow keyup events to poke the counter again
+            SN.C.I.CounterBlackout = false;
+            // Check if the string changed since we last looked
+            SN.U.Counter(form);
+        },
 
-       var joinoptions = { dataType: 'xml',
-                                          success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
-                                                                                               var leave = new_form.id;
-                                                                                               var join = leave.replace('leave', 'join');
-                                                                                               $('form#'+join).replaceWith(new_form);
-                                                                                               $('form#'+leave).ajaxForm(leaveoptions).each(addAjaxHidden);
-                                                                                         }
-                                        };
+        FormXHR: function(f) {
+            f.bind('submit', function(e) {
+                form_id = $(this)[0].id;
+                $.ajax({
+                    type: 'POST',
+                    dataType: 'xml',
+                    url: $(this)[0].action,
+                    data: $(this).serialize() + '&ajax=1',
+                    beforeSend: function(xhr) {
+                        $('#'+form_id).addClass(SN.C.S.Processing);
+                        $('#'+form_id+' .submit').addClass(SN.C.S.Disabled);
+                        $('#'+form_id+' .submit').attr(SN.C.S.Disabled, SN.C.S.Disabled);
+                    },
+                    error: function (xhr, textStatus, errorThrown) {
+                        alert(errorThrown || textStatus);
+                    },
+                    success: function(data, textStatus) {
+                        if (typeof($('form', data)[0]) != 'undefined') {
+                            form_new = document._importNode($('form', data)[0], true);
+                            $('#'+form_id).replaceWith(form_new);
+                            $('#'+form_new.id).each(function() { SN.U.FormXHR($(this)); });
+                        }
+                        else {
+                            $('#'+form_id).replaceWith(document._importNode($('p', data)[0], true));
+                        }
+                    }
+                });
+                return false;
+            });
+        },
 
-       var leaveoptions = { dataType: 'xml',
-                                          success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
-                                                                                               var join = new_form.id;
-                                                                                               var leave = join.replace('join', 'leave');
-                                                                                               $('form#'+leave).replaceWith(new_form);
-                                                                                               $('form#'+join).ajaxForm(joinoptions).each(addAjaxHidden);
-                                                                                         }
-                                        };
+        FormNoticeXHR: function(form) {
+            form_id = form.attr('id');
+            form.append('<input type="hidden" name="ajax" value="1"/>');
+            form.ajaxForm({
+                dataType: 'xml',
+                timeout: '60000',
+                beforeSend: function(xhr) {
+                    if ($('#'+form_id+' #'+SN.C.S.NoticeDataText)[0].value.length === 0) {
+                        form.addClass(SN.C.S.Warning);
+                        return false;
+                    }
+                    form.addClass(SN.C.S.Processing);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).addClass(SN.C.S.Disabled);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).attr(SN.C.S.Disabled, SN.C.S.Disabled);
+                    return true;
+                },
+                error: function (xhr, textStatus, errorThrown) {
+                    form.removeClass(SN.C.S.Processing);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled, SN.C.S.Disabled);
+                    if (textStatus == 'timeout') {
+                        alert ('Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists');
+                    }
+                    else {
+                        if ($('.'+SN.C.S.Error, xhr.responseXML).length > 0) {
+                            form.append(document._importNode($('.'+SN.C.S.Error, xhr.responseXML)[0], true));
+                        }
+                        else {
+                            if(jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) < 0) {
+                                alert('Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.');
+                            }
+                            else {
+                                $('#'+form_id+' #'+SN.C.S.NoticeDataText).val('');
+                                SN.U.FormNoticeEnhancements($('#'+form_id));
+                            }
+                        }
+                    }
+                },
+                success: function(data, textStatus) {
+                    var result;
+                    if ($('#'+SN.C.S.Error, data).length > 0) {
+                        result = document._importNode($('p', data)[0], true);
+                        alert(result.textContent || result.innerHTML);
+                    }
+                    else {
+                        if($('body')[0].id == 'bookmarklet') {
+                            self.close();
+                        }
 
-       function addAjaxHidden() {
-               var ajax = document.createElement('input');
-               ajax.setAttribute('type', 'hidden');
-               ajax.setAttribute('name', 'ajax');
-               ajax.setAttribute('value', 1);
-               this.appendChild(ajax);
-       }
+                        if ($('#'+SN.C.S.CommandResult, data).length > 0) {
+                            result = document._importNode($('p', data)[0], true);
+                            alert(result.textContent || result.innerHTML);
+                        }
+                        else {
+                             notice = document._importNode($('li', data)[0], true);
+                             if ($('#'+notice.id).length === 0) {
+                                var notice_irt_value = $('#'+SN.C.S.NoticeInReplyTo).val();
+                                var notice_irt = '#notices_primary #notice-'+notice_irt_value;
+                                if($('body')[0].id == 'conversation') {
+                                    if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
+                                        $(notice_irt).append('<ul class="notices"></ul>');
+                                    }
+                                    $($(notice_irt+' .notices')[0]).append(notice);
+                                }
+                                else {
+                                    $("#notices_primary .notices").prepend(notice);
+                                }
+                                $('#'+notice.id).css({display:'none'});
+                                $('#'+notice.id).fadeIn(2500);
+                                SN.U.NoticeAttachments();
+                                SN.U.NoticeReply();
+                             }
+                        }
+                        $('#'+form_id+' #'+SN.C.S.NoticeDataText).val('');
+                        $('#'+form_id+' #'+SN.C.S.NoticeDataAttach).val('');
+                        $('#'+form_id+' #'+SN.C.S.NoticeInReplyTo).val('');
+                        $('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
+                        SN.U.FormNoticeEnhancements($('#'+form_id));
+                    }
+                },
+                complete: function(xhr, textStatus) {
+                    form.removeClass(SN.C.S.Processing);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled);
+                    $('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled);
+                }
+            });
+        },
+
+        NoticeReply: function() {
+            if ($('#'+SN.C.S.NoticeDataText).length > 0 && $('#content .notice_reply').length > 0) {
+                $('#content .notice').each(function() {
+                    var notice = $(this)[0];
+                    $($('.notice_reply', notice)[0]).click(function() {
+                        var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
+                        SN.U.NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
+                        return false;
+                    });
+                });
+            }
+        },
 
-       $("form.form_favor").ajaxForm(favoptions);
-       $("form.form_disfavor").ajaxForm(disoptions);
-       $("form.form_group_join").ajaxForm(joinoptions);
-       $("form.form_group_leave").ajaxForm(leaveoptions);
-       $("form.form_favor").each(addAjaxHidden);
-       $("form.form_disfavor").each(addAjaxHidden);
-       $("form.form_group_join").each(addAjaxHidden);
-       $("form.form_group_leave").each(addAjaxHidden);
+        NoticeReplySet: function(nick,id) {
+            if (nick.match(SN.C.I.PatternUsername)) {
+                var text = $('#'+SN.C.S.NoticeDataText);
+                if (text.length) {
+                    replyto = '@' + nick + ' ';
+                    text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
+                    $('#'+SN.C.S.FormNotice+' input#'+SN.C.S.NoticeInReplyTo).val(id);
+                    if (text[0].setSelectionRange) {
+                        var len = text.val().length;
+                        text[0].setSelectionRange(len,len);
+                        text[0].focus();
+                    }
+                    return false;
+                }
+            }
+            return true;
+        },
 
-       $("#form_user_nudge").ajaxForm ({ dataType: 'xml',
-               beforeSubmit: function(xml) { $("#form_user_nudge input[type=submit]").attr("disabled", "disabled");
-                                                                         $("#form_user_nudge input[type=submit]").addClass("disabled");
-                                                                       },
-               success: function(xml) { $("#form_user_nudge").replaceWith(document._importNode($("#nudge_response", xml).get(0),true));
-                                                            $("#form_user_nudge input[type=submit]").removeAttr("disabled");
-                                                            $("#form_user_nudge input[type=submit]").removeClass("disabled");
-                                                          }
-        });
-       $("#form_user_nudge").each(addAjaxHidden);
+        NoticeAttachments: function() {
+            $.fn.jOverlay.options = {
+                method : 'GET',
+                data : '',
+                url : '',
+                color : '#000',
+                opacity : '0.6',
+                zIndex : 99,
+                center : false,
+                imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
+                bgClickToClose : true,
+                success : function() {
+                    $('#jOverlayContent').append('<button class="close">&#215;</button>');
+                    $('#jOverlayContent button').click($.closeOverlay);
+                },
+                timeout : 0,
+                autoHide : true,
+                css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'}
+            };
 
-       var Subscribe = { dataType: 'xml',
-                                         beforeSubmit: function(formData, jqForm, options) { $(".form_user_subscribe input[type=submit]").attr("disabled", "disabled");
-                                                                                                                                             $(".form_user_subscribe input[type=submit]").addClass("disabled");
-                                                                                                                                           },
-                                         success: function(xml) { var form_unsubscribe = document._importNode($('form', xml).get(0), true);
-                                                                                          var form_unsubscribe_id = form_unsubscribe.id;
-                                                                                          var form_subscribe_id = form_unsubscribe_id.replace('unsubscribe', 'subscribe');
-                                                                                          $("form#"+form_subscribe_id).replaceWith(form_unsubscribe);
-                                                                                          $("form#"+form_unsubscribe_id).ajaxForm(UnSubscribe).each(addAjaxHidden);
-                                                                                          $("dd.subscribers").text(parseInt($("dd.subscribers").text())+1);
-                                                                                          $(".form_user_subscribe input[type=submit]").removeAttr("disabled");
-                                                                                          $(".form_user_subscribe input[type=submit]").removeClass("disabled");
-                                                                                    }
-                                       };
+            $('#content .notice a.attachment').click(function() {
+                $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
+                return false;
+            });
 
-       var UnSubscribe = { dataType: 'xml',
-                                               beforeSubmit: function(formData, jqForm, options) { $(".form_user_unsubscribe input[type=submit]").attr("disabled", "disabled");
-                                                                                                                                                   $(".form_user_unsubscribe input[type=submit]").addClass("disabled");
-                                                                                                                                                 },
-                                           success: function(xml) { var form_subscribe = document._importNode($('form', xml).get(0), true);
-                                                                                                var form_subscribe_id = form_subscribe.id;
-                                                                                                var form_unsubscribe_id = form_subscribe_id.replace('subscribe', 'unsubscribe');
-                                                                                                $("form#"+form_unsubscribe_id).replaceWith(form_subscribe);
-                                                                                                $("form#"+form_subscribe_id).ajaxForm(Subscribe).each(addAjaxHidden);
-                                                                                                $("#profile_send_a_new_message").remove();
-                                                                                                $("#profile_nudge").remove();
-                                                                                            $("dd.subscribers").text(parseInt($("dd.subscribers").text())-1);
-                                                                                                $(".form_user_unsubscribe input[type=submit]").removeAttr("disabled");
-                                                                                                $(".form_user_unsubscribe input[type=submit]").removeClass("disabled");
-                                                                                          }
-                                         };
+            var t;
+            $("body:not(#shownotice) #content .notice a.thumbnail").hover(
+                function() {
+                    var anchor = $(this);
+                    $("a.thumbnail").children('img').hide();
+                    anchor.closest(".entry-title").addClass('ov');
 
-       $(".form_user_subscribe").ajaxForm(Subscribe);
-       $(".form_user_unsubscribe").ajaxForm(UnSubscribe);
-       $(".form_user_subscribe").each(addAjaxHidden);
-       $(".form_user_unsubscribe").each(addAjaxHidden);
+                    if (anchor.children('img').length === 0) {
+                        t = setTimeout(function() {
+                            $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
+                                anchor.append(data);
+                            });
+                        }, 500);
+                    }
+                    else {
+                        anchor.children('img').show();
+                    }
+                },
+                function() {
+                    clearTimeout(t);
+                    $("a.thumbnail").children('img').hide();
+                    $(this).closest(".entry-title").removeClass('ov');
+                }
+            );
+        },
 
-       var PostNotice = { dataType: 'xml',
-                                          beforeSubmit: function(formData, jqForm, options) { if ($("#notice_data-text").get(0).value.length == 0) {
-                                                                                                                                                               $("#form_notice").addClass("warning");
-                                                                                                                                                               return false;
-                                                                                                                                                  }
-                                                                                                                                                  $("#form_notice").addClass("processing");
-                                                                                                                                                  $("#notice_action-submit").attr("disabled", "disabled");
-                                                                                                                                                  $("#notice_action-submit").addClass("disabled");
-                                                                                                                                                  return true;
-                                                                                                                                                },
-                                          timeout: '60000',
-                                          error: function (xhr, textStatus, errorThrown) {     $("#form_notice").removeClass("processing");
-                                                                                                                                               $("#notice_action-submit").removeAttr("disabled");
-                                                                                                                                               $("#notice_action-submit").removeClass("disabled");
-                                                                                                                                               if (textStatus == "timeout") {
-                                                                                                                                                       alert ("Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists");
-                                                                                                                                               }
-                                                                                                                                               else {
-                                                                                                                                                       if ($(".error", xhr.responseXML).length > 0) {
-                                                                                                                                                               $('#form_notice').append(document._importNode($(".error", xhr.responseXML).get(0), true));
-                                                                                                                                                       }
-                                                                                                                                                       else {
-                                                                                                                                                               var HTTP20x30x = [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307];
-                                                                                                                                                               if(jQuery.inArray(parseInt(xhr.status), HTTP20x30x) < 0) {
-                                                                                                                                                                       alert("Sorry! We had trouble sending your notice ("+xhr.status+" "+xhr.statusText+"). Please report the problem to the site administrator if this happens again.");
-                                                                                                                                                               }
-                                                                                                                                                               else {
-                                                                                                                                                                       $("#notice_data-text").val("");
-                                                                                     if (maxLength > 0) {
-                                                                                          counter();
-                                                                                     }
-                                                                                                                                                               }
-                                                                                                                                                       }
-                                                                                                                                               }
-                                                                                                                                         },
-                                          success: function(xml) {     if ($("#error", xml).length > 0) {
-                                                                                                       var result = document._importNode($("p", xml).get(0), true);
-                                                                                                       result = result.textContent || result.innerHTML;
-                                                                                                       alert(result);
-                                                                                               }
-                                                                                               else {
-                                                                                                   if ($("#command_result", xml).length > 0) {
-                                                                                                           var result = document._importNode($("p", xml).get(0), true);
-                                                                                                           result = result.textContent || result.innerHTML;
-                                                                                                           alert(result);
-                                                    }
-                                                    else {
-                                                         li = $("li", xml).get(0);
-                                                         if ($("#"+li.id).length == 0) {
-                                                            var notice_irt_value = $('#notice_in-reply-to').val();
-                                                            var notice_irt = '#notices_primary #notice-'+notice_irt_value;
-                                                            if($('body')[0].id == 'conversation') {
-                                                                if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
-                                                                    $(notice_irt).append('<ul class="notices"></ul>');
-                                                                }
-                                                                $($(notice_irt+' .notices')[0]).append(document._importNode(li, true));
-                                                            }
-                                                            else {
-                                                                $("#notices_primary .notices").prepend(document._importNode(li, true));
-                                                            }
-                                                            $('#'+li.id).css({display:'none'});
-                                                            $('#'+li.id).fadeIn(2500);
-                                                            NoticeReply();
-                                                            NoticeAttachments();
-                                                         }
-                                                                                                       }
-                                                                                                       $("#notice_data-text").val("");
-                                                                                               $("#notice_data-attach").val("");
-                                                                                               $("#notice_in-reply-to").val("");
-                                                    $('#notice_data-attach_selected').remove();
-                                                     if (maxLength > 0) {
-                                                          counter();
-                                                     }
-                                                                                               }
-                                                                                               $("#form_notice").removeClass("processing");
-                                                                                               $("#notice_action-submit").removeAttr("disabled");
-                                                                                               $("#notice_action-submit").removeClass("disabled");
-                                                                                        }
-                                          };
-       $("#form_notice").ajaxForm(PostNotice);
-       $("#form_notice").each(addAjaxHidden);
-    NoticeReply();
-    NoticeAttachments();
-    NoticeDataAttach();
-});
+        NoticeDataAttach: function() {
+            NDA = $('#'+SN.C.S.NoticeDataAttach);
+            NDA.change(function() {
+                S = '<div id="'+SN.C.S.NoticeDataAttachSelected+'" class="'+SN.C.S.Success+'"><code>'+$(this).val()+'</code> <button class="close">&#215;</button></div>';
+                NDAS = $('#'+SN.C.S.NoticeDataAttachSelected);
+                if (NDAS.length > 0) {
+                    NDAS.replaceWith(S);
+                }
+                else {
+                    $('#'+SN.C.S.FormNotice).append(S);
+                }
+                $('#'+SN.C.S.NoticeDataAttachSelected+' button').click(function(){
+                    $('#'+SN.C.S.NoticeDataAttachSelected).remove();
+                    NDA.val('');
+                });
+            });
+        },
 
-function NoticeReply() {
-    if ($('#notice_data-text').length > 0 && $('#content .notice_reply').length > 0) {
-        $('#content .notice').each(function() {
-            var notice = $(this)[0];
-            $($('.notice_reply', notice)[0]).click(function() {
-                var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
-                NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
+        NewDirectMessage: function() {
+            NDM = $('.entity_send-a-message a');
+            NDM.attr({'href':NDM.attr('href')+'&ajax=1'});
+            NDM.click(function() {
+                var NDMF = $('.entity_send-a-message form');
+                if (NDMF.length === 0) {
+                    $.get(NDM.attr('href'), null, function(data) {
+                        $('.entity_send-a-message').append(document._importNode($('form', data)[0], true));
+                        NDMF = $('.entity_send-a-message .form_notice');
+                        SN.U.FormNoticeXHR(NDMF);
+                        SN.U.FormNoticeEnhancements(NDMF);
+                        NDMF.append('<button class="close">&#215;</button>');
+                        $('.entity_send-a-message button').click(function(){
+                            NDMF.hide();
+                            return false;
+                        });
+                    });
+                }
+                else {
+                    NDMF.show();
+                    $('.entity_send-a-message textarea').focus();
+                }
                 return false;
             });
-        });
+        }
     }
-}
+};
+
+$(document).ready(function(){
+    if ($('body.user_in').length > 0) {
+        $('.'+SN.C.S.FormNotice).each(function() {
+            SN.U.FormNoticeXHR($(this));
+            SN.U.FormNoticeEnhancements($(this));
+        });
 
-function NoticeReplySet(nick,id) {
-       rgx_username = /^[0-9a-zA-Z\-_.]*$/;
-       if (nick.match(rgx_username)) {
-               var text = $("#notice_data-text");
-               if (text.length) {
-                       replyto = "@" + nick + " ";
-                       text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
-                       $("#form_notice input#notice_in-reply-to").val(id);
-                       if (text.get(0).setSelectionRange) {
-                               var len = text.val().length;
-                               text.get(0).setSelectionRange(len,len);
-                               text.get(0).focus();
-                       }
-                       return false;
-               }
-       }
-       return true;
-}
+        $('.form_user_subscribe').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_user_unsubscribe').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_favor').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_disfavor').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_group_join').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_group_leave').each(function() { SN.U.FormXHR($(this)); });
+        $('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); });
 
-function NoticeAttachments() {
-    $.fn.jOverlay.options = {
-        method : 'GET',
-        data : '',
-        url : '',
-        color : '#000',
-        opacity : '0.6',
-        zIndex : 99,
-        center : false,
-        imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
-        bgClickToClose : true,
-        success : function() {
-            $('#jOverlayContent').append('<button>&#215;</button>');
-            $('#jOverlayContent button').click($.closeOverlay);
-        },
-        timeout : 0,
-        autoHide : true,
-        css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'}
-    };
+        SN.U.NoticeReply();
 
-    $('#content .notice a.attachment').click(function() {
-        $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
-        return false;
-    });
+        SN.U.NoticeDataAttach();
 
-    var t;
-    $("body:not(#shownotice) #content .notice a.thumbnail").hover(
-        function() {
-            var anchor = $(this);
-            $("a.thumbnail").children('img').hide();
-            anchor.closest(".entry-title").addClass('ov');
+        SN.U.NewDirectMessage();
+    }
 
-            if (anchor.children('img').length == 0) {
-                t = setTimeout(function() {
-                    $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
-                        anchor.append(data);
-                    });
-                }, 500);
-            }
-            else {
-                anchor.children('img').show();
-            }
-        },
-        function() {
-            clearTimeout(t);
-            $("a.thumbnail").children('img').hide();
-            $(this).closest(".entry-title").removeClass('ov');
-        }
-    );
-}
+    SN.U.NoticeAttachments();
+});
 
-function NoticeDataAttach() {
-    NDA = $('#notice_data-attach');
-    NDA.change(function() {
-        S = '<div id="notice_data-attach_selected" class="success"><code>'+$(this).val()+'</code> <button>&#215;</button></div>';
-        NDAS = $('#notice_data-attach_selected');
-        (NDAS.length > 0) ? NDAS.replaceWith(S) : $('#form_notice').append(S);
-        $('#notice_data-attach_selected button').click(function(){
-            $('#notice_data-attach_selected').remove();
-            NDA.val('');
-        });
-    });
-}
index 18ae7719b25c9df14146079ece46afade3fd362f..de4d550127c0c5221b7f465206474a12b398755b 100644 (file)
@@ -41,22 +41,18 @@ abstract class ShortUrlApi
         return strlen($url) >= common_config('site', 'shorturllength');
     }
 
-    protected function http_post($data) {
-        $ch = curl_init();
-        curl_setopt($ch, CURLOPT_URL, $this->service_url);
-        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-        curl_setopt($ch, CURLOPT_POST, 1);
-        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
-        $response = curl_exec($ch);
-        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-        curl_close($ch);
-        if (($code < 200) || ($code >= 400)) return false;
-        return $response;
+    protected function http_post($data)
+    {
+        $request = HTTPClient::start();
+        $response = $request->post($this->service_url, null, $data);
+        return $response->getBody();
     }
 
-    protected function http_get($url) {
-        $encoded_url = urlencode($url);
-        return file_get_contents("{$this->service_url}$encoded_url");
+    protected function http_get($url)
+    {
+        $request = HTTPClient::start();
+        $response = $request->get($this->service_url . urlencode($url));
+        return $response->getBody();
     }
 
     protected function tidy($response) {
index 11d40b8e156da190df5b33aa0a45f8aa36a34041..9efa406964651f8abf3ee1f85e08e93c10420f5b 100644 (file)
@@ -73,7 +73,7 @@ class UntrackCommand extends UnimplementedCommand
     }
 }
 
-class NudgeCommand extends UnimplementedCommand
+class NudgeCommand extends Command
 {
     var $other = null;
     function __construct($user, $other)
@@ -81,6 +81,26 @@ class NudgeCommand extends UnimplementedCommand
         parent::__construct($user);
         $this->other = $other;
     }
+    function execute($channel)
+    {
+        $recipient = User::staticGet('nickname', $this->other);
+        if(! $recipient){
+            $channel->error($this->user, sprintf(_('Could not find a user with nickname %s'),
+                               $this->other));
+        }else{
+            if ($recipient->id == $this->user->id) {
+                $channel->error($this->user, _('It does not make a lot of sense to nudge yourself!'));
+            }else{
+                if ($recipient->email && $recipient->emailnotifynudge) {
+                    mail_notify_nudge($this->user, $recipient);
+                }
+                // XXX: notify by IM
+                // XXX: notify by SMS
+                $channel->output($this->user, sprintf(_('Nudge sent to %s'),
+                               $recipient->nickname));
+            }
+        }
+    }
 }
 
 class InviteCommand extends UnimplementedCommand
@@ -124,18 +144,30 @@ class FavCommand extends Command
 
     function execute($channel)
     {
+        if(substr($this->other,0,1)=='#'){
+            //favoriting a specific notice_id
 
-        $recipient =
-          common_relative_profile($this->user, common_canonical_nickname($this->other));
+            $notice = Notice::staticGet(substr($this->other,1));
+            if (!$notice) {
+                $channel->error($this->user, _('Notice with that id does not exist'));
+                return;
+            }
+            $recipient = $notice->getProfile();
+        }else{
+            //favoriting a given user's last notice
 
-        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;
+            $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);
@@ -347,6 +379,71 @@ class MessageCommand extends Command
     }
 }
 
+class ReplyCommand 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)
+    {
+        if(substr($this->other,0,1)=='#'){
+            //replying to a specific notice_id
+
+            $notice = Notice::staticGet(substr($this->other,1));
+            if (!$notice) {
+                $channel->error($this->user, _('Notice with that id does not exist'));
+                return;
+            }
+            $recipient = $notice->getProfile();
+        }else{
+            //replying to a given user's last notice
+
+            $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;
+            }
+        }
+
+        $len = mb_strlen($this->text);
+
+        if ($len == 0) {
+            $channel->error($this->user, _('No content!'));
+            return;
+        }
+
+        $this->text = common_shorten_links($this->text);
+
+        if (Notice::contentTooLong($this->text)) {
+            $channel->error($this->user, sprintf(_('Notice too long - maximum is %d characters, you sent %d'),
+                                                 Notice::maxContent(), mb_strlen($this->text)));
+            return;
+        }
+
+        $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1,
+                                  $notice->id);
+        if ($notice) {
+            $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname));
+        } else {
+            $channel->error($this->user, _('Error saving notice.'));
+        }
+        common_broadcast_notice($notice);
+    }
+}
+
 class GetCommand extends Command
 {
 
@@ -497,6 +594,9 @@ class HelpCommand extends Command
                            "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".
+                           "fav #<notice_id> - add notice with the given id as a 'fave'\n".
+                           "reply #<notice_id> - reply to notice with a given id\n".
+                           "reply <nickname> - reply to the last notice from user\n".
                            "join <group> - join group\n".
                            "drop <group> - leave group\n".
                            "stats - get your stats\n".
@@ -507,7 +607,7 @@ class HelpCommand extends Command
                            "last <nickname> - same as 'get'\n".
                            "on <nickname> - not yet implemented.\n".
                            "off <nickname> - not yet implemented.\n".
-                           "nudge <nickname> - not yet implemented.\n".
+                           "nudge <nickname> - remind a user to update.\n".
                            "invite <phone number> - not yet implemented.\n".
                            "track <word> - not yet implemented.\n".
                            "untrack <word> - not yet implemented.\n".
index 60fc4c3c44802755af6e8bc1c6a5e4ea54bd92f1..b921a17cc25cedec84860fbd19cdd83f50defe1b 100644 (file)
@@ -134,6 +134,17 @@ class CommandInterpreter
             } else {
                 return new MessageCommand($user, $other, $extra);
             }
+         case 'r':
+         case 'reply':
+            if (!$arg) {
+                return null;
+            }
+            list($other, $extra) = $this->split_arg($arg);
+            if (!$extra) {
+                return null;
+            } else {
+                return new ReplyCommand($user, $other, $extra);
+            }
          case 'whois':
             if (!$arg) {
                 return null;
index e29456ed4e9d2345fbcb3ac76916d4fa0e28edbb..68bdbf22934bc596b612a08386ad8903e7c2b310 100644 (file)
@@ -169,6 +169,7 @@ if (isset($conffile)) {
     $_config_files[] = INSTALLDIR.'/config.php';
 }
 
+global $_have_a_config;
 $_have_a_config = false;
 
 foreach ($_config_files as $_config_file) {
@@ -185,7 +186,14 @@ function _have_config()
 }
 
 // XXX: Throw a conniption if database not installed
-
+// XXX: Find a way to use htmlwriter for this instead of handcoded markup
+if (!_have_config()) {
+  echo '<p>'. _('No configuration file found. ') .'</p>';
+  echo '<p>'. _('I looked for configuration files in the following places: ') .'<br/> '. implode($_config_files, '<br/>');
+  echo '<p>'. _('You may wish to run the installer to fix this.') .'</p>';
+  echo '<a href="install.php">'. _('Go to the installer.') .'</a>';
+  exit;
+}
 // Fixup for statusnet.ini
 
 $_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1);
diff --git a/lib/curlclient.php b/lib/curlclient.php
deleted file mode 100644 (file)
index 36fc7d1..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-n<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Utility class for wrapping Curl
- *
- * 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  HTTP
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-define(CURLCLIENT_VERSION, "0.1");
-
-/**
- * Wrapper for Curl
- *
- * Makes Curl HTTP client calls within our HTTPClient framework
- *
- * @category HTTP
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- */
-
-class CurlClient extends HTTPClient
-{
-    function __construct()
-    {
-    }
-
-    function head($url, $headers=null)
-    {
-        $ch = curl_init($url);
-
-        $this->setup($ch);
-
-        curl_setopt_array($ch,
-                          array(CURLOPT_NOBODY => true));
-
-        if (!is_null($headers)) {
-            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-        }
-
-        $result = curl_exec($ch);
-
-        curl_close($ch);
-
-        return $this->parseResults($result);
-    }
-
-    function get($url, $headers=null)
-    {
-        $ch = curl_init($url);
-
-        $this->setup($ch);
-
-        if (!is_null($headers)) {
-            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-        }
-
-        $result = curl_exec($ch);
-
-        curl_close($ch);
-
-        return $this->parseResults($result);
-    }
-
-    function post($url, $headers=null, $body=null)
-    {
-        $ch = curl_init($url);
-
-        $this->setup($ch);
-
-        curl_setopt($ch, CURLOPT_POST, true);
-
-        if (!is_null($body)) {
-            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
-        }
-
-        if (!is_null($headers)) {
-            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-        }
-
-        $result = curl_exec($ch);
-
-        curl_close($ch);
-
-        return $this->parseResults($result);
-    }
-
-    function setup($ch)
-    {
-        curl_setopt_array($ch,
-                          array(CURLOPT_USERAGENT => $this->userAgent(),
-                                CURLOPT_HEADER => true,
-                                CURLOPT_RETURNTRANSFER => true));
-    }
-
-    function userAgent()
-    {
-        $version = curl_version();
-        return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version'];
-    }
-
-    function parseResults($results)
-    {
-        $resp = new HTTPResponse();
-
-        $lines = explode("\r\n", $results);
-
-        if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) {
-            $resp->code = $match[1];
-        } else {
-            throw Exception("Bad format: initial line is not HTTP status line");
-        }
-
-        $lastk = null;
-
-        for ($i = 1; $i < count($lines); $i++) {
-            $l =& $lines[$i];
-            if (mb_strlen($l) == 0) {
-                $resp->body = implode("\r\n", array_slice($lines, $i + 1));
-                break;
-            }
-            if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) {
-                $k = $match[1];
-                $v = $match[2];
-
-                if (array_key_exists($k, $resp->headers)) {
-                    if (is_array($resp->headers[$k])) {
-                        $resp->headers[$k][] = $v;
-                    } else {
-                        $resp->headers[$k] = array($resp->headers[$k], $v);
-                    }
-                } else {
-                    $resp->headers[$k] = $v;
-                }
-                $lastk = $k;
-            } else if (preg_match("#^\s+(.*)$#", $l, $match)) {
-                // continuation line
-                if (is_null($lastk)) {
-                    throw Exception("Bad format: initial whitespace in headers");
-                }
-                $h =& $resp->headers[$lastk];
-                if (is_array($h)) {
-                    $n = count($h);
-                    $h[$n-1] .= $match[1];
-                } else {
-                    $h .= $match[1];
-                }
-            }
-        }
-
-        return $resp;
-    }
-}
index 30e43eefbd65394762bd0a5d4d447603b75997a3..f6cc4b725af22cb2d136b6f017869901cda4e0cd 100644 (file)
@@ -84,7 +84,8 @@ $default =
               'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
         'mail' =>
         array('backend' => 'mail',
-              'params' => null),
+              'params' => null,
+              'domain_check' => true),
         'nickname' =>
         array('blacklist' => array(),
               'featured' => array()),
@@ -227,6 +228,6 @@ $default =
         array('contentlimit' => null),
         'message' =>
         array('contentlimit' => null),
-        'http' =>
-        array('client' => 'curl'), // XXX: should this be the default?
+        'location' =>
+        array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
         );
index f16e31e1037c76dbba9c5d4dbb756fd920a4a88a..3f82620761c6eef8d953cc11fa9611addad5ba96 100644 (file)
@@ -31,6 +31,9 @@ if (!defined('STATUSNET')) {
     exit(1);
 }
 
+require_once 'HTTP/Request2.php';
+require_once 'HTTP/Request2/Response.php';
+
 /**
  * Useful structure for HTTP responses
  *
@@ -38,18 +41,53 @@ if (!defined('STATUSNET')) {
  * ways of doing them. This class hides the specifics of what underlying
  * library (curl or PHP-HTTP or whatever) that's used.
  *
+ * This extends the HTTP_Request2_Response class with methods to get info
+ * about any followed redirects.
+ *
  * @category HTTP
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Brion Vibber <brion@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
  */
-
-class HTTPResponse
+class HTTPResponse extends HTTP_Request2_Response
 {
-    public $code = null;
-    public $headers = array();
-    public $body = null;
+    function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
+    {
+        foreach (get_object_vars($response) as $key => $val) {
+            $this->$key = $val;
+        }
+        $this->url = strval($url);
+        $this->redirectCount = intval($redirects);
+    }
+
+    /**
+     * Get the count of redirects that have been followed, if any.
+     * @return int
+     */
+    function getRedirectCount()
+    {
+        return $this->redirectCount;
+    }
+
+    /**
+     * Gets the final target URL, after any redirects have been followed.
+     * @return string URL
+     */
+    function getUrl()
+    {
+        return $this->url;
+    }
+
+    /**
+     * Check if the response is OK, generally a 200 status code.
+     * @return bool
+     */
+    function isOk()
+    {
+        return ($this->getStatus() == 200);
+    }
 }
 
 /**
@@ -59,64 +97,163 @@ class HTTPResponse
  * ways of doing them. This class hides the specifics of what underlying
  * library (curl or PHP-HTTP or whatever) that's used.
  *
+ * This extends the PEAR HTTP_Request2 package:
+ * - sends StatusNet-specific User-Agent header
+ * - 'follow_redirects' config option, defaulting off
+ * - 'max_redirs' config option, defaulting to 10
+ * - extended response class adds getRedirectCount() and getUrl() methods
+ * - get() and post() convenience functions return body content directly
+ *
  * @category HTTP
  * @package  StatusNet
  * @author   Evan Prodromou <evan@status.net>
+ * @author   Brion Vibber <brion@status.net>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  */
 
-class HTTPClient
+class HTTPClient extends HTTP_Request2
 {
-    static $_client = null;
 
-    static function start()
+    function __construct($url=null, $method=self::METHOD_GET, $config=array())
     {
-        if (!is_null(self::$_client)) {
-            return self::$_client;
-        }
-
-        $type = common_config('http', 'client');
-
-        switch ($type) {
-         case 'curl':
-            self::$_client = new CurlClient();
-            break;
-         default:
-            throw new Exception("Unknown HTTP client type '$type'");
-            break;
-        }
-
-        return self::$_client;
+        $this->config['max_redirs'] = 10;
+        $this->config['follow_redirects'] = true;
+        parent::__construct($url, $method, $config);
+        $this->setHeader('User-Agent', $this->userAgent());
     }
 
-    function head($url, $headers)
+    /**
+     * Convenience/back-compat instantiator
+     * @return HTTPClient
+     */
+    public static function start()
     {
-        throw new Exception("HEAD method unimplemented");
+        return new HTTPClient();
     }
 
-    function get($url, $headers)
+    /**
+     * Convenience function to run a GET request.
+     *
+     * @return HTTPResponse
+     * @throws HTTP_Request2_Exception
+     */
+    public function get($url, $headers=array())
     {
-        throw new Exception("GET method unimplemented");
+        return $this->doRequest($url, self::METHOD_GET, $headers);
     }
 
-    function post($url, $headers, $body)
+    /**
+     * Convenience function to run a HEAD request.
+     *
+     * @return HTTPResponse
+     * @throws HTTP_Request2_Exception
+     */
+    public function head($url, $headers=array())
     {
-        throw new Exception("POST method unimplemented");
+        return $this->doRequest($url, self::METHOD_HEAD, $headers);
     }
 
-    function put($url, $headers, $body)
+    /**
+     * Convenience function to POST form data.
+     *
+     * @param string $url
+     * @param array $headers optional associative array of HTTP headers
+     * @param array $data optional associative array or blob of form data to submit
+     * @return HTTPResponse
+     * @throws HTTP_Request2_Exception
+     */
+    public function post($url, $headers=array(), $data=array())
     {
-        throw new Exception("PUT method unimplemented");
+        if ($data) {
+            $this->addPostParameter($data);
+        }
+        return $this->doRequest($url, self::METHOD_POST, $headers);
     }
 
-    function delete($url, $headers)
+    /**
+     * @return HTTPResponse
+     * @throws HTTP_Request2_Exception
+     */
+    protected function doRequest($url, $method, $headers)
     {
-        throw new Exception("DELETE method unimplemented");
+        $this->setUrl($url);
+        $this->setMethod($method);
+        if ($headers) {
+            foreach ($headers as $header) {
+                $this->setHeader($header);
+            }
+        }
+        $response = $this->send();
+        return $response;
+    }
+    
+    protected function log($level, $detail) {
+        $method = $this->getMethod();
+        $url = $this->getUrl();
+        common_log($level, __CLASS__ . ": HTTP $method $url - $detail");
     }
 
+    /**
+     * Pulls up StatusNet's customized user-agent string, so services
+     * we hit can track down the responsible software.
+     *
+     * @return string
+     */
     function userAgent()
     {
         return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")";
     }
+
+    /**
+     * Actually performs the HTTP request and returns an HTTPResponse object
+     * with response body and header info.
+     *
+     * Wraps around parent send() to add logging and redirection processing.
+     *
+     * @return HTTPResponse
+     * @throw HTTP_Request2_Exception
+     */
+    public function send()
+    {
+        $maxRedirs = intval($this->config['max_redirs']);
+        if (empty($this->config['follow_redirects'])) {
+            $maxRedirs = 0;
+        }
+        $redirs = 0;
+        do {
+            try {
+                $response = parent::send();
+            } catch (HTTP_Request2_Exception $e) {
+                $this->log(LOG_ERR, $e->getMessage());
+                throw $e;
+            }
+            $code = $response->getStatus();
+            if ($code >= 200 && $code < 300) {
+                $reason = $response->getReasonPhrase();
+                $this->log(LOG_INFO, "$code $reason");
+            } elseif ($code >= 300 && $code < 400) {
+                $url = $this->getUrl();
+                $target = $response->getHeader('Location');
+                
+                if (++$redirs >= $maxRedirs) {
+                    common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target");
+                    break;
+                }
+                try {
+                    $this->setUrl($target);
+                    $this->setHeader('Referer', $url);
+                    common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target");
+                    continue;
+                } catch (HTTP_Request2_Exception $e) {
+                    common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target");
+                }
+            } else {
+                $reason = $response->getReasonPhrase();
+                $this->log(LOG_ERR, "$code $reason");
+            }
+            break;
+        } while ($maxRedirs);
+        return new HTTPResponse($response, $this->getUrl(), $redirs);
+    }
 }
index 88f46148140b3d9904074e583d5dc74bc7fafc84..cd2f87e6bd5d331129355dd8351dc595292b6a09 100644 (file)
@@ -79,7 +79,12 @@ class ImageFile
             @unlink($_FILES[$param]['tmp_name']);
             throw new Exception(_('Partial upload.'));
             return;
+         case UPLOAD_ERR_NO_FILE:
+            // No file; probably just a non-AJAX submission.
+            return;
          default:
+            common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
+                $_FILES[$param]['error']);
             throw new Exception(_('System error uploading file.'));
             return;
         }
index 3dcdce5dbf349b87b629e9186bcb98ef519d22a9..73f2ec66059a6010fa956a80cdfe528500d27817 100644 (file)
@@ -176,6 +176,7 @@ function jabber_format_entry($profile, $notice)
     $xs = new XMLStringer();
     $xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im'));
     $xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml'));
+    $xs->element("img", array('src'=> $profile->avatarUrl(AVATAR_MINI_SIZE) , 'alt' => $profile->nickname));
     $xs->element('a', array('href' => $profile->profileurl),
                  $profile->nickname);
     $xs->text(": ");
@@ -184,6 +185,11 @@ function jabber_format_entry($profile, $notice)
     } else {
         $xs->raw(common_render_content($notice->content, $notice));
     }
+    $xs->raw(" ");
+    $xs->element('a', array(
+        'href'=>common_local_url('conversation',
+            array('id' => $notice->conversation)).'#notice-'.$notice->id
+         ),sprintf(_('notice id: %s'),$notice->id));
     $xs->elementEnd('body');
     $xs->elementEnd('html');
 
diff --git a/lib/location.php b/lib/location.php
new file mode 100644 (file)
index 0000000..bbfc15a
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for locations
+ *
+ * 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  Location
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * class for locations
+ *
+ * These are stored in the DB as part of notice and profile records,
+ * but since they're about the same in both, we have a separate class
+ * for them.
+ *
+ * @category Location
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class Location
+{
+    public  $lat;
+    public  $lon;
+    public  $location_id;
+    public  $location_ns;
+    private $_url;
+
+    var $names = array();
+
+    /**
+     * Constructor that makes a Location from a string name
+     *
+     * @param string $name     Human-readable name (any kind)
+     * @param string $language Language, default = common_language()
+     *
+     * @return Location Location with that name (or null if not found)
+     */
+
+    static function fromName($name, $language=null)
+    {
+        if (is_null($language)) {
+            $language = common_language();
+        }
+
+        $location = null;
+
+        // Let a third-party handle it
+
+        Event::handle('LocationFromName', array($name, $language, &$location));
+
+        return $location;
+    }
+
+    /**
+     * Constructor that makes a Location from an ID
+     *
+     * @param integer $id       Identifier ID
+     * @param integer $ns       Namespace of the identifier
+     * @param string  $language Language to return name in (default is common)
+     *
+     * @return Location The location with this ID (or null if none)
+     */
+
+    static function fromId($id, $ns, $language=null)
+    {
+        if (is_null($language)) {
+            $language = common_language();
+        }
+
+        $location = null;
+
+        // Let a third-party handle it
+
+        Event::handle('LocationFromId', array($id, $ns, $language, &$location));
+
+        return $location;
+    }
+
+    /**
+     * Constructor that finds the nearest location to a lat/lon pair
+     *
+     * @param float  $lat      Latitude
+     * @param float  $lon      Longitude
+     * @param string $language Language for results, default = current
+     *
+     * @return Location the location found, or null if none found
+     */
+
+    static function fromLatLon($lat, $lon, $language=null)
+    {
+        if (is_null($language)) {
+            $language = common_language();
+        }
+
+        $location = null;
+
+        // Let a third-party handle it
+
+        if (Event::handle('LocationFromLatLon',
+                          array($lat, $lon, $language, &$location))) {
+            // Default is just the lat/lon pair
+
+            $location = new Location();
+
+            $location->lat = $lat;
+            $location->lon = $lon;
+        }
+
+        return $location;
+    }
+
+    /**
+     * Get the name for this location in the given language
+     *
+     * @param string $language language to use, default = current
+     *
+     * @return string location name or null if not found
+     */
+
+    function getName($language=null)
+    {
+        if (is_null($language)) {
+            $language = common_language();
+        }
+
+        if (array_key_exists($language, $this->names)) {
+            return $this->names[$language];
+        } else {
+            $name = null;
+            Event::handle('LocationNameLanguage', array($this, $language, &$name));
+            if (!empty($name)) {
+                $this->names[$language] = $name;
+                return $name;
+            }
+        }
+    }
+
+    /**
+     * Get an URL suitable for this location
+     *
+     * @return string URL for this location or NULL
+     */
+
+    function getURL()
+    {
+        // Keep one cached
+
+        if (is_string($this->_url)) {
+            return $this->_url;
+        }
+
+        $url = null;
+
+        Event::handle('LocationUrl', array($this, &$url));
+
+        $this->_url = $url;
+
+        return $url;
+    }
+}
diff --git a/lib/mediafile.php b/lib/mediafile.php
new file mode 100644 (file)
index 0000000..29d752f
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Abstraction for media files in general
+ *
+ * TODO: combine with ImageFile?
+ *
+ * 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  Media
+ * @package   StatusNet
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+class MediaFile
+{
+
+    var $filename      = null;
+    var $fileRecord    = null;
+    var $user          = null;
+    var $fileurl       = null;
+    var $short_fileurl = null;
+    var $mimetype      = null;
+
+    function __construct($user = null, $filename = null, $mimetype = null)
+    {
+        if ($user == null) {
+            $this->user = common_current_user();
+        }
+
+        $this->filename   = $filename;
+        $this->mimetype   = $mimetype;
+        $this->fileRecord = $this->storeFile();
+
+        $this->fileurl = common_local_url('attachment',
+                                    array('attachment' => $this->fileRecord->id));
+
+        $this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
+        $this->short_fileurl = common_shorten_url($this->fileurl);
+        $this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
+    }
+
+    function attachToNotice($notice)
+    {
+        File_to_post::processNew($this->fileRecord->id, $notice->id);
+        $this->maybeAddRedir($this->fileRecord->id,
+                             common_local_url('file', array('notice' => $notice->id)));
+    }
+
+    function shortUrl()
+    {
+        return $this->short_fileurl;
+    }
+
+    function delete()
+    {
+        $filepath = File::path($this->filename);
+        @unlink($filepath);
+    }
+
+    function storeFile() {
+
+        $file = new File;
+
+        $file->filename = $this->filename;
+        $file->url      = File::url($this->filename);
+        $filepath       = File::path($this->filename);
+        $file->size     = filesize($filepath);
+        $file->date     = time();
+        $file->mimetype = $this->mimetype;
+
+        $file_id = $file->insert();
+
+        if (!$file_id) {
+            common_log_db_error($file, "INSERT", __FILE__);
+            throw new ClientException(_('There was a database error while saving your file. Please try again.'));
+        }
+
+        return $file;
+    }
+
+    function rememberFile($file, $short)
+    {
+        $this->maybeAddRedir($file->id, $short);
+    }
+
+    function maybeAddRedir($file_id, $url)
+    {
+        $file_redir = File_redirection::staticGet('url', $url);
+
+        if (empty($file_redir)) {
+
+            $file_redir = new File_redirection;
+            $file_redir->url = $url;
+            $file_redir->file_id = $file_id;
+
+            $result = $file_redir->insert();
+
+            if (!$result) {
+                common_log_db_error($file_redir, "INSERT", __FILE__);
+                throw new ClientException(_('There was a database error while saving your file. Please try again.'));
+            }
+        }
+    }
+
+    static function fromUpload($param = 'media', $user = null)
+    {
+        if (empty($user)) {
+            $user = common_current_user();
+        }
+
+        if (!isset($_FILES[$param]['error'])){
+            return;
+        }
+
+        switch ($_FILES[$param]['error']) {
+        case UPLOAD_ERR_OK: // success, jump out
+            break;
+        case UPLOAD_ERR_INI_SIZE:
+            throw new ClientException(_('The uploaded file exceeds the ' .
+                'upload_max_filesize directive in php.ini.'));
+            return;
+        case UPLOAD_ERR_FORM_SIZE:
+            throw new ClientException(
+                _('The uploaded file exceeds the MAX_FILE_SIZE directive' .
+                ' that was specified in the HTML form.'));
+            return;
+        case UPLOAD_ERR_PARTIAL:
+            @unlink($_FILES[$param]['tmp_name']);
+            throw new ClientException(_('The uploaded file was only' .
+                ' partially uploaded.'));
+            return;
+        case UPLOAD_ERR_NO_FILE:
+            // No file; probably just a non-AJAX submission.
+            return;
+        case UPLOAD_ERR_NO_TMP_DIR:
+            throw new ClientException(_('Missing a temporary folder.'));
+            return;
+        case UPLOAD_ERR_CANT_WRITE:
+            throw new ClientException(_('Failed to write file to disk.'));
+            return;
+        case UPLOAD_ERR_EXTENSION:
+            throw new ClientException(_('File upload stopped by extension.'));
+            return;
+        default:
+            common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
+                $_FILES[$param]['error']);
+            throw new ClientException(_('System error uploading file.'));
+            return;
+        }
+
+        if (!MediaFile::respectsQuota($user, $_FILES['attach']['size'])) {
+
+            // Should never actually get here
+
+            @unlink($_FILES[$param]['tmp_name']);
+            throw new ClientException(_('File exceeds user\'s quota!'));
+            return;
+        }
+
+        $mimetype = MediaFile::getUploadedFileType($_FILES[$param]['tmp_name']);
+
+        $filename = null;
+
+        if (isset($mimetype)) {
+
+            $basename = basename($_FILES[$param]['name']);
+            $filename = File::filename($user->getProfile(), $basename, $mimetype);
+            $filepath = File::path($filename);
+
+            $result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);
+
+            if (!$result) {
+                throw new ClientException(_('File could not be moved to destination directory.'));
+                return;
+            }
+
+        } else {
+            throw new ClientException(_('Could not determine file\'s mime-type!'));
+            return;
+        }
+
+        return new MediaFile($user, $filename, $mimetype);
+    }
+
+    static function fromFilehandle($fh, $user) {
+
+        $stream = stream_get_meta_data($fh);
+
+        if (!MediaFile::respectsQuota($user, filesize($stream['uri']))) {
+
+            // Should never actually get here
+
+            throw new ClientException(_('File exceeds user\'s quota!'));
+            return;
+        }
+
+        $mimetype = MediaFile::getUploadedFileType($fh);
+
+        $filename = null;
+
+        if (isset($mimetype)) {
+
+            $filename = File::filename($user->getProfile(), "email", $mimetype);
+
+            $filepath = File::path($filename);
+
+            $result = copy($stream['uri'], $filepath) && chmod($filepath, 0664);
+
+            if (!$result) {
+                throw new ClientException(_('File could not be moved to destination directory.' .
+                    $stream['uri'] . ' ' . $filepath));
+            }
+        } else {
+            throw new ClientException(_('Could not determine file\'s mime-type!'));
+            return;
+        }
+
+        return new MediaFile($user, $filename, $mimetype);
+    }
+
+    static function getUploadedFileType($f) {
+        require_once 'MIME/Type.php';
+
+        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+        $cmd = common_config('attachments', 'filecommand');
+
+        $filetype = null;
+
+        if (is_string($f)) {
+
+            // assuming a filename
+
+            $filetype = MIME_Type::autoDetect($f);
+        } else {
+
+            // assuming a filehandle
+
+            $stream  = stream_get_meta_data($f);
+            $filetype = MIME_Type::autoDetect($stream['uri']);
+        }
+
+        if (in_array($filetype, common_config('attachments', 'supported'))) {
+            return $filetype;
+        }
+        $media = MIME_Type::getMedia($filetype);
+        if ('application' !== $media) {
+            $hint = sprintf(_(' Try using another %s format.'), $media);
+        } else {
+            $hint = '';
+        }
+        throw new ClientException(sprintf(
+            _('%s is not a supported filetype on this server.'), $filetype) . $hint);
+    }
+
+    static function respectsQuota($user, $filesize)
+    {
+        $file = new File;
+        $result = $file->isRespectsQuota($user, $filesize);
+        if ($result === true) {
+            return true;
+        } else {
+            throw new ClientException($result);
+        }
+    }
+
+}
\ No newline at end of file
index e25ebfa08f29a2ecaf7a47ae17ff66156c0a34ca..b034be3122b4400528a194949232d455a8650e74 100644 (file)
@@ -80,10 +80,21 @@ class MessageForm extends Form
     /**
      * ID of the form
      *
-     * @return int ID of the form
+     * @return string ID of the form
      */
 
     function id()
+    {
+        return 'form_notice-direct';
+    }
+
+   /**
+     * Class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
     {
         return 'form_notice';
     }
index 9864d15eb084dfb18fe4df869dddf25ef8ad2cc9..1be011c182fe83ff037500fb8b5cec13053be452 100644 (file)
@@ -105,7 +105,7 @@ class NoticeForm extends Form
     /**
      * ID of the form
      *
-     * @return int ID of the form
+     * @return string ID of the form
      */
 
     function id()
@@ -113,6 +113,17 @@ class NoticeForm extends Form
         return 'form_notice';
     }
 
+   /**
+     * Class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_notice';
+    }
+
     /**
      * Action of the form
      *
index 6c296f82a7bfb154db87c28686b3a3592236de74..8b3015cc3ebf1a3325c5326ceffa6461c3c5419b 100644 (file)
@@ -199,6 +199,7 @@ class NoticeListItem extends Widget
     {
         $this->out->elementStart('div', 'entry-content');
         $this->showNoticeLink();
+        $this->showNoticeLocation();
         $this->showNoticeSource();
         $this->showContext();
         $this->out->elementEnd('div');
@@ -369,6 +370,44 @@ class NoticeListItem extends Widget
         $this->out->elementEnd('a');
     }
 
+    /**
+     * show the notice location
+     *
+     * shows the notice location in the correct language.
+     *
+     * If an URL is available, makes a link. Otherwise, just a span.
+     *
+     * @return void
+     */
+
+    function showNoticeLocation()
+    {
+        $id = $this->notice->id;
+
+        $location = $this->notice->getLocation();
+
+        if (empty($location)) {
+            return;
+        }
+
+        $name = $location->getName();
+
+        if (empty($name)) {
+            // XXX: Could be a translation issue. Fall back to... something?
+            return;
+        }
+
+        $url  = $location->getUrl();
+
+        if (empty($url)) {
+            $this->out->element('span', array('class' => 'location'), $name);
+        } else {
+            $this->out->element('a', array('class' => 'location',
+                                           'href' => $url),
+                                $name);
+        }
+    }
+
     /**
      * Show the source of the notice
      *
index f1827726e71e843d4e2e17daeedc6005308304f0..1a86e2460e77e1598d6331b26423bccd8515d909 100644 (file)
@@ -43,7 +43,7 @@ require_once 'OAuth.php';
  * @link     http://status.net/
  *
  */
-class OAuthClientCurlException extends Exception
+class OAuthClientException extends Exception
 {
 }
 
@@ -97,9 +97,14 @@ class OAuthClient
     function getRequestToken($url)
     {
         $response = $this->oAuthGet($url);
-        parse_str($response);
-        $token = new OAuthToken($oauth_token, $oauth_token_secret);
-        return $token;
+        $arr = array();
+        parse_str($response, $arr);
+        if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) {
+            $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']);
+            return $token;
+        } else {
+            throw new OAuthClientException();
+        }
     }
 
     /**
@@ -177,7 +182,7 @@ class OAuthClient
     }
 
     /**
-     * Make a HTTP request using cURL.
+     * Make a HTTP request.
      *
      * @param string $url    Where to make the
      * @param array  $params post parameters
@@ -186,40 +191,32 @@ class OAuthClient
      */
     function httpRequest($url, $params = null)
     {
-        $options = array(
-            CURLOPT_RETURNTRANSFER => true,
-            CURLOPT_FAILONERROR    => true,
-            CURLOPT_HEADER         => false,
-            CURLOPT_FOLLOWLOCATION => true,
-            CURLOPT_USERAGENT      => 'StatusNet',
-            CURLOPT_CONNECTTIMEOUT => 120,
-            CURLOPT_TIMEOUT        => 120,
-            CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
-            CURLOPT_SSL_VERIFYPEER => false,
-
-            // Twitter is strict about accepting invalid "Expect" headers
-
-            CURLOPT_HTTPHEADER => array('Expect:')
-        );
+        $request = new HTTPClient($url);
+        $request->setConfig(array(
+            'connect_timeout' => 120,
+            'timeout' => 120,
+            'follow_redirects' => true,
+            'ssl_verify_peer' => false,
+        ));
+
+        // Twitter is strict about accepting invalid "Expect" headers
+        $request->setHeader('Expect', '');
 
         if (isset($params)) {
-            $options[CURLOPT_POST]       = true;
-            $options[CURLOPT_POSTFIELDS] = $params;
+            $request->setMethod(HTTP_Request2::METHOD_POST);
+            $request->setBody($params);
         }
 
-        $ch = curl_init($url);
-        curl_setopt_array($ch, $options);
-        $response = curl_exec($ch);
-
-        if ($response === false) {
-            $msg  = curl_error($ch);
-            $code = curl_errno($ch);
-            throw new OAuthClientCurlException($msg, $code);
+        try {
+            $response = $request->send();
+            $code = $response->getStatus();
+            if ($code < 200 || $code >= 400) {
+                throw new OAuthClientException($response->getBody(), $code);
+            }
+            return $response->getBody();
+        } catch (Exception $e) {
+            throw new OAuthClientException($e->getMessage(), $e->getCode());
         }
-
-        curl_close($ch);
-
-        return $response;
     }
 
 }
index 175bf8440b61b3f76608b398a2015112b667e424..5698c403875e52b584b80a5fd5f86d8b93a4699f 100644 (file)
@@ -44,20 +44,16 @@ function ping_broadcast_notice($notice) {
                                                                                                                                array('nickname' => $profile->nickname)),
                                                                                           $tags));
 
-            $context = stream_context_create(array('http' => array('method' => "POST",
-                                                                   'header' =>
-                                                                   "Content-Type: text/xml\r\n".
-                                                                   "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n",
-                                                                   'content' => $req)));
-            $file = file_get_contents($notify_url, false, $context);
+            $request = HTTPClient::start();
+            $httpResponse = $request->post($notify_url, array('Content-Type: text/xml'), $req);
 
-            if ($file === false || mb_strlen($file) == 0) {
+            if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) {
                 common_log(LOG_WARNING,
                            "XML-RPC empty results for ping ($notify_url, $notice->id) ");
                 continue;
             }
 
-            $response = xmlrpc_decode($file);
+            $response = xmlrpc_decode($httpResponse->getBody());
 
             if (is_array($response) && xmlrpc_is_fault($response)) {
                 common_log(LOG_WARNING,
index 4fb0834fd0fdb38e173968a944b9a3a7cf875e92..888cbdd20cf55c2b901a9a9f68a467d0a71a0ac7 100644 (file)
@@ -71,562 +71,561 @@ class Router
     {
         $m = Net_URL_Mapper::getInstance();
 
-        // In the "root"
+        if (Event::handle('StartInitializeRouter', array(&$m))) {
 
-        $m->connect('', array('action' => 'public'));
-        $m->connect('rss', array('action' => 'publicrss'));
-        $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'));
+            // In the "root"
 
-        // docs
+            $m->connect('', array('action' => 'public'));
+            $m->connect('rss', array('action' => 'publicrss'));
+            $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'));
 
-        $m->connect('doc/:title', array('action' => 'doc'));
+            // docs
 
-        // main stuff is repetitive
+            $m->connect('doc/:title', array('action' => 'doc'));
 
-        $main = array('login', 'logout', 'register', 'subscribe',
-                      'unsubscribe', 'confirmaddress', 'recoverpassword',
-                      'invite', 'favor', 'disfavor', 'sup',
-                      'block', 'unblock', 'subedit',
-                      'groupblock', 'groupunblock');
+            // main stuff is repetitive
 
-        foreach ($main as $a) {
-            $m->connect('main/'.$a, array('action' => $a));
-        }
-
-        $m->connect('main/sup/:seconds', array('action' => 'sup'),
-                    array('seconds' => '[0-9]+'));
-
-        $m->connect('main/tagother/:id', array('action' => 'tagother'));
+            $main = array('login', 'logout', 'register', 'subscribe',
+                          'unsubscribe', 'confirmaddress', 'recoverpassword',
+                          'invite', 'favor', 'disfavor', 'sup',
+                          'block', 'unblock', 'subedit',
+                          'groupblock', 'groupunblock');
 
-        $m->connect('main/oembed',
-                    array('action' => 'oembed'));
-
-        // these take a code
-
-        foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
-            $m->connect('main/'.$c.'/:code', array('action' => $c));
-        }
+            foreach ($main as $a) {
+                $m->connect('main/'.$a, array('action' => $a));
+            }
 
-        // exceptional
+            $m->connect('main/sup/:seconds', array('action' => 'sup'),
+                        array('seconds' => '[0-9]+'));
 
-        $m->connect('main/remote', array('action' => 'remotesubscribe'));
-        $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
-
-        foreach (Router::$bare as $action) {
-            $m->connect('index.php?action=' . $action, array('action' => $action));
-        }
-
-        // settings
-
-        foreach (array('profile', 'avatar', 'password', 'im',
-                       'email', 'sms', 'userdesign', '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/'.$s.'?q=:q',
-                        array('action' => $s.'search'),
-                        array('q' => '.+'));
-        }
-
-        // The second of these is needed to make the link work correctly
-        // when inserted into the page. The first is needed to match the
-        // route on the way in. Seems to be another Net_URL_Mapper bug to me.
-        $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
-        $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
-                    array('q' => '.+'));
-
-        $m->connect('attachment/:attachment',
-                    array('action' => 'attachment'),
-                    array('attachment' => '[0-9]+'));
-
-        $m->connect('attachment/:attachment/ajax',
-                    array('action' => 'attachment_ajax'),
-                    array('attachment' => '[0-9]+'));
-
-        $m->connect('attachment/:attachment/thumbnail',
-                    array('action' => 'attachment_thumbnail'),
-                    array('attachment' => '[0-9]+'));
-
-        $m->connect('notice/new', array('action' => 'newnotice'));
-        $m->connect('notice/new?replyto=:replyto',
-                    array('action' => 'newnotice'),
-                    array('replyto' => '[A-Za-z0-9_-]+'));
-        $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
-                    array('action' => 'newnotice'),
-                    array('replyto' => '[A-Za-z0-9_-]+'),
-                    array('inreplyto' => '[0-9]+'));
-
-        $m->connect('notice/:notice/file',
-            array('action' => 'file'),
-            array('notice' => '[0-9]+'));
-
-        $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]+'));
-
-        // conversation
-
-        $m->connect('conversation/:id',
-                    array('action' => 'conversation'),
-                    array('id' => '[0-9]+'));
-
-        $m->connect('message/new', array('action' => 'newmessage'));
-        $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
-        $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' => '[\pL\pN_\-\.]{1,64}'));
-
-        $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', 'designsettings') as $n) {
-            $m->connect('group/:nickname/'.$n,
-                        array('action' => 'group'.$n),
-                        array('nickname' => '[a-zA-Z0-9]+'));
-        }
+            $m->connect('main/tagother/:id', array('action' => 'tagother'));
 
-        $m->connect('group/:nickname/foaf',
-                    array('action' => 'foafgroup'),
-                    array('nickname' => '[a-zA-Z0-9]+'));
-
-        $m->connect('group/:nickname/blocked',
-                    array('action' => 'blockedfromgroup'),
-                    array('nickname' => '[a-zA-Z0-9]+'));
-
-        $m->connect('group/:nickname/makeadmin',
-                    array('action' => 'makeadmin'),
-                    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/public_timeline.:format',
-                    array('action' => 'ApiTimelinePublic',
-                    'format' => '(xml|json|rss|atom)'));
+            $m->connect('main/oembed',
+                        array('action' => 'oembed'));
 
-        $m->connect('api/statuses/friends_timeline.:format',
-                    array('action' => 'ApiTimelineFriends',
-                          'format' => '(xml|json|rss|atom)'));
+            $m->connect('main/xrds',
+                        array('action' => 'publicxrds'));
 
-        $m->connect('api/statuses/friends_timeline/:id.:format',
-                    array('action' => 'ApiTimelineFriends',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json|rss|atom)'));
-        $m->connect('api/statuses/home_timeline.:format',
-                    array('action' => 'ApiTimelineFriends',
-                          'format' => '(xml|json|rss|atom)'));
+            // these take a code
 
-        $m->connect('api/statuses/home_timeline/:id.:format',
-                    array('action' => 'ApiTimelineFriends',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json|rss|atom)'));
+            foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
+                $m->connect('main/'.$c.'/:code', array('action' => $c));
+            }
 
-        $m->connect('api/statuses/user_timeline.:format',
-                    array('action' => 'ApiTimelineUser',
-                    'format' => '(xml|json|rss|atom)'));
+            // exceptional
 
-        $m->connect('api/statuses/user_timeline/:id.:format',
-                    array('action' => 'ApiTimelineUser',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json|rss|atom)'));
+            $m->connect('main/remote', array('action' => 'remotesubscribe'));
+            $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
 
-        $m->connect('api/statuses/mentions.:format',
-                    array('action' => 'ApiTimelineMentions',
-                    'format' => '(xml|json|rss|atom)'));
+            foreach (Router::$bare as $action) {
+                $m->connect('index.php?action=' . $action, array('action' => $action));
+            }
 
-        $m->connect('api/statuses/mentions/:id.:format',
-                    array('action' => 'ApiTimelineMentions',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json|rss|atom)'));
+            // settings
 
-        $m->connect('api/statuses/replies.:format',
-                    array('action' => 'ApiTimelineMentions',
-                    'format' => '(xml|json|rss|atom)'));
-
-        $m->connect('api/statuses/replies/:id.:format',
-                    array('action' => 'ApiTimelineMentions',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json|rss|atom)'));
+            foreach (array('profile', 'avatar', 'password', 'im',
+                           'email', 'sms', 'userdesign', 'other') as $s) {
+                $m->connect('settings/'.$s, array('action' => $s.'settings'));
+            }
 
-        $m->connect('api/statuses/friends.:format',
-                     array('action' => 'ApiUserFriends',
-                           'format' => '(xml|json)'));
+            // search
 
-        $m->connect('api/statuses/friends/:id.:format',
-                    array('action' => 'ApiUserFriends',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json)'));
+            foreach (array('group', 'people', 'notice') as $s) {
+                $m->connect('search/'.$s, array('action' => $s.'search'));
+                $m->connect('search/'.$s.'?q=:q',
+                            array('action' => $s.'search'),
+                            array('q' => '.+'));
+            }
 
-        $m->connect('api/statuses/followers.:format',
-                     array('action' => 'ApiUserFollowers',
-                           'format' => '(xml|json)'));
-
-        $m->connect('api/statuses/followers/:id.:format',
-                    array('action' => 'ApiUserFollowers',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json)'));
-
-        $m->connect('api/statuses/show.:format',
-                    array('action' => 'ApiStatusesShow',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statuses/show/:id.:format',
-                    array('action' => 'ApiStatusesShow',
-                          'id' => '[0-9]+',
-                          'format' => '(xml|json)'));
+            // The second of these is needed to make the link work correctly
+            // when inserted into the page. The first is needed to match the
+            // route on the way in. Seems to be another Net_URL_Mapper bug to me.
+            $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
+            $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
+                        array('q' => '.+'));
 
-        $m->connect('api/statuses/update.:format',
-                    array('action' => 'ApiStatusesUpdate',
-                          'format' => '(xml|json)'));
+            $m->connect('attachment/:attachment',
+                        array('action' => 'attachment'),
+                        array('attachment' => '[0-9]+'));
+
+            $m->connect('attachment/:attachment/ajax',
+                        array('action' => 'attachment_ajax'),
+                        array('attachment' => '[0-9]+'));
+
+            $m->connect('attachment/:attachment/thumbnail',
+                        array('action' => 'attachment_thumbnail'),
+                        array('attachment' => '[0-9]+'));
+
+            $m->connect('notice/new', array('action' => 'newnotice'));
+            $m->connect('notice/new?replyto=:replyto',
+                        array('action' => 'newnotice'),
+                        array('replyto' => '[A-Za-z0-9_-]+'));
+            $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
+                        array('action' => 'newnotice'),
+                        array('replyto' => '[A-Za-z0-9_-]+'),
+                        array('inreplyto' => '[0-9]+'));
+
+            $m->connect('notice/:notice/file',
+                        array('action' => 'file'),
+                        array('notice' => '[0-9]+'));
+
+            $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('bookmarklet/new', array('action' => 'bookmarklet'));
+
+            // conversation
+
+            $m->connect('conversation/:id',
+                        array('action' => 'conversation'),
+                        array('id' => '[0-9]+'));
+
+            $m->connect('message/new', array('action' => 'newmessage'));
+            $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
+            $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' => '[\pL\pN_\-\.]{1,64}'));
+
+            $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', 'designsettings') as $n) {
+                $m->connect('group/:nickname/'.$n,
+                            array('action' => 'group'.$n),
+                            array('nickname' => '[a-zA-Z0-9]+'));
+            }
+
+            $m->connect('group/:nickname/foaf',
+                        array('action' => 'foafgroup'),
+                        array('nickname' => '[a-zA-Z0-9]+'));
 
-        $m->connect('api/statuses/destroy.:format',
-                  array('action' => 'ApiStatusesDestroy',
-                        'format' => '(xml|json)'));
+            $m->connect('group/:nickname/blocked',
+                        array('action' => 'blockedfromgroup'),
+                        array('nickname' => '[a-zA-Z0-9]+'));
 
-        $m->connect('api/statuses/destroy/:id.:format',
-                  array('action' => 'ApiStatusesDestroy',
-                        'id' => '[0-9]+',
-                        'format' => '(xml|json)'));
+            $m->connect('group/:nickname/makeadmin',
+                        array('action' => 'makeadmin'),
+                        array('nickname' => '[a-zA-Z0-9]+'));
 
-        // users
+            $m->connect('group/:id/id',
+                        array('action' => 'groupbyid'),
+                        array('id' => '[0-9]+'));
 
-        $m->connect('api/users/show/:id.:format',
-                    array('action' => 'ApiUserShow',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
+            $m->connect('group/:nickname',
+                        array('action' => 'showgroup'),
+                        array('nickname' => '[a-zA-Z0-9]+'));
 
-        $m->connect('api/users/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'users'),
-                    array('method' => 'show(\.(xml|json))?'));
+            $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/public_timeline.:format',
+                        array('action' => 'ApiTimelinePublic',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/friends_timeline.:format',
+                        array('action' => 'ApiTimelineFriends',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/friends_timeline/:id.:format',
+                        array('action' => 'ApiTimelineFriends',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
+            $m->connect('api/statuses/home_timeline.:format',
+                        array('action' => 'ApiTimelineFriends',
+                              'format' => '(xml|json|rss|atom)'));
 
-        // direct messages
+            $m->connect('api/statuses/home_timeline/:id.:format',
+                        array('action' => 'ApiTimelineFriends',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
 
+            $m->connect('api/statuses/user_timeline.:format',
+                        array('action' => 'ApiTimelineUser',
+                              'format' => '(xml|json|rss|atom)'));
 
-        $m->connect('api/direct_messages.:format',
-                    array('action' => 'ApiDirectMessage',
-                          'format' => '(xml|json|rss|atom)'));
+            $m->connect('api/statuses/user_timeline/:id.:format',
+                        array('action' => 'ApiTimelineUser',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/mentions.:format',
+                        array('action' => 'ApiTimelineMentions',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/mentions/:id.:format',
+                        array('action' => 'ApiTimelineMentions',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/replies.:format',
+                        array('action' => 'ApiTimelineMentions',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statuses/replies/:id.:format',
+                        array('action' => 'ApiTimelineMentions',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
 
-        $m->connect('api/direct_messages/sent.:format',
-                    array('action' => 'ApiDirectMessage',
-                          'format' => '(xml|json|rss|atom)',
-                          'sent' => true));
+            $m->connect('api/statuses/friends.:format',
+                        array('action' => 'ApiUserFriends',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/direct_messages/new.:format',
-                     array('action' => 'ApiDirectMessageNew',
-                           'format' => '(xml|json)'));
+            $m->connect('api/statuses/friends/:id.:format',
+                        array('action' => 'ApiUserFriends',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        // friendships
+            $m->connect('api/statuses/followers.:format',
+                        array('action' => 'ApiUserFollowers',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/show.:format',
-                    array('action' => 'ApiFriendshipsShow',
-                          'format' => '(xml|json)'));
+            $m->connect('api/statuses/followers/:id.:format',
+                        array('action' => 'ApiUserFollowers',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/exists.:format',
-                    array('action' => 'ApiFriendshipsExists',
-                          'format' => '(xml|json)'));
+            $m->connect('api/statuses/show.:format',
+                        array('action' => 'ApiStatusesShow',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/create.:format',
-                    array('action' => 'ApiFriendshipsCreate',
-                          'format' => '(xml|json)'));
+            $m->connect('api/statuses/show/:id.:format',
+                        array('action' => 'ApiStatusesShow',
+                              'id' => '[0-9]+',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/destroy.:format',
-                     array('action' => 'ApiFriendshipsDestroy',
-                          'format' => '(xml|json)'));
+            $m->connect('api/statuses/update.:format',
+                        array('action' => 'ApiStatusesUpdate',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/create/:id.:format',
-                    array('action' => 'ApiFriendshipsCreate',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
+            $m->connect('api/statuses/destroy.:format',
+                        array('action' => 'ApiStatusesDestroy',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/destroy/:id.:format',
-                    array('action' => 'ApiFriendshipsDestroy',
-                    'id' => '[a-zA-Z0-9]+',
-                    'format' => '(xml|json)'));
+            $m->connect('api/statuses/destroy/:id.:format',
+                        array('action' => 'ApiStatusesDestroy',
+                              'id' => '[0-9]+',
+                              'format' => '(xml|json)'));
 
-        // Social graph
+            // users
 
-        $m->connect('api/friends/ids/:id.:format',
-                    array('action' => 'apiFriends',
-                          'ids_only' => true));
+            $m->connect('api/users/show/:id.:format',
+                        array('action' => 'ApiUserShow',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/followers/ids/:id.:format',
-                    array('action' => 'apiFollowers',
-                          'ids_only' => true));
+            // direct messages
 
-        $m->connect('api/friends/ids.:format',
-                    array('action' => 'apiFriends',
-                          'ids_only' => true));
+            $m->connect('api/direct_messages.:format',
+                        array('action' => 'ApiDirectMessage',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/direct_messages/sent.:format',
+                        array('action' => 'ApiDirectMessage',
+                              'format' => '(xml|json|rss|atom)',
+                              'sent' => true));
+
+            $m->connect('api/direct_messages/new.:format',
+                        array('action' => 'ApiDirectMessageNew',
+                              'format' => '(xml|json)'));
+
+            // friendships
 
-        $m->connect('api/followers/ids.:format',
-                     array('action' => 'apiFollowers',
-                          'ids_only' => true));
+            $m->connect('api/friendships/show.:format',
+                        array('action' => 'ApiFriendshipsShow',
+                              'format' => '(xml|json)'));
 
-        // account
+            $m->connect('api/friendships/exists.:format',
+                        array('action' => 'ApiFriendshipsExists',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/account/verify_credentials.:format',
-                    array('action' => 'ApiAccountVerifyCredentials'));
+            $m->connect('api/friendships/create.:format',
+                        array('action' => 'ApiFriendshipsCreate',
+                              'format' => '(xml|json)'));
 
-        // special case where verify_credentials is called w/out a format
+            $m->connect('api/friendships/destroy.:format',
+                        array('action' => 'ApiFriendshipsDestroy',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/account/verify_credentials',
-                    array('action' => 'ApiAccountVerifyCredentials'));
+            $m->connect('api/friendships/create/:id.:format',
+                        array('action' => 'ApiFriendshipsCreate',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/account/rate_limit_status.:format',
-                    array('action' => 'ApiAccountRateLimitStatus'));
+            $m->connect('api/friendships/destroy/:id.:format',
+                        array('action' => 'ApiFriendshipsDestroy',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        // favorites
+            // Social graph
 
-        $m->connect('api/favorites.:format',
-                    array('action' => 'ApiTimelineFavorites',
-                    'format' => '(xml|json|rss|atom)'));
+            $m->connect('api/friends/ids/:id.:format',
+                        array('action' => 'apiFriends',
+                              'ids_only' => true));
 
-        $m->connect('api/favorites/:id.:format',
-                    array('action' => 'ApiTimelineFavorites',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xmljson|rss|atom)'));
+            $m->connect('api/followers/ids/:id.:format',
+                        array('action' => 'apiFollowers',
+                              'ids_only' => true));
 
-        $m->connect('api/favorites/create/:id.:format',
-                    array('action' => 'ApiFavoriteCreate',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
+            $m->connect('api/friends/ids.:format',
+                        array('action' => 'apiFriends',
+                              'ids_only' => true));
 
-        $m->connect('api/favorites/destroy/:id.:format',
-                    array('action' => 'ApiFavoriteDestroy',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
+            $m->connect('api/followers/ids.:format',
+                        array('action' => 'apiFollowers',
+                              'ids_only' => true));
 
-        // notifications
+            // account
 
-        $m->connect('api/notifications/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'favorites'));
+            $m->connect('api/account/verify_credentials.:format',
+                        array('action' => 'ApiAccountVerifyCredentials'));
 
-        // blocks
+            // special case where verify_credentials is called w/out a format
 
-        $m->connect('api/blocks/create/:id.:format',
-                    array('action' => 'ApiBlockCreate',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
+            $m->connect('api/account/verify_credentials',
+                        array('action' => 'ApiAccountVerifyCredentials'));
 
-        $m->connect('api/blocks/destroy/:id.:format',
-                    array('action' => 'ApiBlockDestroy',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
-        // help
+            $m->connect('api/account/rate_limit_status.:format',
+                        array('action' => 'ApiAccountRateLimitStatus'));
 
-        $m->connect('api/help/test.:format',
-                    array('action' => 'ApiHelpTest',
-                          'format' => '(xml|json)'));
+            // favorites
 
-        // statusnet
+            $m->connect('api/favorites.:format',
+                        array('action' => 'ApiTimelineFavorites',
+                              'format' => '(xml|json|rss|atom)'));
 
-        $m->connect('api/statusnet/version.:format',
-                    array('action' => 'ApiStatusnetVersion',
-                          'format' => '(xml|json)'));
+            $m->connect('api/favorites/:id.:format',
+                        array('action' => 'ApiTimelineFavorites',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xmljson|rss|atom)'));
 
-        $m->connect('api/statusnet/config.:format',
-                    array('action' => 'ApiStatusnetConfig',
-                   'format' => '(xml|json)'));
+            $m->connect('api/favorites/create/:id.:format',
+                        array('action' => 'ApiFavoriteCreate',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        // For older methods, we provide "laconica" base action
-
-        $m->connect('api/laconica/version.:format',
-                    array('action' => 'ApiStatusnetVersion',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/laconica/config.:format',
-                    array('action' => 'ApiStatusnetConfig',
-                    'format' => '(xml|json)'));
+            $m->connect('api/favorites/destroy/:id.:format',
+                        array('action' => 'ApiFavoriteDestroy',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+            // blocks
 
-        // Groups and tags are newer than 0.8.1 so no backward-compatibility
-        // necessary
+            $m->connect('api/blocks/create/:id.:format',
+                        array('action' => 'ApiBlockCreate',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
 
-        // Groups
-        //'list' has to be handled differently, as php will not allow a method to be named 'list'
+            $m->connect('api/blocks/destroy/:id.:format',
+                        array('action' => 'ApiBlockDestroy',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+            // help
+
+            $m->connect('api/help/test.:format',
+                        array('action' => 'ApiHelpTest',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/statusnet/groups/timeline/:id.:format',
-                    array('action' => 'ApiTimelineGroup',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xmljson|rss|atom)'));
+            // statusnet
 
-        $m->connect('api/statusnet/groups/show.:format',
-                    array('action' => 'ApiGroupShow',
-                    'format' => '(xml|json)'));
+            $m->connect('api/statusnet/version.:format',
+                        array('action' => 'ApiStatusnetVersion',
+                              'format' => '(xml|json)'));
 
-        $m->connect('api/statusnet/groups/show/:id.:format',
-                    array('action' => 'ApiGroupShow',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/join.:format',
-                    array('action' => 'ApiGroupJoin',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/join/:id.:format',
-                    array('action' => 'ApiGroupJoin',
-                    'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/leave.:format',
-                    array('action' => 'ApiGroupLeave',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/leave/:id.:format',
-                    array('action' => 'ApiGroupLeave',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/is_member.:format',
-                    array('action' => 'ApiGroupIsMember',
-                          'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/list.:format',
-                    array('action' => 'ApiGroupList',
-                          'format' => '(xml|json|rss|atom)'));
-
-        $m->connect('api/statusnet/groups/list/:id.:format',
-                    array('action' => 'ApiGroupList',
-                          'id' => '[a-zA-Z0-9]+',
-                          'format' => '(xml|json|rss|atom)'));
-
-        $m->connect('api/statusnet/groups/list_all.:format',
-                    array('action' => 'ApiGroupListAll',
-                          'format' => '(xml|json|rss|atom)'));
-
-        $m->connect('api/statusnet/groups/membership.:format',
-                    array('action' => 'ApiGroupMembership',
-                         'format' => '(xml|json)'));
-
-        $m->connect('api/statusnet/groups/membership/:id.:format',
-                    array('action' => 'ApiGroupMembership',
-                           'id' => '[a-zA-Z0-9]+',
-                           'format' => '(xml|json)'));
-                           
-        $m->connect('api/statusnet/groups/create.:format',
-                    array('action' => 'ApiGroupCreate',
-                          'format' => '(xml|json)'));
-        // Tags
-        $m->connect('api/statusnet/tags/timeline/:tag.:format',
-                    array('action' => 'ApiTimelineTag',
-                          'format' => '(xmljson|rss|atom)'));
-
-        // search
-        $m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
-        $m->connect('api/search.json', array('action' => 'twitapisearchjson'));
-        $m->connect('api/trends.json', array('action' => 'twitapitrends'));
-
-        // user stuff
-
-        foreach (array('subscriptions', 'subscribers',
-                       'nudge', 'all', 'foaf', 'xrds',
-                       'replies', 'inbox', 'outbox', 'microsummary') as $a) {
-            $m->connect(':nickname/'.$a,
-                        array('action' => $a),
+            $m->connect('api/statusnet/config.:format',
+                        array('action' => 'ApiStatusnetConfig',
+                              'format' => '(xml|json)'));
+
+            // For older methods, we provide "laconica" base action
+
+            $m->connect('api/laconica/version.:format',
+                        array('action' => 'ApiStatusnetVersion',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/laconica/config.:format',
+                        array('action' => 'ApiStatusnetConfig',
+                              'format' => '(xml|json)'));
+
+            // Groups and tags are newer than 0.8.1 so no backward-compatibility
+            // necessary
+
+            // Groups
+            //'list' has to be handled differently, as php will not allow a method to be named 'list'
+
+            $m->connect('api/statusnet/groups/timeline/:id.:format',
+                        array('action' => 'ApiTimelineGroup',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xmljson|rss|atom)'));
+
+            $m->connect('api/statusnet/groups/show.:format',
+                        array('action' => 'ApiGroupShow',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/show/:id.:format',
+                        array('action' => 'ApiGroupShow',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/join.:format',
+                        array('action' => 'ApiGroupJoin',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/join/:id.:format',
+                        array('action' => 'ApiGroupJoin',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/leave.:format',
+                        array('action' => 'ApiGroupLeave',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/leave/:id.:format',
+                        array('action' => 'ApiGroupLeave',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/is_member.:format',
+                        array('action' => 'ApiGroupIsMember',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/list.:format',
+                        array('action' => 'ApiGroupList',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statusnet/groups/list/:id.:format',
+                        array('action' => 'ApiGroupList',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statusnet/groups/list_all.:format',
+                        array('action' => 'ApiGroupListAll',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/statusnet/groups/membership.:format',
+                        array('action' => 'ApiGroupMembership',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/membership/:id.:format',
+                        array('action' => 'ApiGroupMembership',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/statusnet/groups/create.:format',
+                        array('action' => 'ApiGroupCreate',
+                              'format' => '(xml|json)'));
+            // Tags
+            $m->connect('api/statusnet/tags/timeline/:tag.:format',
+                        array('action' => 'ApiTimelineTag',
+                              'format' => '(xmljson|rss|atom)'));
+
+            // search
+            $m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
+            $m->connect('api/search.json', array('action' => 'twitapisearchjson'));
+            $m->connect('api/trends.json', array('action' => 'twitapitrends'));
+
+            $m->connect('getfile/:filename',
+                        array('action' => 'getfile'),
+                        array('filename' => '[A-Za-z0-9._-]+'));
+
+            // user stuff
+
+            foreach (array('subscriptions', 'subscribers',
+                           'nudge', 'all', 'foaf', 'xrds',
+                           '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}'));
-        }
 
-        foreach (array('subscriptions', 'subscribers') as $a) {
-            $m->connect(':nickname/'.$a.'/:tag',
-                        array('action' => $a),
-                        array('tag' => '[a-zA-Z0-9]+',
+            $m->connect(':nickname/avatar/:size',
+                        array('action' => 'avatarbynickname'),
+                        array('size' => '(original|96|48|24)',
                               '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/tag/:tag/rss',
+                        array('action' => 'userrss'),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'),
+                        array('tag' => '[a-zA-Z0-9]+'));
 
-        $m->connect(':nickname/favorites',
-                    array('action' => 'showfavorites'),
-                    array('nickname' => '[a-zA-Z0-9]{1,64}'));
+            $m->connect(':nickname/tag/:tag',
+                        array('action' => 'showstream'),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'),
+                        array('tag' => '[a-zA-Z0-9]+'));
 
-        $m->connect(':nickname/avatar/:size',
-                    array('action' => 'avatarbynickname'),
-                    array('size' => '(original|96|48|24)',
-                          'nickname' => '[a-zA-Z0-9]{1,64}'));
-
-        $m->connect(':nickname/tag/:tag/rss',
-            array('action' => 'userrss'),
-            array('nickname' => '[a-zA-Z0-9]{1,64}'),
-            array('tag' => '[a-zA-Z0-9]+'));
-
-        $m->connect(':nickname/tag/:tag',
-                    array('action' => 'showstream'),
-                    array('nickname' => '[a-zA-Z0-9]{1,64}'),
-                    array('tag' => '[a-zA-Z0-9]+'));
-
-        $m->connect(':nickname',
-                    array('action' => 'showstream'),
-                    array('nickname' => '[a-zA-Z0-9]{1,64}'));
+            $m->connect(':nickname',
+                        array('action' => 'showstream'),
+                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
 
-        Event::handle('RouterInitialized', array($m));
+            Event::handle('RouterInitialized', array($m));
+        }
 
         return $m;
     }
index ede846e5b02e852e5473a336af04160d410188f9..2a10c6b9359d503dce7c7829f8d5aeff2c4b2f10 100644 (file)
@@ -172,26 +172,9 @@ class Snapshot
     {
         // XXX: Use OICU2 and OAuth to make authorized requests
 
-        $postdata = http_build_query($this->stats);
-
-        $opts =
-          array('http' =>
-                array(
-                      'method'  => 'POST',
-                      'header'  => 'Content-type: '.
-                                   'application/x-www-form-urlencoded',
-                      'content' => $postdata,
-                      'user_agent' => 'StatusNet/'.STATUSNET_VERSION
-                      )
-                );
-
-        $context = stream_context_create($opts);
-
         $reporturl = common_config('snapshot', 'reporturl');
-
-        $result = @file_get_contents($reporturl, false, $context);
-
-        return $result;
+        $request = HTTPClient::start();
+        $request->post($reporturl, null, $this->stats);
     }
 
     /**
index 00696583eea7b1d14dcd24fb0332110dd8aa877a..bf7282858abaea20dd4430c4d18780f77d5f9359 100644 (file)
@@ -422,7 +422,7 @@ function common_render_text($text)
 function common_replace_urls_callback($text, $callback, $notice_id = null) {
     // Start off with a regex
     $regex = '#'.
-    '(?:^|[\s\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
+    '(?:^|[\s\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
     '('.
         '(?:'.
             '(?:'. //Known protocols
@@ -452,9 +452,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
         ')'.
         '(?:'.
             '(?:\:\d+)?'. //:port
-            '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@]*)?'. // /path
-            '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@\/]*)?'. // ?query string
-            '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\@/\?\#]*)?'. // #fragment
+            '(?:/[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@]*)?'. // /path
+            '(?:\?[\pN\pL\$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@\/]*)?'. // ?query string
+            '(?:\#[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\@/\?\#]*)?'. // #fragment
         ')(?<![\?\.\,\#\,])'.
     ')'.
     '#ixu';
@@ -480,6 +480,10 @@ function callback_helper($matches, $callback, $notice_id) {
         array(
             'left'=>'{',
             'right'=>'}'
+        ),
+        array(
+            'left'=>'<',
+            'right'=>'>'
         )
     );
     $cannotEndWith=array('.','?',',','#');
@@ -781,12 +785,18 @@ function common_path($relative, $ssl=false)
         if (is_string(common_config('site', 'sslserver')) &&
             mb_strlen(common_config('site', 'sslserver')) > 0) {
             $serverpart = common_config('site', 'sslserver');
-        } else {
+        } else if (common_config('site', 'server')) {
             $serverpart = common_config('site', 'server');
+        } else {
+            common_log(LOG_ERR, 'Site Sever not configured, unable to determine site name.');
         }
     } else {
         $proto = 'http';
-        $serverpart = common_config('site', 'server');
+        if (common_config('site', 'server')) {
+            $serverpart = common_config('site', 'server');
+        } else {
+            common_log(LOG_ERR, 'Site Sever not configured, unable to determine site name.');
+        }
     }
 
     return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
diff --git a/lib/xrdsoutputter.php b/lib/xrdsoutputter.php
new file mode 100644 (file)
index 0000000..4b77ed5
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Low-level generator for HTML
+ *
+ * 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   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2008 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/xmloutputter.php';
+
+/**
+ * Low-level generator for XRDS XML
+ *
+ * @category Output
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      Action
+ * @see      XMLOutputter
+ */
+class XRDSOutputter extends XMLOutputter
+{
+    public function startXRDS()
+    {
+        header('Content-Type: application/xrds+xml');
+        $this->startXML();
+        $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+    }
+    
+    public function endXRDS()
+    {
+        $this->elementEnd('XRDS');
+        $this->endXML();
+    }
+
+    /**
+     * Show service.
+     *
+     * @param string $type    XRDS type
+     * @param string $uri     URI
+     * @param array  $params  type parameters, null by default
+     * @param array  $sigs    type signatures, null by default
+     * @param string $localId local ID, null by default
+     *
+     * @return void
+     */
+    function showXrdsService($type, $uri, $params=null, $sigs=null, $localId=null)
+    {
+        $this->elementStart('Service');
+        if ($uri) {
+            $this->element('URI', null, $uri);
+        }
+        $this->element('Type', null, $type);
+        if ($params) {
+            foreach ($params as $param) {
+                $this->element('Type', null, $param);
+            }
+        }
+        if ($sigs) {
+            foreach ($sigs as $sig) {
+                $this->element('Type', null, $sig);
+            }
+        }
+        if ($localId) {
+            $this->element('LocalID', null, $localId);
+        }
+        $this->elementEnd('Service');
+    }
+}
index c14569746f185f8a547fcadee21aeb71ad8da8fa..51236001aa3fb9aca78ed0a65de62a4a299c7622 100644 (file)
@@ -22,6 +22,7 @@
  * @category  Plugin
  * @package   StatusNet
  * @author    Evan Prodromou <evan@status.net>
+ * @author    Brion Vibber <brion@status.net>
  * @copyright 2009 StatusNet, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://status.net/
@@ -69,14 +70,12 @@ class BlogspamNetPlugin extends Plugin
     {
         $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);
+        $requestBody = xmlrpc_encode_request('testComment', array($args));
+
+        $request = HTTPClient::start();
+        $httpResponse = $request->post($this->baseUrl, array('Content-Type: text/xml'), $requestBody);
+
+        $response = xmlrpc_decode($httpResponse->getBody());
         if (xmlrpc_is_fault($response)) {
             throw new ServerException("$response[faultString] ($response[faultCode])", 500);
         } else {
index fc3adcfadf6be9b26377b44b17fae07f5c6caa38..b68534b24f4e111e97410c24d1284d7f34639940 100644 (file)
@@ -58,7 +58,7 @@ class FacebookPlugin extends Plugin
      * @return boolean hook return
      */
 
-    function onRouterInitialized(&$m)
+    function onRouterInitialized($m)
     {
 
         // Facebook App stuff
index f5ad3d06b4be00efc25c41571ca8138fb5384237..a10fdf90d4e55b5214be9a982d9eef80aa0dbdca 100644 (file)
@@ -83,8 +83,8 @@ class FacebookAction extends Action
     function showStylesheets()
     {
         $this->cssLink('css/display.css', 'base');
-        $this->cssLink('css/display.css',null,'screen, projection, tv');
-        $this->cssLink('css/facebookapp.css', 'base');
+        $this->cssLink('css/display.css', null, 'screen, projection, tv');
+        $this->cssLink('plugins/Facebook/facebookapp.css');
     }
 
     function showScripts()
diff --git a/plugins/Facebook/facebookapp.css b/plugins/Facebook/facebookapp.css
new file mode 100644 (file)
index 0000000..8cd06f7
--- /dev/null
@@ -0,0 +1,115 @@
+* {
+font-size:14px;
+font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
+}
+
+#wrap {
+background-color:#F0F2F5;
+padding-left:1.795%;
+padding-right:1.795%;
+width:auto;
+}
+
+p,label,
+h1,h2,h3,h4,h5,h6 {
+color:#000;
+}
+
+#header {
+width:131%;
+}
+
+#content {
+width:92.7%;
+}
+
+#aside_primary {
+display:none;
+}
+
+#site_nav_local_views a {
+background-color:#D0DFE7;
+}
+#site_nav_local_views a:hover {
+background-color:#FAFBFC;
+}
+
+#form_notice .form_note + label,
+#form_notice #notice_data-attach {
+display:none;
+}
+
+#form_notice #notice_action-submit {
+height:47px !important;
+}
+
+
+span.facebook-button {
+border: 2px solid #aaa;
+padding: 3px;
+display: block;
+float: left;
+margin-right: 20px;
+-moz-border-radius: 4px; 
+border-radius:4px; 
+-webkit-border-radius:4px;
+font-weight: bold;
+background-color:#A9BF4F;
+color:#fff;
+font-size:1.2em
+}
+
+span.facebook-button a { color:#fff }
+
+.facebook_guide {
+margin-bottom:18px;
+}
+.facebook_guide p {
+font-weight:bold;
+}
+
+
+input {
+height:auto !important;
+}
+
+#facebook-friends {
+float:left;
+width:100%;
+}
+
+#facebook-friends li {
+float:left;
+margin-right:2%;
+margin-bottom:11px;
+width:18%;
+height:115px;
+}
+#facebook-friends li a {
+float:left;
+}
+
+#add_to_profile {
+position:absolute;
+right:18px;
+top:10px;
+z-index:2;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt, 
+.notice div.entry-content dd {
+margin-right:5px;
+}
+
+#content_inner p {
+margin-bottom:18px;
+}
+
+#content_inner ul {
+list-style-type:none;
+}
+
+.form_settings label {
+margin-right:18px;
+}
index ecda1717c3e37b2b184a12ce8460e6ab2005fd1c..3380b4c857d4de1f365d5534cf80fbc76ae5c71e 100644 (file)
@@ -105,6 +105,7 @@ class FacebookinviteAction extends FacebookAction
         $multi_params = array('showborder' => 'false');
         $multi_params['actiontext'] = $actiontext;
         $multi_params['bypass'] = 'cancel';
+        $multi_params['cols'] = 4;
 
         // Get a list of users who are already using the app for exclusion
         $exclude_ids = $this->facebook->api_client->friends_getAppUsers();
diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php
new file mode 100644 (file)
index 0000000..e18957c
--- /dev/null
@@ -0,0 +1,305 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to convert string locations to Geonames IDs and vice versa
+ *
+ * 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  Action
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Plugin to convert string locations to Geonames IDs and vice versa
+ *
+ * This handles most of the events that Location class emits. It uses
+ * the geonames.org Web service to convert names like 'Montreal, Quebec, Canada'
+ * into IDs and lat/lon pairs.
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @seeAlso  Location
+ */
+
+class GeonamesPlugin extends Plugin
+{
+    const NAMESPACE = 1;
+
+    /**
+     * convert a name into a Location object
+     *
+     * @param string   $name      Name to convert
+     * @param string   $language  ISO code for anguage the name is in
+     * @param Location &$location Location object (may be null)
+     *
+     * @return boolean whether to continue (results in $location)
+     */
+
+    function onLocationFromName($name, $language, &$location)
+    {
+        $client = HTTPClient::start();
+
+        // XXX: break down a name by commas, narrow by each
+
+        $str = http_build_query(array('maxRows' => 1,
+                                      'q' => $name,
+                                      'lang' => $language,
+                                      'type' => 'json'));
+
+        $result = $client->get('http://ws.geonames.org/search?'.$str);
+
+        if ($result->isOk()) {
+            $rj = json_decode($result->getBody());
+            if (count($rj->geonames) > 0) {
+                $n = $rj->geonames[0];
+
+                $location = new Location();
+
+                $location->lat              = $n->lat;
+                $location->lon              = $n->lng;
+                $location->names[$language] = $n->name;
+                $location->location_id      = $n->geonameId;
+                $location->location_ns      = self::NAMESPACE;
+
+                // handled, don't continue processing!
+                return false;
+            }
+        }
+
+        // Continue processing; we don't have the answer
+        return true;
+    }
+
+    /**
+     * convert an id into a Location object
+     *
+     * @param string   $id        Name to convert
+     * @param string   $ns        Name to convert
+     * @param string   $language  ISO code for language for results
+     * @param Location &$location Location object (may be null)
+     *
+     * @return boolean whether to continue (results in $location)
+     */
+
+    function onLocationFromId($id, $ns, $language, &$location)
+    {
+        if ($ns != self::NAMESPACE) {
+            // It's not one of our IDs... keep processing
+            return true;
+        }
+
+        $client = HTTPClient::start();
+
+        $str = http_build_query(array('geonameId' => $id,
+                                      'lang' => $language));
+
+        $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str);
+
+        if ($result->isOk()) {
+
+            $rj = json_decode($result->getBody());
+
+            if (count($rj->geonames) > 0) {
+
+                $parts = array();
+
+                foreach ($rj->geonames as $level) {
+                    if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) {
+                        $parts[] = $level->name;
+                    }
+                }
+
+                $last = $rj->geonames[count($rj->geonames)-1];
+
+                if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) {
+                    $parts[] = $last->name;
+                }
+
+                $location = new Location();
+
+                $location->location_id      = $last->geonameId;
+                $location->location_ns      = self::NAMESPACE;
+                $location->lat              = $last->lat;
+                $location->lon              = $last->lng;
+                $location->names[$language] = implode(', ', array_reverse($parts));
+            }
+        }
+
+        // We're responsible for this NAMESPACE; nobody else
+        // can resolve it
+
+        return false;
+    }
+
+    /**
+     * convert a lat/lon pair into a Location object
+     *
+     * Given a lat/lon, we try to find a Location that's around
+     * it or nearby. We prefer populated places (cities, towns, villages).
+     *
+     * @param string   $lat       Latitude
+     * @param string   $lon       Longitude
+     * @param string   $language  ISO code for language for results
+     * @param Location &$location Location object (may be null)
+     *
+     * @return boolean whether to continue (results in $location)
+     */
+
+    function onLocationFromLatLon($lat, $lon, $language, &$location)
+    {
+        $client = HTTPClient::start();
+
+        $str = http_build_query(array('lat' => $lat,
+                                      'lng' => $lon,
+                                      'lang' => $language));
+
+        $result =
+          $client->get('http://ws.geonames.org/findNearbyPlaceNameJSON?'.$str);
+
+        if ($result->isOk()) {
+
+            $rj = json_decode($result->getBody());
+
+            if (count($rj->geonames) > 0) {
+
+                $n = $rj->geonames[0];
+
+                $parts = array();
+
+                $location = new Location();
+
+                $parts[] = $n->name;
+
+                if (!empty($n->adminName1)) {
+                    $parts[] = $n->adminName1;
+                }
+
+                if (!empty($n->countryName)) {
+                    $parts[] = $n->countryName;
+                }
+
+                $location->location_id = $n->geonameId;
+                $location->location_ns = self::NAMESPACE;
+                $location->lat         = $lat;
+                $location->lon         = $lon;
+
+                $location->names[$language] = implode(', ', $parts);
+
+                // Success! We handled it, so no further processing
+
+                return false;
+            }
+        }
+
+        // For some reason we don't know, so pass.
+
+        return true;
+    }
+
+    /**
+     * Human-readable name for a location
+     *
+     * Given a location, we try to retrieve a human-readable name
+     * in the target language.
+     *
+     * @param Location $location Location to get the name for
+     * @param string   $language ISO code for language to find name in
+     * @param string   &$name    Place to put the name
+     *
+     * @return boolean whether to continue
+     */
+
+    function onLocationNameLanguage($location, $language, &$name)
+    {
+        if ($location->location_ns != self::NAMESPACE) {
+            // It's not one of our IDs... keep processing
+            return true;
+        }
+
+        $client = HTTPClient::start();
+
+        $str = http_build_query(array('geonameId' => $id,
+                                      'lang' => $language));
+
+        $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str);
+
+        if ($result->isOk()) {
+
+            $rj = json_decode($result->getBody());
+
+            if (count($rj->geonames) > 0) {
+
+                $parts = array();
+
+                foreach ($rj->geonames as $level) {
+                    if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) {
+                        $parts[] = $level->name;
+                    }
+                }
+
+                $last = $rj->geonames[count($rj->geonames)-1];
+
+                if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) {
+                    $parts[] = $last->name;
+                }
+
+                if (count($parts)) {
+                    $name = implode(', ', array_reverse($parts));
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Human-readable name for a location
+     *
+     * Given a location, we try to retrieve a geonames.org URL.
+     *
+     * @param Location $location Location to get the url for
+     * @param string   &$url     Place to put the url
+     *
+     * @return boolean whether to continue
+     */
+
+    function onLocationUrl($location, &$url)
+    {
+        if ($location->location_ns != self::NAMESPACE) {
+            // It's not one of our IDs... keep processing
+            return true;
+        }
+
+        $url = 'http://www.geonames.org/' . $location->location_id;
+
+        // it's been filled, so don't process further.
+        return false;
+    }
+}
index 7665b6c1e4f5b1330148b8ec41a9cd9eed5ab460..852253b02326c91ea2e228b5c40c12a36498836a 100644 (file)
@@ -58,7 +58,10 @@ class LilUrl extends ShortUrlApi
         $y = @simplexml_load_string($response);
         if (!isset($y->body)) return $url;
         $x = $y->body->p[0]->a->attributes();
-        if (isset($x['href'])) return $x['href'];
+        if (isset($x['href'])) {
+            common_log(LOG_INFO, __CLASS__ . ": shortened $url to $x[href]");
+            return $x['href'];
+        }
         return $url;
     }
 }
index 60f7a60c797f60e80b9914f20a1403eefdeb4aaa..915d15c07562c7af3dd6168726a57feebbf2ab62 100644 (file)
@@ -129,18 +129,12 @@ class LinkbackPlugin extends Plugin
             }
         }
 
-        $request = xmlrpc_encode_request('pingback.ping', $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($endpoint, false, $context);
-        if (!$file) {
-            common_log(LOG_WARNING,
-                      "Pingback request failed for '$url' ($endpoint)");
-        } else {
-            $response = xmlrpc_decode($file);
+        $request = HTTPClient::start();
+        try {
+            $response = $request->post($endpoint,
+                array('Content-Type: text/xml'),
+                xmlrpc_encode_request('pingback.ping', $args));
+            $response = xmlrpc_decode($response->getBody());
             if (xmlrpc_is_fault($response)) {
                 common_log(LOG_WARNING,
                        "Pingback error for '$url' ($endpoint): ".
@@ -150,6 +144,9 @@ class LinkbackPlugin extends Plugin
                        "Pingback success for '$url' ($endpoint): ".
                        "'$response'");
             }
+        } catch (HTTP_Request2_Exception $e) {
+            common_log(LOG_WARNING,
+                   "Pingback request failed for '$url' ($endpoint)");
         }
     }
 
index a933a1155510296983c2cd1bf2cdcbc7d2776bb9..2309eea9df9ae5d24728769c30e975a620f43e8e 100644 (file)
@@ -62,17 +62,59 @@ class OpenIDPlugin extends Plugin
      * @return boolean hook return
      */
 
-    function onRouterInitialized(&$m)
+    function onStartInitializeRouter($m)
     {
         $m->connect('main/openid', array('action' => 'openidlogin'));
+        $m->connect('main/openidtrust', array('action' => 'openidtrust'));
         $m->connect('settings/openid', array('action' => 'openidsettings'));
-        $m->connect('xrds', array('action' => 'publicxrds'));
         $m->connect('index.php?action=finishopenidlogin', array('action' => 'finishopenidlogin'));
         $m->connect('index.php?action=finishaddopenid', array('action' => 'finishaddopenid'));
-
+        $m->connect('main/openidserver', array('action' => 'openidserver'));
+        
         return true;
     }
 
+    function onEndPublicXRDS($action, &$xrdsOutputter)
+    {
+        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'));
+        $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
+        //consumer
+        foreach (array('finishopenidlogin', 'finishaddopenid') as $finish) {
+            $xrdsOutputter->showXrdsService(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
+                                common_local_url($finish));
+        }
+        //provider
+        $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/server',
+                            common_local_url('openidserver'),
+                            null,
+                            null,
+                            'http://specs.openid.net/auth/2.0/identifier_select');
+        $xrdsOutputter->elementEnd('XRD');
+    }
+
+    function onEndUserXRDS($action, &$xrdsOutputter)
+    {
+        $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'openid',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'));
+        $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
+        
+        //consumer
+        $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/return_to',
+                            common_local_url('finishopenidlogin'));
+                            
+        //provider
+        $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/signon',
+                            common_local_url('openidserver'),
+                            null,
+                            null,
+                            common_profile_url($action->user->nickname));
+        $xrdsOutputter->elementEnd('XRD');
+    }
+
     function onEndLoginGroupNav(&$action)
     {
         $action_name = $action->trimmed('action');
@@ -107,6 +149,8 @@ class OpenIDPlugin extends Plugin
          case 'XrdsAction':
          case 'PublicxrdsAction':
          case 'OpenidsettingsAction':
+         case 'OpenidserverAction':
+         case 'OpenidtrustAction':
             require_once(INSTALLDIR.'/plugins/OpenID/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
             return false;
          case 'User_openid':
@@ -136,6 +180,7 @@ class OpenIDPlugin extends Plugin
         {
          case 'openidlogin':
          case 'finishopenidlogin':
+         case 'openidserver':
             $login = true;
             return false;
          default:
@@ -150,11 +195,19 @@ class OpenIDPlugin extends Plugin
      * @return void
      */
 
-    function onEndHeadChildren($action)
+    function onEndShowHeadElements($action)
     {
-        // for client side of OpenID authentication
-        $action->element('meta', array('http-equiv' => 'X-XRDS-Location',
-                                       'content' => common_local_url('publicxrds')));
+        if($action instanceof ShowstreamAction){
+            $action->element('link', array('rel' => 'openid2.provider',
+                                           'href' => common_local_url('openidserver')));
+            $action->element('link', array('rel' => 'openid2.local_id',
+                                           'href' => $action->profile->profileurl));
+            $action->element('link', array('rel' => 'openid.server',
+                                           'href' => common_local_url('openidserver')));
+            $action->element('link', array('rel' => 'openid.delegate',
+                                           'href' => $action->profile->profileurl));
+        }
+        return true;
     }
 
     /**
@@ -235,6 +288,14 @@ class OpenIDPlugin extends Plugin
                                    new ColumnDef('created', 'datetime',
                                                  null, false),
                                    new ColumnDef('modified', 'timestamp')));
+        $schema->ensureTable('user_openid_trustroot',
+                             array(new ColumnDef('trustroot', 'varchar',
+                                                 '255', false, 'PRI'),
+                                   new ColumnDef('user_id', 'integer',
+                                                 null, false, 'PRI'),
+                                   new ColumnDef('created', 'datetime',
+                                                 null, false),
+                                   new ColumnDef('modified', 'timestamp')));
         return true;
     }
 }
diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php
new file mode 100644 (file)
index 0000000..4654b72
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Table Definition for user_openid_trustroot
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class User_openid_trustroot extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'user_openid_trustroot';                     // table name
+    public $trustroot;                         // varchar(255) primary_key not_null
+    public $user_id;                         // int(4)  primary_key not_null
+    public $created;                         // datetime()   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=null)
+    { return Memcached_DataObject::staticGet('User_openid_trustroot',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+    
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv);
+    }
+}
index 50a9c15c87a8730e2b9fb7ce11f415e97cb93d32..ff0b451d30e28e4e1b367e666df9894750d99b4a 100644 (file)
@@ -265,7 +265,7 @@ class FinishopenidloginAction extends Action
             $fullname = '';
         }
 
-        if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
+        if (!empty($sreg['email']) && Validate::email($sreg['email'], common_config('email', 'check_domain'))) {
             $email = $sreg['email'];
         } else {
             $email = '';
index 0944117c00fc5e0b2a62401184ac7ea2ac3db822..ff7a9389949c39a0184d354fab0f38b46d0778ec 100644 (file)
@@ -23,6 +23,7 @@ require_once(INSTALLDIR.'/plugins/OpenID/User_openid.php');
 
 require_once('Auth/OpenID.php');
 require_once('Auth/OpenID/Consumer.php');
+require_once('Auth/OpenID/Server.php');
 require_once('Auth/OpenID/SReg.php');
 require_once('Auth/OpenID/MySQLStore.php');
 
@@ -50,6 +51,13 @@ function oid_consumer()
     return $consumer;
 }
 
+function oid_server()
+{
+    $store = oid_store();
+    $server = new Auth_OpenID_Server($store, common_local_url('openidserver'));
+    return $server;
+}
+
 function oid_clear_last()
 {
     oid_set_last('');
@@ -241,7 +249,7 @@ function oid_update_user(&$user, &$sreg)
 
     $orig_user = clone($user);
 
-    if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+    if ($sreg['email'] && Validate::email($sreg['email'], common_config('email', 'check_domain'))) {
         $user->email = $sreg['email'];
     }
 
diff --git a/plugins/OpenID/openidserver.php b/plugins/OpenID/openidserver.php
new file mode 100644 (file)
index 0000000..dab97c9
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Settings for OpenID
+ *
+ * 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  Settings
+ * @package   StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/action.php';
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
+require_once(INSTALLDIR.'/plugins/OpenID/User_openid_trustroot.php');
+
+/**
+ * Settings for OpenID
+ *
+ * Lets users add, edit and delete OpenIDs from their account
+ *
+ * @category Settings
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+class OpenidserverAction extends Action
+{
+    var $oserver;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->oserver = oid_server();
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $request = $this->oserver->decodeRequest();
+        if (in_array($request->mode, array('checkid_immediate',
+            'checkid_setup'))) {
+            $user = common_current_user();
+            if(!$user){
+                if($request->immediate){
+                    //cannot prompt the user to login in immediate mode, so answer false
+                    $response = $this->generateDenyResponse($request);
+                }else{
+                    /* Go log in, and then come back. */
+                    common_set_returnto($_SERVER['REQUEST_URI']);
+                    common_redirect(common_local_url('login'));
+                    return;
+                }
+            }else if(common_profile_url($user->nickname) == $request->identity || $request->idSelect()){
+                $user_openid_trustroot = User_openid_trustroot::pkeyGet(
+                                                array('user_id'=>$user->id, 'trustroot'=>$request->trust_root));
+                if(empty($user_openid_trustroot)){
+                    if($request->immediate){
+                        //cannot prompt the user to trust this trust root in immediate mode, so answer false
+                        $response = $this->generateDenyResponse($request);
+                    }else{
+                        common_ensure_session();
+                        $_SESSION['openid_trust_root'] = $request->trust_root;
+                        $allowResponse = $this->generateAllowResponse($request, $user);
+                        $this->oserver->encodeResponse($allowResponse); //sign the response
+                        $denyResponse = $this->generateDenyResponse($request);
+                        $this->oserver->encodeResponse($denyResponse); //sign the response
+                        $_SESSION['openid_allow_url'] = $allowResponse->encodeToUrl();
+                        $_SESSION['openid_deny_url'] = $denyResponse->encodeToUrl();
+                        //ask the user to trust this trust root
+                        common_redirect(common_local_url('openidtrust'));
+                        return;
+                    }
+                }else{
+                    //user has previously authorized this trust root
+                    $response = $this->generateAllowResponse($request, $user);
+                    //$response = $request->answer(true, null, common_profile_url($user->nickname));
+                }
+            } else if ($request->immediate) {
+                $response = $this->generateDenyResponse($request);
+            } else {
+                //invalid
+                $this->clientError(sprintf(_('You are not authorized to use the identity %s'),$request->identity),$code=403);
+            }
+        } else {
+            $response = $this->oserver->handleRequest($request);
+        }
+
+        if($response){
+            $response = $this->oserver->encodeResponse($response);
+            if ($response->code != AUTH_OPENID_HTTP_OK) {
+                header(sprintf("HTTP/1.1 %d ", $response->code),
+                       true, $response->code);
+            }
+
+            if($response->headers){
+                foreach ($response->headers as $k => $v) {
+                    header("$k: $v");
+                }
+            }
+            $this->raw($response->body);
+        }else{
+            $this->clientError(_('Just an OpenID provider. Nothing to see here, move along...'),$code=500);
+        }
+    }
+
+    function generateAllowResponse($request, $user){
+        $response = $request->answer(true, null, common_profile_url($user->nickname));
+        
+        $profile = $user->getProfile();
+        $sreg_data = array(
+            'fullname' => $profile->fullname,
+            'nickname' => $user->nickname,
+            'email' => $user->email,
+            'language' => $user->language,
+            'timezone' => $user->timezone);
+        $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
+        $sreg_response = Auth_OpenID_SRegResponse::extractResponse(
+                              $sreg_request, $sreg_data);
+        $sreg_response->toMessage($response->fields);
+        return $response;
+    }
+
+    function generateDenyResponse($request){
+        $response = $request->answer(false);
+        return $response;
+    }
+}
diff --git a/plugins/OpenID/openidtrust.php b/plugins/OpenID/openidtrust.php
new file mode 100644 (file)
index 0000000..29c7bdc
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
+require_once(INSTALLDIR.'/plugins/OpenID/User_openid_trustroot.php');
+
+class OpenidtrustAction extends Action
+{
+    var $trust_root;
+    var $allowUrl;
+    var $denyUrl;
+    var $user;
+
+    /**
+     * Is this a read-only action?
+     *
+     * @return boolean false
+     */
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title of the page
+     */
+
+    function title()
+    {
+        return _('OpenID Identity Verification');
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        common_ensure_session();
+        $this->user = common_current_user();
+        if(empty($this->user)){
+            /* Go log in, and then come back. */
+            common_set_returnto($_SERVER['REQUEST_URI']);
+            common_redirect(common_local_url('login'));
+            return;
+        }
+        $this->trust_root = $_SESSION['openid_trust_root'];
+        $this->allowUrl = $_SESSION['openid_allow_url'];
+        $this->denyUrl = $_SESSION['openid_deny_url'];
+        if(empty($this->trust_root) || empty($this->allowUrl) || empty($this->denyUrl)){
+            $this->clientError(_('This page should only be reached during OpenID processing, not directly.'));
+            return;
+        }
+        return true;
+    }
+    
+    function handle($args)
+    {
+        parent::handle($args);
+        if($_SERVER['REQUEST_METHOD'] == 'POST'){
+            $this->handleSubmit();
+        }else{
+            $this->showPage();
+        }
+    }
+
+    function handleSubmit()
+    {
+        unset($_SESSION['openid_trust_root']);
+        unset($_SESSION['openid_allow_url']);
+        unset($_SESSION['openid_deny_url']);
+        if($this->arg('allow'))
+        {
+            //save to database
+            $user_openid_trustroot = new User_openid_trustroot();
+            $user_openid_trustroot->user_id = $this->user->id;
+            $user_openid_trustroot->trustroot = $this->trust_root;
+            $user_openid_trustroot->created = DB_DataObject_Cast::dateTime();
+            if (!$user_openid_trustroot->insert()) {
+                $err = PEAR::getStaticProperty('DB_DataObject','lastError');
+                common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
+            }
+            common_redirect($this->allowUrl, $code=302);
+        }else{
+            common_redirect($this->denyUrl, $code=302);
+        }
+    }
+
+    /**
+     * Show page notice
+     *
+     * Display a notice for how to use the page, or the
+     * error if it exists.
+     *
+     * @return void
+     */
+
+    function showPageNotice()
+    {
+        $this->element('p',null,sprintf(_('%s  has asked to verify your identity. Click Continue to verify your identity and login without creating a new password.'),$this->trust_root));
+    }
+
+    /**
+     * Core of the display code
+     *
+     * Shows the login form.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $this->elementStart('form', array('method' => 'post',
+                                   'id' => 'form_openidtrust',
+                                   'class' => 'form_settings',
+                                   'action' => common_local_url('openidtrust')));
+        $this->elementStart('fieldset');
+        $this->submit('allow', _('Continue'));
+        $this->submit('deny', _('Cancel'));
+        
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+}
diff --git a/plugins/OpenID/publicxrds.php b/plugins/OpenID/publicxrds.php
deleted file mode 100644 (file)
index 1b2b359..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-
-/**
- * Public XRDS for OpenID
- *
- * PHP version 5
- *
- * @category Action
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, 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('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/plugins/OpenID/openid.php';
-
-/**
- * Public XRDS for OpenID
- *
- * @category Action
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @author   Robin Millette <millette@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * @todo factor out similarities with XrdsAction
- */
-class PublicxrdsAction extends Action
-{
-    /**
-     * Is read only?
-     *
-     * @return boolean true
-     */
-    function isReadOnly($args)
-    {
-        return true;
-    }
-
-    /**
-     * Class handler.
-     *
-     * @param array $args array of arguments
-     *
-     * @return nothing
-     */
-    function handle($args)
-    {
-        parent::handle($args);
-        header('Content-Type: application/xrds+xml');
-        $this->startXML();
-        $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        foreach (array('finishopenidlogin', 'finishaddopenid') as $finish) {
-            $this->showService(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
-                                common_local_url($finish));
-        }
-        $this->elementEnd('XRD');
-        $this->elementEnd('XRDS');
-        $this->endXML();
-    }
-
-    /**
-     * Show service.
-     *
-     * @param string $type    XRDS type
-     * @param string $uri     URI
-     * @param array  $params  type parameters, null by default
-     * @param array  $sigs    type signatures, null by default
-     * @param string $localId local ID, null by default
-     *
-     * @return void
-     */
-    function showService($type, $uri, $params=null, $sigs=null, $localId=null)
-    {
-        $this->elementStart('Service');
-        if ($uri) {
-            $this->element('URI', null, $uri);
-        }
-        $this->element('Type', null, $type);
-        if ($params) {
-            foreach ($params as $param) {
-                $this->element('Type', null, $param);
-            }
-        }
-        if ($sigs) {
-            foreach ($sigs as $sig) {
-                $this->element('Type', null, $sig);
-            }
-        }
-        if ($localId) {
-            $this->element('LocalID', null, $localId);
-        }
-        $this->elementEnd('Service');
-    }
-}
-
index e1e82e352c58e56ed704387daf490e78be6a639f..d15a869cba3b9f967736cb73e5f357f64cd5d4be 100644 (file)
@@ -65,21 +65,21 @@ class PubSubHubBubPlugin extends Plugin
         $feeds = array();
 
         //public timeline feeds
-        $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'public_timeline.rss'));
-        $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'public_timeline.atom'));
+        $feeds[]=common_local_url('ApiTimelinePublic',array('format' => 'rss'));
+        $feeds[]=common_local_url('ApiTimelinePublic',array('format' => 'atom'));
 
         //author's own feeds
         $user = User::staticGet('id',$notice->profile_id);
-        $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
-        $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
+        $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'rss'));
+        $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'atom'));
 
         //tag feeds
         $tag = new Notice_tag();
         $tag->notice_id = $notice->id;
         if ($tag->find()) {
             while ($tag->fetch()) {
-                $feeds[]=common_local_url('api',array('apiaction' => 'tags','method' => 'timeline', 'argument'=>$tag->tag.'.atom'));
-                $feeds[]=common_local_url('api',array('apiaction' => 'tags','method' => 'timeline', 'argument'=>$tag->tag.'.rss'));
+                $feeds[]=common_local_url('ApiTimelineTag',array('tag'=>$tag->tag, 'format'=>'rss'));
+                $feeds[]=common_local_url('ApiTimelineTag',array('tag'=>$tag->tag, 'format'=>'atom'));
             }
         }
 
@@ -89,8 +89,8 @@ class PubSubHubBubPlugin extends Plugin
         if ($group_inbox->find()) {
             while ($group_inbox->fetch()) {
                 $group = User_group::staticGet('id',$group_inbox->group_id);
-                $feeds[]=common_local_url('api',array('apiaction' => 'groups','method' => 'timeline','argument' => $group->nickname.'.rss'));
-                $feeds[]=common_local_url('api',array('apiaction' => 'groups','method' => 'timeline','argument' => $group->nickname.'.atom'));
+                $feeds[]=common_local_url('ApiTimelineGroup',array('id' => $group->nickname,'format'=>'rss'));
+                $feeds[]=common_local_url('ApiTimelineGroup',array('id' => $group->nickname,'format'=>'atom'));
             }
         }
 
@@ -100,18 +100,17 @@ class PubSubHubBubPlugin extends Plugin
         if ($notice_inbox->find()) {
             while ($notice_inbox->fetch()) {
                 $user = User::staticGet('id',$notice_inbox->user_id);
-                $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
-                $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
+                $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'rss'));
+                $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'atom'));
             }
         }
 
-        /* TODO: when the reply page gets RSS and ATOM feeds, implement this
         //feed of user replied to
         if($notice->reply_to){
                 $user = User::staticGet('id',$notice->reply_to);
-                $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.rss'));
-                $feeds[]=common_local_url('api',array('apiaction' => 'statuses','method' => 'user_timeline','argument' => $user->nickname.'.atom'));
-        }*/
+                $feeds[]=common_local_url('ApiTimelineMentions',array('id' => $user->nickname,'format'=>'rss'));
+                $feeds[]=common_local_url('ApiTimelineMentions',array('id' => $user->nickname,'format'=>'atom'));
+        }
 
         foreach(array_unique($feeds) as $feed){
             if(! $publisher->publish_update($feed)){
index 82d7720487f9d005a82542bea19258c6500d4cbd..d59d63e47c9f99b60279b2b6ef8cf3e388518458 100644 (file)
@@ -65,15 +65,6 @@ class SimpleUrlPlugin extends Plugin
 class SimpleUrl extends ShortUrlApi
 {
     protected function shorten_imp($url) {
-        $curlh = curl_init();
-        curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
-        curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet');
-        curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
-        curl_setopt($curlh, CURLOPT_URL, $this->service_url.urlencode($url));
-        $short_url = curl_exec($curlh);
-
-        curl_close($curlh);
-        return $short_url;
+        return $this->http_get($url);
     }
 }
index cfa05116251bda9e6a09aae6d6ae465b70ea2d99..5f3ad81f526a238ad7fadd5b930d09469d88de54 100644 (file)
@@ -32,7 +32,7 @@ class TemplatePlugin extends Plugin {
   // capture the RouterInitialized event
   // and connect a new API method
   // for updating the template
-  function onRouterInitialized( &$m ) {
+  function onRouterInitialized( $m ) {
     $m->connect( 'template/update', array(
       'action'      => 'template',
     ));
index 09352188ed669965a9b51ece9ac848811c58915d..d3bcda5984d7c432c93dbb80ca2d9ee4eb49ed5a 100644 (file)
@@ -11,8 +11,7 @@ Installation
 
 To enable the plugin, add the following to your config.php:
 
-    require_once(INSTALLDIR . '/plugins/TwitterBridge/TwitterBridgePlugin.php');
-    $tb = new TwitterBridgePlugin();
+    addPlugin("TwitterBridge");
 
 OAuth is used to to access protected resources on Twitter (as opposed to
 HTTP Basic Auth)*.  To use Twitter bridging you will need to register
index e69567fc7bedcba7b2c487035743926cef8d4baf..ad3c2e551cb0291490b233f5412f9c7a5982d4c0 100644 (file)
@@ -60,12 +60,12 @@ class TwitterBridgePlugin extends Plugin
      *
      * Hook for RouterInitialized event.
      *
-     * @param Net_URL_Mapper &$m path-to-action mapper
+     * @param Net_URL_Mapper $m path-to-action mapper
      *
      * @return boolean hook return
      */
 
-    function onRouterInitialized(&$m)
+    function onRouterInitialized($m)
     {
         $m->connect('twitter/authorization',
                     array('action' => 'twitterauthorization'));
index ed2bf48a224f37f3251e48aa7dfc9ea36a7827d9..671e3c7afa35cb721a8195aadcc2953fd414dbca 100755 (executable)
@@ -152,8 +152,8 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon
             $friends_ids = $client->friendsIds();
         } catch (Exception $e) {
             common_log(LOG_WARNING, $this->name() .
-                       ' - cURL error getting friend ids ' .
-                       $e->getCode() . ' - ' . $e->getMessage());
+                       ' - error getting friend ids: ' .
+                       $e->getMessage());
             return $friends;
         }
 
index 81bbbc7c5f765f3022b0492115ef29653575a39b..b5428316bdd3915c2d82900a91252dd1135c613c 100755 (executable)
@@ -109,12 +109,16 @@ class TwitterStatusFetcher extends ParallelizingDaemon
         $flink->find();
 
         $flinks = array();
+        common_log(LOG_INFO, "hello");
 
         while ($flink->fetch()) {
 
             if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
                 FOREIGN_NOTICE_RECV) {
                 $flinks[] = clone($flink);
+                common_log(LOG_INFO, "sync: foreign id $flink->foreign_id");
+            } else {
+                common_log(LOG_INFO, "nothing to sync");
             }
         }
 
@@ -515,31 +519,32 @@ class TwitterStatusFetcher extends ParallelizingDaemon
         return $id;
     }
 
+    /**
+     * Fetch a remote avatar image and save to local storage.
+     *
+     * @param string $url avatar source URL
+     * @param string $filename bare local filename for download
+     * @return bool true on success, false on failure
+     */
     function fetchAvatar($url, $filename)
     {
-        $avatarfile = Avatar::path($filename);
+        common_debug($this->name() . " - Fetching Twitter avatar: $url");
 
-        $out = fopen($avatarfile, 'wb');
-        if (!$out) {
-            common_log(LOG_WARNING, $this->name() .
-                       " - Couldn't open file $filename");
+        $request = HTTPClient::start();
+        $response = $request->get($url);
+        if ($response->isOk()) {
+            $avatarfile = Avatar::path($filename);
+            $ok = file_put_contents($avatarfile, $response->getBody());
+            if (!$ok) {
+                common_log(LOG_WARNING, $this->name() .
+                           " - Couldn't open file $filename");
+                return false;
+            }
+        } else {
             return false;
         }
 
-        common_debug($this->name() . " - Fetching Twitter avatar: $url");
-
-        $ch = curl_init();
-        curl_setopt($ch, CURLOPT_URL, $url);
-        curl_setopt($ch, CURLOPT_FILE, $out);
-        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
-        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
-        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
-        $result = curl_exec($ch);
-        curl_close($ch);
-
-        fclose($out);
-
-        return $result;
+        return true;
     }
 }
 
index 1a5248a9b92fc6d6a0e35d033d1655aa65cf13dc..3c6803e49aa8d6985e591f03f873e3850782176d 100644 (file)
@@ -215,7 +215,7 @@ function broadcast_basicauth($notice, $flink)
 
     try {
         $status = $client->statusesUpdate($statustxt);
-    } catch (BasicAuthCurlException $e) {
+    } catch (HTTP_Request2_Exception $e) {
         return process_error($e, $flink);
     }
 
index 2a93ff13e20fc306a963951da7eb0fb8ceae3b0a..f1daefab129584300422952a09f79cea3a2efca0 100644 (file)
@@ -125,7 +125,7 @@ class TwitterauthorizationAction extends Action
 
             $auth_link = $client->getAuthorizeLink($req_tok);
 
-        } catch (TwitterOAuthClientException $e) {
+        } catch (OAuthClientException $e) {
             $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
                            $e->getCode(), $e->getMessage());
             $this->serverError(_('Couldn\'t link your Twitter account.'));
index 1040d72fb6229d1bbc8a78c969bfd464eb0119e2..d1cf45aec608f0d0f4d9836d008f6c4feb724a95 100644 (file)
@@ -31,26 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-/**
- * Exception wrapper for cURL errors
- *
- * @category Integration
- * @package  StatusNet
- * @author Adrian Lang <mail@adrianlang.de>
- * @author Brenda Wallace <shiny@cpan.org>
- * @author Craig Andrews <candrews@integralblue.com>
- * @author Dan Moore <dan@moore.cx>
- * @author Evan Prodromou <evan@status.net>
- * @author mEDI <medi@milaro.net>
- * @author Sarven Capadisli <csarven@status.net>
- * @author Zach Copley <zach@status.net> * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- *
- */
-class BasicAuthCurlException extends Exception
-{
-}
-
 /**
  * Class for talking to the Twitter API with HTTP Basic Auth.
  *
@@ -198,45 +178,27 @@ class TwitterBasicAuthClient
      */
     function httpRequest($url, $params = null, $auth = true)
     {
-        $options = array(
-                         CURLOPT_RETURNTRANSFER => true,
-                         CURLOPT_FAILONERROR    => true,
-                         CURLOPT_HEADER         => false,
-                         CURLOPT_FOLLOWLOCATION => true,
-                         CURLOPT_USERAGENT      => 'StatusNet',
-                         CURLOPT_CONNECTTIMEOUT => 120,
-                         CURLOPT_TIMEOUT        => 120,
-                         CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
-                         CURLOPT_SSL_VERIFYPEER => false,
-
-                         // Twitter is strict about accepting invalid "Expect" headers
-
-                         CURLOPT_HTTPHEADER => array('Expect:')
-                         );
-
-        if (isset($params)) {
-            $options[CURLOPT_POST]       = true;
-            $options[CURLOPT_POSTFIELDS] = $params;
-        }
+        $request = HTTPClient::start();
+        $request->setConfig(array(
+            'follow_redirects' => true,
+            'connect_timeout' => 120,
+            'timeout' => 120,
+            'ssl_verifypeer' => false,
+        ));
 
         if ($auth) {
-            $options[CURLOPT_USERPWD] = $this->screen_name .
-              ':' . $this->password;
+            $request->setAuth($this->screen_name, $this->password);
         }
 
-        $ch = curl_init($url);
-        curl_setopt_array($ch, $options);
-        $response = curl_exec($ch);
-
-        if ($response === false) {
-            $msg  = curl_error($ch);
-            $code = curl_errno($ch);
-            throw new BasicAuthCurlException($msg, $code);
+        if (isset($params)) {
+            // Twitter is strict about accepting invalid "Expect" headers
+            $headers = array('Expect:');
+            $response = $request->post($url, $headers, $params);
+        } else {
+            $response = $request->get($url);
         }
 
-        curl_close($ch);
-
-        return $response;
+        return $response->getBody();
     }
 
 }
index 0c5649aa45cf5dad291210ee022a46d168d2e77d..334fc13ba1f03cce143c113376af9f2d0a482ba9 100644 (file)
@@ -68,14 +68,13 @@ class WikiHashtagsPlugin extends Plugin
                 $editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit',
                                    urlencode($tag));
 
-                $context = stream_context_create(array('http' => array('method' => "GET",
-                                                                       'header' =>
-                                                                       "User-Agent: " . $this->userAgent())));
-                $html = @file_get_contents($url, false, $context);
+                $request = HTTPClient::start();
+                $response = $request->get($url);
+                $html = $response->getBody();
 
                 $action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section'));
 
-                if (!empty($html)) {
+                if ($response->isOk() && !empty($html)) {
                     $action->element('style', null,
                                      "span.editsection { display: none }\n".
                                      "table.toc { display: none }");
@@ -100,10 +99,4 @@ class WikiHashtagsPlugin extends Plugin
 
         return true;
     }
-
-    function userAgent()
-    {
-        return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION .
-          ' StatusNet/' . STATUSNET_VERSION;
-    }
 }
index 08f733b07c5856041f5264642008463e7978a4d9..afcac539a66c1380e5cbf13c96a8ad4bc9cf94ad 100755 (executable)
@@ -46,8 +46,8 @@ class EnjitQueueHandler extends QueueHandler
 
     function start()
     {
-                $this->log(LOG_INFO, "Starting EnjitQueueHandler");
-                $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
+        $this->log(LOG_INFO, "Starting EnjitQueueHandler");
+        $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
         return true;
     }
 
@@ -56,16 +56,16 @@ class EnjitQueueHandler extends QueueHandler
 
         $profile = Profile::staticGet($notice->profile_id);
 
-                $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);
+        $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);
 
-                if ( ! $notice->is_local ) {
-                    $this->log(LOG_INFO, "Skipping remote notice");
-                    return "skipped";
-                }
+        if ( ! $notice->is_local ) {
+            $this->log(LOG_INFO, "Skipping remote notice");
+            return "skipped";
+        }
 
-                #
-                # Build an Atom message from the notice
-                #
+        #
+        # Build an Atom message from the notice
+        #
         $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
         $msg = $profile->nickname . ': ' . $notice->content;
 
@@ -86,36 +86,18 @@ class EnjitQueueHandler extends QueueHandler
         $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
         $atom .= "</entry>\n";
 
-                $url  = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
-                $data = "msg=$atom";
+        $url  = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
+        $data = array(
+            'msg' => $atom,
+        );
 
-                #
-                # POST the message to $config['enjit']['apiurl']
-                #
-        $ch   = curl_init();
+        #
+        # POST the message to $config['enjit']['apiurl']
+        #
+        $request = HTTPClient::start();
+        $response = $request->post($url, null, $data);
 
-        curl_setopt($ch, CURLOPT_URL, $url);
-
-                curl_setopt($ch, CURLOPT_HEADER, 1);
-        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-        curl_setopt($ch, CURLOPT_POST, 1) ;
-        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
-
-                # SSL and Debugging options
-                #
-        # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-        # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-                # curl_setopt($ch, CURLOPT_VERBOSE, 1);
-
-        $result = curl_exec($ch);
-
-        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE );
-
-                $this->log(LOG_INFO, "Response Code: $code");
-
-        curl_close($ch);
-
-                return $code;
+        return $response->isOk();
     }
 
 }
index 84dff8912ead1de3eb781406a702716ee99f565e..b4e4d9f08df7d5a1610a772eeffb3d9f31c62993 100755 (executable)
@@ -29,6 +29,7 @@ END_OF_HELP;
 require_once INSTALLDIR.'/scripts/commandline.inc';
 
 require_once(INSTALLDIR . '/lib/mail.php');
+require_once(INSTALLDIR . '/lib/mediafile.php');
 require_once('Mail/mimeDecode.php');
 
 # FIXME: we use both Mail_mimeDecode and mailparse
@@ -71,43 +72,27 @@ class MailerDaemon
                                           'Max notice size is %d chars.'),
                                         Notice::maxContent()));
         }
-        $fileRecords = array();
-        foreach($attachments as $attachment){
-            $mimetype = $this->getUploadedFileType($attachment);
-            $stream  = stream_get_meta_data($attachment);
-            if (!$this->isRespectsQuota($user,filesize($stream['uri']))) {
-                die('error() should trigger an exception before reaching here.');
-            }
-            $filename = $this->saveFile($user, $attachment,$mimetype);
-
-            fclose($attachment);
-
-            if (empty($filename)) {
-                $this->error($from,_('Couldn\'t save file.'));
-            }
 
-            $fileRecord = $this->storeFile($filename, $mimetype);
-            $fileRecords[] = $fileRecord;
-            $fileurl = common_local_url('attachment',
-                array('attachment' => $fileRecord->id));
+        $mediafiles = array();
 
-            // not sure this is necessary -- Zach
-            $this->maybeAddRedir($fileRecord->id, $fileurl);
+        foreach($attachments as $attachment){
 
-            $short_fileurl = common_shorten_url($fileurl);
-            $msg .= ' ' . $short_fileurl;
+            $mf = null;
 
-            if (Notice::contentTooLong($msg)) {
-                $this->deleteFile($filename);
-                $this->error($from, sprintf(_('Max notice size is %d chars, including attachment URL.'),
-                                            Notice::maxContent()));
+            try {
+                $mf = MediaFile::fromFileHandle($attachment, $user);
+            } catch(ClientException $ce) {
+                $this->error($from, $ce->getMessage());
             }
 
-            // Also, not sure this is necessary -- Zach
-            $this->maybeAddRedir($fileRecord->id, $short_fileurl);
+            $msg .= ' ' . $mf->shortUrl();
+
+            array_push($mediafiles, $mf);
+            fclose($attachment);
         }
 
-        $err = $this->add_notice($user, $msg, $fileRecords);
+        $err = $this->add_notice($user, $msg, $mediafiles);
+
         if (is_string($err)) {
             $this->error($from, $err);
             return false;
@@ -116,89 +101,6 @@ class MailerDaemon
         }
     }
 
-    function saveFile($user, $attachment, $mimetype) {
-
-        $filename = File::filename($user->getProfile(), "email", $mimetype);
-
-        $filepath = File::path($filename);
-
-        $stream  = stream_get_meta_data($attachment);
-        if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) {
-            return $filename;
-        } else {
-            $this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath));
-        }
-    }
-
-    function storeFile($filename, $mimetype) {
-
-        $file = new File;
-        $file->filename = $filename;
-
-        $file->url = File::url($filename);
-
-        $filepath = File::path($filename);
-
-        $file->size = filesize($filepath);
-        $file->date = time();
-        $file->mimetype = $mimetype;
-
-        $file_id = $file->insert();
-
-        if (!$file_id) {
-            common_log_db_error($file, "INSERT", __FILE__);
-            $this->error(null,_('There was a database error while saving your file. Please try again.'));
-        }
-
-        return $file;
-    }
-
-    function maybeAddRedir($file_id, $url)
-    {
-        $file_redir = File_redirection::staticGet('url', $url);
-
-        if (empty($file_redir)) {
-            $file_redir = new File_redirection;
-            $file_redir->url = $url;
-            $file_redir->file_id = $file_id;
-
-            $result = $file_redir->insert();
-
-            if (!$result) {
-                common_log_db_error($file_redir, "INSERT", __FILE__);
-                $this->error(null,_('There was a database error while saving your file. Please try again.'));
-            }
-        }
-    }
-
-    function getUploadedFileType($fileHandle) {
-        require_once 'MIME/Type.php';
-
-        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
-        $cmd = common_config('attachments', 'filecommand');
-
-        $stream  = stream_get_meta_data($fileHandle);
-        $filetype = MIME_Type::autoDetect($stream['uri']);
-        if (in_array($filetype, common_config('attachments', 'supported'))) {
-            return $filetype;
-        }
-        $media = MIME_Type::getMedia($filetype);
-        if ('application' !== $media) {
-            $hint = sprintf(_(' Try using another %s format.'), $media);
-        } else {
-            $hint = '';
-        }
-        $this->error(null,sprintf(
-            _('%s is not a supported filetype on this server.'), $filetype) . $hint);
-    }
-
-    function isRespectsQuota($user,$fileSize) {
-        $file = new File;
-        $ret = $file->isRespectsQuota($user,$fileSize);
-        if (true === $ret) return true;
-        $this->error(null,$ret);
-    }
-
     function error($from, $msg)
     {
         file_put_contents("php://stderr", $msg . "\n");
@@ -258,7 +160,7 @@ class MailerDaemon
         common_log($level, 'MailDaemon: '.$msg);
     }
 
-    function add_notice($user, $msg, $fileRecords)
+    function add_notice($user, $msg, $mediafiles)
     {
         try {
             $notice = Notice::saveNew($user->id, $msg, 'mail');
@@ -266,8 +168,8 @@ class MailerDaemon
             $this->log(LOG_ERR, $e->getMessage());
             return $e->getMessage();
         }
-        foreach($fileRecords as $fileRecord){
-            $this->attachFile($notice, $fileRecord);
+        foreach($mediafiles as $mf){
+            $mf->attachToNotice($notice);
         }
         common_broadcast_notice($notice);
         $this->log(LOG_INFO,
@@ -275,14 +177,6 @@ class MailerDaemon
         return true;
     }
 
-    function attachFile($notice, $filerec)
-    {
-        File_to_post::processNew($filerec->id, $notice->id);
-
-        $this->maybeAddRedir($filerec->id,
-            common_local_url('file', array('notice' => $notice->id)));
-    }
-
     function parse_message($fname)
     {
         $contents = file_get_contents($fname);
diff --git a/tests/LocationTest.php b/tests/LocationTest.php
new file mode 100644 (file)
index 0000000..62849eb
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+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('STATUSNET', true);
+
+require_once INSTALLDIR . '/lib/common.php';
+
+// Make sure this is loaded
+// XXX: how to test other plugins...?
+
+addPlugin('Geonames');
+
+class LocationTest extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * @dataProvider locationNames
+     */
+
+    public function testLocationFromName($name, $language, $location)
+    {
+        $result = Location::fromName($name, $language);
+        $this->assertEquals($result, $location);
+    }
+
+    static public function locationNames()
+    {
+        return array(array('Montreal', 'en', null),
+                     array('San Francisco, CA', 'en', null),
+                     array('Paris, France', 'en', null),
+                     array('Paris, Texas', 'en', null));
+    }
+
+    /**
+     * @dataProvider locationIds
+     */
+
+    public function testLocationFromId($id, $ns, $language, $location)
+    {
+        $result = Location::fromId($id, $ns, $language);
+        $this->assertEquals($result, $location);
+    }
+
+    static public function locationIds()
+    {
+        return array(array(6077243, GeonamesPlugin::NAMESPACE, 'en', null),
+                     array(5391959, GeonamesPlugin::NAMESPACE, 'en', null));
+    }
+
+    /**
+     * @dataProvider locationLatLons
+     */
+
+    public function testLocationFromLatLon($lat, $lon, $language, $location)
+    {
+        $result = Location::fromLatLon($lat, $lon, $language);
+        $this->assertEquals($result, $location);
+    }
+
+    static public function locationLatLons()
+    {
+        return array(array(37.77493, -122.41942, 'en', null),
+                     array(45.509, -73.588, 'en', null));
+    }
+
+    /**
+     * @dataProvider nameOfLocation
+     */
+
+    public function testLocationGetName($location, $language, $name)
+    {
+        $result = $location->getName($language);
+        $this->assertEquals($result, $name);
+    }
+
+    static public function nameOfLocation()
+    {
+        return array(array(new Location(), 'en', 'Montreal'),
+                     array(new Location(), 'fr', 'Montréal'));
+    }
+}
+
index 0cc03ccce72887dc6cbbd497f8c305e3af60191a..d83f9faf58e1b296cac5824bc08874dc05595239 100644 (file)
@@ -185,12 +185,12 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
                      array('http://example.com/path/(foo)/bar',
                            '<a href="http://example.com/path/(foo)/bar" title="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'),
                      array('http://example.com/path/[foo]/bar',
-                           '<a href="http://example.com/path/[foo]/bar" title="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'),
+                           '<a href="http://example.com/path/" title="http://example.com/path/" rel="external">http://example.com/path/</a>[foo]/bar'),
                      array('http://example.com/path/foo/(bar)',
                            '<a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'),
                      //Not a valid url - urls cannot contain unencoded square brackets
                      array('http://example.com/path/foo/[bar]',
-                           '<a href="http://example.com/path/foo/[bar]" title="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'),
+                           '<a href="http://example.com/path/foo/" title="http://example.com/path/foo/" rel="external">http://example.com/path/foo/</a>[bar]'),
                      array('Hey, check out my cool site http://example.com okay?',
                            'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a> okay?'),
                      array('What about parens (e.g. http://example.com/path/foo/(bar))?',
index d9dca98156c85d6e11fcc58ebe512d9e79b0daa9..878662c5620e330e3200564642cd0e14c4e27073 100644 (file)
@@ -432,21 +432,21 @@ border-width:1px;
 border-style:solid;
 }
 
-#form_notice {
+.form_notice {
 width:45%;
 float:left;
 position:relative;
 line-height:1;
 }
-#form_notice fieldset {
+.form_notice fieldset {
 border:0;
 padding:0;
 position:relative;
 }
-#form_notice legend {
+.form_notice legend {
 display:none;
 }
-#form_notice textarea {
+.form_notice textarea {
 float:left;
 border-radius:7px;
 -moz-border-radius:7px;
@@ -458,44 +458,44 @@ padding:7px 7px 16px 7px;
 position:relative;
 z-index:2;
 }
-#form_notice label {
+.form_notice label {
 display:block;
 float:left;
 font-size:1.3em;
 margin-bottom:7px;
 }
-#form_notice label[for=notice_data-attach],
-#form_notice #notice_data-attach {
+.form_notice label[for=notice_data-attach],
+.form_notice #notice_data-attach {
 position:absolute;
 top:25px;
 right:10.5%;
 cursor:pointer;
 }
-#form_notice label[for=notice_data-attach] {
+.form_notice label[for=notice_data-attach] {
 text-indent:-9999px;
 width:16px;
 height:16px;
 }
-#form_notice #notice_data-attach {
+.form_notice #notice_data-attach {
 padding:0;
 height:16px;
 }
-#form_notice .form_note {
+.form_notice .form_note {
 position:absolute;
 bottom:2px;
 right:21.715%;
 z-index:9;
 }
-#form_notice .form_note dt {
+.form_notice .form_note dt {
 font-weight:bold;
 display:none;
 }
-#notice_text-count {
+.form_notice #notice_text-count {
 font-weight:bold;
 line-height:1.15;
 padding:1px 2px;
 }
-#form_notice #notice_action-submit {
+.form_notice #notice_action-submit {
 width:14%;
 height:47px;
 padding:0;
@@ -503,24 +503,24 @@ position:absolute;
 bottom:0;
 right:0;
 }
-#form_notice label[for=to] {
+.form_notice label[for=to] {
 margin-top:7px;
 }
-#form_notice select[id=to] {
+.form_notice select[id=to] {
 margin-bottom:7px;
 margin-left:18px;
 float:left;
 max-width:322px;
 }
-#form_notice .error,
-#form_notice .success {
+.form_notice .error,
+.form_notice .success {
 float:left;
 clear:both;
 width:81.5%;
 margin-bottom:0;
 line-height:1.618;
 }
-#form_notice #notice_data-attach_selected code {
+.form_notice #notice_data-attach_selected code {
 float:left;
 width:90%;
 display:block;
@@ -528,11 +528,21 @@ font-size:1.1em;
 line-height:1.8;
 overflow:auto;
 }
-#form_notice #notice_data-attach_selected button {
+.form_notice #notice_data-attach_selected button {
 float:right;
 font-size:0.8em;
 }
 
+button.close {
+width:16px;
+height:16px;
+text-indent:-9999px;
+padding:0;
+border:0;
+text-align:center;
+font-weight:bold;
+}
+
 /* entity_profile */
 .entity_profile {
 position:relative;
@@ -670,6 +680,40 @@ border-radius:4px;
 margin-bottom:18px;
 }
 
+
+.entity_send-a-message button {
+position:absolute;
+top:0;
+right:0;
+}
+
+.entity_send-a-message .form_notice {
+position:absolute;
+top:34px;
+right:-1px;
+padding:1.795%;
+width:65%;
+z-index:2;
+ border-radius:7px;
+-moz-border-radius:7px;
+-webkit-radius-border:7px;
+border-width:1px;
+border-style:solid;
+}
+.entity_send-a-message .form_notice legend {
+display:block;
+margin-bottom:11px;
+}
+
+.entity_send-a-message .form_notice label,
+.entity_send-a-message .form_notice select {
+display:none;
+}
+.entity_send-a-message .form_notice input.submit {
+text-align:center;
+}
+
+
 .entity_tags ul {
 list-style-type:none;
 display:inline;
@@ -991,11 +1035,6 @@ margin-bottom:11px;
 position:absolute;
 top:0;
 right:0;
-width:29px;
-height:29px;
-text-align:center;
-font-weight:bold;
-padding:0;
 }
 #jOverlayContent h1 {
 max-width:425px;
@@ -1295,3 +1334,13 @@ display:none;
 .guide {
 clear:both;
 }
+
+#bookmarklet address {
+display:none;
+}
+#bookmarklet .form_notice {
+width:auto;
+}
+#bookmarklet #wrap {
+min-width:0;
+}
diff --git a/theme/base/css/facebookapp.css b/theme/base/css/facebookapp.css
deleted file mode 100644 (file)
index e6b1c9e..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-* {
-font-size:14px;
-font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
-}
-
-#wrap {
-background-color:#F0F2F5;
-padding-left:18px;
-padding-right:18px;
-width:auto;
-}
-
-p,label,
-h1,h2,h3,h4,h5,h6 {
-color:#000;
-}
-
-#content {
-width:95%;
-}
-
-#site_nav_local_views a {
-background-color:#D0DFE7;
-}
-#site_nav_local_views a:hover {
-background-color:#FAFBFC;
-}
-
-span.facebook-button {
-border: 2px solid #aaa;
-padding: 3px;
-display: block;
-float: left;
-margin-right: 20px;
--moz-border-radius: 4px; 
-border-radius:4px; 
--webkit-border-radius:4px;
-font-weight: bold;
-background-color:#A9BF4F;
-color:#fff;
-font-size:1.2em
-}
-
-span.facebook-button a { color:#fff }
-
-.facebook_guide {
-margin-bottom:18px;
-}
-.facebook_guide p {
-font-weight:bold;
-}
-
-
-input {
-height:auto !important;
-}
-
-#facebook-friends {
-float:left;
-width:100%;
-}
-
-#facebook-friends li {
-float:left;
-margin-right:2%;
-margin-bottom:11px;
-width:18%;
-height:115px;
-}
-#facebook-friends li a {
-float:left;
-}
-
-#add_to_profile {
-position:absolute;
-right:18px;
-top:10px;
-z-index:2;
-}
-
-.notice div.entry-content dl,
-.notice div.entry-content dt, 
-.notice div.entry-content dd {
-margin-right:5px;
-}
-
-#content_inner p {
-margin-bottom:18px;
-}
-
-#content_inner ul {
-list-style-type:none;
-}
-
-.form_settings label {
-margin-right:18px;
-}
index 3e128b84ed607f38df446a4c88f273edc412bcb9..84bc1b1d647f9f3987d7f426182bdbec3036f89b 100644 (file)
@@ -3,10 +3,10 @@ input.checkbox,
 input.radio {
 top:0;
 }
-#form_notice textarea {
+.form_notice textarea {
 width:78%;
 }
-#form_notice .form_note + label {
+.form_notice .form_note + label {
 position:absolute;
 top:25px;
 left:83%;
@@ -15,14 +15,14 @@ height:16px;
 width:16px;
 display:block;
 }
-#form_notice #notice_action-submit {
+.form_notice #notice_action-submit {
 width:17%;
 max-width:17%;
 }
-#form_notice #notice_data-attach_selected {
+.form_notice #notice_data-attach_selected {
 width:78.5%;
 }
-#form_notice #notice_data-attach_selected button {
+.form_notice #notice_data-attach_selected button {
 padding:0 4px;
 }
 .notice-options input.submit {
index c4e3713302fe9bea6d32d83bb2449a4c5107a6f3..f83c30177c6a5aa8c13cc8bbaae41e7819c93ddf 100644 (file)
Binary files a/theme/base/images/icons/icons-01.png and b/theme/base/images/icons/icons-01.png differ
diff --git a/theme/base/images/icons/twotone/green/x.gif b/theme/base/images/icons/twotone/green/x.gif
new file mode 100644 (file)
index 0000000..ffb2efe
Binary files /dev/null and b/theme/base/images/icons/twotone/green/x.gif differ
index 6833373b456a2920542406078562b95d88ee61a0..910f915a867fda3747128d53e631f16a6c4e9c30 100644 (file)
@@ -18,7 +18,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 font-size:1em;
 }
 address {
-margin-right:5.8%;
+margin-right:5.7%;
 }
 
 input, textarea, select, option {
@@ -28,7 +28,8 @@ input, textarea, select,
 .entity_remote_subscribe {
 border-color:#AAAAAA;
 }
-#filter_tags ul li {
+#filter_tags ul li,
+.entity_send-a-message .form_notice {
 border-color:#DDDDDD;
 }
 
@@ -37,14 +38,14 @@ background:none;
 }
 
 input.submit,
-#form_notice.warning #notice_text-count,
+.form_notice.warning #notice_text-count,
 .form_settings .form_note,
 .entity_remote_subscribe {
 background-color:#9BB43E;
 }
 
 input:focus, textarea:focus, select:focus,
-#form_notice.warning #notice_data-text {
+.form_notice.warning #notice_data-text {
 border-color:#9BB43E;
 box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
@@ -84,16 +85,43 @@ background-color:#C8D1D5;
 #notice_text-count {
 color:#333333;
 }
-#form_notice.warning #notice_text-count {
+.form_notice.warning #notice_text-count {
 color:#000000;
 }
-#form_notice label[for=notice_data-attach] {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
+.form_notice label[for=notice_data-attach] {
+background-position:0 -328px;
 }
-#form_notice #notice_data-attach {
+.form_notice #notice_data-attach {
 opacity:0;
 }
 
+.form_notice label[for=notice_data-attach],
+#export_data li a.rss,
+#export_data li a.atom,
+#export_data li a.foaf,
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.form_make_admin input.submit,
+.notice .attachment,
+.notice-options .notice_reply,
+.notice-options form.form_favor input.submit,
+.notice-options form.form_disfavor input.submit,
+.notice-options .notice_delete,
+#new_group a,
+.pagination .nav_prev a,
+.pagination .nav_next a,
+button.close {
+background-image:url(../../base/images/icons/icons-01.png);
+background-repeat:no-repeat;
+background-color:transparent;
+}
+
 #wrap form.processing input.submit {
 background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
@@ -112,7 +140,8 @@ box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
 border-color:transparent;
 }
 #content,
-#site_nav_local_views .current a {
+#site_nav_local_views .current a,
+.entity_send-a-message .form_notice {
 background-color:#FFFFFF;
 }
 
@@ -138,6 +167,10 @@ background-color:#F7E8E8;
 background-color:#EFF3DC;
 }
 
+button.close {
+background-position:0 -1120px;
+}
+
 #anon_notice {
 background-color:#87B4C8;
 color:#FFFFFF;
@@ -152,30 +185,15 @@ background-color:#9BB43E;
 background-repeat:no-repeat;
 }
 #export_data li a.rss {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 -130px;
 }
 #export_data li a.atom {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 -64px;
 }
 #export_data li a.foaf {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 1px;
 }
 
-.entity_edit a,
-.entity_send-a-message a,
-.form_user_nudge input.submit,
-.form_user_block input.submit,
-.form_user_unblock input.submit,
-.form_group_block input.submit,
-.form_group_unblock input.submit,
-.entity_nudge p,
-.form_make_admin input.submit {
-background-repeat: no-repeat;
-background-color:transparent;
-}
 .form_group_join input.submit,
 .form_group_leave input.submit
 .form_user_subscribe input.submit,
@@ -190,48 +208,49 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -718px;
 }
 .entity_send-a-message a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -849px;
 }
+.entity_send-a-message .form_notice {
+box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+}
+
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -785px;
 }
 .form_user_block input.submit,
 .form_user_unblock input.submit,
 .form_group_block input.submit,
 .form_group_unblock input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -918px;
 }
 .form_make_admin input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -983px;
 }
 
 /* NOTICES */
 .notice .attachment {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px;
+background-position:0 -394px;
 }
 #attachments .attachment {
 background:none;
 }
 .notice-options .notice_reply {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px;
+background-position:0 -589px;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px;
+background-position:0 -457px;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px;
+background-position:0 -523px;
 }
 .notice-options .notice_delete {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px;
+background-position:0 -655px;
 }
 
 .notices div.entry-content,
@@ -268,7 +287,7 @@ background-color:rgba(200, 200, 200, 0.300);
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px;
+background-position:0 -1054px;
 }
 
 .pagination .nav_prev a,
@@ -277,10 +296,8 @@ background-repeat:no-repeat;
 border-color:#C8D1D5;
 }
 .pagination .nav_prev a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:10% -187px;
 }
 .pagination .nav_next a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:105% -252px;
 }
index cbbd49ce6ca6a87a7d928834d158d764ad1a55af..a0d3cd6825705645325d21996b354fcd980b455d 100644 (file)
@@ -6,9 +6,9 @@ color:#FFFFFF;
 #site_nav_local_views a {
 background-color:#C8D1D5;
 }
-#form_notice .form_note + label {
+.form_notice .form_note + label {
 background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
 }
-#form_notice #notice_data-attach {
+.form_notice #notice_data-attach {
 filter: alpha(opacity=0);
 }
index 6339c9314e4b6f77b816fb2d03aa7c3f0ec002e2..2aa087331e89215eea97e36e7f8d37d137aaa451 100644 (file)
@@ -28,7 +28,8 @@ input, textarea, select,
 .entity_remote_subscribe {
 border-color:#AAAAAA;
 }
-#filter_tags ul li {
+#filter_tags ul li,
+.entity_send-a-message .form_notice {
 border-color:#DDDDDD;
 }
 
@@ -37,14 +38,14 @@ background:none;
 }
 
 input.submit,
-#form_notice.warning #notice_text-count,
+.form_notice.warning #notice_text-count,
 .form_settings .form_note,
 .entity_remote_subscribe {
 background-color:#9BB43E;
 }
 
 input:focus, textarea:focus, select:focus,
-#form_notice.warning #notice_data-text {
+.form_notice.warning #notice_data-text {
 border-color:#9BB43E;
 box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
@@ -84,16 +85,43 @@ background-color:#CEE1E9;
 #notice_text-count {
 color:#333333;
 }
-#form_notice.warning #notice_text-count {
+.form_notice.warning #notice_text-count {
 color:#000000;
 }
-#form_notice label[for=notice_data-attach] {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
+.form_notice label[for=notice_data-attach] {
+background-position:0 -328px;
 }
-#form_notice #notice_data-attach {
+.form_notice #notice_data-attach {
 opacity:0;
 }
 
+.form_notice label[for=notice_data-attach],
+#export_data li a.rss,
+#export_data li a.atom,
+#export_data li a.foaf,
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.form_make_admin input.submit,
+.notice .attachment,
+.notice-options .notice_reply,
+.notice-options form.form_favor input.submit,
+.notice-options form.form_disfavor input.submit,
+.notice-options .notice_delete,
+#new_group a,
+.pagination .nav_prev a,
+.pagination .nav_next a,
+button.close {
+background-image:url(../../base/images/icons/icons-01.png);
+background-repeat:no-repeat;
+background-color:transparent;
+}
+
 #wrap form.processing input.submit {
 background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
@@ -112,7 +140,8 @@ box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
 border-color:transparent;
 }
 #content,
-#site_nav_local_views .current a {
+#site_nav_local_views .current a,
+.entity_send-a-message .form_notice {
 background-color:#FFFFFF;
 }
 
@@ -138,6 +167,10 @@ background-color:#F7E8E8;
 background-color:#EFF3DC;
 }
 
+button.close {
+background-position:0 -1120px;
+}
+
 #anon_notice {
 background-color:#87B4C8;
 color:#FFFFFF;
@@ -152,30 +185,15 @@ background-color:#9BB43E;
 background-repeat:no-repeat;
 }
 #export_data li a.rss {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 -130px;
 }
 #export_data li a.atom {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 -64px;
 }
 #export_data li a.foaf {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:0 1px;
 }
 
-.entity_edit a,
-.entity_send-a-message a,
-.form_user_nudge input.submit,
-.form_user_block input.submit,
-.form_user_unblock input.submit,
-.form_group_block input.submit,
-.form_group_unblock input.submit,
-.entity_nudge p,
-.form_make_admin input.submit {
-background-repeat: no-repeat;
-background-color:transparent;
-}
 .form_group_join input.submit,
 .form_group_leave input.submit
 .form_user_subscribe input.submit,
@@ -190,48 +208,49 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -718px;
 }
 .entity_send-a-message a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -849px;
 }
+.entity_send-a-message .form_notice {
+box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
+}
+
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -785px;
 }
 .form_user_block input.submit,
 .form_user_unblock input.submit,
 .form_group_block input.submit,
 .form_group_unblock input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -918px;
 }
 .form_make_admin input.submit {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position: 0 -983px;
 }
 
 /* NOTICES */
 .notice .attachment {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px;
+background-position:0 -394px;
 }
 #attachments .attachment {
 background:none;
 }
 .notice-options .notice_reply {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px;
+background-position:0 -589px;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px;
+background-position:0 -457px;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px;
+background-position:0 -523px;
 }
 .notice-options .notice_delete {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px;
+background-position:0 -655px;
 }
 
 .notices div.entry-content,
@@ -268,7 +287,7 @@ background-color:rgba(200, 200, 200, 0.300);
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px;
+background-position:0 -1054px;
 }
 
 .pagination .nav_prev a,
@@ -277,10 +296,8 @@ background-repeat:no-repeat;
 border-color:#CEE1E9;
 }
 .pagination .nav_prev a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:10% -187px;
 }
 .pagination .nav_next a {
-background-image:url(../../base/images/icons/icons-01.png);
 background-position:105% -252px;
 }
index 044c32ff16b0746742e902369da22a488cae8eaa..9ede1e324b3257c08089a5fe84768ce34924be4d 100644 (file)
@@ -6,10 +6,10 @@ color:#FFFFFF;
 #site_nav_local_views a {
 background-color:#D9DADB;
 }
-#form_notice .form_note + label {
+.form_notice .form_note + label {
 background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
 }
-#form_notice #notice_data-attach {
+.form_notice #notice_data-attach {
 filter: alpha(opacity=0);
 }
 .notice-options form.form_favor input.submit {